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