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