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