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