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