Home | History | Annotate | Download | only in ref
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one
      3  * or more contributor license agreements. See the NOTICE file
      4  * distributed with this work for additional information
      5  * regarding copyright ownership. The ASF licenses this file
      6  * to you under the Apache License, Version 2.0 (the  "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *     http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 /*
     19  * $Id: IncrementalSAXSource_Filter.java 468653 2006-10-28 07:07:05Z minchau $
     20  */
     21 
     22 package org.apache.xml.dtm.ref;
     23 
     24 import java.io.IOException;
     25 
     26 import org.apache.xml.res.XMLErrorResources;
     27 import org.apache.xml.res.XMLMessages;
     28 import org.apache.xml.utils.ThreadControllerWrapper;
     29 
     30 import org.xml.sax.Attributes;
     31 import org.xml.sax.ContentHandler;
     32 import org.xml.sax.DTDHandler;
     33 import org.xml.sax.ErrorHandler;
     34 import org.xml.sax.InputSource;
     35 import org.xml.sax.Locator;
     36 import org.xml.sax.SAXException;
     37 import org.xml.sax.SAXNotRecognizedException;
     38 import org.xml.sax.SAXNotSupportedException;
     39 import org.xml.sax.SAXParseException;
     40 import org.xml.sax.XMLReader;
     41 import org.xml.sax.ext.LexicalHandler;
     42 
     43 /** <p>IncrementalSAXSource_Filter implements IncrementalSAXSource, using a
     44  * standard SAX2 event source as its input and parcelling out those
     45  * events gradually in reponse to deliverMoreNodes() requests.  Output from the
     46  * filter will be passed along to a SAX handler registered as our
     47  * listener, but those callbacks will pass through a counting stage
     48  * which periodically yields control back to the controller coroutine.
     49  * </p>
     50  *
     51  * <p>%REVIEW%: This filter is not currenly intended to be reusable
     52  * for parsing additional streams/documents. We may want to consider
     53  * making it resettable at some point in the future. But it's a
     54  * small object, so that'd be mostly a convenience issue; the cost
     55  * of allocating each time is trivial compared to the cost of processing
     56  * any nontrival stream.</p>
     57  *
     58  * <p>For a brief usage example, see the unit-test main() method.</p>
     59  *
     60  * <p>This is a simplification of the old CoroutineSAXParser, focusing
     61  * specifically on filtering. The resulting controller protocol is _far_
     62  * simpler and less error-prone; the only controller operation is deliverMoreNodes(),
     63  * and the only requirement is that deliverMoreNodes(false) be called if you want to
     64  * discard the rest of the stream and the previous deliverMoreNodes() didn't return
     65  * false.
     66  * */
     67 public class IncrementalSAXSource_Filter
     68 implements IncrementalSAXSource, ContentHandler, DTDHandler, LexicalHandler, ErrorHandler, Runnable
     69 {
     70   boolean DEBUG=false; //Internal status report
     71 
     72   //
     73   // Data
     74   //
     75   private CoroutineManager fCoroutineManager = null;
     76   private int fControllerCoroutineID = -1;
     77   private int fSourceCoroutineID = -1;
     78 
     79   private ContentHandler clientContentHandler=null; // %REVIEW% support multiple?
     80   private LexicalHandler clientLexicalHandler=null; // %REVIEW% support multiple?
     81   private DTDHandler clientDTDHandler=null; // %REVIEW% support multiple?
     82   private ErrorHandler clientErrorHandler=null; // %REVIEW% support multiple?
     83   private int eventcounter;
     84   private int frequency=5;
     85 
     86   // Flag indicating that no more events should be delivered -- either
     87   // because input stream ran to completion (endDocument), or because
     88   // the user requested an early stop via deliverMoreNodes(false).
     89   private boolean fNoMoreEvents=false;
     90 
     91   // Support for startParse()
     92   private XMLReader fXMLReader=null;
     93   private InputSource fXMLReaderInputSource=null;
     94 
     95   //
     96   // Constructors
     97   //
     98 
     99   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   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
    808