001: /*
002: * Copyright 1999-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: IncrementalSAXSource_Filter.java,v 1.12 2004/08/17 18:57:21 jycli Exp $
018: */
019:
020: package org.apache.xml.dtm.ref;
021:
022: import java.io.IOException;
023:
024: import org.apache.xml.res.XMLErrorResources;
025: import org.apache.xml.res.XMLMessages;
026: import org.apache.xml.utils.ThreadControllerWrapper;
027:
028: import org.xml.sax.Attributes;
029: import org.xml.sax.ContentHandler;
030: import org.xml.sax.DTDHandler;
031: import org.xml.sax.ErrorHandler;
032: import org.xml.sax.InputSource;
033: import org.xml.sax.Locator;
034: import org.xml.sax.SAXException;
035: import org.xml.sax.SAXNotRecognizedException;
036: import org.xml.sax.SAXNotSupportedException;
037: import org.xml.sax.SAXParseException;
038: import org.xml.sax.XMLReader;
039: import org.xml.sax.ext.LexicalHandler;
040:
041: /** <p>IncrementalSAXSource_Filter implements IncrementalSAXSource, using a
042: * standard SAX2 event source as its input and parcelling out those
043: * events gradually in reponse to deliverMoreNodes() requests. Output from the
044: * filter will be passed along to a SAX handler registered as our
045: * listener, but those callbacks will pass through a counting stage
046: * which periodically yields control back to the controller coroutine.
047: * </p>
048: *
049: * <p>%REVIEW%: This filter is not currenly intended to be reusable
050: * for parsing additional streams/documents. We may want to consider
051: * making it resettable at some point in the future. But it's a
052: * small object, so that'd be mostly a convenience issue; the cost
053: * of allocating each time is trivial compared to the cost of processing
054: * any nontrival stream.</p>
055: *
056: * <p>For a brief usage example, see the unit-test main() method.</p>
057: *
058: * <p>This is a simplification of the old CoroutineSAXParser, focusing
059: * specifically on filtering. The resulting controller protocol is _far_
060: * simpler and less error-prone; the only controller operation is deliverMoreNodes(),
061: * and the only requirement is that deliverMoreNodes(false) be called if you want to
062: * discard the rest of the stream and the previous deliverMoreNodes() didn't return
063: * false.
064: * */
065: public class IncrementalSAXSource_Filter implements
066: IncrementalSAXSource, ContentHandler, DTDHandler,
067: LexicalHandler, ErrorHandler, Runnable {
068: boolean DEBUG = false; //Internal status report
069:
070: //
071: // Data
072: //
073: private CoroutineManager fCoroutineManager = null;
074: private int fControllerCoroutineID = -1;
075: private int fSourceCoroutineID = -1;
076:
077: private ContentHandler clientContentHandler = null; // %REVIEW% support multiple?
078: private LexicalHandler clientLexicalHandler = null; // %REVIEW% support multiple?
079: private DTDHandler clientDTDHandler = null; // %REVIEW% support multiple?
080: private ErrorHandler clientErrorHandler = null; // %REVIEW% support multiple?
081: private int eventcounter;
082: private int frequency = 5;
083:
084: // Flag indicating that no more events should be delivered -- either
085: // because input stream ran to completion (endDocument), or because
086: // the user requested an early stop via deliverMoreNodes(false).
087: private boolean fNoMoreEvents = false;
088:
089: // Support for startParse()
090: private XMLReader fXMLReader = null;
091: private InputSource fXMLReaderInputSource = null;
092:
093: //
094: // Constructors
095: //
096:
097: public IncrementalSAXSource_Filter() {
098: this .init(new CoroutineManager(), -1, -1);
099: }
100:
101: /** Create a IncrementalSAXSource_Filter which is not yet bound to a specific
102: * SAX event source.
103: * */
104: public IncrementalSAXSource_Filter(CoroutineManager co,
105: int controllerCoroutineID) {
106: this .init(co, controllerCoroutineID, -1);
107: }
108:
109: //
110: // Factories
111: //
112: static public IncrementalSAXSource createIncrementalSAXSource(
113: CoroutineManager co, int controllerCoroutineID) {
114: return new IncrementalSAXSource_Filter(co,
115: controllerCoroutineID);
116: }
117:
118: //
119: // Public methods
120: //
121:
122: public void init(CoroutineManager co, int controllerCoroutineID,
123: int sourceCoroutineID) {
124: if (co == null)
125: co = new CoroutineManager();
126: fCoroutineManager = co;
127: fControllerCoroutineID = co
128: .co_joinCoroutineSet(controllerCoroutineID);
129: fSourceCoroutineID = co.co_joinCoroutineSet(sourceCoroutineID);
130: if (fControllerCoroutineID == -1 || fSourceCoroutineID == -1)
131: throw new RuntimeException(XMLMessages.createXMLMessage(
132: XMLErrorResources.ER_COJOINROUTINESET_FAILED, null)); //"co_joinCoroutineSet() failed");
133:
134: fNoMoreEvents = false;
135: eventcounter = frequency;
136: }
137:
138: /** Bind our input streams to an XMLReader.
139: *
140: * Just a convenience routine; obviously you can explicitly register
141: * this as a listener with the same effect.
142: * */
143: public void setXMLReader(XMLReader eventsource) {
144: fXMLReader = eventsource;
145: eventsource.setContentHandler(this );
146: eventsource.setDTDHandler(this );
147: eventsource.setErrorHandler(this ); // to report fatal errors in filtering mode
148:
149: // Not supported by all SAX2 filters:
150: try {
151: eventsource.setProperty(
152: "http://xml.org/sax/properties/lexical-handler",
153: this );
154: } catch (SAXNotRecognizedException e) {
155: // Nothing we can do about it
156: } catch (SAXNotSupportedException e) {
157: // Nothing we can do about it
158: }
159:
160: // Should we also bind as other varieties of handler?
161: // (DTDHandler and so on)
162: }
163:
164: // Register a content handler for us to output to
165: public void setContentHandler(ContentHandler handler) {
166: clientContentHandler = handler;
167: }
168:
169: // Register a DTD handler for us to output to
170: public void setDTDHandler(DTDHandler handler) {
171: clientDTDHandler = handler;
172: }
173:
174: // Register a lexical handler for us to output to
175: // Not all filters support this...
176: // ??? Should we register directly on the filter?
177: // NOTE NAME -- subclassing issue in the Xerces version
178: public void setLexicalHandler(LexicalHandler handler) {
179: clientLexicalHandler = handler;
180: }
181:
182: // Register an error handler for us to output to
183: // NOTE NAME -- subclassing issue in the Xerces version
184: public void setErrHandler(ErrorHandler handler) {
185: clientErrorHandler = handler;
186: }
187:
188: // Set the number of events between resumes of our coroutine
189: // Immediately resets number of events before _next_ resume as well.
190: public void setReturnFrequency(int events) {
191: if (events < 1)
192: events = 1;
193: frequency = eventcounter = events;
194: }
195:
196: //
197: // ContentHandler methods
198: // These pass the data to our client ContentHandler...
199: // but they also count the number of events passing through,
200: // and resume our coroutine each time that counter hits zero and
201: // is reset.
202: //
203: // Note that for everything except endDocument and fatalError, we do the count-and-yield
204: // BEFORE passing the call along. I'm hoping that this will encourage JIT
205: // compilers to realize that these are tail-calls, reducing the expense of
206: // the additional layer of data flow.
207: //
208: // %REVIEW% Glenn suggests that pausing after endElement, endDocument,
209: // and characters may be sufficient. I actually may not want to
210: // stop after characters, since in our application these wind up being
211: // concatenated before they're processed... but that risks huge blocks of
212: // text causing greater than usual readahead. (Unlikely? Consider the
213: // possibility of a large base-64 block in a SOAP stream.)
214: //
215: public void characters(char[] ch, int start, int length)
216: throws org.xml.sax.SAXException {
217: if (--eventcounter <= 0) {
218: co_yield(true);
219: eventcounter = frequency;
220: }
221: if (clientContentHandler != null)
222: clientContentHandler.characters(ch, start, length);
223: }
224:
225: public void endDocument() throws org.xml.sax.SAXException {
226: // EXCEPTION: In this case we need to run the event BEFORE we yield.
227: if (clientContentHandler != null)
228: clientContentHandler.endDocument();
229:
230: eventcounter = 0;
231: co_yield(false);
232: }
233:
234: public void endElement(java.lang.String namespaceURI,
235: java.lang.String localName, java.lang.String qName)
236: throws org.xml.sax.SAXException {
237: if (--eventcounter <= 0) {
238: co_yield(true);
239: eventcounter = frequency;
240: }
241: if (clientContentHandler != null)
242: clientContentHandler.endElement(namespaceURI, localName,
243: qName);
244: }
245:
246: public void endPrefixMapping(java.lang.String prefix)
247: throws org.xml.sax.SAXException {
248: if (--eventcounter <= 0) {
249: co_yield(true);
250: eventcounter = frequency;
251: }
252: if (clientContentHandler != null)
253: clientContentHandler.endPrefixMapping(prefix);
254: }
255:
256: public void ignorableWhitespace(char[] ch, int start, int length)
257: throws org.xml.sax.SAXException {
258: if (--eventcounter <= 0) {
259: co_yield(true);
260: eventcounter = frequency;
261: }
262: if (clientContentHandler != null)
263: clientContentHandler.ignorableWhitespace(ch, start, length);
264: }
265:
266: public void processingInstruction(java.lang.String target,
267: java.lang.String data) throws org.xml.sax.SAXException {
268: if (--eventcounter <= 0) {
269: co_yield(true);
270: eventcounter = frequency;
271: }
272: if (clientContentHandler != null)
273: clientContentHandler.processingInstruction(target, data);
274: }
275:
276: public void setDocumentLocator(Locator locator) {
277: if (--eventcounter <= 0) {
278: // This can cause a hang. -sb
279: // co_yield(true);
280: eventcounter = frequency;
281: }
282: if (clientContentHandler != null)
283: clientContentHandler.setDocumentLocator(locator);
284: }
285:
286: public void skippedEntity(java.lang.String name)
287: throws org.xml.sax.SAXException {
288: if (--eventcounter <= 0) {
289: co_yield(true);
290: eventcounter = frequency;
291: }
292: if (clientContentHandler != null)
293: clientContentHandler.skippedEntity(name);
294: }
295:
296: public void startDocument() throws org.xml.sax.SAXException {
297: co_entry_pause();
298:
299: // Otherwise, begin normal event delivery
300: if (--eventcounter <= 0) {
301: co_yield(true);
302: eventcounter = frequency;
303: }
304: if (clientContentHandler != null)
305: clientContentHandler.startDocument();
306: }
307:
308: public void startElement(java.lang.String namespaceURI,
309: java.lang.String localName, java.lang.String qName,
310: Attributes atts) throws org.xml.sax.SAXException {
311: if (--eventcounter <= 0) {
312: co_yield(true);
313: eventcounter = frequency;
314: }
315: if (clientContentHandler != null)
316: clientContentHandler.startElement(namespaceURI, localName,
317: qName, atts);
318: }
319:
320: public void startPrefixMapping(java.lang.String prefix,
321: java.lang.String uri) throws org.xml.sax.SAXException {
322: if (--eventcounter <= 0) {
323: co_yield(true);
324: eventcounter = frequency;
325: }
326: if (clientContentHandler != null)
327: clientContentHandler.startPrefixMapping(prefix, uri);
328: }
329:
330: //
331: // LexicalHandler support. Not all SAX2 filters support these events
332: // but we may want to pass them through when they exist...
333: //
334: // %REVIEW% These do NOT currently affect the eventcounter; I'm asserting
335: // that they're rare enough that it makes little or no sense to
336: // pause after them. As such, it may make more sense for folks who
337: // actually want to use them to register directly with the filter.
338: // But I want 'em here for now, to remind us to recheck this assertion!
339: //
340: public void comment(char[] ch, int start, int length)
341: throws org.xml.sax.SAXException {
342: if (null != clientLexicalHandler)
343: clientLexicalHandler.comment(ch, start, length);
344: }
345:
346: public void endCDATA() throws org.xml.sax.SAXException {
347: if (null != clientLexicalHandler)
348: clientLexicalHandler.endCDATA();
349: }
350:
351: public void endDTD() throws org.xml.sax.SAXException {
352: if (null != clientLexicalHandler)
353: clientLexicalHandler.endDTD();
354: }
355:
356: public void endEntity(java.lang.String name)
357: throws org.xml.sax.SAXException {
358: if (null != clientLexicalHandler)
359: clientLexicalHandler.endEntity(name);
360: }
361:
362: public void startCDATA() throws org.xml.sax.SAXException {
363: if (null != clientLexicalHandler)
364: clientLexicalHandler.startCDATA();
365: }
366:
367: public void startDTD(java.lang.String name,
368: java.lang.String publicId, java.lang.String systemId)
369: throws org.xml.sax.SAXException {
370: if (null != clientLexicalHandler)
371: clientLexicalHandler.startDTD(name, publicId, systemId);
372: }
373:
374: public void startEntity(java.lang.String name)
375: throws org.xml.sax.SAXException {
376: if (null != clientLexicalHandler)
377: clientLexicalHandler.startEntity(name);
378: }
379:
380: //
381: // DTDHandler support.
382:
383: public void notationDecl(String a, String b, String c)
384: throws SAXException {
385: if (null != clientDTDHandler)
386: clientDTDHandler.notationDecl(a, b, c);
387: }
388:
389: public void unparsedEntityDecl(String a, String b, String c,
390: String d) throws SAXException {
391: if (null != clientDTDHandler)
392: clientDTDHandler.unparsedEntityDecl(a, b, c, d);
393: }
394:
395: //
396: // ErrorHandler support.
397: //
398: // PROBLEM: Xerces is apparently _not_ calling the ErrorHandler for
399: // exceptions thrown by the ContentHandler, which prevents us from
400: // handling this properly when running in filtering mode with Xerces
401: // as our event source. It's unclear whether this is a Xerces bug
402: // or a SAX design flaw.
403: //
404: // %REVIEW% Current solution: In filtering mode, it is REQUIRED that
405: // event source make sure this method is invoked if the event stream
406: // abends before endDocument is delivered. If that means explicitly calling
407: // us in the exception handling code because it won't be delivered as part
408: // of the normal SAX ErrorHandler stream, that's fine; Not Our Problem.
409: //
410: public void error(SAXParseException exception) throws SAXException {
411: if (null != clientErrorHandler)
412: clientErrorHandler.error(exception);
413: }
414:
415: public void fatalError(SAXParseException exception)
416: throws SAXException {
417: // EXCEPTION: In this case we need to run the event BEFORE we yield --
418: // just as with endDocument, this terminates the event stream.
419: if (null != clientErrorHandler)
420: clientErrorHandler.error(exception);
421:
422: eventcounter = 0;
423: co_yield(false);
424:
425: }
426:
427: public void warning(SAXParseException exception)
428: throws SAXException {
429: if (null != clientErrorHandler)
430: clientErrorHandler.error(exception);
431: }
432:
433: //
434: // coroutine support
435: //
436:
437: public int getSourceCoroutineID() {
438: return fSourceCoroutineID;
439: }
440:
441: public int getControllerCoroutineID() {
442: return fControllerCoroutineID;
443: }
444:
445: /** @return the CoroutineManager this CoroutineFilter object is bound to.
446: * If you're using the do...() methods, applications should only
447: * need to talk to the CoroutineManager once, to obtain the
448: * application's Coroutine ID.
449: * */
450: public CoroutineManager getCoroutineManager() {
451: return fCoroutineManager;
452: }
453:
454: /** <p>In the SAX delegation code, I've inlined the count-down in
455: * the hope of encouraging compilers to deliver better
456: * performance. However, if we subclass (eg to directly connect the
457: * output to a DTM builder), that would require calling super in
458: * order to run that logic... which seems inelegant. Hence this
459: * routine for the convenience of subclasses: every [frequency]
460: * invocations, issue a co_yield.</p>
461: *
462: * @param moreExepected Should always be true unless this is being called
463: * at the end of endDocument() handling.
464: * */
465: protected void count_and_yield(boolean moreExpected)
466: throws SAXException {
467: if (!moreExpected)
468: eventcounter = 0;
469:
470: if (--eventcounter <= 0) {
471: co_yield(true);
472: eventcounter = frequency;
473: }
474: }
475:
476: /**
477: * co_entry_pause is called in startDocument() before anything else
478: * happens. It causes the filter to wait for a "go ahead" request
479: * from the controller before delivering any events. Note that
480: * the very first thing the controller tells us may be "I don't
481: * need events after all"!
482: */
483: private void co_entry_pause() throws SAXException {
484: if (fCoroutineManager == null) {
485: // Nobody called init()? Do it now...
486: init(null, -1, -1);
487: }
488:
489: try {
490: Object arg = fCoroutineManager
491: .co_entry_pause(fSourceCoroutineID);
492: if (arg == Boolean.FALSE)
493: co_yield(false);
494: } catch (NoSuchMethodException e) {
495: // Coroutine system says we haven't registered. That's an
496: // application coding error, and is unrecoverable.
497: if (DEBUG)
498: e.printStackTrace();
499: throw new SAXException(e);
500: }
501: }
502:
503: /**
504: * Co_Yield handles coroutine interactions while a parse is in progress.
505: *
506: * When moreRemains==true, we are pausing after delivering events, to
507: * ask if more are needed. We will resume the controller thread with
508: * co_resume(Boolean.TRUE, ...)
509: * When control is passed back it may indicate
510: * Boolean.TRUE indication to continue delivering events
511: * Boolean.FALSE indication to discontinue events and shut down.
512: *
513: * When moreRemains==false, we shut down immediately without asking the
514: * controller's permission. Normally this means end of document has been
515: * reached.
516: *
517: * Shutting down a IncrementalSAXSource_Filter requires terminating the incoming
518: * SAX event stream. If we are in control of that stream (if it came
519: * from an XMLReader passed to our startReader() method), we can do so
520: * very quickly by throwing a reserved exception to it. If the stream is
521: * coming from another source, we can't do that because its caller may
522: * not be prepared for this "normal abnormal exit", and instead we put
523: * ourselves in a "spin" mode where events are discarded.
524: */
525: private void co_yield(boolean moreRemains) throws SAXException {
526: // Horrendous kluge to run filter to completion. See below.
527: if (fNoMoreEvents)
528: return;
529:
530: try // Coroutine manager might throw no-such.
531: {
532: Object arg = Boolean.FALSE;
533: if (moreRemains) {
534: // Yield control, resume parsing when done
535: arg = fCoroutineManager.co_resume(Boolean.TRUE,
536: fSourceCoroutineID, fControllerCoroutineID);
537:
538: }
539:
540: // If we're at end of document or were told to stop early
541: if (arg == Boolean.FALSE) {
542: fNoMoreEvents = true;
543:
544: if (fXMLReader != null) // Running under startParseThread()
545: throw new StopException(); // We'll co_exit from there.
546:
547: // Yield control. We do NOT expect anyone to ever ask us again.
548: fCoroutineManager.co_exit_to(Boolean.FALSE,
549: fSourceCoroutineID, fControllerCoroutineID);
550: }
551: } catch (NoSuchMethodException e) {
552: // Shouldn't happen unless we've miscoded our coroutine logic
553: // "Shut down the garbage smashers on the detention level!"
554: fNoMoreEvents = true;
555: fCoroutineManager.co_exit(fSourceCoroutineID);
556: throw new SAXException(e);
557: }
558: }
559:
560: //
561: // Convenience: Run an XMLReader in a thread
562: //
563:
564: /** Launch a thread that will run an XMLReader's parse() operation within
565: * a thread, feeding events to this IncrementalSAXSource_Filter. Mostly a convenience
566: * routine, but has the advantage that -- since we invoked parse() --
567: * we can halt parsing quickly via a StopException rather than waiting
568: * for the SAX stream to end by itself.
569: *
570: * @throws SAXException is parse thread is already in progress
571: * or parsing can not be started.
572: * */
573: public void startParse(InputSource source) throws SAXException {
574: if (fNoMoreEvents)
575: throw new SAXException(
576: XMLMessages
577: .createXMLMessage(
578: XMLErrorResources.ER_INCRSAXSRCFILTER_NOT_RESTARTABLE,
579: null)); //"IncrmentalSAXSource_Filter not currently restartable.");
580: if (fXMLReader == null)
581: throw new SAXException(XMLMessages.createXMLMessage(
582: XMLErrorResources.ER_XMLRDR_NOT_BEFORE_STARTPARSE,
583: null)); //"XMLReader not before startParse request");
584:
585: fXMLReaderInputSource = source;
586:
587: // Xalan thread pooling...
588: // org.apache.xalan.transformer.TransformerImpl.runTransformThread(this);
589: ThreadControllerWrapper.runThread(this , -1);
590: }
591:
592: /* Thread logic to support startParseThread()
593: */
594: public void run() {
595: // Guard against direct invocation of start().
596: if (fXMLReader == null)
597: return;
598:
599: if (DEBUG)
600: System.out
601: .println("IncrementalSAXSource_Filter parse thread launched");
602:
603: // Initially assume we'll run successfully.
604: Object arg = Boolean.FALSE;
605:
606: // For the duration of this operation, all coroutine handshaking
607: // will occur in the co_yield method. That's the nice thing about
608: // coroutines; they give us a way to hand off control from the
609: // middle of a synchronous method.
610: try {
611: fXMLReader.parse(fXMLReaderInputSource);
612: } catch (IOException ex) {
613: arg = ex;
614: } catch (StopException ex) {
615: // Expected and harmless
616: if (DEBUG)
617: System.out
618: .println("Active IncrementalSAXSource_Filter normal stop exception");
619: } catch (SAXException ex) {
620: Exception inner = ex.getException();
621: if (inner instanceof StopException) {
622: // Expected and harmless
623: if (DEBUG)
624: System.out
625: .println("Active IncrementalSAXSource_Filter normal stop exception");
626: } else {
627: // Unexpected malfunction
628: if (DEBUG) {
629: System.out
630: .println("Active IncrementalSAXSource_Filter UNEXPECTED SAX exception: "
631: + inner);
632: inner.printStackTrace();
633: }
634: arg = ex;
635: }
636: } // end parse
637:
638: // Mark as no longer running in thread.
639: fXMLReader = null;
640:
641: try {
642: // Mark as done and yield control to the controller coroutine
643: fNoMoreEvents = true;
644: fCoroutineManager.co_exit_to(arg, fSourceCoroutineID,
645: fControllerCoroutineID);
646: } catch (java.lang.NoSuchMethodException e) {
647: // Shouldn't happen unless we've miscoded our coroutine logic
648: // "CPO, shut down the garbage smashers on the detention level!"
649: e.printStackTrace(System.err);
650: fCoroutineManager.co_exit(fSourceCoroutineID);
651: }
652: }
653:
654: /** Used to quickly terminate parse when running under a
655: startParse() thread. Only its type is important. */
656: class StopException extends RuntimeException {
657: static final long serialVersionUID = -1129245796185754956L;
658: }
659:
660: /** deliverMoreNodes() is a simple API which tells the coroutine
661: * parser that we need more nodes. This is intended to be called
662: * from one of our partner routines, and serves to encapsulate the
663: * details of how incremental parsing has been achieved.
664: *
665: * @param parsemore If true, tells the incremental filter to generate
666: * another chunk of output. If false, tells the filter that we're
667: * satisfied and it can terminate parsing of this document.
668: *
669: * @return Boolean.TRUE if there may be more events available by invoking
670: * deliverMoreNodes() again. Boolean.FALSE if parsing has run to completion (or been
671: * terminated by deliverMoreNodes(false). Or an exception object if something
672: * malfunctioned. %REVIEW% We _could_ actually throw the exception, but
673: * that would require runinng deliverMoreNodes() in a try/catch... and for many
674: * applications, exception will be simply be treated as "not TRUE" in
675: * any case.
676: * */
677: public Object deliverMoreNodes(boolean parsemore) {
678: // If parsing is already done, we can immediately say so
679: if (fNoMoreEvents)
680: return Boolean.FALSE;
681:
682: try {
683: Object result = fCoroutineManager.co_resume(
684: parsemore ? Boolean.TRUE : Boolean.FALSE,
685: fControllerCoroutineID, fSourceCoroutineID);
686: if (result == Boolean.FALSE)
687: fCoroutineManager.co_exit(fControllerCoroutineID);
688:
689: return result;
690: }
691:
692: // SHOULD NEVER OCCUR, since the coroutine number and coroutine manager
693: // are those previously established for this IncrementalSAXSource_Filter...
694: // So I'm just going to return it as a parsing exception, for now.
695: catch (NoSuchMethodException e) {
696: return e;
697: }
698: }
699:
700: //================================================================
701: /** Simple unit test. Attempt coroutine parsing of document indicated
702: * by first argument (as a URI), report progress.
703: */
704: /*
705: public static void main(String args[])
706: {
707: System.out.println("Starting...");
708:
709: org.xml.sax.XMLReader theSAXParser=
710: new org.apache.xerces.parsers.SAXParser();
711:
712:
713: for(int arg=0;arg<args.length;++arg)
714: {
715: // The filter is not currently designed to be restartable
716: // after a parse has ended. Generate a new one each time.
717: IncrementalSAXSource_Filter filter=
718: new IncrementalSAXSource_Filter();
719: // Use a serializer as our sample output
720: org.apache.xml.serialize.XMLSerializer trace;
721: trace=new org.apache.xml.serialize.XMLSerializer(System.out,null);
722: filter.setContentHandler(trace);
723: filter.setLexicalHandler(trace);
724:
725: try
726: {
727: InputSource source = new InputSource(args[arg]);
728: Object result=null;
729: boolean more=true;
730:
731: // init not issued; we _should_ automagically Do The Right Thing
732:
733: // Bind parser, kick off parsing in a thread
734: filter.setXMLReader(theSAXParser);
735: filter.startParse(source);
736:
737: for(result = filter.deliverMoreNodes(more);
738: (result instanceof Boolean && ((Boolean)result)==Boolean.TRUE);
739: result = filter.deliverMoreNodes(more))
740: {
741: System.out.println("\nSome parsing successful, trying more.\n");
742:
743: // Special test: Terminate parsing early.
744: if(arg+1<args.length && "!".equals(args[arg+1]))
745: {
746: ++arg;
747: more=false;
748: }
749:
750: }
751:
752: if (result instanceof Boolean && ((Boolean)result)==Boolean.FALSE)
753: {
754: System.out.println("\nFilter ended (EOF or on request).\n");
755: }
756: else if (result == null) {
757: System.out.println("\nUNEXPECTED: Filter says shut down prematurely.\n");
758: }
759: else if (result instanceof Exception) {
760: System.out.println("\nFilter threw exception:");
761: ((Exception)result).printStackTrace();
762: }
763:
764: }
765: catch(SAXException e)
766: {
767: e.printStackTrace();
768: }
769: } // end for
770: }
771: */
772: } // class IncrementalSAXSource_Filter
|