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