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