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