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      * <pre>{@code
 170      * <element name="foo">
 171      *   <choice>
 172      *     <element name="bar"><empty/></element>
 173      *     <text/>
 174      *   </choice>
 175      * </element>
 176      * }</pre>
 177      *
 178      * Assume we hit the following instance:
 179      * <pre>{@code
 180      * <foo> <bar/></foo>
 181      * }</pre>
 182      *
 183      * Then this first space needs to be ignored (for otherwise, we will
 184      * end up treating this space as the match to &lt;text/> and won't
 185      * be able to process &lt;bar>.)
 186      *
 187      * Now assume the following instance:
 188      * <pre>{@code
 189      * <foo/>
 190      * }</pre>
 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 &lt;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 &lt;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()}.
 488      * <p>
 489      * For example, consider the following schema:
 490      * <pre>{@code
 491      *  <oneOrMore>
 492      *   <element name="foo"><empty/></element>
 493      *  </oneOrMore>
 494      *  code fragment X
 495      *  <element name="bob"/>
 496      * }</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 }