Thu, 31 Aug 2017 15:18:52 +0800
merge
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 */
26 package com.sun.tools.internal.jxc.gen.config;
28 import java.text.MessageFormat;
29 import java.util.ArrayList;
30 import java.util.Stack;
31 import java.util.StringTokenizer;
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;
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 {
61 public NGCCRuntime() {
62 reset();
63 }
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 <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 }
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();
106 // add a dummy attributes at the bottom as a "centinel."
107 attStack.push(new AttributesImpl());
108 }
110 // current content handler can be acccessed via set/getContentHandler.
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; }
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;
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 }
141 /** accumulated text. */
142 private StringBuffer text = new StringBuffer();
147 /** The current NGCCHandler. Always equals to handlerStack.peek() */
148 private NGCCEventReceiver currentHandler;
150 public int replace( NGCCEventReceiver o, NGCCEventReceiver n ) {
151 if(o!=currentHandler)
152 throw new IllegalStateException(); // bug of RelaxNGCC
153 currentHandler = n;
155 return 0; // we only have one thread.
156 }
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 <text/> and won't
184 * be able to process <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 <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 <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
226 // truncate StringBuffer, but avoid excessive allocation.
227 if(text.length()>1024) text = new StringBuffer();
228 else text.setLength(0);
229 }
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 }
237 public void startElement(String uri, String localname, String qname, Attributes atts)
238 throws SAXException {
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 }
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 }
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 }
281 public void endElement(String uri, String localname, String qname)
282 throws SAXException {
284 if(redirect!=null) {
285 redirect.endElement(uri,localname,qname);
286 redirectionDepth--;
288 if(redirectionDepth!=0)
289 return;
291 // finished redirection.
292 for( int i=0; i<namespaces.size(); i+=2 )
293 redirect.endPrefixMapping((String)namespaces.get(i));
294 redirect.endDocument();
296 redirect = null;
297 // then process this element normally
298 }
300 processPendingText(false);
302 currentHandler.leaveElement(uri, localname, qname);
303 // System.out.println("endElement:"+localname);
304 }
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 }
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);
329 currentHandler.enterAttribute(uri,local,qname);
330 currentHandler.text(value);
331 currentHandler.leaveAttribute(uri,local,qname);
332 }
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 }
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 }
353 public void skippedEntity( String name ) throws SAXException {
354 if(redirect!=null)
355 redirect.skippedEntity(name);
356 }
358 public void processingInstruction( String target, String data ) throws SAXException {
359 if(redirect!=null)
360 redirect.processingInstruction(target,data);
361 }
363 /** Impossible token. This value can never be a valid XML name. */
364 static final String IMPOSSIBLE = "\u0000";
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.)
380 // pass around an "impossible" token.
381 currentHandler.leaveElement(IMPOSSIBLE,IMPOSSIBLE,IMPOSSIBLE);
383 reset();
384 }
385 public void startDocument() {}
390 //
391 //
392 // event dispatching methods
393 //
394 //
396 public void sendEnterAttribute( int threadId,
397 String uri, String local, String qname) throws SAXException {
399 currentHandler.enterAttribute(uri,local,qname);
400 }
402 public void sendEnterElement( int threadId,
403 String uri, String local, String qname, Attributes atts) throws SAXException {
405 currentHandler.enterElement(uri,local,qname,atts);
406 }
408 public void sendLeaveAttribute( int threadId,
409 String uri, String local, String qname) throws SAXException {
411 currentHandler.leaveAttribute(uri,local,qname);
412 }
414 public void sendLeaveElement( int threadId,
415 String uri, String local, String qname) throws SAXException {
417 currentHandler.leaveElement(uri,local,qname);
418 }
420 public void sendText(int threadId, String value) throws SAXException {
421 currentHandler.text(value);
422 }
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;
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;
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 {
455 redirect = child;
456 redirect.setDocumentLocator(locator);
457 redirect.startDocument();
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 );
468 redirect.startElement(uri,local,qname,currentAtts);
469 redirectionDepth=1;
470 }
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;
502 /**
503 * Stack to preserve old nsEffectivePtr values.
504 */
505 private final Stack nsEffectiveStack = new Stack();
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);
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 }
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 }
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 }