001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.xml.internal.xsom.impl.parser.state;
027:
028: import java.text.MessageFormat;
029: import java.util.ArrayList;
030: import java.util.Stack;
031: import java.util.StringTokenizer;
032:
033: import org.xml.sax.Attributes;
034: import org.xml.sax.ContentHandler;
035: import org.xml.sax.Locator;
036: import org.xml.sax.SAXException;
037: import org.xml.sax.SAXParseException;
038:
039: /**
040: * Runtime Engine for RELAXNGCC execution.
041: *
042: * This class has the following functionalities:
043: *
044: * <ol>
045: * <li>Managing a stack of NGCCHandler objects and
046: * switching between them appropriately.
047: *
048: * <li>Keep track of all Attributes.
049: *
050: * <li>manage mapping between namespace URIs and prefixes.
051: *
052: * <li>TODO: provide support for interleaving.
053: *
054: * @version $Id: NGCCRuntime.java,v 1.15 2002/09/29 02:55:48 okajima Exp $
055: * @author Kohsuke Kawaguchi (kk@kohsuke.org)
056: */
057: public class NGCCRuntime implements ContentHandler, NGCCEventSource {
058:
059: public NGCCRuntime() {
060: reset();
061: }
062:
063: /**
064: * Sets the root handler, which will be used to parse the
065: * root element.
066: * <p>
067: * This method can be called right after the object is created
068: * or the reset method is called. You can't replace the root
069: * handler while parsing is in progress.
070: * <p>
071: * Usually a generated class that corresponds to the <start>
072: * pattern will be used as the root handler, but any NGCCHandler
073: * can be a root handler.
074: *
075: * @exception IllegalStateException
076: * If this method is called but it doesn't satisfy the
077: * pre-condition stated above.
078: */
079: public void setRootHandler(NGCCHandler rootHandler) {
080: if (currentHandler != null)
081: throw new IllegalStateException();
082: currentHandler = rootHandler;
083: }
084:
085: /**
086: * Cleans up all the data structure so that the object can be reused later.
087: * Normally, applications do not need to call this method directly,
088: *
089: * as the runtime resets itself after the endDocument method.
090: */
091: public void reset() {
092: attStack.clear();
093: currentAtts = null;
094: currentHandler = null;
095: indent = 0;
096: locator = null;
097: namespaces.clear();
098: needIndent = true;
099: 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:
111: public void setDocumentLocator(Locator _loc) {
112: this .locator = _loc;
113: }
114:
115: /**
116: * Gets the source location of the current event.
117: *
118: * <p>
119: * One can call this method from RelaxNGCC handlers to access
120: * the line number information. Note that to
121: */
122: public Locator getLocator() {
123: 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: /** The current NGCCHandler. Always equals to handlerStack.peek() */
147: private NGCCEventReceiver currentHandler;
148:
149: public int replace(NGCCEventReceiver o, NGCCEventReceiver n) {
150: if (o != currentHandler)
151: throw new IllegalStateException(); // bug of RelaxNGCC
152: currentHandler = n;
153:
154: return 0; // we only have one thread.
155: }
156:
157: /**
158: * Processes buffered text.
159: *
160: * This method will be called by the start/endElement event to process
161: * buffered text as a text event.
162: *
163: * <p>
164: * Whitespace handling is a tricky business. Consider the following
165: * schema fragment:
166: *
167: * <xmp>
168: * <element name="foo">
169: * <choice>
170: * <element name="bar"><empty/></element>
171: * <text/>
172: * </choice>
173: * </element>
174: * </xmp>
175: *
176: * Assume we hit the following instance:
177: * <xmp>
178: * <foo> <bar/></foo>
179: * </xmp>
180: *
181: * Then this first space needs to be ignored (for otherwise, we will
182: * end up treating this space as the match to <text/> and won't
183: * be able to process <bar>.)
184: *
185: * Now assume the following instance:
186: * <xmp>
187: * <foo/>
188: * </xmp>
189: *
190: * This time, we need to treat this empty string as a text, for
191: * otherwise we won't be able to accept this instance.
192: *
193: * <p>
194: * This is very difficult to solve in general, but one seemingly
195: * easy solution is to use the type of next event. If a text is
196: * followed by a start tag, it follows from the constraint on
197: * RELAX NG that that text must be either whitespaces or a match
198: * to <text/>.
199: *
200: * <p>
201: * On the contrary, if a text is followed by a end tag, then it
202: * cannot be whitespace unless the content model can accept empty,
203: * in which case sending a text event will be harmlessly ignored
204: * by the NGCCHandler.
205: *
206: * <p>
207: * Thus this method take one parameter, which controls the
208: * behavior of this method.
209: *
210: * <p>
211: * TODO: according to the constraint of RELAX NG, if characters
212: * follow an end tag, then they must be either whitespaces or
213: * must match to <text/>.
214: *
215: * @param possiblyWhitespace
216: * True if the buffered character can be ignorabale. False if
217: * it needs to be consumed.
218: */
219: private void processPendingText(boolean ignorable)
220: 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
225:
226: // truncate StringBuffer, but avoid excessive allocation.
227: if (text.length() > 1024)
228: text = new StringBuffer();
229: else
230: 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,
240: String qname, Attributes atts) 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(String uri, String localName,
268: 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,
275: String qname) throws SAXException {
276: attStack.pop();
277: if (attStack.isEmpty())
278: currentAtts = null;
279: else
280: currentAtts = (AttributesImpl) attStack.peek();
281: nsEffectivePtr = ((Integer) nsEffectiveStack.pop()).intValue();
282: }
283:
284: public void endElement(String uri, String localname, String qname)
285: throws SAXException {
286:
287: if (redirect != null) {
288: redirect.endElement(uri, localname, qname);
289: redirectionDepth--;
290:
291: if (redirectionDepth != 0)
292: return;
293:
294: // finished redirection.
295: for (int i = 0; i < namespaces.size(); i += 2)
296: redirect.endPrefixMapping((String) namespaces.get(i));
297: redirect.endDocument();
298:
299: redirect = null;
300: // then process this element normally
301: }
302:
303: processPendingText(false);
304:
305: currentHandler.leaveElement(uri, localname, qname);
306: // System.out.println("endElement:"+localname);
307: }
308:
309: public void characters(char[] ch, int start, int length)
310: throws SAXException {
311: if (redirect != null)
312: redirect.characters(ch, start, length);
313: else
314: text.append(ch, start, length);
315: }
316:
317: public void ignorableWhitespace(char[] ch, int start, int length)
318: throws SAXException {
319: if (redirect != null)
320: redirect.ignorableWhitespace(ch, start, length);
321: else
322: text.append(ch, start, length);
323: }
324:
325: public int getAttributeIndex(String uri, String localname) {
326: return currentAtts.getIndex(uri, localname);
327: }
328:
329: public void consumeAttribute(int index) throws SAXException {
330: final String uri = currentAtts.getURI(index);
331: final String local = currentAtts.getLocalName(index);
332: final String qname = currentAtts.getQName(index);
333: final String value = currentAtts.getValue(index);
334: currentAtts.removeAttribute(index);
335:
336: currentHandler.enterAttribute(uri, local, qname);
337: currentHandler.text(value);
338: currentHandler.leaveAttribute(uri, local, qname);
339: }
340:
341: public void startPrefixMapping(String prefix, String uri)
342: throws SAXException {
343: if (redirect != null)
344: redirect.startPrefixMapping(prefix, uri);
345: else {
346: namespaces.add(prefix);
347: namespaces.add(uri);
348: }
349: }
350:
351: public void endPrefixMapping(String prefix) throws SAXException {
352: if (redirect != null)
353: redirect.endPrefixMapping(prefix);
354: else {
355: namespaces.remove(namespaces.size() - 1);
356: namespaces.remove(namespaces.size() - 1);
357: }
358: }
359:
360: public void skippedEntity(String name) throws SAXException {
361: if (redirect != null)
362: redirect.skippedEntity(name);
363: }
364:
365: public void processingInstruction(String target, String data)
366: throws SAXException {
367: if (redirect != null)
368: redirect.processingInstruction(target, data);
369: }
370:
371: /** Impossible token. This value can never be a valid XML name. */
372: static final String IMPOSSIBLE = "\u0000";
373:
374: public void endDocument() throws SAXException {
375: // consume the special "end document" token so that all the handlers
376: // currently at the stack will revert to their respective parents.
377: //
378: // this is necessary to handle a grammar like
379: // <start><ref name="X"/></start>
380: // <define name="X">
381: // <element name="root"><empty/></element>
382: // </define>
383: //
384: // With this grammar, when the endElement event is consumed, two handlers
385: // are on the stack (because a child object won't revert to its parent
386: // unless it sees a next event.)
387:
388: // pass around an "impossible" token.
389: currentHandler.leaveElement(IMPOSSIBLE, IMPOSSIBLE, IMPOSSIBLE);
390:
391: reset();
392: }
393:
394: public void startDocument() {
395: }
396:
397: //
398: //
399: // event dispatching methods
400: //
401: //
402:
403: public void sendEnterAttribute(int threadId, String uri,
404: String local, String qname) throws SAXException {
405:
406: currentHandler.enterAttribute(uri, local, qname);
407: }
408:
409: public void sendEnterElement(int threadId, String uri,
410: String local, String qname, Attributes atts)
411: throws SAXException {
412:
413: currentHandler.enterElement(uri, local, qname, atts);
414: }
415:
416: public void sendLeaveAttribute(int threadId, String uri,
417: String local, String qname) throws SAXException {
418:
419: currentHandler.leaveAttribute(uri, local, qname);
420: }
421:
422: public void sendLeaveElement(int threadId, String uri,
423: String local, String qname) throws SAXException {
424:
425: currentHandler.leaveElement(uri, local, qname);
426: }
427:
428: public void sendText(int threadId, String value)
429: throws SAXException {
430: currentHandler.text(value);
431: }
432:
433: //
434: //
435: // redirection of SAX2 events.
436: //
437: //
438: /** When redirecting a sub-tree, this value will be non-null. */
439: private ContentHandler redirect = null;
440:
441: /**
442: * Counts the depth of the elements when we are re-directing
443: * a sub-tree to another ContentHandler.
444: */
445: private int redirectionDepth = 0;
446:
447: /**
448: * This method can be called only from the enterElement handler.
449: * The sub-tree rooted at the new element will be redirected
450: * to the specified ContentHandler.
451: *
452: * <p>
453: * Currently active NGCCHandler will only receive the leaveElement
454: * event of the newly started element.
455: *
456: * @param uri,local,qname
457: * Parameters passed to the enter element event. Used to
458: * simulate the startElement event for the new ContentHandler.
459: */
460: public void redirectSubtree(ContentHandler child, String uri,
461: String local, String qname) throws SAXException {
462:
463: redirect = child;
464: redirect.setDocumentLocator(locator);
465: redirect.startDocument();
466:
467: // TODO: when a prefix is re-bound to something else,
468: // the following code is potentially dangerous. It should be
469: // modified to report active bindings only.
470: for (int i = 0; i < namespaces.size(); i += 2)
471: redirect.startPrefixMapping((String) namespaces.get(i),
472: (String) namespaces.get(i + 1));
473:
474: redirect.startElement(uri, local, qname, currentAtts);
475: redirectionDepth = 1;
476: }
477:
478: //
479: //
480: // validation context implementation
481: //
482: //
483: /** in-scope namespace mapping.
484: * namespaces[2n ] := prefix
485: * namespaces[2n+1] := namespace URI */
486: private final ArrayList namespaces = new ArrayList();
487: /**
488: * Index on the namespaces array, which points to
489: * the top of the effective bindings. Because of the
490: * timing difference between the startPrefixMapping method
491: * and the execution of the corresponding actions,
492: * this value can be different from <code>namespaces.size()</code>.
493: * <p>
494: * For example, consider the following schema:
495: * <pre><xmp>
496: * <oneOrMore>
497: * <element name="foo"><empty/></element>
498: * </oneOrMore>
499: * code fragment X
500: * <element name="bob"/>
501: * </xmp></pre>
502: * Code fragment X is executed after we see a startElement event,
503: * but at this time the namespaces variable already include new
504: * namespace bindings declared on "bob".
505: */
506: private int nsEffectivePtr = 0;
507:
508: /**
509: * Stack to preserve old nsEffectivePtr values.
510: */
511: private final Stack nsEffectiveStack = new Stack();
512:
513: public String resolveNamespacePrefix(String prefix) {
514: for (int i = nsEffectivePtr - 2; i >= 0; i -= 2)
515: if (namespaces.get(i).equals(prefix))
516: return (String) namespaces.get(i + 1);
517:
518: // no binding was found.
519: if (prefix.equals(""))
520: return ""; // return the default no-namespace
521: if (prefix.equals("xml")) // pre-defined xml prefix
522: return "http://www.w3.org/XML/1998/namespace";
523: else
524: return null; // prefix undefined
525: }
526:
527: // error reporting
528: protected void unexpectedX(String token) throws SAXException {
529: throw new SAXParseException(MessageFormat.format(
530: "Unexpected {0} appears at line {1} column {2}",
531: new Object[] { token,
532: new Integer(getLocator().getLineNumber()),
533: new Integer(getLocator().getColumnNumber()) }),
534: getLocator());
535: }
536:
537: //
538: //
539: // trace functions
540: //
541: //
542: private int indent = 0;
543: private boolean needIndent = true;
544:
545: private void printIndent() {
546: for (int i = 0; i < indent; i++)
547: System.out.print(" ");
548: }
549:
550: public void trace(String s) {
551: if (needIndent) {
552: needIndent = false;
553: printIndent();
554: }
555: System.out.print(s);
556: }
557:
558: public void traceln(String s) {
559: trace(s);
560: trace("\n");
561: needIndent = true;
562: }
563: }
|