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