1 /* 2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.tools.internal.jxc.gen.config; 27 28 import java.text.MessageFormat; 29 import java.util.ArrayList; 30 import java.util.Stack; 31 import java.util.StringTokenizer; 32 33 import org.xml.sax.Attributes; 34 import org.xml.sax.ContentHandler; 35 import org.xml.sax.Locator; 36 import org.xml.sax.SAXException; 37 import org.xml.sax.SAXParseException; 38 39 /** 40 * Runtime Engine for RELAXNGCC execution. 41 * 42 * This class has the following functionalities: 43 * 44 * <ol> 45 * <li>Managing a stack of NGCCHandler objects and 46 * switching between them appropriately. 47 * 48 * <li>Keep track of all Attributes. 49 * 50 * <li>manage mapping between namespace URIs and prefixes. 51 * 52 * <li>TODO: provide support for interleaving. 53 * </ol> 54 * <p><b> 55 * Auto-generated, do not edit. 56 * </b></p> 57 * @version $Id: NGCCRuntime.java,v 1.15 2002/09/29 02:55:48 okajima Exp $ 58 * @author Kohsuke Kawaguchi (kk@kohsuke.org) 59 */ 60 public class NGCCRuntime implements ContentHandler, NGCCEventSource { 61 62 public NGCCRuntime() { 63 reset(); 64 } 65 66 /** 67 * Sets the root handler, which will be used to parse the 68 * root element. 69 * <p> 70 * This method can be called right after the object is created 71 * or the reset method is called. You can't replace the root 72 * handler while parsing is in progress. 73 * <p> 74 * Usually a generated class that corresponds to the {@code <start>} 75 * pattern will be used as the root handler, but any NGCCHandler 76 * can be a root handler. 77 * 78 * @exception IllegalStateException 79 * If this method is called but it doesn't satisfy the 80 * pre-condition stated above. 81 */ 82 public void setRootHandler( NGCCHandler rootHandler ) { 83 if(currentHandler!=null) 84 throw new IllegalStateException(); 85 currentHandler = rootHandler; 86 } 87 88 89 /** 90 * Cleans up all the data structure so that the object can be reused later. 91 * Normally, applications do not need to call this method directly, 92 * 93 * as the runtime resets itself after the endDocument method. 94 */ 95 public void reset() { 96 attStack.clear(); 97 currentAtts = null; 98 currentHandler = null; 99 indent=0; 100 locator = null; 101 namespaces.clear(); 102 needIndent = true; 103 redirect = null; 104 redirectionDepth = 0; 105 text = new StringBuffer(); 106 107 // add a dummy attributes at the bottom as a "centinel." 108 attStack.push(new AttributesImpl()); 109 } 110 111 // current content handler can be acccessed via set/getContentHandler. 112 113 private Locator locator; 114 public void setDocumentLocator( Locator _loc ) { this.locator=_loc; } 115 /** 116 * Gets the source location of the current event. 117 * 118 * <p> 119 * One can call this method from RelaxNGCC handlers to access 120 * the line number information. Note that to 121 */ 122 public Locator getLocator() { return locator; } 123 124 125 /** stack of {@link Attributes}. */ 126 private final Stack attStack = new Stack(); 127 /** current attributes set. always equal to attStack.peek() */ 128 private AttributesImpl currentAtts; 129 130 /** 131 * Attributes that belong to the current element. 132 * <p> 133 * It's generally not recommended for applications to use 134 * this method. RelaxNGCC internally removes processed attributes, 135 * so this doesn't correctly reflect all the attributes an element 136 * carries. 137 */ 138 public Attributes getCurrentAttributes() { 139 return currentAtts; 140 } 141 142 /** accumulated text. */ 143 private StringBuffer text = new StringBuffer(); 144 145 146 147 148 /** The current NGCCHandler. Always equals to handlerStack.peek() */ 149 private NGCCEventReceiver currentHandler; 150 151 public int replace( NGCCEventReceiver o, NGCCEventReceiver n ) { 152 if(o!=currentHandler) 153 throw new IllegalStateException(); // bug of RelaxNGCC 154 currentHandler = n; 155 156 return 0; // we only have one thread. 157 } 158 159 /** 160 * Processes buffered text. 161 * 162 * This method will be called by the start/endElement event to process 163 * buffered text as a text event. 164 * 165 * <p> 166 * Whitespace handling is a tricky business. Consider the following 167 * schema fragment: 168 * 169 * <xmp> 170 * <element name="foo"> 171 * <choice> 172 * <element name="bar"><empty/></element> 173 * <text/> 174 * </choice> 175 * </element> 176 * </xmp> 177 * 178 * Assume we hit the following instance: 179 * <xmp> 180 * <foo> <bar/></foo> 181 * </xmp> 182 * 183 * Then this first space needs to be ignored (for otherwise, we will 184 * end up treating this space as the match to <text/> and won't 185 * be able to process <bar>.) 186 * 187 * Now assume the following instance: 188 * <xmp> 189 * <foo/> 190 * </xmp> 191 * 192 * This time, we need to treat this empty string as a text, for 193 * otherwise we won't be able to accept this instance. 194 * 195 * <p> 196 * This is very difficult to solve in general, but one seemingly 197 * easy solution is to use the type of next event. If a text is 198 * followed by a start tag, it follows from the constraint on 199 * RELAX NG that that text must be either whitespaces or a match 200 * to <text/>. 201 * 202 * <p> 203 * On the contrary, if a text is followed by a end tag, then it 204 * cannot be whitespace unless the content model can accept empty, 205 * in which case sending a text event will be harmlessly ignored 206 * by the NGCCHandler. 207 * 208 * <p> 209 * Thus this method take one parameter, which controls the 210 * behavior of this method. 211 * 212 * <p> 213 * TODO: according to the constraint of RELAX NG, if characters 214 * follow an end tag, then they must be either whitespaces or 215 * must match to <text/>. 216 * 217 * @param possiblyWhitespace 218 * True if the buffered character can be ignorabale. False if 219 * it needs to be consumed. 220 */ 221 private void processPendingText(boolean ignorable) throws SAXException { 222 if(ignorable && text.toString().trim().length()==0) 223 ; // ignore. See the above javadoc comment for the description 224 else 225 currentHandler.text(text.toString()); // otherwise consume this token 226 227 // truncate StringBuffer, but avoid excessive allocation. 228 if(text.length()>1024) text = new StringBuffer(); 229 else text.setLength(0); 230 } 231 232 public void processList( String str ) throws SAXException { 233 StringTokenizer t = new StringTokenizer(str, " \t\r\n"); 234 while(t.hasMoreTokens()) 235 currentHandler.text(t.nextToken()); 236 } 237 238 public void startElement(String uri, String localname, String qname, Attributes atts) 239 throws SAXException { 240 241 if(redirect!=null) { 242 redirect.startElement(uri,localname,qname,atts); 243 redirectionDepth++; 244 } else { 245 processPendingText(true); 246 // System.out.println("startElement:"+localname+"->"+_attrStack.size()); 247 currentHandler.enterElement(uri, localname, qname, atts); 248 } 249 } 250 251 /** 252 * Called by the generated handler code when an enter element 253 * event is consumed. 254 * 255 * <p> 256 * Pushes a new attribute set. 257 * 258 * <p> 259 * Note that attributes are NOT pushed at the startElement method, 260 * because the processing of the enterElement event can trigger 261 * other attribute events and etc. 262 * <p> 263 * This method will be called from one of handlers when it truely 264 * consumes the enterElement event. 265 */ 266 public void onEnterElementConsumed( 267 String uri, String localName, String qname,Attributes atts) throws SAXException { 268 attStack.push(currentAtts=new AttributesImpl(atts)); 269 nsEffectiveStack.push( new Integer(nsEffectivePtr) ); 270 nsEffectivePtr = namespaces.size(); 271 } 272 273 public void onLeaveElementConsumed(String uri, String localName, String qname) throws SAXException { 274 attStack.pop(); 275 if(attStack.isEmpty()) 276 currentAtts = null; 277 else 278 currentAtts = (AttributesImpl)attStack.peek(); 279 nsEffectivePtr = ((Integer)nsEffectiveStack.pop()).intValue(); 280 } 281 282 public void endElement(String uri, String localname, String qname) 283 throws SAXException { 284 285 if(redirect!=null) { 286 redirect.endElement(uri,localname,qname); 287 redirectionDepth--; 288 289 if(redirectionDepth!=0) 290 return; 291 292 // finished redirection. 293 for( int i=0; i<namespaces.size(); i+=2 ) 294 redirect.endPrefixMapping((String)namespaces.get(i)); 295 redirect.endDocument(); 296 297 redirect = null; 298 // then process this element normally 299 } 300 301 processPendingText(false); 302 303 currentHandler.leaveElement(uri, localname, qname); 304 // System.out.println("endElement:"+localname); 305 } 306 307 public void characters(char[] ch, int start, int length) throws SAXException { 308 if(redirect!=null) 309 redirect.characters(ch,start,length); 310 else 311 text.append(ch,start,length); 312 } 313 public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 314 if(redirect!=null) 315 redirect.ignorableWhitespace(ch,start,length); 316 else 317 text.append(ch,start,length); 318 } 319 320 public int getAttributeIndex(String uri, String localname) { 321 return currentAtts.getIndex(uri, localname); 322 } 323 public void consumeAttribute(int index) throws SAXException { 324 final String uri = currentAtts.getURI(index); 325 final String local = currentAtts.getLocalName(index); 326 final String qname = currentAtts.getQName(index); 327 final String value = currentAtts.getValue(index); 328 currentAtts.removeAttribute(index); 329 330 currentHandler.enterAttribute(uri,local,qname); 331 currentHandler.text(value); 332 currentHandler.leaveAttribute(uri,local,qname); 333 } 334 335 336 public void startPrefixMapping( String prefix, String uri ) throws SAXException { 337 if(redirect!=null) 338 redirect.startPrefixMapping(prefix,uri); 339 else { 340 namespaces.add(prefix); 341 namespaces.add(uri); 342 } 343 } 344 345 public void endPrefixMapping( String prefix ) throws SAXException { 346 if(redirect!=null) 347 redirect.endPrefixMapping(prefix); 348 else { 349 namespaces.remove(namespaces.size()-1); 350 namespaces.remove(namespaces.size()-1); 351 } 352 } 353 354 public void skippedEntity( String name ) throws SAXException { 355 if(redirect!=null) 356 redirect.skippedEntity(name); 357 } 358 359 public void processingInstruction( String target, String data ) throws SAXException { 360 if(redirect!=null) 361 redirect.processingInstruction(target,data); 362 } 363 364 /** Impossible token. This value can never be a valid XML name. */ 365 static final String IMPOSSIBLE = "\u0000"; 366 367 public void endDocument() throws SAXException { 368 // consume the special "end document" token so that all the handlers 369 // currently at the stack will revert to their respective parents. 370 // 371 // this is necessary to handle a grammar like 372 // <start><ref name="X"/></start> 373 // <define name="X"> 374 // <element name="root"><empty/></element> 375 // </define> 376 // 377 // With this grammar, when the endElement event is consumed, two handlers 378 // are on the stack (because a child object won't revert to its parent 379 // unless it sees a next event.) 380 381 // pass around an "impossible" token. 382 currentHandler.leaveElement(IMPOSSIBLE,IMPOSSIBLE,IMPOSSIBLE); 383 384 reset(); 385 } 386 public void startDocument() {} 387 388 389 390 391 // 392 // 393 // event dispatching methods 394 // 395 // 396 397 public void sendEnterAttribute( int threadId, 398 String uri, String local, String qname) throws SAXException { 399 400 currentHandler.enterAttribute(uri,local,qname); 401 } 402 403 public void sendEnterElement( int threadId, 404 String uri, String local, String qname, Attributes atts) throws SAXException { 405 406 currentHandler.enterElement(uri,local,qname,atts); 407 } 408 409 public void sendLeaveAttribute( int threadId, 410 String uri, String local, String qname) throws SAXException { 411 412 currentHandler.leaveAttribute(uri,local,qname); 413 } 414 415 public void sendLeaveElement( int threadId, 416 String uri, String local, String qname) throws SAXException { 417 418 currentHandler.leaveElement(uri,local,qname); 419 } 420 421 public void sendText(int threadId, String value) throws SAXException { 422 currentHandler.text(value); 423 } 424 425 426 // 427 // 428 // redirection of SAX2 events. 429 // 430 // 431 /** When redirecting a sub-tree, this value will be non-null. */ 432 private ContentHandler redirect = null; 433 434 /** 435 * Counts the depth of the elements when we are re-directing 436 * a sub-tree to another ContentHandler. 437 */ 438 private int redirectionDepth = 0; 439 440 /** 441 * This method can be called only from the enterElement handler. 442 * The sub-tree rooted at the new element will be redirected 443 * to the specified ContentHandler. 444 * 445 * <p> 446 * Currently active NGCCHandler will only receive the leaveElement 447 * event of the newly started element. 448 * 449 * @param uri,local,qname 450 * Parameters passed to the enter element event. Used to 451 * simulate the startElement event for the new ContentHandler. 452 */ 453 public void redirectSubtree( ContentHandler child, 454 String uri, String local, String qname ) throws SAXException { 455 456 redirect = child; 457 redirect.setDocumentLocator(locator); 458 redirect.startDocument(); 459 460 // TODO: when a prefix is re-bound to something else, 461 // the following code is potentially dangerous. It should be 462 // modified to report active bindings only. 463 for( int i=0; i<namespaces.size(); i+=2 ) 464 redirect.startPrefixMapping( 465 (String)namespaces.get(i), 466 (String)namespaces.get(i+1) 467 ); 468 469 redirect.startElement(uri,local,qname,currentAtts); 470 redirectionDepth=1; 471 } 472 473 // 474 // 475 // validation context implementation 476 // 477 // 478 /** in-scope namespace mapping. 479 * namespaces[2n ] := prefix 480 * namespaces[2n+1] := namespace URI */ 481 private final ArrayList namespaces = new ArrayList(); 482 /** 483 * Index on the namespaces array, which points to 484 * the top of the effective bindings. Because of the 485 * timing difference between the startPrefixMapping method 486 * and the execution of the corresponding actions, 487 * this value can be different from <code>namespaces.size()</code>. 488 * <p> 489 * For example, consider the following schema: 490 * <pre><xmp> 491 * <oneOrMore> 492 * <element name="foo"><empty/></element> 493 * </oneOrMore> 494 * code fragment X 495 * <element name="bob"/> 496 * </xmp></pre> 497 * Code fragment X is executed after we see a startElement event, 498 * but at this time the namespaces variable already include new 499 * namespace bindings declared on "bob". 500 */ 501 private int nsEffectivePtr=0; 502 503 /** 504 * Stack to preserve old nsEffectivePtr values. 505 */ 506 private final Stack nsEffectiveStack = new Stack(); 507 508 public String resolveNamespacePrefix( String prefix ) { 509 for( int i = nsEffectivePtr-2; i>=0; i-=2 ) 510 if( namespaces.get(i).equals(prefix) ) 511 return (String)namespaces.get(i+1); 512 513 // no binding was found. 514 if(prefix.equals("")) return ""; // return the default no-namespace 515 if(prefix.equals("xml")) // pre-defined xml prefix 516 return "http://www.w3.org/XML/1998/namespace"; 517 else return null; // prefix undefined 518 } 519 520 521 // error reporting 522 protected void unexpectedX(String token) throws SAXException { 523 throw new SAXParseException(MessageFormat.format( 524 "Unexpected {0} appears at line {1} column {2}", 525 new Object[]{ 526 token, 527 new Integer(getLocator().getLineNumber()), 528 new Integer(getLocator().getColumnNumber()) }), 529 getLocator()); 530 } 531 532 533 534 535 // 536 // 537 // trace functions 538 // 539 // 540 private int indent=0; 541 private boolean needIndent=true; 542 private void printIndent() { 543 for( int i=0; i<indent; i++ ) 544 System.out.print(" "); 545 } 546 public void trace( String s ) { 547 if(needIndent) { 548 needIndent=false; 549 printIndent(); 550 } 551 System.out.print(s); 552 } 553 public void traceln( String s ) { 554 trace(s); 555 trace("\n"); 556 needIndent=true; 557 } 558 }