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