View Javadoc
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.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      * <xmp>
169      * <element name="foo">
170      *   <choice>
171      *     <element name="bar"><empty/></element>
172      *     <text/>
173      *   </choice>
174      * </element>
175      * </xmp>
176      *
177      * Assume we hit the following instance:
178      * <xmp>
179      * <foo> <bar/></foo>
180      * </xmp>
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      * <xmp>
188      * <foo/>
189      * </xmp>
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()</code>.
487      * <p>
488      * For example, consider the following schema:
489      * <pre><xmp>
490      *  <oneOrMore>
491      *   <element name="foo"><empty/></element>
492      *  </oneOrMore>
493      *  code fragment X
494      *  <element name="bob"/>
495      * </xmp></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 }