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 &lt;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 &lt;text/> and won't
 182      * be able to process &lt;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 &lt;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 &lt;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 }