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

Save This Page
Home » openjdk-7 » com.sun.tools.internal.jxc.gen » config » [javadoc | source]