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
22package org.apache.xml.dtm.ref;
23
24import java.io.IOException;
25
26import org.apache.xml.res.XMLErrorResources;
27import org.apache.xml.res.XMLMessages;
28import org.apache.xml.utils.ThreadControllerWrapper;
29
30import org.xml.sax.Attributes;
31import org.xml.sax.ContentHandler;
32import org.xml.sax.DTDHandler;
33import org.xml.sax.ErrorHandler;
34import org.xml.sax.InputSource;
35import org.xml.sax.Locator;
36import org.xml.sax.SAXException;
37import org.xml.sax.SAXNotRecognizedException;
38import org.xml.sax.SAXNotSupportedException;
39import org.xml.sax.SAXParseException;
40import org.xml.sax.XMLReader;
41import 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 * */
67public class IncrementalSAXSource_Filter
68implements 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