001package org.apache.commons.digester3; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import static java.lang.String.format; 023 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.Reader; 029import java.lang.reflect.InvocationTargetException; 030import java.net.MalformedURLException; 031import java.net.URL; 032import java.net.URLConnection; 033import java.util.ArrayList; 034import java.util.Collections; 035import java.util.EmptyStackException; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039import java.util.Stack; 040import java.util.concurrent.Callable; 041import java.util.concurrent.ExecutorService; 042import java.util.concurrent.Future; 043 044import javax.xml.parsers.ParserConfigurationException; 045import javax.xml.parsers.SAXParser; 046import javax.xml.parsers.SAXParserFactory; 047import javax.xml.validation.Schema; 048 049import org.apache.commons.logging.Log; 050import org.apache.commons.logging.LogFactory; 051import org.xml.sax.Attributes; 052import org.xml.sax.ContentHandler; 053import org.xml.sax.EntityResolver; 054import org.xml.sax.ErrorHandler; 055import org.xml.sax.InputSource; 056import org.xml.sax.Locator; 057import org.xml.sax.SAXException; 058import org.xml.sax.SAXNotRecognizedException; 059import org.xml.sax.SAXNotSupportedException; 060import org.xml.sax.SAXParseException; 061import org.xml.sax.XMLReader; 062import org.xml.sax.helpers.DefaultHandler; 063 064/** 065 * <p> 066 * A <strong>Digester</strong> processes an XML input stream by matching a series of element nesting patterns to execute 067 * Rules that have been added prior to the start of parsing. 068 * </p> 069 * <p> 070 * See the <a href="package-summary.html#package_description">Digester Developer Guide</a> for more information. 071 * </p> 072 * <p> 073 * <strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may only be used within the context of a single 074 * thread at a time, and a call to <code>parse()</code> must be completed before another can be initiated even from the 075 * same thread. 076 * </p> 077 * <p> 078 * A Digester instance should not be used for parsing more than one input document. The problem is that the Digester 079 * class has quite a few member variables whose values "evolve" as SAX events are received during a parse. When reusing 080 * the Digester instance, all these members must be reset back to their initial states before the second parse begins. 081 * The "clear()" method makes a stab at resetting these, but it is actually rather a difficult problem. If you are 082 * determined to reuse Digester instances, then at the least you should call the clear() method before each parse, and 083 * must call it if the Digester parse terminates due to an exception during a parse. 084 * </p> 085 * <p> 086 * <strong>LEGACY IMPLEMENTATION NOTE</strong> - When using the legacy XML schema support (instead of using the 087 * {@link Schema} class), a bug in Xerces 2.0.2 prevents the support of XML schema. You need Xerces 2.1/2.3 and up to 088 * make this class work with the legacy XML schema support. 089 * </p> 090 * <p> 091 * This package was inspired by the <code>XmlMapper</code> class that was part of Tomcat 3.0 and 3.1, but is organized 092 * somewhat differently. 093 * </p> 094 */ 095public class Digester 096 extends DefaultHandler 097{ 098 099 // --------------------------------------------------------- Constructors 100 101 /** 102 * Construct a new Digester with default properties. 103 */ 104 public Digester() 105 { 106 super(); 107 } 108 109 /** 110 * Construct a new Digester, allowing a SAXParser to be passed in. This allows Digester to be used in environments 111 * which are unfriendly to JAXP1.1 (such as WebLogic 6.0). This may help in places where you are able to load JAXP 112 * 1.1 classes yourself. 113 * 114 * @param parser The SAXParser used to parse XML streams 115 */ 116 public Digester( SAXParser parser ) 117 { 118 super(); 119 this.parser = parser; 120 } 121 122 /** 123 * Construct a new Digester, allowing an XMLReader to be passed in. This allows Digester to be used in environments 124 * which are unfriendly to JAXP1.1 (such as WebLogic 6.0). Note that if you use this option you have to configure 125 * namespace and validation support yourself, as these properties only affect the SAXParser and emtpy constructor. 126 * 127 * @param reader The XMLReader used to parse XML streams 128 */ 129 public Digester( XMLReader reader ) 130 { 131 super(); 132 this.reader = reader; 133 } 134 135 // --------------------------------------------------- Instance Variables 136 137 /** 138 * The body text of the current element. 139 */ 140 private StringBuilder bodyText = new StringBuilder(); 141 142 /** 143 * The stack of body text string buffers for surrounding elements. 144 */ 145 private final Stack<StringBuilder> bodyTexts = new Stack<StringBuilder>(); 146 147 /** 148 * Stack whose elements are List objects, each containing a list of Rule objects as returned from Rules.getMatch(). 149 * As each xml element in the input is entered, the matching rules are pushed onto this stack. After the end tag is 150 * reached, the matches are popped again. The depth of is stack is therefore exactly the same as the current 151 * "nesting" level of the input xml. 152 * 153 * @since 1.6 154 */ 155 private final Stack<List<Rule>> matches = new Stack<List<Rule>>(); 156 157 /** 158 * The class loader to use for instantiating application objects. If not specified, the context class loader, or the 159 * class loader used to load Digester itself, is used, based on the value of the <code>useContextClassLoader</code> 160 * variable. 161 */ 162 private ClassLoader classLoader = null; 163 164 /** 165 * Has this Digester been configured yet. 166 */ 167 private boolean configured = false; 168 169 /** 170 * The EntityResolver used by the SAX parser. By default it use this class 171 */ 172 private EntityResolver entityResolver; 173 174 /** 175 * The URLs of entityValidator that have been registered, keyed by the public identifier that corresponds. 176 */ 177 private final HashMap<String, URL> entityValidator = new HashMap<String, URL>(); 178 179 /** 180 * The application-supplied error handler that is notified when parsing warnings, errors, or fatal errors occur. 181 */ 182 private ErrorHandler errorHandler = null; 183 184 /** 185 * The SAXParserFactory that is created the first time we need it. 186 */ 187 private SAXParserFactory factory = null; 188 189 /** 190 * The Locator associated with our parser. 191 */ 192 private Locator locator = null; 193 194 /** 195 * The current match pattern for nested element processing. 196 */ 197 private String match = ""; 198 199 /** 200 * Do we want a "namespace aware" parser. 201 */ 202 private boolean namespaceAware = false; 203 204 /** 205 * The executor service to run asynchronous parse method. 206 * @since 3.1 207 */ 208 private ExecutorService executorService; 209 210 /** 211 * Registered namespaces we are currently processing. The key is the namespace prefix that was declared in the 212 * document. The value is an Stack of the namespace URIs this prefix has been mapped to -- the top Stack element is 213 * the most current one. (This architecture is required because documents can declare nested uses of the same prefix 214 * for different Namespace URIs). 215 */ 216 private final HashMap<String, Stack<String>> namespaces = new HashMap<String, Stack<String>>(); 217 218 /** 219 * Do we want a "XInclude aware" parser. 220 */ 221 private boolean xincludeAware = false; 222 223 /** 224 * The parameters stack being utilized by CallMethodRule and CallParamRule rules. 225 * 226 * @since 2.0 227 */ 228 private final Stack<Object[]> params = new Stack<Object[]>(); 229 230 /** 231 * The SAXParser we will use to parse the input stream. 232 */ 233 private SAXParser parser = null; 234 235 /** 236 * The public identifier of the DTD we are currently parsing under (if any). 237 */ 238 private String publicId = null; 239 240 /** 241 * The XMLReader used to parse digester rules. 242 */ 243 private XMLReader reader = null; 244 245 /** 246 * The "root" element of the stack (in other words, the last object that was popped. 247 */ 248 private Object root = null; 249 250 /** 251 * The <code>Rules</code> implementation containing our collection of <code>Rule</code> instances and associated 252 * matching policy. If not established before the first rule is added, a default implementation will be provided. 253 */ 254 private Rules rules = null; 255 256 /** 257 * The XML schema to use for validating an XML instance. 258 * 259 * @since 2.0 260 */ 261 private Schema schema = null; 262 263 /** 264 * The object stack being constructed. 265 */ 266 private final Stack<Object> stack = new Stack<Object>(); 267 268 /** 269 * Do we want to use the Context ClassLoader when loading classes for instantiating new objects. Default is 270 * <code>true</code>. 271 */ 272 private boolean useContextClassLoader = true; 273 274 /** 275 * Do we want to use a validating parser. 276 */ 277 private boolean validating = false; 278 279 /** 280 * The Log to which most logging calls will be made. 281 */ 282 private Log log = LogFactory.getLog( "org.apache.commons.digester3.Digester" ); 283 284 /** 285 * The Log to which all SAX event related logging calls will be made. 286 */ 287 private Log saxLog = LogFactory.getLog( "org.apache.commons.digester3.Digester.sax" ); 288 289 /** 290 * The schema language supported. By default, we use this one. 291 */ 292 protected static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; 293 294 /** 295 * An optional class that substitutes values in attributes and body text. This may be null and so a null check is 296 * always required before use. 297 */ 298 private Substitutor substitutor; 299 300 /** Stacks used for interrule communication, indexed by name String */ 301 private final HashMap<String, Stack<Object>> stacksByName = new HashMap<String, Stack<Object>>(); 302 303 /** 304 * If not null, then calls by the parser to this object's characters, startElement, endElement and 305 * processingInstruction methods are forwarded to the specified object. This is intended to allow rules to 306 * temporarily "take control" of the sax events. In particular, this is used by NodeCreateRule. 307 * <p> 308 * See setCustomContentHandler. 309 */ 310 private ContentHandler customContentHandler = null; 311 312 /** 313 * Object which will receive callbacks for every pop/push action on the default stack or named stacks. 314 */ 315 private StackAction stackAction = null; 316 317 // ------------------------------------------------------------- Properties 318 319 /** 320 * Return the currently mapped namespace URI for the specified prefix, if any; otherwise return <code>null</code>. 321 * These mappings come and go dynamically as the document is parsed. 322 * 323 * @param prefix Prefix to look up 324 * @return the currently mapped namespace URI for the specified prefix 325 */ 326 public String findNamespaceURI( String prefix ) 327 { 328 Stack<String> nsStack = namespaces.get( prefix ); 329 if ( nsStack == null ) 330 { 331 return null; 332 } 333 try 334 { 335 return ( nsStack.peek() ); 336 } 337 catch ( EmptyStackException e ) 338 { 339 return null; 340 } 341 } 342 343 /** 344 * Return the class loader to be used for instantiating application objects when required. This is determined based 345 * upon the following rules: 346 * <ul> 347 * <li>The class loader set by <code>setClassLoader()</code>, if any</li> 348 * <li>The thread context class loader, if it exists and the <code>useContextClassLoader</code> property is set to 349 * true</li> 350 * <li>The class loader used to load the Digester class itself. 351 * </ul> 352 * 353 * @return the class loader to be used for instantiating application objects. 354 */ 355 public ClassLoader getClassLoader() 356 { 357 if ( this.classLoader != null ) 358 { 359 return ( this.classLoader ); 360 } 361 if ( this.useContextClassLoader ) 362 { 363 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 364 if ( classLoader != null ) 365 { 366 return ( classLoader ); 367 } 368 } 369 return ( this.getClass().getClassLoader() ); 370 } 371 372 /** 373 * Set the class loader to be used for instantiating application objects when required. 374 * 375 * @param classLoader The new class loader to use, or <code>null</code> to revert to the standard rules 376 */ 377 public void setClassLoader( ClassLoader classLoader ) 378 { 379 this.classLoader = classLoader; 380 } 381 382 /** 383 * Return the current depth of the element stack. 384 * 385 * @return the current depth of the element stack. 386 */ 387 public int getCount() 388 { 389 return ( stack.size() ); 390 } 391 392 /** 393 * Return the name of the XML element that is currently being processed. 394 * 395 * @return the name of the XML element that is currently being processed. 396 */ 397 public String getCurrentElementName() 398 { 399 String elementName = match; 400 int lastSlash = elementName.lastIndexOf( '/' ); 401 if ( lastSlash >= 0 ) 402 { 403 elementName = elementName.substring( lastSlash + 1 ); 404 } 405 return ( elementName ); 406 } 407 408 /** 409 * Return the error handler for this Digester. 410 * 411 * @return the error handler for this Digester. 412 */ 413 public ErrorHandler getErrorHandler() 414 { 415 return ( this.errorHandler ); 416 } 417 418 /** 419 * Set the error handler for this Digester. 420 * 421 * @param errorHandler The new error handler 422 */ 423 public void setErrorHandler( ErrorHandler errorHandler ) 424 { 425 this.errorHandler = errorHandler; 426 } 427 428 /** 429 * Return the SAXParserFactory we will use, creating one if necessary. 430 * 431 * @return the SAXParserFactory we will use, creating one if necessary. 432 */ 433 public SAXParserFactory getFactory() 434 { 435 if ( factory == null ) 436 { 437 factory = SAXParserFactory.newInstance(); 438 factory.setNamespaceAware( namespaceAware ); 439 factory.setXIncludeAware( xincludeAware ); 440 factory.setValidating( validating ); 441 factory.setSchema( schema ); 442 } 443 return ( factory ); 444 } 445 446 /** 447 * Returns a flag indicating whether the requested feature is supported by the underlying implementation of 448 * <code>org.xml.sax.XMLReader</code>. See <a href="http://www.saxproject.org">the saxproject website</a> for 449 * information about the standard SAX2 feature flags. 450 * 451 * @param feature Name of the feature to inquire about 452 * @return true, if the requested feature is supported by the underlying implementation of 453 * <code>org.xml.sax.XMLReader</code>, false otherwise 454 * @exception ParserConfigurationException if a parser configuration error occurs 455 * @exception SAXNotRecognizedException if the property name is not recognized 456 * @exception SAXNotSupportedException if the property name is recognized but not supported 457 */ 458 public boolean getFeature( String feature ) 459 throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException 460 { 461 return ( getFactory().getFeature( feature ) ); 462 } 463 464 /** 465 * Sets a flag indicating whether the requested feature is supported by the underlying implementation of 466 * <code>org.xml.sax.XMLReader</code>. See <a href="http://www.saxproject.org">the saxproject website</a> for 467 * information about the standard SAX2 feature flags. In order to be effective, this method must be called 468 * <strong>before</strong> the <code>getParser()</code> method is called for the first time, either directly or 469 * indirectly. 470 * 471 * @param feature Name of the feature to set the status for 472 * @param value The new value for this feature 473 * @exception ParserConfigurationException if a parser configuration error occurs 474 * @exception SAXNotRecognizedException if the property name is not recognized 475 * @exception SAXNotSupportedException if the property name is recognized but not supported 476 */ 477 public void setFeature( String feature, boolean value ) 478 throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException 479 { 480 getFactory().setFeature( feature, value ); 481 } 482 483 /** 484 * Return the current Logger associated with this instance of the Digester 485 * 486 * @return the current Logger associated with this instance of the Digester 487 */ 488 public Log getLogger() 489 { 490 return log; 491 } 492 493 /** 494 * Set the current logger for this Digester. 495 * 496 * @param log the current logger for this Digester. 497 */ 498 public void setLogger( Log log ) 499 { 500 this.log = log; 501 } 502 503 /** 504 * Gets the logger used for logging SAX-related information. <strong>Note</strong> the output is finely grained. 505 * 506 * @return the logger used for logging SAX-related information 507 * @since 1.6 508 */ 509 public Log getSAXLogger() 510 { 511 return saxLog; 512 } 513 514 /** 515 * Sets the logger used for logging SAX-related information. <strong>Note</strong> the output is finely grained. 516 * 517 * @param saxLog the logger used for logging SAX-related information, not null 518 * @since 1.6 519 */ 520 public void setSAXLogger( Log saxLog ) 521 { 522 this.saxLog = saxLog; 523 } 524 525 /** 526 * Return the current rule match path 527 * 528 * @return the current rule match path 529 */ 530 public String getMatch() 531 { 532 return match; 533 } 534 535 /** 536 * Return a Stack whose elements are List objects, each containing a list of 537 * Rule objects as returned from Rules.getMatch(). 538 * 539 * @return a Stack whose elements are List objects, each containing a list of 540 * Rule objects as returned from Rules.getMatch(). 541 * @since 3.0 542 */ 543 public Stack<List<Rule>> getMatches() 544 { 545 return matches; 546 } 547 548 /** 549 * Return the "namespace aware" flag for parsers we create. 550 * 551 * @return the "namespace aware" flag for parsers we create. 552 */ 553 public boolean getNamespaceAware() 554 { 555 return ( this.namespaceAware ); 556 } 557 558 /** 559 * Set the "namespace aware" flag for parsers we create. 560 * 561 * @param namespaceAware The new "namespace aware" flag 562 */ 563 public void setNamespaceAware( boolean namespaceAware ) 564 { 565 this.namespaceAware = namespaceAware; 566 } 567 568 /** 569 * Return the XInclude-aware flag for parsers we create. XInclude functionality additionally requires 570 * namespace-awareness. 571 * 572 * @return The XInclude-aware flag 573 * @see #getNamespaceAware() 574 * @since 2.0 575 */ 576 public boolean getXIncludeAware() 577 { 578 return ( this.xincludeAware ); 579 } 580 581 /** 582 * Set the XInclude-aware flag for parsers we create. This additionally requires namespace-awareness. 583 * 584 * @param xincludeAware The new XInclude-aware flag 585 * @see #setNamespaceAware(boolean) 586 * @since 2.0 587 */ 588 public void setXIncludeAware( boolean xincludeAware ) 589 { 590 this.xincludeAware = xincludeAware; 591 } 592 593 /** 594 * Set the public id of the current file being parse. 595 * 596 * @param publicId the DTD/Schema public's id. 597 */ 598 public void setPublicId( String publicId ) 599 { 600 this.publicId = publicId; 601 } 602 603 /** 604 * Return the public identifier of the DTD we are currently parsing under, if any. 605 * 606 * @return the public identifier of the DTD we are currently parsing under, if any. 607 */ 608 public String getPublicId() 609 { 610 return ( this.publicId ); 611 } 612 613 /** 614 * Return the namespace URI that will be applied to all subsequently added <code>Rule</code> objects. 615 * 616 * @return the namespace URI that will be applied to all subsequently added <code>Rule</code> objects. 617 */ 618 public String getRuleNamespaceURI() 619 { 620 return ( getRules().getNamespaceURI() ); 621 } 622 623 /** 624 * Set the namespace URI that will be applied to all subsequently added <code>Rule</code> objects. 625 * 626 * @param ruleNamespaceURI Namespace URI that must match on all subsequently added rules, or <code>null</code> for 627 * matching regardless of the current namespace URI 628 */ 629 public void setRuleNamespaceURI( String ruleNamespaceURI ) 630 { 631 getRules().setNamespaceURI( ruleNamespaceURI ); 632 } 633 634 /** 635 * Return the SAXParser we will use to parse the input stream. 636 * 637 * If there is a problem creating the parser, return <code>null</code>. 638 * 639 * @return the SAXParser we will use to parse the input stream 640 */ 641 public SAXParser getParser() 642 { 643 // Return the parser we already created (if any) 644 if ( parser != null ) 645 { 646 return ( parser ); 647 } 648 649 // Create a new parser 650 try 651 { 652 parser = getFactory().newSAXParser(); 653 } 654 catch ( Exception e ) 655 { 656 log.error( "Digester.getParser: ", e ); 657 return ( null ); 658 } 659 660 return ( parser ); 661 } 662 663 /** 664 * Return the current value of the specified property for the underlying <code>XMLReader</code> implementation. 665 * 666 * See <a href="http://www.saxproject.org">the saxproject website</a> for information about the standard SAX2 667 * properties. 668 * 669 * @param property Property name to be retrieved 670 * @return the current value of the specified property for the underlying <code>XMLReader</code> implementation. 671 * @exception SAXNotRecognizedException if the property name is not recognized 672 * @exception SAXNotSupportedException if the property name is recognized but not supported 673 */ 674 public Object getProperty( String property ) 675 throws SAXNotRecognizedException, SAXNotSupportedException 676 { 677 return ( getParser().getProperty( property ) ); 678 } 679 680 /** 681 * Set the current value of the specified property for the underlying <code>XMLReader</code> implementation. See <a 682 * href="http://www.saxproject.org">the saxproject website</a> for information about the standard SAX2 properties. 683 * 684 * @param property Property name to be set 685 * @param value Property value to be set 686 * @exception SAXNotRecognizedException if the property name is not recognized 687 * @exception SAXNotSupportedException if the property name is recognized but not supported 688 */ 689 public void setProperty( String property, Object value ) 690 throws SAXNotRecognizedException, SAXNotSupportedException 691 { 692 getParser().setProperty( property, value ); 693 } 694 695 /** 696 * Return the <code>Rules</code> implementation object containing our rules collection and associated matching 697 * policy. If none has been established, a default implementation will be created and returned. 698 * 699 * @return the <code>Rules</code> implementation object. 700 */ 701 public Rules getRules() 702 { 703 if ( this.rules == null ) 704 { 705 this.rules = new RulesBase(); 706 this.rules.setDigester( this ); 707 } 708 return ( this.rules ); 709 } 710 711 /** 712 * Set the <code>Rules</code> implementation object containing our rules collection and associated matching policy. 713 * 714 * @param rules New Rules implementation 715 */ 716 public void setRules( Rules rules ) 717 { 718 this.rules = rules; 719 this.rules.setDigester( this ); 720 } 721 722 /** 723 * Return the XML Schema used when parsing. 724 * 725 * @return The {@link Schema} instance in use. 726 * @since 2.0 727 */ 728 public Schema getXMLSchema() 729 { 730 return ( this.schema ); 731 } 732 733 /** 734 * Set the XML Schema to be used when parsing. 735 * 736 * @param schema The {@link Schema} instance to use. 737 * @since 2.0 738 */ 739 public void setXMLSchema( Schema schema ) 740 { 741 this.schema = schema; 742 } 743 744 /** 745 * Return the boolean as to whether the context ClassLoader should be used. 746 * 747 * @return true, if the context ClassLoader should be used, false otherwise. 748 */ 749 public boolean getUseContextClassLoader() 750 { 751 return useContextClassLoader; 752 } 753 754 /** 755 * Determine whether to use the Context ClassLoader (the one found by calling 756 * <code>Thread.currentThread().getContextClassLoader()</code>) to resolve/load classes that are defined in various 757 * rules. If not using Context ClassLoader, then the class-loading defaults to using the calling-class' ClassLoader. 758 * 759 * @param use determines whether to use Context ClassLoader. 760 */ 761 public void setUseContextClassLoader( boolean use ) 762 { 763 useContextClassLoader = use; 764 } 765 766 /** 767 * Return the validating parser flag. 768 * 769 * @return the validating parser flag. 770 */ 771 public boolean getValidating() 772 { 773 return ( this.validating ); 774 } 775 776 /** 777 * Set the validating parser flag. This must be called before <code>parse()</code> is called the first time. 778 * By default the value of this is set to false. 779 * 780 * It essentially just controls the DTD validation. To use modern schema languages use the 781 * {@link #setXMLSchema(Schema)} method to associate a schema to a parser. 782 * 783 * @param validating The new validating parser flag. 784 * @see javax.xml.parsers.SAXParserFactory#setValidating(boolean) for more detail. 785 */ 786 public void setValidating( boolean validating ) 787 { 788 this.validating = validating; 789 } 790 791 /** 792 * Return the XMLReader to be used for parsing the input document. 793 * 794 * FIXME: there is a bug in JAXP/XERCES that prevent the use of a parser that contains a schema with a DTD. 795 * 796 * @return the XMLReader to be used for parsing the input document. 797 * @exception SAXException if no XMLReader can be instantiated 798 */ 799 public XMLReader getXMLReader() 800 throws SAXException 801 { 802 if ( reader == null ) 803 { 804 reader = getParser().getXMLReader(); 805 } 806 807 reader.setDTDHandler( this ); 808 reader.setContentHandler( this ); 809 810 if ( entityResolver == null ) 811 { 812 reader.setEntityResolver( this ); 813 } 814 else 815 { 816 reader.setEntityResolver( entityResolver ); 817 } 818 819 if ( this.errorHandler != null ) 820 { 821 reader.setErrorHandler( this.errorHandler ); 822 } 823 else 824 { 825 reader.setErrorHandler( this ); 826 } 827 828 return reader; 829 } 830 831 /** 832 * Gets the <code>Substitutor</code> used to convert attributes and body text. 833 * 834 * @return the <code>Substitutor</code> used to convert attributes and body text, 835 * null if not substitutions are to be performed. 836 */ 837 public Substitutor getSubstitutor() 838 { 839 return substitutor; 840 } 841 842 /** 843 * Sets the <code>Substitutor</code> to be used to convert attributes and body text. 844 * 845 * @param substitutor the Substitutor to be used to convert attributes and body text or null if not substitution of 846 * these values is to be performed. 847 */ 848 public void setSubstitutor( Substitutor substitutor ) 849 { 850 this.substitutor = substitutor; 851 } 852 853 /** 854 * returns the custom SAX ContentHandler where events are redirected. 855 * 856 * @return the custom SAX ContentHandler where events are redirected. 857 * @see #setCustomContentHandler(ContentHandler) 858 * @since 1.7 859 */ 860 public ContentHandler getCustomContentHandler() 861 { 862 return customContentHandler; 863 } 864 865 /** 866 * Redirects (or cancels redirecting) of SAX ContentHandler events to an external object. 867 * <p> 868 * When this object's customContentHandler is non-null, any SAX events received from the parser will simply be 869 * passed on to the specified object instead of this object handling them. This allows Rule classes to take control 870 * of the SAX event stream for a while in order to do custom processing. Such a rule should save the old value 871 * before setting a new one, and restore the old value in order to resume normal digester processing. 872 * <p> 873 * An example of a Rule which needs this feature is NodeCreateRule. 874 * <p> 875 * Note that saving the old value is probably not needed as it should always be null; a custom rule that wants to 876 * take control could only have been called when there was no custom content handler. But it seems cleaner to 877 * properly save/restore the value and maybe some day this will come in useful. 878 * <p> 879 * Note also that this is not quite equivalent to 880 * 881 * <pre> 882 * digester.getXMLReader().setContentHandler( handler ) 883 * </pre> 884 * 885 * for these reasons: 886 * <ul> 887 * <li>Some xml parsers don't like having setContentHandler called after parsing has started. The Aelfred parser is 888 * one example.</li> 889 * <li>Directing the events via the Digester object potentially allows us to log information about those SAX events 890 * at the digester level.</li> 891 * </ul> 892 * 893 * @param handler the custom SAX ContentHandler where events are redirected. 894 * @since 1.7 895 */ 896 public void setCustomContentHandler( ContentHandler handler ) 897 { 898 customContentHandler = handler; 899 } 900 901 /** 902 * Define a callback object which is invoked whenever an object is pushed onto a digester object stack, 903 * or popped off one. 904 * 905 * @param stackAction the callback object which is invoked whenever an object is pushed onto a digester 906 * object stack, or popped off one. 907 * @since 1.8 908 */ 909 public void setStackAction( StackAction stackAction ) 910 { 911 this.stackAction = stackAction; 912 } 913 914 /** 915 * Return the callback object which is invoked whenever an object is pushed onto a digester object stack, 916 * or popped off one. 917 * 918 * @return the callback object which is invoked whenever an object is pushed onto a digester object stack, 919 * or popped off one. 920 * @see #setStackAction(StackAction) 921 * @since 1.8 922 */ 923 public StackAction getStackAction() 924 { 925 return stackAction; 926 } 927 928 /** 929 * Get the most current namespaces for all prefixes. 930 * 931 * @return Map A map with namespace prefixes as keys and most current namespace URIs for the corresponding prefixes 932 * as values 933 * @since 1.8 934 */ 935 public Map<String, String> getCurrentNamespaces() 936 { 937 if ( !namespaceAware ) 938 { 939 log.warn( "Digester is not namespace aware" ); 940 } 941 Map<String, String> currentNamespaces = new HashMap<String, String>(); 942 for ( Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet() ) 943 { 944 try 945 { 946 currentNamespaces.put( nsEntry.getKey(), nsEntry.getValue().peek() ); 947 } 948 catch ( RuntimeException e ) 949 { 950 // rethrow, after logging 951 log.error( e.getMessage(), e ); 952 throw e; 953 } 954 } 955 return currentNamespaces; 956 } 957 958 /** 959 * Returns the executor service used to run asynchronous parse method. 960 * 961 * @return the executor service used to run asynchronous parse method 962 * @since 3.1 963 */ 964 public ExecutorService getExecutorService() 965 { 966 return executorService; 967 } 968 969 /** 970 * Sets the executor service to run asynchronous parse method. 971 * 972 * @param executorService the executor service to run asynchronous parse method 973 * @since 3.1 974 */ 975 public void setExecutorService( ExecutorService executorService ) 976 { 977 this.executorService = executorService; 978 } 979 980 // ------------------------------------------------- ContentHandler Methods 981 982 /** 983 * {@inheritDoc} 984 */ 985 @Override 986 public void characters( char buffer[], int start, int length ) 987 throws SAXException 988 { 989 if ( customContentHandler != null ) 990 { 991 // forward calls instead of handling them here 992 customContentHandler.characters( buffer, start, length ); 993 return; 994 } 995 996 if ( saxLog.isDebugEnabled() ) 997 { 998 saxLog.debug( "characters(" + new String( buffer, start, length ) + ")" ); 999 } 1000 1001 bodyText.append( buffer, start, length ); 1002 } 1003 1004 /** 1005 * {@inheritDoc} 1006 */ 1007 @Override 1008 public void endDocument() 1009 throws SAXException 1010 { 1011 if ( saxLog.isDebugEnabled() ) 1012 { 1013 if ( getCount() > 1 ) 1014 { 1015 saxLog.debug( "endDocument(): " + getCount() + " elements left" ); 1016 } 1017 else 1018 { 1019 saxLog.debug( "endDocument()" ); 1020 } 1021 } 1022 1023 // Fire "finish" events for all defined rules 1024 for ( Rule rule : getRules().rules() ) 1025 { 1026 try 1027 { 1028 rule.finish(); 1029 } 1030 catch ( Exception e ) 1031 { 1032 log.error( "Finish event threw exception", e ); 1033 throw createSAXException( e ); 1034 } 1035 catch ( Error e ) 1036 { 1037 log.error( "Finish event threw error", e ); 1038 throw e; 1039 } 1040 } 1041 1042 // Perform final cleanup 1043 clear(); 1044 } 1045 1046 /** 1047 * {@inheritDoc} 1048 */ 1049 @Override 1050 public void endElement( String namespaceURI, String localName, String qName ) 1051 throws SAXException 1052 { 1053 if ( customContentHandler != null ) 1054 { 1055 // forward calls instead of handling them here 1056 customContentHandler.endElement( namespaceURI, localName, qName ); 1057 return; 1058 } 1059 1060 boolean debug = log.isDebugEnabled(); 1061 1062 if ( debug ) 1063 { 1064 if ( saxLog.isDebugEnabled() ) 1065 { 1066 saxLog.debug( "endElement(" + namespaceURI + "," + localName + "," + qName + ")" ); 1067 } 1068 log.debug( " match='" + match + "'" ); 1069 log.debug( " bodyText='" + bodyText + "'" ); 1070 } 1071 1072 // the actual element name is either in localName or qName, depending 1073 // on whether the parser is namespace aware 1074 String name = localName; 1075 if ( ( name == null ) || ( name.length() < 1 ) ) 1076 { 1077 name = qName; 1078 } 1079 1080 // Fire "body" events for all relevant rules 1081 List<Rule> rules = matches.pop(); 1082 if ( ( rules != null ) && ( rules.size() > 0 ) ) 1083 { 1084 String bodyText = this.bodyText.toString(); 1085 Substitutor substitutor = getSubstitutor(); 1086 if ( substitutor != null ) 1087 { 1088 bodyText = substitutor.substitute( bodyText ); 1089 } 1090 for ( int i = 0; i < rules.size(); i++ ) 1091 { 1092 try 1093 { 1094 Rule rule = rules.get( i ); 1095 if ( debug ) 1096 { 1097 log.debug( " Fire body() for " + rule ); 1098 } 1099 rule.body( namespaceURI, name, bodyText ); 1100 } 1101 catch ( Exception e ) 1102 { 1103 log.error( "Body event threw exception", e ); 1104 throw createSAXException( e ); 1105 } 1106 catch ( Error e ) 1107 { 1108 log.error( "Body event threw error", e ); 1109 throw e; 1110 } 1111 } 1112 } 1113 else 1114 { 1115 if ( debug ) 1116 { 1117 log.debug( " No rules found matching '" + match + "'." ); 1118 } 1119 } 1120 1121 // Recover the body text from the surrounding element 1122 bodyText = bodyTexts.pop(); 1123 if ( debug ) 1124 { 1125 log.debug( " Popping body text '" + bodyText.toString() + "'" ); 1126 } 1127 1128 // Fire "end" events for all relevant rules in reverse order 1129 if ( rules != null ) 1130 { 1131 for ( int i = 0; i < rules.size(); i++ ) 1132 { 1133 int j = ( rules.size() - i ) - 1; 1134 try 1135 { 1136 Rule rule = rules.get( j ); 1137 if ( debug ) 1138 { 1139 log.debug( " Fire end() for " + rule ); 1140 } 1141 rule.end( namespaceURI, name ); 1142 } 1143 catch ( Exception e ) 1144 { 1145 log.error( "End event threw exception", e ); 1146 throw createSAXException( e ); 1147 } 1148 catch ( Error e ) 1149 { 1150 log.error( "End event threw error", e ); 1151 throw e; 1152 } 1153 } 1154 } 1155 1156 // Recover the previous match expression 1157 int slash = match.lastIndexOf( '/' ); 1158 if ( slash >= 0 ) 1159 { 1160 match = match.substring( 0, slash ); 1161 } 1162 else 1163 { 1164 match = ""; 1165 } 1166 } 1167 1168 /** 1169 * {@inheritDoc} 1170 */ 1171 @Override 1172 public void endPrefixMapping( String prefix ) 1173 throws SAXException 1174 { 1175 if ( saxLog.isDebugEnabled() ) 1176 { 1177 saxLog.debug( "endPrefixMapping(" + prefix + ")" ); 1178 } 1179 1180 // Deregister this prefix mapping 1181 Stack<String> stack = namespaces.get( prefix ); 1182 if ( stack == null ) 1183 { 1184 return; 1185 } 1186 try 1187 { 1188 stack.pop(); 1189 if ( stack.empty() ) 1190 { 1191 namespaces.remove( prefix ); 1192 } 1193 } 1194 catch ( EmptyStackException e ) 1195 { 1196 throw createSAXException( "endPrefixMapping popped too many times" ); 1197 } 1198 } 1199 1200 /** 1201 * {@inheritDoc} 1202 */ 1203 @Override 1204 public void ignorableWhitespace( char buffer[], int start, int len ) 1205 throws SAXException 1206 { 1207 if ( saxLog.isDebugEnabled() ) 1208 { 1209 saxLog.debug( "ignorableWhitespace(" + new String( buffer, start, len ) + ")" ); 1210 } 1211 1212 // No processing required 1213 } 1214 1215 /** 1216 * {@inheritDoc} 1217 */ 1218 @Override 1219 public void processingInstruction( String target, String data ) 1220 throws SAXException 1221 { 1222 if ( customContentHandler != null ) 1223 { 1224 // forward calls instead of handling them here 1225 customContentHandler.processingInstruction( target, data ); 1226 return; 1227 } 1228 1229 if ( saxLog.isDebugEnabled() ) 1230 { 1231 saxLog.debug( "processingInstruction('" + target + "','" + data + "')" ); 1232 } 1233 1234 // No processing is required 1235 } 1236 1237 /** 1238 * Gets the document locator associated with our parser. 1239 * 1240 * @return the Locator supplied by the document parser 1241 */ 1242 public Locator getDocumentLocator() 1243 { 1244 return locator; 1245 } 1246 1247 /** 1248 * {@inheritDoc} 1249 */ 1250 @Override 1251 public void setDocumentLocator( Locator locator ) 1252 { 1253 if ( saxLog.isDebugEnabled() ) 1254 { 1255 saxLog.debug( "setDocumentLocator(" + locator + ")" ); 1256 } 1257 1258 this.locator = locator; 1259 } 1260 1261 /** 1262 * {@inheritDoc} 1263 */ 1264 @Override 1265 public void skippedEntity( String name ) 1266 throws SAXException 1267 { 1268 if ( saxLog.isDebugEnabled() ) 1269 { 1270 saxLog.debug( "skippedEntity(" + name + ")" ); 1271 } 1272 1273 // No processing required 1274 } 1275 1276 /** 1277 * {@inheritDoc} 1278 */ 1279 @Override 1280 public void startDocument() 1281 throws SAXException 1282 { 1283 if ( saxLog.isDebugEnabled() ) 1284 { 1285 saxLog.debug( "startDocument()" ); 1286 } 1287 1288 // ensure that the digester is properly configured, as 1289 // the digester could be used as a SAX ContentHandler 1290 // rather than via the parse() methods. 1291 configure(); 1292 } 1293 1294 /** 1295 * {@inheritDoc} 1296 */ 1297 @Override 1298 public void startElement( String namespaceURI, String localName, String qName, Attributes list ) 1299 throws SAXException 1300 { 1301 boolean debug = log.isDebugEnabled(); 1302 1303 if ( customContentHandler != null ) 1304 { 1305 // forward calls instead of handling them here 1306 customContentHandler.startElement( namespaceURI, localName, qName, list ); 1307 return; 1308 } 1309 1310 if ( saxLog.isDebugEnabled() ) 1311 { 1312 saxLog.debug( "startElement(" + namespaceURI + "," + localName + "," + qName + ")" ); 1313 } 1314 1315 // Save the body text accumulated for our surrounding element 1316 bodyTexts.push( bodyText ); 1317 if ( debug ) 1318 { 1319 log.debug( " Pushing body text '" + bodyText.toString() + "'" ); 1320 } 1321 bodyText = new StringBuilder(); 1322 1323 // the actual element name is either in localName or qName, depending 1324 // on whether the parser is namespace aware 1325 String name = localName; 1326 if ( ( name == null ) || ( name.length() < 1 ) ) 1327 { 1328 name = qName; 1329 } 1330 1331 // Compute the current matching rule 1332 StringBuilder sb = new StringBuilder( match ); 1333 if ( match.length() > 0 ) 1334 { 1335 sb.append( '/' ); 1336 } 1337 sb.append( name ); 1338 match = sb.toString(); 1339 if ( debug ) 1340 { 1341 log.debug( " New match='" + match + "'" ); 1342 } 1343 1344 // Fire "begin" events for all relevant rules 1345 List<Rule> rules = getRules().match( namespaceURI, match, localName, list ); 1346 matches.push( rules ); 1347 if ( ( rules != null ) && ( rules.size() > 0 ) ) 1348 { 1349 Substitutor substitutor = getSubstitutor(); 1350 if ( substitutor != null ) 1351 { 1352 list = substitutor.substitute( list ); 1353 } 1354 for ( int i = 0; i < rules.size(); i++ ) 1355 { 1356 try 1357 { 1358 Rule rule = rules.get( i ); 1359 if ( debug ) 1360 { 1361 log.debug( " Fire begin() for " + rule ); 1362 } 1363 rule.begin( namespaceURI, name, list ); 1364 } 1365 catch ( Exception e ) 1366 { 1367 log.error( "Begin event threw exception", e ); 1368 throw createSAXException( e ); 1369 } 1370 catch ( Error e ) 1371 { 1372 log.error( "Begin event threw error", e ); 1373 throw e; 1374 } 1375 } 1376 } 1377 else 1378 { 1379 if ( debug ) 1380 { 1381 log.debug( " No rules found matching '" + match + "'." ); 1382 } 1383 } 1384 } 1385 1386 /** 1387 * {@inheritDoc} 1388 */ 1389 @Override 1390 public void startPrefixMapping( String prefix, String namespaceURI ) 1391 throws SAXException 1392 { 1393 if ( saxLog.isDebugEnabled() ) 1394 { 1395 saxLog.debug( "startPrefixMapping(" + prefix + "," + namespaceURI + ")" ); 1396 } 1397 1398 // Register this prefix mapping 1399 Stack<String> stack = namespaces.get( prefix ); 1400 if ( stack == null ) 1401 { 1402 stack = new Stack<String>(); 1403 namespaces.put( prefix, stack ); 1404 } 1405 stack.push( namespaceURI ); 1406 } 1407 1408 // ----------------------------------------------------- DTDHandler Methods 1409 1410 /** 1411 * {@inheritDoc} 1412 */ 1413 @Override 1414 public void notationDecl( String name, String publicId, String systemId ) 1415 { 1416 if ( saxLog.isDebugEnabled() ) 1417 { 1418 saxLog.debug( "notationDecl(" + name + "," + publicId + "," + systemId + ")" ); 1419 } 1420 } 1421 1422 /** 1423 * {@inheritDoc} 1424 */ 1425 @Override 1426 public void unparsedEntityDecl( String name, String publicId, String systemId, String notation ) 1427 { 1428 if ( saxLog.isDebugEnabled() ) 1429 { 1430 saxLog.debug( "unparsedEntityDecl(" + name + "," + publicId + "," + systemId + "," + notation + ")" ); 1431 } 1432 } 1433 1434 // ----------------------------------------------- EntityResolver Methods 1435 1436 /** 1437 * Set the <code>EntityResolver</code> used by SAX when resolving public id and system id. This must be called 1438 * before the first call to <code>parse()</code>. 1439 * 1440 * @param entityResolver a class that implement the <code>EntityResolver</code> interface. 1441 */ 1442 public void setEntityResolver( EntityResolver entityResolver ) 1443 { 1444 this.entityResolver = entityResolver; 1445 } 1446 1447 /** 1448 * Return the Entity Resolver used by the SAX parser. 1449 * 1450 * @return the Entity Resolver used by the SAX parser. 1451 */ 1452 public EntityResolver getEntityResolver() 1453 { 1454 return entityResolver; 1455 } 1456 1457 /** 1458 * {@inheritDoc} 1459 */ 1460 @Override 1461 public InputSource resolveEntity( String publicId, String systemId ) 1462 throws SAXException 1463 { 1464 if ( saxLog.isDebugEnabled() ) 1465 { 1466 saxLog.debug( "resolveEntity('" + publicId + "', '" + systemId + "')" ); 1467 } 1468 1469 if ( publicId != null ) 1470 { 1471 this.publicId = publicId; 1472 } 1473 1474 // Has this system identifier been registered? 1475 URL entityURL = null; 1476 if ( publicId != null ) 1477 { 1478 entityURL = entityValidator.get( publicId ); 1479 } 1480 1481 // Redirect the schema location to a local destination 1482 if ( entityURL == null && systemId != null ) 1483 { 1484 entityURL = entityValidator.get( systemId ); 1485 } 1486 1487 if ( entityURL == null ) 1488 { 1489 if ( systemId == null ) 1490 { 1491 // cannot resolve 1492 if ( log.isDebugEnabled() ) 1493 { 1494 log.debug( " Cannot resolve null entity, returning null InputSource" ); 1495 } 1496 return ( null ); 1497 1498 } 1499 // try to resolve using system ID 1500 if ( log.isDebugEnabled() ) 1501 { 1502 log.debug( " Trying to resolve using system ID '" + systemId + "'" ); 1503 } 1504 try 1505 { 1506 entityURL = new URL( systemId ); 1507 } 1508 catch ( MalformedURLException e ) 1509 { 1510 throw new IllegalArgumentException( "Malformed URL '" + systemId + "' : " + e.getMessage() ); 1511 } 1512 } 1513 1514 // Return an input source to our alternative URL 1515 if ( log.isDebugEnabled() ) 1516 { 1517 log.debug( " Resolving to alternate DTD '" + entityURL + "'" ); 1518 } 1519 1520 try 1521 { 1522 return createInputSourceFromURL( entityURL ); 1523 } 1524 catch ( Exception e ) 1525 { 1526 throw createSAXException( e ); 1527 } 1528 } 1529 1530 // ------------------------------------------------- ErrorHandler Methods 1531 1532 /** 1533 * {@inheritDoc} 1534 */ 1535 @Override 1536 public void error( SAXParseException exception ) 1537 throws SAXException 1538 { 1539 log.error( "Parse Error at line " + exception.getLineNumber() + " column " + exception.getColumnNumber() + ": " 1540 + exception.getMessage(), exception ); 1541 } 1542 1543 /** 1544 * {@inheritDoc} 1545 */ 1546 @Override 1547 public void fatalError( SAXParseException exception ) 1548 throws SAXException 1549 { 1550 log.error( "Parse Fatal Error at line " + exception.getLineNumber() + " column " + exception.getColumnNumber() 1551 + ": " + exception.getMessage(), exception ); 1552 } 1553 1554 /** 1555 * {@inheritDoc} 1556 */ 1557 @Override 1558 public void warning( SAXParseException exception ) 1559 throws SAXException 1560 { 1561 log.warn( "Parse Warning Error at line " + exception.getLineNumber() + " column " 1562 + exception.getColumnNumber() + ": " + exception.getMessage(), exception ); 1563 } 1564 1565 // ------------------------------------------------------- Public Methods 1566 1567 /** 1568 * Parse the content of the specified file using this Digester. Returns the root element from the object stack (if 1569 * any). 1570 * 1571 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1572 * @param file File containing the XML data to be parsed 1573 * @return the root element from the object stack (if any) 1574 * @exception IOException if an input/output error occurs 1575 * @exception SAXException if a parsing exception occurs 1576 */ 1577 public <T> T parse( File file ) 1578 throws IOException, SAXException 1579 { 1580 if ( file == null ) 1581 { 1582 throw new IllegalArgumentException( "File to parse is null" ); 1583 } 1584 1585 InputSource input = new InputSource( new FileInputStream( file ) ); 1586 input.setSystemId( file.toURI().toURL().toString() ); 1587 1588 return ( this.<T> parse( input ) ); 1589 } 1590 1591 /** 1592 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1593 * 1594 * @param <T> The result type returned by the returned Future's {@code get} method 1595 * @param file File containing the XML data to be parsed 1596 * @return a Future that can be used to track when the parse has been fully processed. 1597 * @see Digester#parse(File) 1598 * @since 3.1 1599 */ 1600 public <T> Future<T> asyncParse( final File file ) 1601 { 1602 return asyncParse( new Callable<T>() 1603 { 1604 1605 public T call() 1606 throws Exception 1607 { 1608 return Digester.this.<T> parse( file ); 1609 } 1610 1611 } ); 1612 } 1613 1614 /** 1615 * Parse the content of the specified input source using this Digester. Returns the root element from the object 1616 * stack (if any). 1617 * 1618 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1619 * @param input Input source containing the XML data to be parsed 1620 * @return the root element from the object stack (if any) 1621 * @exception IOException if an input/output error occurs 1622 * @exception SAXException if a parsing exception occurs 1623 */ 1624 public <T> T parse( InputSource input ) 1625 throws IOException, SAXException 1626 { 1627 if ( input == null ) 1628 { 1629 throw new IllegalArgumentException( "InputSource to parse is null" ); 1630 } 1631 1632 configure(); 1633 1634 String systemId = input.getSystemId(); 1635 if ( systemId == null ) 1636 { 1637 systemId = "(already loaded from stream)"; 1638 } 1639 1640 try 1641 { 1642 getXMLReader().parse( input ); 1643 } 1644 catch ( IOException e ) 1645 { 1646 log.error( format( "An error occurred while reading stream from '%s', see nested exceptions", systemId ), 1647 e ); 1648 throw e; 1649 } 1650 cleanup(); 1651 return this.<T> getRoot(); 1652 } 1653 1654 /** 1655 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1656 * 1657 * @param <T> The result type returned by the returned Future's {@code get} method 1658 * @param input Input source containing the XML data to be parsed 1659 * @return a Future that can be used to track when the parse has been fully processed. 1660 * @see Digester#parse(InputSource) 1661 * @since 3.1 1662 */ 1663 public <T> Future<T> asyncParse( final InputSource input ) 1664 { 1665 return asyncParse( new Callable<T>() 1666 { 1667 1668 public T call() 1669 throws Exception 1670 { 1671 return Digester.this.<T> parse( input ); 1672 } 1673 1674 } ); 1675 } 1676 1677 /** 1678 * Parse the content of the specified input stream using this Digester. Returns the root element from the object 1679 * stack (if any). 1680 * 1681 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1682 * @param input Input stream containing the XML data to be parsed 1683 * @return the root element from the object stack (if any) 1684 * @exception IOException if an input/output error occurs 1685 * @exception SAXException if a parsing exception occurs 1686 */ 1687 public <T> T parse( InputStream input ) 1688 throws IOException, SAXException 1689 { 1690 if ( input == null ) 1691 { 1692 throw new IllegalArgumentException( "InputStream to parse is null" ); 1693 } 1694 1695 return ( this.<T> parse( new InputSource( input ) ) ); 1696 } 1697 1698 /** 1699 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1700 * 1701 * @param <T> The result type returned by the returned Future's {@code get} method 1702 * @param input Input stream containing the XML data to be parsed 1703 * @return a Future that can be used to track when the parse has been fully processed. 1704 * @see Digester#parse(InputStream) 1705 * @since 3.1 1706 */ 1707 public <T> Future<T> asyncParse( final InputStream input ) 1708 { 1709 return asyncParse( new Callable<T>() 1710 { 1711 1712 public T call() 1713 throws Exception 1714 { 1715 return Digester.this.<T> parse( input ); 1716 } 1717 1718 } ); 1719 } 1720 1721 /** 1722 * Parse the content of the specified reader using this Digester. Returns the root element from the object stack (if 1723 * any). 1724 * 1725 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1726 * @param reader Reader containing the XML data to be parsed 1727 * @return the root element from the object stack (if any) 1728 * @exception IOException if an input/output error occurs 1729 * @exception SAXException if a parsing exception occurs 1730 */ 1731 public <T> T parse( Reader reader ) 1732 throws IOException, SAXException 1733 { 1734 if ( reader == null ) 1735 { 1736 throw new IllegalArgumentException( "Reader to parse is null" ); 1737 } 1738 1739 return ( this.<T> parse( new InputSource( reader ) ) ); 1740 } 1741 1742 /** 1743 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1744 * 1745 * @param <T> The result type returned by the returned Future's {@code get} method 1746 * @param reader Reader containing the XML data to be parsed 1747 * @return a Future that can be used to track when the parse has been fully processed. 1748 * @see Digester#parse(Reader) 1749 * @since 3.1 1750 */ 1751 public <T> Future<T> asyncParse( final Reader reader ) 1752 { 1753 return asyncParse( new Callable<T>() 1754 { 1755 1756 public T call() 1757 throws Exception 1758 { 1759 return Digester.this.<T> parse( reader ); 1760 } 1761 1762 } ); 1763 } 1764 1765 /** 1766 * Parse the content of the specified URI using this Digester. Returns the root element from the object stack (if 1767 * any). 1768 * 1769 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1770 * @param uri URI containing the XML data to be parsed 1771 * @return the root element from the object stack (if any) 1772 * @exception IOException if an input/output error occurs 1773 * @exception SAXException if a parsing exception occurs 1774 */ 1775 public <T> T parse( String uri ) 1776 throws IOException, SAXException 1777 { 1778 if ( uri == null ) 1779 { 1780 throw new IllegalArgumentException( "String URI to parse is null" ); 1781 } 1782 1783 return ( this.<T> parse( createInputSourceFromURL( uri ) ) ); 1784 } 1785 1786 /** 1787 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1788 * 1789 * @param <T> The result type returned by the returned Future's {@code get} method 1790 * @param uri URI containing the XML data to be parsed 1791 * @return a Future that can be used to track when the parse has been fully processed. 1792 * @see Digester#parse(String) 1793 * @since 3.1 1794 */ 1795 public <T> Future<T> asyncParse( final String uri ) 1796 { 1797 return asyncParse( new Callable<T>() 1798 { 1799 1800 public T call() 1801 throws Exception 1802 { 1803 return Digester.this.<T> parse( uri ); 1804 } 1805 1806 } ); 1807 } 1808 1809 /** 1810 * Parse the content of the specified URL using this Digester. Returns the root element from the object stack (if 1811 * any). 1812 * 1813 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1814 * @param url URL containing the XML data to be parsed 1815 * @return the root element from the object stack (if any) 1816 * @exception IOException if an input/output error occurs 1817 * @exception SAXException if a parsing exception occurs 1818 * @since 1.8 1819 */ 1820 public <T> T parse( URL url ) 1821 throws IOException, SAXException 1822 { 1823 if ( url == null ) 1824 { 1825 throw new IllegalArgumentException( "URL to parse is null" ); 1826 } 1827 1828 return ( this.<T> parse( createInputSourceFromURL( url ) ) ); 1829 } 1830 1831 /** 1832 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1833 * 1834 * @param <T> The result type returned by the returned Future's {@code get} method 1835 * @param url URL containing the XML data to be parsed 1836 * @return a Future that can be used to track when the parse has been fully processed. 1837 * @see Digester#parse(URL) 1838 * @since 3.1 1839 */ 1840 public <T> Future<T> asyncParse( final URL url ) 1841 { 1842 return asyncParse( new Callable<T>() 1843 { 1844 1845 public T call() 1846 throws Exception 1847 { 1848 return Digester.this.<T> parse( url ); 1849 } 1850 1851 } ); 1852 } 1853 1854 /** 1855 * Execute the parse in async mode. 1856 * 1857 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1858 * @param callable 1859 * @return a Future that can be used to track when the parse has been fully processed. 1860 * @since 3.1 1861 */ 1862 private <T> Future<T> asyncParse( Callable<T> callable ) 1863 { 1864 if ( executorService == null ) 1865 { 1866 throw new IllegalStateException( "ExecutorService not set" ); 1867 } 1868 1869 return executorService.submit( callable ); 1870 } 1871 1872 /** 1873 * <p> 1874 * Register the specified DTD URL for the specified public identifier. This must be called before the first call to 1875 * <code>parse()</code>. 1876 * </p> 1877 * <p> 1878 * <code>Digester</code> contains an internal <code>EntityResolver</code> implementation. This maps 1879 * <code>PUBLICID</code>'s to URLs (from which the resource will be loaded). A common use case for this method is to 1880 * register local URLs (possibly computed at runtime by a classloader) for DTDs. This allows the performance 1881 * advantage of using a local version without having to ensure every <code>SYSTEM</code> URI on every processed xml 1882 * document is local. This implementation provides only basic functionality. If more sophisticated features are 1883 * required, using {@link #setEntityResolver} to set a custom resolver is recommended. 1884 * </p> 1885 * <p> 1886 * <strong>Note:</strong> This method will have no effect when a custom <code>EntityResolver</code> has been set. 1887 * (Setting a custom <code>EntityResolver</code> overrides the internal implementation.) 1888 * </p> 1889 * 1890 * @param publicId Public identifier of the DTD to be resolved 1891 * @param entityURL The URL to use for reading this DTD 1892 * @since 1.8 1893 */ 1894 public void register( String publicId, URL entityURL ) 1895 { 1896 if ( log.isDebugEnabled() ) 1897 { 1898 log.debug( "register('" + publicId + "', '" + entityURL + "'" ); 1899 } 1900 entityValidator.put( publicId, entityURL ); 1901 } 1902 1903 /** 1904 * <p> 1905 * Convenience method that registers the string version of an entity URL instead of a URL version. 1906 * </p> 1907 * 1908 * @param publicId Public identifier of the entity to be resolved 1909 * @param entityURL The URL to use for reading this entity 1910 */ 1911 public void register( String publicId, String entityURL ) 1912 { 1913 if ( log.isDebugEnabled() ) 1914 { 1915 log.debug( "register('" + publicId + "', '" + entityURL + "'" ); 1916 } 1917 try 1918 { 1919 entityValidator.put( publicId, new URL( entityURL ) ); 1920 } 1921 catch ( MalformedURLException e ) 1922 { 1923 throw new IllegalArgumentException( "Malformed URL '" + entityURL + "' : " + e.getMessage() ); 1924 } 1925 } 1926 1927 /** 1928 * Convenience method that registers DTD URLs for the specified public identifiers. 1929 * 1930 * @param entityValidator The URLs of entityValidator that have been registered, keyed by the public 1931 * identifier that corresponds. 1932 * @since 3.0 1933 */ 1934 public void registerAll( Map<String, URL> entityValidator ) 1935 { 1936 this.entityValidator.putAll( entityValidator ); 1937 } 1938 1939 /** 1940 * <p> 1941 * <code>List</code> of <code>InputSource</code> instances created by a <code>createInputSourceFromURL()</code> 1942 * method call. These represent open input streams that need to be closed to avoid resource leaks, as well as 1943 * potentially locked JAR files on Windows. 1944 * </p> 1945 */ 1946 protected List<InputSource> inputSources = new ArrayList<InputSource>( 5 ); 1947 1948 /** 1949 * Given a URL, return an InputSource that reads from that URL. 1950 * <p> 1951 * Ideally this function would not be needed and code could just use <code>new InputSource(entityURL)</code>. 1952 * Unfortunately it appears that when the entityURL points to a file within a jar archive a caching mechanism inside 1953 * the InputSource implementation causes a file-handle to the jar file to remain open. On Windows systems this then 1954 * causes the jar archive file to be locked on disk ("in use") which makes it impossible to delete the jar file - 1955 * and that really stuffs up "undeploy" in webapps in particular. 1956 * <p> 1957 * In JDK1.4 and later, Apache XercesJ is used as the xml parser. The InputSource object provided is converted into 1958 * an XMLInputSource, and eventually passed to an instance of XMLDocumentScannerImpl to specify the source data to 1959 * be converted into tokens for the rest of the XMLReader code to handle. XMLDocumentScannerImpl calls 1960 * fEntityManager.startDocumentEntity(source), where fEntityManager is declared in ancestor class XMLScanner to be 1961 * an XMLEntityManager. In that class, if the input source stream is null, then: 1962 * 1963 * <pre> 1964 * URL location = new URL( expandedSystemId ); 1965 * URLConnection connect = location.openConnection(); 1966 * if ( connect instanceof HttpURLConnection ) 1967 * { 1968 * setHttpProperties( connect, xmlInputSource ); 1969 * } 1970 * stream = connect.getInputStream(); 1971 * </pre> 1972 * 1973 * This method pretty much duplicates the standard behaviour, except that it calls URLConnection.setUseCaches(false) 1974 * before opening the connection. 1975 * 1976 * @param url The URL has to be read 1977 * @return The InputSource that reads from the input URL 1978 * @throws IOException if any error occurs while reading the input URL 1979 * @since 1.8 1980 */ 1981 public InputSource createInputSourceFromURL( URL url ) 1982 throws IOException 1983 { 1984 URLConnection connection = url.openConnection(); 1985 connection.setUseCaches( false ); 1986 InputStream stream = connection.getInputStream(); 1987 InputSource source = new InputSource( stream ); 1988 source.setSystemId( url.toExternalForm() ); 1989 inputSources.add( source ); 1990 return source; 1991 } 1992 1993 /** 1994 * <p> 1995 * Convenience method that creates an <code>InputSource</code> from the string version of a URL. 1996 * </p> 1997 * 1998 * @param url URL for which to create an <code>InputSource</code> 1999 * @return The InputSource that reads from the input URL 2000 * @throws IOException if any error occurs while reading the input URL 2001 * @since 1.8 2002 */ 2003 public InputSource createInputSourceFromURL( String url ) 2004 throws IOException 2005 { 2006 return createInputSourceFromURL( new URL( url ) ); 2007 } 2008 2009 // --------------------------------------------------------- Rule Methods 2010 2011 /** 2012 * <p> 2013 * Register a new Rule matching the specified pattern. This method sets the <code>Digester</code> property on the 2014 * rule. 2015 * </p> 2016 * 2017 * @param pattern Element matching pattern 2018 * @param rule Rule to be registered 2019 */ 2020 public void addRule( String pattern, Rule rule ) 2021 { 2022 rule.setDigester( this ); 2023 getRules().add( pattern, rule ); 2024 } 2025 2026 /** 2027 * Register a set of Rule instances defined in a RuleSet. 2028 * 2029 * @param ruleSet The RuleSet instance to configure from 2030 */ 2031 public void addRuleSet( RuleSet ruleSet ) 2032 { 2033 String oldNamespaceURI = getRuleNamespaceURI(); 2034 String newNamespaceURI = ruleSet.getNamespaceURI(); 2035 if ( log.isDebugEnabled() ) 2036 { 2037 if ( newNamespaceURI == null ) 2038 { 2039 log.debug( "addRuleSet() with no namespace URI" ); 2040 } 2041 else 2042 { 2043 log.debug( "addRuleSet() with namespace URI " + newNamespaceURI ); 2044 } 2045 } 2046 setRuleNamespaceURI( newNamespaceURI ); 2047 ruleSet.addRuleInstances( this ); 2048 setRuleNamespaceURI( oldNamespaceURI ); 2049 } 2050 2051 /** 2052 * Add a "bean property setter" rule for the specified parameters. 2053 * 2054 * @param pattern Element matching pattern 2055 * @see BeanPropertySetterRule 2056 */ 2057 public void addBeanPropertySetter( String pattern ) 2058 { 2059 addRule( pattern, new BeanPropertySetterRule() ); 2060 } 2061 2062 /** 2063 * Add a "bean property setter" rule for the specified parameters. 2064 * 2065 * @param pattern Element matching pattern 2066 * @param propertyName Name of property to set 2067 * @see BeanPropertySetterRule 2068 */ 2069 public void addBeanPropertySetter( String pattern, String propertyName ) 2070 { 2071 addRule( pattern, new BeanPropertySetterRule( propertyName ) ); 2072 } 2073 2074 /** 2075 * Add an "call method" rule for a method which accepts no arguments. 2076 * 2077 * @param pattern Element matching pattern 2078 * @param methodName Method name to be called 2079 * @see CallMethodRule 2080 */ 2081 public void addCallMethod( String pattern, String methodName ) 2082 { 2083 addRule( pattern, new CallMethodRule( methodName ) ); 2084 } 2085 2086 /** 2087 * Add an "call method" rule for the specified parameters. 2088 * 2089 * @param pattern Element matching pattern 2090 * @param methodName Method name to be called 2091 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element) 2092 * @see CallMethodRule 2093 */ 2094 public void addCallMethod( String pattern, String methodName, int paramCount ) 2095 { 2096 addRule( pattern, new CallMethodRule( methodName, paramCount ) ); 2097 } 2098 2099 /** 2100 * Add an "call method" rule for the specified parameters. If <code>paramCount</code> is set to zero the rule will 2101 * use the body of the matched element as the single argument of the method, unless <code>paramTypes</code> is null 2102 * or empty, in this case the rule will call the specified method with no arguments. 2103 * 2104 * @param pattern Element matching pattern 2105 * @param methodName Method name to be called 2106 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element) 2107 * @param paramTypes Set of Java class names for the types of the expected parameters (if you wish to use a 2108 * primitive type, specify the corresonding Java wrapper class instead, such as 2109 * <code>java.lang.Boolean</code> for a <code>boolean</code> parameter) 2110 * @see CallMethodRule 2111 */ 2112 public void addCallMethod( String pattern, String methodName, int paramCount, String paramTypes[] ) 2113 { 2114 addRule( pattern, new CallMethodRule( methodName, paramCount, paramTypes ) ); 2115 } 2116 2117 /** 2118 * Add an "call method" rule for the specified parameters. If <code>paramCount</code> is set to zero the rule will 2119 * use the body of the matched element as the single argument of the method, unless <code>paramTypes</code> is null 2120 * or empty, in this case the rule will call the specified method with no arguments. 2121 * 2122 * @param pattern Element matching pattern 2123 * @param methodName Method name to be called 2124 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element) 2125 * @param paramTypes The Java class names of the arguments (if you wish to use a primitive type, specify the 2126 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a 2127 * <code>boolean</code> parameter) 2128 * @see CallMethodRule 2129 */ 2130 public void addCallMethod( String pattern, String methodName, int paramCount, Class<?> paramTypes[] ) 2131 { 2132 addRule( pattern, new CallMethodRule( methodName, paramCount, paramTypes ) ); 2133 } 2134 2135 /** 2136 * Add a "call parameter" rule for the specified parameters. 2137 * 2138 * @param pattern Element matching pattern 2139 * @param paramIndex Zero-relative parameter index to set (from the body of this element) 2140 * @see CallParamRule 2141 */ 2142 public void addCallParam( String pattern, int paramIndex ) 2143 { 2144 addRule( pattern, new CallParamRule( paramIndex ) ); 2145 } 2146 2147 /** 2148 * Add a "call parameter" rule for the specified parameters. 2149 * 2150 * @param pattern Element matching pattern 2151 * @param paramIndex Zero-relative parameter index to set (from the specified attribute) 2152 * @param attributeName Attribute whose value is used as the parameter value 2153 * @see CallParamRule 2154 */ 2155 public void addCallParam( String pattern, int paramIndex, String attributeName ) 2156 { 2157 addRule( pattern, new CallParamRule( paramIndex, attributeName ) ); 2158 } 2159 2160 /** 2161 * Add a "call parameter" rule. This will either take a parameter from the stack or from the current element body 2162 * text. 2163 * 2164 * @param pattern Element matching pattern 2165 * @param paramIndex The zero-relative parameter number 2166 * @param fromStack Should the call parameter be taken from the top of the stack? 2167 * @see CallParamRule 2168 */ 2169 public void addCallParam( String pattern, int paramIndex, boolean fromStack ) 2170 { 2171 addRule( pattern, new CallParamRule( paramIndex, fromStack ) ); 2172 } 2173 2174 /** 2175 * Add a "call parameter" rule that sets a parameter from the stack. This takes a parameter from the given position 2176 * on the stack. 2177 * 2178 * @param pattern Element matching pattern 2179 * @param paramIndex The zero-relative parameter number 2180 * @param stackIndex set the call parameter to the stackIndex'th object down the stack, where 0 is the top of the 2181 * stack, 1 the next element down and so on 2182 * @see CallMethodRule 2183 */ 2184 public void addCallParam( String pattern, int paramIndex, int stackIndex ) 2185 { 2186 addRule( pattern, new CallParamRule( paramIndex, stackIndex ) ); 2187 } 2188 2189 /** 2190 * Add a "call parameter" rule that sets a parameter from the current <code>Digester</code> matching path. This is 2191 * sometimes useful when using rules that support wildcards. 2192 * 2193 * @param pattern the pattern that this rule should match 2194 * @param paramIndex The zero-relative parameter number 2195 * @see CallMethodRule 2196 */ 2197 public void addCallParamPath( String pattern, int paramIndex ) 2198 { 2199 addRule( pattern, new PathCallParamRule( paramIndex ) ); 2200 } 2201 2202 /** 2203 * Add a "call parameter" rule that sets a parameter from a caller-provided object. This can be used to pass 2204 * constants such as strings to methods; it can also be used to pass mutable objects, providing ways for objects to 2205 * do things like "register" themselves with some shared object. 2206 * <p> 2207 * Note that when attempting to locate a matching method to invoke, the true type of the paramObj is used, so that 2208 * despite the paramObj being passed in here as type Object, the target method can declare its parameters as being 2209 * the true type of the object (or some ancestor type, according to the usual type-conversion rules). 2210 * 2211 * @param pattern Element matching pattern 2212 * @param paramIndex The zero-relative parameter number 2213 * @param paramObj Any arbitrary object to be passed to the target method. 2214 * @see CallMethodRule 2215 * @since 1.6 2216 */ 2217 public void addObjectParam( String pattern, int paramIndex, Object paramObj ) 2218 { 2219 addRule( pattern, new ObjectParamRule( paramIndex, paramObj ) ); 2220 } 2221 2222 /** 2223 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2224 * will be propagated. 2225 * 2226 * @param pattern Element matching pattern 2227 * @param className Java class name of the object creation factory class 2228 * @see FactoryCreateRule 2229 */ 2230 public void addFactoryCreate( String pattern, String className ) 2231 { 2232 addFactoryCreate( pattern, className, false ); 2233 } 2234 2235 /** 2236 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2237 * will be propagated. 2238 * 2239 * @param pattern Element matching pattern 2240 * @param clazz Java class of the object creation factory class 2241 * @see FactoryCreateRule 2242 */ 2243 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz ) 2244 { 2245 addFactoryCreate( pattern, clazz, false ); 2246 } 2247 2248 /** 2249 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2250 * will be propagated. 2251 * 2252 * @param pattern Element matching pattern 2253 * @param className Java class name of the object creation factory class 2254 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2255 * @see FactoryCreateRule 2256 */ 2257 public void addFactoryCreate( String pattern, String className, String attributeName ) 2258 { 2259 addFactoryCreate( pattern, className, attributeName, false ); 2260 } 2261 2262 /** 2263 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2264 * will be propagated. 2265 * 2266 * @param pattern Element matching pattern 2267 * @param clazz Java class of the object creation factory class 2268 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2269 * @see FactoryCreateRule 2270 */ 2271 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz, 2272 String attributeName ) 2273 { 2274 addFactoryCreate( pattern, clazz, attributeName, false ); 2275 } 2276 2277 /** 2278 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2279 * will be propagated. 2280 * 2281 * @param pattern Element matching pattern 2282 * @param creationFactory Previously instantiated ObjectCreationFactory to be utilized 2283 * @see FactoryCreateRule 2284 */ 2285 public void addFactoryCreate( String pattern, ObjectCreationFactory<?> creationFactory ) 2286 { 2287 addFactoryCreate( pattern, creationFactory, false ); 2288 } 2289 2290 /** 2291 * Add a "factory create" rule for the specified parameters. 2292 * 2293 * @param pattern Element matching pattern 2294 * @param className Java class name of the object creation factory class 2295 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2296 * ignored. 2297 * @see FactoryCreateRule 2298 */ 2299 public void addFactoryCreate( String pattern, String className, boolean ignoreCreateExceptions ) 2300 { 2301 addRule( pattern, new FactoryCreateRule( className, ignoreCreateExceptions ) ); 2302 } 2303 2304 /** 2305 * Add a "factory create" rule for the specified parameters. 2306 * 2307 * @param pattern Element matching pattern 2308 * @param clazz Java class of the object creation factory class 2309 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2310 * ignored. 2311 * @see FactoryCreateRule 2312 */ 2313 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz, 2314 boolean ignoreCreateExceptions ) 2315 { 2316 addRule( pattern, new FactoryCreateRule( clazz, ignoreCreateExceptions ) ); 2317 } 2318 2319 /** 2320 * Add a "factory create" rule for the specified parameters. 2321 * 2322 * @param pattern Element matching pattern 2323 * @param className Java class name of the object creation factory class 2324 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2325 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2326 * ignored. 2327 * @see FactoryCreateRule 2328 */ 2329 public void addFactoryCreate( String pattern, String className, String attributeName, 2330 boolean ignoreCreateExceptions ) 2331 { 2332 addRule( pattern, new FactoryCreateRule( className, attributeName, ignoreCreateExceptions ) ); 2333 } 2334 2335 /** 2336 * Add a "factory create" rule for the specified parameters. 2337 * 2338 * @param pattern Element matching pattern 2339 * @param clazz Java class of the object creation factory class 2340 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2341 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2342 * ignored. 2343 * @see FactoryCreateRule 2344 */ 2345 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz, 2346 String attributeName, boolean ignoreCreateExceptions ) 2347 { 2348 addRule( pattern, new FactoryCreateRule( clazz, attributeName, ignoreCreateExceptions ) ); 2349 } 2350 2351 /** 2352 * Add a "factory create" rule for the specified parameters. 2353 * 2354 * @param pattern Element matching pattern 2355 * @param creationFactory Previously instantiated ObjectCreationFactory to be utilized 2356 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2357 * ignored. 2358 * @see FactoryCreateRule 2359 */ 2360 public void addFactoryCreate( String pattern, ObjectCreationFactory<?> creationFactory, 2361 boolean ignoreCreateExceptions ) 2362 { 2363 creationFactory.setDigester( this ); 2364 addRule( pattern, new FactoryCreateRule( creationFactory, ignoreCreateExceptions ) ); 2365 } 2366 2367 /** 2368 * Add an "object create" rule for the specified parameters. 2369 * 2370 * @param pattern Element matching pattern 2371 * @param className Java class name to be created 2372 * @see ObjectCreateRule 2373 */ 2374 public void addObjectCreate( String pattern, String className ) 2375 { 2376 addRule( pattern, new ObjectCreateRule( className ) ); 2377 } 2378 2379 /** 2380 * Add an "object create" rule for the specified parameters. 2381 * 2382 * @param pattern Element matching pattern 2383 * @param clazz Java class to be created 2384 * @see ObjectCreateRule 2385 */ 2386 public void addObjectCreate( String pattern, Class<?> clazz ) 2387 { 2388 addRule( pattern, new ObjectCreateRule( clazz ) ); 2389 } 2390 2391 /** 2392 * Add an "object create" rule for the specified parameters. 2393 * 2394 * @param pattern Element matching pattern 2395 * @param className Default Java class name to be created 2396 * @param attributeName Attribute name that optionally overrides the default Java class name to be created 2397 * @see ObjectCreateRule 2398 */ 2399 public void addObjectCreate( String pattern, String className, String attributeName ) 2400 { 2401 addRule( pattern, new ObjectCreateRule( className, attributeName ) ); 2402 } 2403 2404 /** 2405 * Add an "object create" rule for the specified parameters. 2406 * 2407 * @param pattern Element matching pattern 2408 * @param attributeName Attribute name that optionally overrides 2409 * @param clazz Default Java class to be created the default Java class name to be created 2410 * @see ObjectCreateRule 2411 */ 2412 public void addObjectCreate( String pattern, String attributeName, Class<?> clazz ) 2413 { 2414 addRule( pattern, new ObjectCreateRule( attributeName, clazz ) ); 2415 } 2416 2417 /** 2418 * Adds an {@link SetNestedPropertiesRule}. 2419 * 2420 * @param pattern register the rule with this pattern 2421 * @since 1.6 2422 */ 2423 public void addSetNestedProperties( String pattern ) 2424 { 2425 addRule( pattern, new SetNestedPropertiesRule() ); 2426 } 2427 2428 /** 2429 * Adds an {@link SetNestedPropertiesRule}. 2430 * 2431 * @param pattern register the rule with this pattern 2432 * @param elementName elment name that a property maps to 2433 * @param propertyName property name of the element mapped from 2434 * @since 1.6 2435 */ 2436 public void addSetNestedProperties( String pattern, String elementName, String propertyName ) 2437 { 2438 addRule( pattern, new SetNestedPropertiesRule( elementName, propertyName ) ); 2439 } 2440 2441 /** 2442 * Adds an {@link SetNestedPropertiesRule}. 2443 * 2444 * @param pattern register the rule with this pattern 2445 * @param elementNames elment names that (in order) map to properties 2446 * @param propertyNames property names that (in order) elements are mapped to 2447 * @since 1.6 2448 */ 2449 public void addSetNestedProperties( String pattern, String[] elementNames, String[] propertyNames ) 2450 { 2451 addRule( pattern, new SetNestedPropertiesRule( elementNames, propertyNames ) ); 2452 } 2453 2454 /** 2455 * Add a "set next" rule for the specified parameters. 2456 * 2457 * @param pattern Element matching pattern 2458 * @param methodName Method name to call on the parent element 2459 * @see SetNextRule 2460 */ 2461 public void addSetNext( String pattern, String methodName ) 2462 { 2463 addRule( pattern, new SetNextRule( methodName ) ); 2464 } 2465 2466 /** 2467 * Add a "set next" rule for the specified parameters. 2468 * 2469 * @param pattern Element matching pattern 2470 * @param methodName Method name to call on the parent element 2471 * @param paramType Java class name of the expected parameter type (if you wish to use a primitive type, specify the 2472 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a 2473 * <code>boolean</code> parameter) 2474 * @see SetNextRule 2475 */ 2476 public void addSetNext( String pattern, String methodName, String paramType ) 2477 { 2478 addRule( pattern, new SetNextRule( methodName, paramType ) ); 2479 } 2480 2481 /** 2482 * Add {@link SetRootRule} with the specified parameters. 2483 * 2484 * @param pattern Element matching pattern 2485 * @param methodName Method name to call on the root object 2486 * @see SetRootRule 2487 */ 2488 public void addSetRoot( String pattern, String methodName ) 2489 { 2490 addRule( pattern, new SetRootRule( methodName ) ); 2491 } 2492 2493 /** 2494 * Add {@link SetRootRule} with the specified parameters. 2495 * 2496 * @param pattern Element matching pattern 2497 * @param methodName Method name to call on the root object 2498 * @param paramType Java class name of the expected parameter type 2499 * @see SetRootRule 2500 */ 2501 public void addSetRoot( String pattern, String methodName, String paramType ) 2502 { 2503 addRule( pattern, new SetRootRule( methodName, paramType ) ); 2504 } 2505 2506 /** 2507 * Add a "set properties" rule for the specified parameters. 2508 * 2509 * @param pattern Element matching pattern 2510 * @see SetPropertiesRule 2511 */ 2512 public void addSetProperties( String pattern ) 2513 { 2514 addRule( pattern, new SetPropertiesRule() ); 2515 } 2516 2517 /** 2518 * Add a "set properties" rule with a single overridden parameter. See 2519 * {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)} 2520 * 2521 * @param pattern Element matching pattern 2522 * @param attributeName map this attribute 2523 * @param propertyName to this property 2524 * @see SetPropertiesRule 2525 */ 2526 public void addSetProperties( String pattern, String attributeName, String propertyName ) 2527 { 2528 addRule( pattern, new SetPropertiesRule( attributeName, propertyName ) ); 2529 } 2530 2531 /** 2532 * Add a "set properties" rule with overridden parameters. See 2533 * {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)} 2534 * 2535 * @param pattern Element matching pattern 2536 * @param attributeNames names of attributes with custom mappings 2537 * @param propertyNames property names these attributes map to 2538 * @see SetPropertiesRule 2539 */ 2540 public void addSetProperties( String pattern, String[] attributeNames, String[] propertyNames ) 2541 { 2542 addRule( pattern, new SetPropertiesRule( attributeNames, propertyNames ) ); 2543 } 2544 2545 /** 2546 * Add a "set property" rule for the specified parameters. 2547 * 2548 * @param pattern Element matching pattern 2549 * @param name Attribute name containing the property name to be set 2550 * @param value Attribute name containing the property value to set 2551 * @see SetPropertyRule 2552 */ 2553 public void addSetProperty( String pattern, String name, String value ) 2554 { 2555 addRule( pattern, new SetPropertyRule( name, value ) ); 2556 } 2557 2558 /** 2559 * Add a "set top" rule for the specified parameters. 2560 * 2561 * @param pattern Element matching pattern 2562 * @param methodName Method name to call on the parent element 2563 * @see SetTopRule 2564 */ 2565 public void addSetTop( String pattern, String methodName ) 2566 { 2567 addRule( pattern, new SetTopRule( methodName ) ); 2568 } 2569 2570 /** 2571 * Add a "set top" rule for the specified parameters. 2572 * 2573 * @param pattern Element matching pattern 2574 * @param methodName Method name to call on the parent element 2575 * @param paramType Java class name of the expected parameter type (if you wish to use a primitive type, specify the 2576 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a 2577 * <code>boolean</code> parameter) 2578 * @see SetTopRule 2579 */ 2580 public void addSetTop( String pattern, String methodName, String paramType ) 2581 { 2582 addRule( pattern, new SetTopRule( methodName, paramType ) ); 2583 } 2584 2585 // --------------------------------------------------- Object Stack Methods 2586 2587 /** 2588 * Clear the current contents of the default object stack, the param stack, all named stacks, and other internal 2589 * variables. 2590 * <p> 2591 * Calling this method <i>might</i> allow another document of the same type to be correctly parsed. However this 2592 * method was not intended for this purpose (just to tidy up memory usage). In general, a separate Digester object 2593 * should be created for each document to be parsed. 2594 * <p> 2595 * Note that this method is called automatically after a document has been successfully parsed by a Digester 2596 * instance. However it is not invoked automatically when a parse fails, so when reusing a Digester instance (which 2597 * is not recommended) this method <i>must</i> be called manually after a parse failure. 2598 */ 2599 public void clear() 2600 { 2601 match = ""; 2602 bodyTexts.clear(); 2603 params.clear(); 2604 publicId = null; 2605 stack.clear(); 2606 stacksByName.clear(); 2607 customContentHandler = null; 2608 } 2609 2610 /** 2611 * Return the top object on the stack without removing it. 2612 * 2613 * If there are no objects on the stack, return <code>null</code>. 2614 * 2615 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2616 * @return the top object on the stack without removing it. 2617 */ 2618 public <T> T peek() 2619 { 2620 try 2621 { 2622 return this.<T> npeSafeCast( stack.peek() ); 2623 } 2624 catch ( EmptyStackException e ) 2625 { 2626 log.warn( "Empty stack (returning null)" ); 2627 return ( null ); 2628 } 2629 } 2630 2631 /** 2632 * Return the n'th object down the stack, where 0 is the top element and [getCount()-1] is the bottom element. If 2633 * the specified index is out of range, return <code>null</code>. 2634 * 2635 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2636 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on. 2637 * @return the n'th object down the stack 2638 */ 2639 public <T> T peek( int n ) 2640 { 2641 int index = ( stack.size() - 1 ) - n; 2642 if ( index < 0 ) 2643 { 2644 log.warn( "Empty stack (returning null)" ); 2645 return ( null ); 2646 } 2647 try 2648 { 2649 return this.<T> npeSafeCast( stack.get( index ) ); 2650 } 2651 catch ( EmptyStackException e ) 2652 { 2653 log.warn( "Empty stack (returning null)" ); 2654 return ( null ); 2655 } 2656 } 2657 2658 /** 2659 * Pop the top object off of the stack, and return it. If there are no objects on the stack, return 2660 * <code>null</code>. 2661 * 2662 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2663 * @return the top object popped off of the stack 2664 */ 2665 public <T> T pop() 2666 { 2667 try 2668 { 2669 T popped = this.<T> npeSafeCast( stack.pop() ); 2670 if ( stackAction != null ) 2671 { 2672 popped = stackAction.onPop( this, null, popped ); 2673 } 2674 return popped; 2675 } 2676 catch ( EmptyStackException e ) 2677 { 2678 log.warn( "Empty stack (returning null)" ); 2679 return ( null ); 2680 } 2681 } 2682 2683 /** 2684 * Push a new object onto the top of the object stack. 2685 * 2686 * @param <T> any type of the pushed object 2687 * @param object The new object 2688 */ 2689 public <T> void push( T object ) 2690 { 2691 if ( stackAction != null ) 2692 { 2693 object = stackAction.onPush( this, null, object ); 2694 } 2695 2696 if ( stack.size() == 0 ) 2697 { 2698 root = object; 2699 } 2700 stack.push( object ); 2701 } 2702 2703 /** 2704 * Pushes the given object onto the stack with the given name. If no stack already exists with the given name then 2705 * one will be created. 2706 * 2707 * @param <T> any type of the pushed object 2708 * @param stackName the name of the stack onto which the object should be pushed 2709 * @param value the Object to be pushed onto the named stack. 2710 * @since 1.6 2711 */ 2712 public <T> void push( String stackName, T value ) 2713 { 2714 if ( stackAction != null ) 2715 { 2716 value = stackAction.onPush( this, stackName, value ); 2717 } 2718 2719 Stack<Object> namedStack = stacksByName.get( stackName ); 2720 if ( namedStack == null ) 2721 { 2722 namedStack = new Stack<Object>(); 2723 stacksByName.put( stackName, namedStack ); 2724 } 2725 namedStack.push( value ); 2726 } 2727 2728 /** 2729 * <p> 2730 * Pops (gets and removes) the top object from the stack with the given name. 2731 * </p> 2732 * <p> 2733 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2734 * </p> 2735 * 2736 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2737 * @param stackName the name of the stack from which the top value is to be popped. 2738 * @return the top <code>Object</code> on the stack or throws {@code EmptyStackException} 2739 * if the stack is either empty or has not been created yet 2740 * @since 1.6 2741 */ 2742 public <T> T pop( String stackName ) 2743 { 2744 T result = null; 2745 Stack<Object> namedStack = stacksByName.get( stackName ); 2746 if ( namedStack == null ) 2747 { 2748 if ( log.isDebugEnabled() ) 2749 { 2750 log.debug( "Stack '" + stackName + "' is empty" ); 2751 } 2752 throw new EmptyStackException(); 2753 } 2754 2755 result = this.<T> npeSafeCast( namedStack.pop() ); 2756 2757 if ( stackAction != null ) 2758 { 2759 result = stackAction.onPop( this, stackName, result ); 2760 } 2761 2762 return result; 2763 } 2764 2765 /** 2766 * <p> 2767 * Gets the top object from the stack with the given name. This method does not remove the object from the stack. 2768 * </p> 2769 * <p> 2770 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2771 * </p> 2772 * 2773 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2774 * @param stackName the name of the stack to be peeked 2775 * @return the top <code>Object</code> on the stack or null if the stack is either empty or has not been created yet 2776 * @since 1.6 2777 */ 2778 public <T> T peek( String stackName ) 2779 { 2780 return this.<T> npeSafeCast( peek( stackName, 0 ) ); 2781 } 2782 2783 /** 2784 * <p> 2785 * Gets the top object from the stack with the given name. This method does not remove the object from the stack. 2786 * </p> 2787 * <p> 2788 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2789 * </p> 2790 * 2791 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2792 * @param stackName the name of the stack to be peeked 2793 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on. 2794 * @return the specified <code>Object</code> on the stack. 2795 * @since 1.6 2796 */ 2797 public <T> T peek( String stackName, int n ) 2798 { 2799 T result = null; 2800 Stack<Object> namedStack = stacksByName.get( stackName ); 2801 if ( namedStack == null ) 2802 { 2803 if ( log.isDebugEnabled() ) 2804 { 2805 log.debug( "Stack '" + stackName + "' is empty" ); 2806 } 2807 throw new EmptyStackException(); 2808 } 2809 2810 int index = ( namedStack.size() - 1 ) - n; 2811 if ( index < 0 ) 2812 { 2813 throw new EmptyStackException(); 2814 } 2815 result = this.<T> npeSafeCast( namedStack.get( index ) ); 2816 2817 return result; 2818 } 2819 2820 /** 2821 * <p> 2822 * Is the stack with the given name empty? 2823 * </p> 2824 * <p> 2825 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2826 * </p> 2827 * 2828 * @param stackName the name of the stack whose emptiness should be evaluated 2829 * @return true if the given stack if empty 2830 * @since 1.6 2831 */ 2832 public boolean isEmpty( String stackName ) 2833 { 2834 boolean result = true; 2835 Stack<Object> namedStack = stacksByName.get( stackName ); 2836 if ( namedStack != null ) 2837 { 2838 result = namedStack.isEmpty(); 2839 } 2840 return result; 2841 } 2842 2843 /** 2844 * Returns the root element of the tree of objects created as a result of applying the rule objects to the input 2845 * XML. 2846 * <p> 2847 * If the digester stack was "primed" by explicitly pushing a root object onto the stack before parsing started, 2848 * then that root object is returned here. 2849 * <p> 2850 * Alternatively, if a Rule which creates an object (eg ObjectCreateRule) matched the root element of the xml, then 2851 * the object created will be returned here. 2852 * <p> 2853 * In other cases, the object most recently pushed onto an empty digester stack is returned. This would be a most 2854 * unusual use of digester, however; one of the previous configurations is much more likely. 2855 * <p> 2856 * Note that when using one of the Digester.parse methods, the return value from the parse method is exactly the 2857 * same as the return value from this method. However when the Digester is being used as a SAXContentHandler, no 2858 * such return value is available; in this case, this method allows you to access the root object that has been 2859 * created after parsing has completed. 2860 * 2861 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2862 * @return the root object that has been created after parsing or null if the digester has not parsed any XML yet. 2863 */ 2864 public <T> T getRoot() 2865 { 2866 return this.<T> npeSafeCast( root ); 2867 } 2868 2869 /** 2870 * This method allows the "root" variable to be reset to null. 2871 * <p> 2872 * It is not considered safe for a digester instance to be reused to parse multiple xml documents. However if you 2873 * are determined to do so, then you should call both clear() and resetRoot() before each parse. 2874 * 2875 * @since 1.7 2876 */ 2877 public void resetRoot() 2878 { 2879 root = null; 2880 } 2881 2882 // ------------------------------------------------ Parameter Stack Methods 2883 2884 // ------------------------------------------------------ Protected Methods 2885 2886 /** 2887 * <p> 2888 * Clean up allocated resources after parsing is complete. The default method closes input streams that have been 2889 * created by Digester itself. If you override this method in a subclass, be sure to call 2890 * <code>super.cleanup()</code> to invoke this logic. 2891 * </p> 2892 * 2893 * @since 1.8 2894 */ 2895 protected void cleanup() 2896 { 2897 // If we created any InputSource objects in this instance, 2898 // they each have an input stream that should be closed 2899 for ( InputSource source : inputSources ) 2900 { 2901 try 2902 { 2903 source.getByteStream().close(); 2904 } 2905 catch ( IOException e ) 2906 { 2907 // Fall through so we get them all 2908 if ( log.isWarnEnabled() ) 2909 { 2910 log.warn( format( "An error occurred while closing resource %s (%s)", 2911 source.getPublicId(), 2912 source.getSystemId() ), e ); 2913 } 2914 } 2915 } 2916 inputSources.clear(); 2917 } 2918 2919 /** 2920 * <p> 2921 * Provide a hook for lazy configuration of this <code>Digester</code> instance. The default implementation does 2922 * nothing, but subclasses can override as needed. 2923 * </p> 2924 * <p> 2925 * <strong>Note</strong> This method may be called more than once. Once only initialization code should be placed in 2926 * {@link #initialize} or the code should take responsibility by checking and setting the {@link #configured} flag. 2927 * </p> 2928 */ 2929 protected void configure() 2930 { 2931 // Do not configure more than once 2932 if ( configured ) 2933 { 2934 return; 2935 } 2936 2937 // Perform lazy configuration as needed 2938 initialize(); // call hook method for subclasses that want to be initialized once only 2939 // Nothing else required by default 2940 2941 // Set the configuration flag to avoid repeating 2942 configured = true; 2943 } 2944 2945 /** 2946 * Checks the Digester instance has been configured. 2947 * 2948 * @return true, if the Digester instance has been configured, false otherwise 2949 * @since 3.0 2950 */ 2951 public boolean isConfigured() 2952 { 2953 return configured; 2954 } 2955 2956 /** 2957 * <p> 2958 * Provides a hook for lazy initialization of this <code>Digester</code> instance. The default implementation does 2959 * nothing, but subclasses can override as needed. Digester (by default) only calls this method once. 2960 * </p> 2961 * <p> 2962 * <strong>Note</strong> This method will be called by {@link #configure} only when the {@link #configured} flag is 2963 * false. Subclasses that override <code>configure</code> or who set <code>configured</code> may find that this 2964 * method may be called more than once. 2965 * </p> 2966 * 2967 * @since 1.6 2968 */ 2969 protected void initialize() 2970 { 2971 // Perform lazy initialization as needed 2972 // Nothing required by default 2973 } 2974 2975 // -------------------------------------------------------- Package Methods 2976 2977 /** 2978 * Return the set of DTD URL registrations, keyed by public identifier. NOTE: the returned map is in read-only mode. 2979 * 2980 * @return the read-only Map of DTD URL registrations. 2981 */ 2982 Map<String, URL> getRegistrations() 2983 { 2984 return Collections.unmodifiableMap( entityValidator ); 2985 } 2986 2987 /** 2988 * <p> 2989 * Return the top object on the parameters stack without removing it. If there are no objects on the stack, return 2990 * <code>null</code>. 2991 * </p> 2992 * <p> 2993 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 2994 * </p> 2995 * 2996 * @return the top object on the parameters stack without removing it. 2997 */ 2998 public Object[] peekParams() 2999 { 3000 try 3001 { 3002 return ( params.peek() ); 3003 } 3004 catch ( EmptyStackException e ) 3005 { 3006 log.warn( "Empty stack (returning null)" ); 3007 return ( null ); 3008 } 3009 } 3010 3011 /** 3012 * <p> 3013 * Return the n'th object down the parameters stack, where 0 is the top element and [getCount()-1] is the bottom 3014 * element. If the specified index is out of range, return <code>null</code>. 3015 * </p> 3016 * <p> 3017 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3018 * </p> 3019 * 3020 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on. 3021 * @return the n'th object down the parameters stack 3022 */ 3023 public Object[] peekParams( int n ) 3024 { 3025 int index = ( params.size() - 1 ) - n; 3026 if ( index < 0 ) 3027 { 3028 log.warn( "Empty stack (returning null)" ); 3029 return ( null ); 3030 } 3031 try 3032 { 3033 return ( params.get( index ) ); 3034 } 3035 catch ( EmptyStackException e ) 3036 { 3037 log.warn( "Empty stack (returning null)" ); 3038 return ( null ); 3039 } 3040 } 3041 3042 /** 3043 * <p> 3044 * Pop the top object off of the parameters stack, and return it. If there are no objects on the stack, return 3045 * <code>null</code>. 3046 * </p> 3047 * <p> 3048 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3049 * </p> 3050 * 3051 * @return the top object popped off of the parameters stack 3052 */ 3053 public Object[] popParams() 3054 { 3055 try 3056 { 3057 if ( log.isTraceEnabled() ) 3058 { 3059 log.trace( "Popping params" ); 3060 } 3061 return ( params.pop() ); 3062 } 3063 catch ( EmptyStackException e ) 3064 { 3065 log.warn( "Empty stack (returning null)" ); 3066 return ( null ); 3067 } 3068 } 3069 3070 /** 3071 * <p> 3072 * Push a new object onto the top of the parameters stack. 3073 * </p> 3074 * <p> 3075 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3076 * </p> 3077 * 3078 * @param object The new object 3079 */ 3080 public void pushParams( Object... object ) 3081 { 3082 if ( log.isTraceEnabled() ) 3083 { 3084 log.trace( "Pushing params" ); 3085 } 3086 params.push( object ); 3087 } 3088 3089 /** 3090 * Create a SAX exception which also understands about the location in the digester file where the exception occurs 3091 * 3092 * @param message the custom SAX exception message 3093 * @param e the exception cause 3094 * @return the new SAX exception 3095 */ 3096 public SAXException createSAXException( String message, Exception e ) 3097 { 3098 if ( ( e != null ) && ( e instanceof InvocationTargetException ) ) 3099 { 3100 Throwable t = ( (InvocationTargetException) e ).getTargetException(); 3101 if ( ( t != null ) && ( t instanceof Exception ) ) 3102 { 3103 e = (Exception) t; 3104 } 3105 } 3106 if ( locator != null ) 3107 { 3108 String error = 3109 "Error at line " + locator.getLineNumber() + " char " + locator.getColumnNumber() + ": " + message; 3110 if ( e != null ) 3111 { 3112 return new SAXParseException( error, locator, e ); 3113 } 3114 return new SAXParseException( error, locator ); 3115 } 3116 log.error( "No Locator!" ); 3117 if ( e != null ) 3118 { 3119 return new SAXException( message, e ); 3120 } 3121 return new SAXException( message ); 3122 } 3123 3124 /** 3125 * Create a SAX exception which also understands about the location in the digester file where the exception occurs 3126 * 3127 * @param e the exception cause 3128 * @return the new SAX exception 3129 */ 3130 public SAXException createSAXException( Exception e ) 3131 { 3132 if ( e instanceof InvocationTargetException ) 3133 { 3134 Throwable t = ( (InvocationTargetException) e ).getTargetException(); 3135 if ( ( t != null ) && ( t instanceof Exception ) ) 3136 { 3137 e = (Exception) t; 3138 } 3139 } 3140 return createSAXException( e.getMessage(), e ); 3141 } 3142 3143 /** 3144 * Create a SAX exception which also understands about the location in the digester file where the exception occurs 3145 * 3146 * @param message the custom SAX exception message 3147 * @return the new SAX exception 3148 */ 3149 public SAXException createSAXException( String message ) 3150 { 3151 return createSAXException( message, null ); 3152 } 3153 3154 /** 3155 * Helps casting the input object to given type, avoiding NPEs. 3156 * 3157 * @since 3.0 3158 * @param <T> the type the input object has to be cast. 3159 * @param obj the object has to be cast. 3160 * @return the casted object, if input object is not null, null otherwise. 3161 */ 3162 private <T> T npeSafeCast( Object obj ) 3163 { 3164 if ( obj == null ) 3165 { 3166 return null; 3167 } 3168 3169 @SuppressWarnings( "unchecked" ) 3170 T result = (T) obj; 3171 return result; 3172 } 3173 3174}