• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IncrementalSAXSource_Filter()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    * */
IncrementalSAXSource_Filter(CoroutineManager co, int controllerCoroutineID)106   public IncrementalSAXSource_Filter(CoroutineManager co, int controllerCoroutineID)
107   {
108     this.init( co, controllerCoroutineID, -1 );
109   }
110 
111   //
112   // Factories
113   //
createIncrementalSAXSource(CoroutineManager co, int controllerCoroutineID)114   static public IncrementalSAXSource createIncrementalSAXSource(CoroutineManager co, int controllerCoroutineID) {
115     return new IncrementalSAXSource_Filter(co, controllerCoroutineID);
116   }
117 
118   //
119   // Public methods
120   //
121 
init( CoroutineManager co, int controllerCoroutineID, int sourceCoroutineID)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    * */
setXMLReader(XMLReader eventsource)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
setContentHandler(ContentHandler handler)170   public void setContentHandler(ContentHandler handler)
171   {
172     clientContentHandler=handler;
173   }
174   // Register a DTD handler for us to output to
setDTDHandler(DTDHandler handler)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
setLexicalHandler(LexicalHandler handler)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
setErrHandler(ErrorHandler handler)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.
setReturnFrequency(int events)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   //
characters(char[] ch, int start, int length)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   }
endDocument()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   }
endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName)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   }
endPrefixMapping(java.lang.String prefix)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   }
ignorableWhitespace(char[] ch, int start, int length)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   }
processingInstruction(java.lang.String target, java.lang.String data)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   }
setDocumentLocator(Locator locator)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   }
skippedEntity(java.lang.String name)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   }
startDocument()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   }
startElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName, Attributes atts)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   }
startPrefixMapping(java.lang.String prefix, java.lang.String uri)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   //
comment(char[] ch, int start, int length)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   }
endCDATA()363   public void endCDATA()
364        throws org.xml.sax.SAXException
365   {
366     if(null!=clientLexicalHandler)
367       clientLexicalHandler.endCDATA();
368   }
endDTD()369   public void endDTD()
370        throws org.xml.sax.SAXException
371   {
372     if(null!=clientLexicalHandler)
373       clientLexicalHandler.endDTD();
374   }
endEntity(java.lang.String name)375   public void endEntity(java.lang.String name)
376        throws org.xml.sax.SAXException
377   {
378     if(null!=clientLexicalHandler)
379       clientLexicalHandler.endEntity(name);
380   }
startCDATA()381   public void startCDATA()
382        throws org.xml.sax.SAXException
383   {
384     if(null!=clientLexicalHandler)
385       clientLexicalHandler.startCDATA();
386   }
startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId)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   }
startEntity(java.lang.String name)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 
notationDecl(String a, String b, String c)404   public void notationDecl(String a, String b, String c) throws SAXException
405   {
406   	if(null!=clientDTDHandler)
407 	  	clientDTDHandler.notationDecl(a,b,c);
408   }
unparsedEntityDecl(String a, String b, String c, String d)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   //
error(SAXParseException exception)430   public void error(SAXParseException exception) throws SAXException
431   {
432     if(null!=clientErrorHandler)
433       clientErrorHandler.error(exception);
434   }
435 
fatalError(SAXParseException exception)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 
warning(SAXParseException exception)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 
getSourceCoroutineID()459   public int getSourceCoroutineID() {
460     return fSourceCoroutineID;
461   }
getControllerCoroutineID()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    * */
getCoroutineManager()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    * */
count_and_yield(boolean moreExpected)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    */
co_entry_pause()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    */
co_yield(boolean moreRemains)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    * */
startParse(InputSource source)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    */
run()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    * */
deliverMoreNodes(boolean parsemore)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