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