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 <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 <text/> and won't
181 * be able to process <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 <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 <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 }