001 package 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 022 import static java.lang.String.format; 023 024 import java.io.File; 025 import java.io.FileInputStream; 026 import java.io.IOException; 027 import java.io.InputStream; 028 import java.io.Reader; 029 import java.lang.reflect.InvocationTargetException; 030 import java.net.MalformedURLException; 031 import java.net.URL; 032 import java.net.URLConnection; 033 import java.util.ArrayList; 034 import java.util.Collections; 035 import java.util.EmptyStackException; 036 import java.util.HashMap; 037 import java.util.List; 038 import java.util.Map; 039 import java.util.Stack; 040 import java.util.concurrent.Callable; 041 import java.util.concurrent.ExecutorService; 042 import java.util.concurrent.Future; 043 044 import javax.xml.parsers.ParserConfigurationException; 045 import javax.xml.parsers.SAXParser; 046 import javax.xml.parsers.SAXParserFactory; 047 import javax.xml.validation.Schema; 048 049 import org.apache.commons.logging.Log; 050 import org.apache.commons.logging.LogFactory; 051 import org.xml.sax.Attributes; 052 import org.xml.sax.ContentHandler; 053 import org.xml.sax.EntityResolver; 054 import org.xml.sax.ErrorHandler; 055 import org.xml.sax.InputSource; 056 import org.xml.sax.Locator; 057 import org.xml.sax.SAXException; 058 import org.xml.sax.SAXNotRecognizedException; 059 import org.xml.sax.SAXNotSupportedException; 060 import org.xml.sax.SAXParseException; 061 import org.xml.sax.XMLReader; 062 import 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 */ 095 public 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 * 779 * @param validating The new validating parser flag. 780 */ 781 public void setValidating( boolean validating ) 782 { 783 this.validating = validating; 784 } 785 786 /** 787 * Return the XMLReader to be used for parsing the input document. 788 * 789 * FIXME: there is a bug in JAXP/XERCES that prevent the use of a parser that contains a schema with a DTD. 790 * 791 * @return the XMLReader to be used for parsing the input document. 792 * @exception SAXException if no XMLReader can be instantiated 793 */ 794 public XMLReader getXMLReader() 795 throws SAXException 796 { 797 if ( reader == null ) 798 { 799 reader = getParser().getXMLReader(); 800 } 801 802 reader.setDTDHandler( this ); 803 reader.setContentHandler( this ); 804 805 if ( entityResolver == null ) 806 { 807 reader.setEntityResolver( this ); 808 } 809 else 810 { 811 reader.setEntityResolver( entityResolver ); 812 } 813 814 reader.setErrorHandler( this ); 815 return reader; 816 } 817 818 /** 819 * Gets the <code>Substitutor</code> used to convert attributes and body text. 820 * 821 * @return the <code>Substitutor</code> used to convert attributes and body text, 822 * null if not substitutions are to be performed. 823 */ 824 public Substitutor getSubstitutor() 825 { 826 return substitutor; 827 } 828 829 /** 830 * Sets the <code>Substitutor</code> to be used to convert attributes and body text. 831 * 832 * @param substitutor the Substitutor to be used to convert attributes and body text or null if not substitution of 833 * these values is to be performed. 834 */ 835 public void setSubstitutor( Substitutor substitutor ) 836 { 837 this.substitutor = substitutor; 838 } 839 840 /** 841 * returns the custom SAX ContentHandler where events are redirected. 842 * 843 * @return the custom SAX ContentHandler where events are redirected. 844 * @see #setCustomContentHandler(ContentHandler) 845 * @since 1.7 846 */ 847 public ContentHandler getCustomContentHandler() 848 { 849 return customContentHandler; 850 } 851 852 /** 853 * Redirects (or cancels redirecting) of SAX ContentHandler events to an external object. 854 * <p> 855 * When this object's customContentHandler is non-null, any SAX events received from the parser will simply be 856 * passed on to the specified object instead of this object handling them. This allows Rule classes to take control 857 * of the SAX event stream for a while in order to do custom processing. Such a rule should save the old value 858 * before setting a new one, and restore the old value in order to resume normal digester processing. 859 * <p> 860 * An example of a Rule which needs this feature is NodeCreateRule. 861 * <p> 862 * Note that saving the old value is probably not needed as it should always be null; a custom rule that wants to 863 * take control could only have been called when there was no custom content handler. But it seems cleaner to 864 * properly save/restore the value and maybe some day this will come in useful. 865 * <p> 866 * Note also that this is not quite equivalent to 867 * 868 * <pre> 869 * digester.getXMLReader().setContentHandler( handler ) 870 * </pre> 871 * 872 * for these reasons: 873 * <ul> 874 * <li>Some xml parsers don't like having setContentHandler called after parsing has started. The Aelfred parser is 875 * one example.</li> 876 * <li>Directing the events via the Digester object potentially allows us to log information about those SAX events 877 * at the digester level.</li> 878 * </ul> 879 * 880 * @param handler the custom SAX ContentHandler where events are redirected. 881 * @since 1.7 882 */ 883 public void setCustomContentHandler( ContentHandler handler ) 884 { 885 customContentHandler = handler; 886 } 887 888 /** 889 * Define a callback object which is invoked whenever an object is pushed onto a digester object stack, 890 * or popped off one. 891 * 892 * @param stackAction the callback object which is invoked whenever an object is pushed onto a digester 893 * object stack, or popped off one. 894 * @since 1.8 895 */ 896 public void setStackAction( StackAction stackAction ) 897 { 898 this.stackAction = stackAction; 899 } 900 901 /** 902 * Return the callback object which is invoked whenever an object is pushed onto a digester object stack, 903 * or popped off one. 904 * 905 * @return the callback object which is invoked whenever an object is pushed onto a digester object stack, 906 * or popped off one. 907 * @see #setStackAction(StackAction) 908 * @since 1.8 909 */ 910 public StackAction getStackAction() 911 { 912 return stackAction; 913 } 914 915 /** 916 * Get the most current namespaces for all prefixes. 917 * 918 * @return Map A map with namespace prefixes as keys and most current namespace URIs for the corresponding prefixes 919 * as values 920 * @since 1.8 921 */ 922 public Map<String, String> getCurrentNamespaces() 923 { 924 if ( !namespaceAware ) 925 { 926 log.warn( "Digester is not namespace aware" ); 927 } 928 Map<String, String> currentNamespaces = new HashMap<String, String>(); 929 for ( Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet() ) 930 { 931 try 932 { 933 currentNamespaces.put( nsEntry.getKey(), nsEntry.getValue().peek() ); 934 } 935 catch ( RuntimeException e ) 936 { 937 // rethrow, after logging 938 log.error( e.getMessage(), e ); 939 throw e; 940 } 941 } 942 return currentNamespaces; 943 } 944 945 /** 946 * Returns the executor service used to run asynchronous parse method. 947 * 948 * @return the executor service used to run asynchronous parse method 949 * @since 3.1 950 */ 951 public ExecutorService getExecutorService() 952 { 953 return executorService; 954 } 955 956 /** 957 * Sets the executor service to run asynchronous parse method. 958 * 959 * @param executorService the executor service to run asynchronous parse method 960 * @since 3.1 961 */ 962 public void setExecutorService( ExecutorService executorService ) 963 { 964 this.executorService = executorService; 965 } 966 967 // ------------------------------------------------- ContentHandler Methods 968 969 /** 970 * {@inheritDoc} 971 */ 972 @Override 973 public void characters( char buffer[], int start, int length ) 974 throws SAXException 975 { 976 if ( customContentHandler != null ) 977 { 978 // forward calls instead of handling them here 979 customContentHandler.characters( buffer, start, length ); 980 return; 981 } 982 983 if ( saxLog.isDebugEnabled() ) 984 { 985 saxLog.debug( "characters(" + new String( buffer, start, length ) + ")" ); 986 } 987 988 bodyText.append( buffer, start, length ); 989 } 990 991 /** 992 * {@inheritDoc} 993 */ 994 @Override 995 public void endDocument() 996 throws SAXException 997 { 998 if ( saxLog.isDebugEnabled() ) 999 { 1000 if ( getCount() > 1 ) 1001 { 1002 saxLog.debug( "endDocument(): " + getCount() + " elements left" ); 1003 } 1004 else 1005 { 1006 saxLog.debug( "endDocument()" ); 1007 } 1008 } 1009 1010 // Fire "finish" events for all defined rules 1011 for ( Rule rule : getRules().rules() ) 1012 { 1013 try 1014 { 1015 rule.finish(); 1016 } 1017 catch ( Exception e ) 1018 { 1019 log.error( "Finish event threw exception", e ); 1020 throw createSAXException( e ); 1021 } 1022 catch ( Error e ) 1023 { 1024 log.error( "Finish event threw error", e ); 1025 throw e; 1026 } 1027 } 1028 1029 // Perform final cleanup 1030 clear(); 1031 } 1032 1033 /** 1034 * {@inheritDoc} 1035 */ 1036 @Override 1037 public void endElement( String namespaceURI, String localName, String qName ) 1038 throws SAXException 1039 { 1040 if ( customContentHandler != null ) 1041 { 1042 // forward calls instead of handling them here 1043 customContentHandler.endElement( namespaceURI, localName, qName ); 1044 return; 1045 } 1046 1047 boolean debug = log.isDebugEnabled(); 1048 1049 if ( debug ) 1050 { 1051 if ( saxLog.isDebugEnabled() ) 1052 { 1053 saxLog.debug( "endElement(" + namespaceURI + "," + localName + "," + qName + ")" ); 1054 } 1055 log.debug( " match='" + match + "'" ); 1056 log.debug( " bodyText='" + bodyText + "'" ); 1057 } 1058 1059 // the actual element name is either in localName or qName, depending 1060 // on whether the parser is namespace aware 1061 String name = localName; 1062 if ( ( name == null ) || ( name.length() < 1 ) ) 1063 { 1064 name = qName; 1065 } 1066 1067 // Fire "body" events for all relevant rules 1068 List<Rule> rules = matches.pop(); 1069 if ( ( rules != null ) && ( rules.size() > 0 ) ) 1070 { 1071 String bodyText = this.bodyText.toString(); 1072 Substitutor substitutor = getSubstitutor(); 1073 if ( substitutor != null ) 1074 { 1075 bodyText = substitutor.substitute( bodyText ); 1076 } 1077 for ( int i = 0; i < rules.size(); i++ ) 1078 { 1079 try 1080 { 1081 Rule rule = rules.get( i ); 1082 if ( debug ) 1083 { 1084 log.debug( " Fire body() for " + rule ); 1085 } 1086 rule.body( namespaceURI, name, bodyText ); 1087 } 1088 catch ( Exception e ) 1089 { 1090 log.error( "Body event threw exception", e ); 1091 throw createSAXException( e ); 1092 } 1093 catch ( Error e ) 1094 { 1095 log.error( "Body event threw error", e ); 1096 throw e; 1097 } 1098 } 1099 } 1100 else 1101 { 1102 if ( debug ) 1103 { 1104 log.debug( " No rules found matching '" + match + "'." ); 1105 } 1106 } 1107 1108 // Recover the body text from the surrounding element 1109 bodyText = bodyTexts.pop(); 1110 if ( debug ) 1111 { 1112 log.debug( " Popping body text '" + bodyText.toString() + "'" ); 1113 } 1114 1115 // Fire "end" events for all relevant rules in reverse order 1116 if ( rules != null ) 1117 { 1118 for ( int i = 0; i < rules.size(); i++ ) 1119 { 1120 int j = ( rules.size() - i ) - 1; 1121 try 1122 { 1123 Rule rule = rules.get( j ); 1124 if ( debug ) 1125 { 1126 log.debug( " Fire end() for " + rule ); 1127 } 1128 rule.end( namespaceURI, name ); 1129 } 1130 catch ( Exception e ) 1131 { 1132 log.error( "End event threw exception", e ); 1133 throw createSAXException( e ); 1134 } 1135 catch ( Error e ) 1136 { 1137 log.error( "End event threw error", e ); 1138 throw e; 1139 } 1140 } 1141 } 1142 1143 // Recover the previous match expression 1144 int slash = match.lastIndexOf( '/' ); 1145 if ( slash >= 0 ) 1146 { 1147 match = match.substring( 0, slash ); 1148 } 1149 else 1150 { 1151 match = ""; 1152 } 1153 } 1154 1155 /** 1156 * {@inheritDoc} 1157 */ 1158 @Override 1159 public void endPrefixMapping( String prefix ) 1160 throws SAXException 1161 { 1162 if ( saxLog.isDebugEnabled() ) 1163 { 1164 saxLog.debug( "endPrefixMapping(" + prefix + ")" ); 1165 } 1166 1167 // Deregister this prefix mapping 1168 Stack<String> stack = namespaces.get( prefix ); 1169 if ( stack == null ) 1170 { 1171 return; 1172 } 1173 try 1174 { 1175 stack.pop(); 1176 if ( stack.empty() ) 1177 { 1178 namespaces.remove( prefix ); 1179 } 1180 } 1181 catch ( EmptyStackException e ) 1182 { 1183 throw createSAXException( "endPrefixMapping popped too many times" ); 1184 } 1185 } 1186 1187 /** 1188 * {@inheritDoc} 1189 */ 1190 @Override 1191 public void ignorableWhitespace( char buffer[], int start, int len ) 1192 throws SAXException 1193 { 1194 if ( saxLog.isDebugEnabled() ) 1195 { 1196 saxLog.debug( "ignorableWhitespace(" + new String( buffer, start, len ) + ")" ); 1197 } 1198 1199 // No processing required 1200 } 1201 1202 /** 1203 * {@inheritDoc} 1204 */ 1205 @Override 1206 public void processingInstruction( String target, String data ) 1207 throws SAXException 1208 { 1209 if ( customContentHandler != null ) 1210 { 1211 // forward calls instead of handling them here 1212 customContentHandler.processingInstruction( target, data ); 1213 return; 1214 } 1215 1216 if ( saxLog.isDebugEnabled() ) 1217 { 1218 saxLog.debug( "processingInstruction('" + target + "','" + data + "')" ); 1219 } 1220 1221 // No processing is required 1222 } 1223 1224 /** 1225 * Gets the document locator associated with our parser. 1226 * 1227 * @return the Locator supplied by the document parser 1228 */ 1229 public Locator getDocumentLocator() 1230 { 1231 return locator; 1232 } 1233 1234 /** 1235 * {@inheritDoc} 1236 */ 1237 @Override 1238 public void setDocumentLocator( Locator locator ) 1239 { 1240 if ( saxLog.isDebugEnabled() ) 1241 { 1242 saxLog.debug( "setDocumentLocator(" + locator + ")" ); 1243 } 1244 1245 this.locator = locator; 1246 } 1247 1248 /** 1249 * {@inheritDoc} 1250 */ 1251 @Override 1252 public void skippedEntity( String name ) 1253 throws SAXException 1254 { 1255 if ( saxLog.isDebugEnabled() ) 1256 { 1257 saxLog.debug( "skippedEntity(" + name + ")" ); 1258 } 1259 1260 // No processing required 1261 } 1262 1263 /** 1264 * {@inheritDoc} 1265 */ 1266 @Override 1267 public void startDocument() 1268 throws SAXException 1269 { 1270 if ( saxLog.isDebugEnabled() ) 1271 { 1272 saxLog.debug( "startDocument()" ); 1273 } 1274 1275 // ensure that the digester is properly configured, as 1276 // the digester could be used as a SAX ContentHandler 1277 // rather than via the parse() methods. 1278 configure(); 1279 } 1280 1281 /** 1282 * {@inheritDoc} 1283 */ 1284 @Override 1285 public void startElement( String namespaceURI, String localName, String qName, Attributes list ) 1286 throws SAXException 1287 { 1288 boolean debug = log.isDebugEnabled(); 1289 1290 if ( customContentHandler != null ) 1291 { 1292 // forward calls instead of handling them here 1293 customContentHandler.startElement( namespaceURI, localName, qName, list ); 1294 return; 1295 } 1296 1297 if ( saxLog.isDebugEnabled() ) 1298 { 1299 saxLog.debug( "startElement(" + namespaceURI + "," + localName + "," + qName + ")" ); 1300 } 1301 1302 // Save the body text accumulated for our surrounding element 1303 bodyTexts.push( bodyText ); 1304 if ( debug ) 1305 { 1306 log.debug( " Pushing body text '" + bodyText.toString() + "'" ); 1307 } 1308 bodyText = new StringBuilder(); 1309 1310 // the actual element name is either in localName or qName, depending 1311 // on whether the parser is namespace aware 1312 String name = localName; 1313 if ( ( name == null ) || ( name.length() < 1 ) ) 1314 { 1315 name = qName; 1316 } 1317 1318 // Compute the current matching rule 1319 StringBuilder sb = new StringBuilder( match ); 1320 if ( match.length() > 0 ) 1321 { 1322 sb.append( '/' ); 1323 } 1324 sb.append( name ); 1325 match = sb.toString(); 1326 if ( debug ) 1327 { 1328 log.debug( " New match='" + match + "'" ); 1329 } 1330 1331 // Fire "begin" events for all relevant rules 1332 List<Rule> rules = getRules().match( namespaceURI, match, localName, list ); 1333 matches.push( rules ); 1334 if ( ( rules != null ) && ( rules.size() > 0 ) ) 1335 { 1336 Substitutor substitutor = getSubstitutor(); 1337 if ( substitutor != null ) 1338 { 1339 list = substitutor.substitute( list ); 1340 } 1341 for ( int i = 0; i < rules.size(); i++ ) 1342 { 1343 try 1344 { 1345 Rule rule = rules.get( i ); 1346 if ( debug ) 1347 { 1348 log.debug( " Fire begin() for " + rule ); 1349 } 1350 rule.begin( namespaceURI, name, list ); 1351 } 1352 catch ( Exception e ) 1353 { 1354 log.error( "Begin event threw exception", e ); 1355 throw createSAXException( e ); 1356 } 1357 catch ( Error e ) 1358 { 1359 log.error( "Begin event threw error", e ); 1360 throw e; 1361 } 1362 } 1363 } 1364 else 1365 { 1366 if ( debug ) 1367 { 1368 log.debug( " No rules found matching '" + match + "'." ); 1369 } 1370 } 1371 } 1372 1373 /** 1374 * {@inheritDoc} 1375 */ 1376 @Override 1377 public void startPrefixMapping( String prefix, String namespaceURI ) 1378 throws SAXException 1379 { 1380 if ( saxLog.isDebugEnabled() ) 1381 { 1382 saxLog.debug( "startPrefixMapping(" + prefix + "," + namespaceURI + ")" ); 1383 } 1384 1385 // Register this prefix mapping 1386 Stack<String> stack = namespaces.get( prefix ); 1387 if ( stack == null ) 1388 { 1389 stack = new Stack<String>(); 1390 namespaces.put( prefix, stack ); 1391 } 1392 stack.push( namespaceURI ); 1393 } 1394 1395 // ----------------------------------------------------- DTDHandler Methods 1396 1397 /** 1398 * {@inheritDoc} 1399 */ 1400 @Override 1401 public void notationDecl( String name, String publicId, String systemId ) 1402 { 1403 if ( saxLog.isDebugEnabled() ) 1404 { 1405 saxLog.debug( "notationDecl(" + name + "," + publicId + "," + systemId + ")" ); 1406 } 1407 } 1408 1409 /** 1410 * {@inheritDoc} 1411 */ 1412 @Override 1413 public void unparsedEntityDecl( String name, String publicId, String systemId, String notation ) 1414 { 1415 if ( saxLog.isDebugEnabled() ) 1416 { 1417 saxLog.debug( "unparsedEntityDecl(" + name + "," + publicId + "," + systemId + "," + notation + ")" ); 1418 } 1419 } 1420 1421 // ----------------------------------------------- EntityResolver Methods 1422 1423 /** 1424 * Set the <code>EntityResolver</code> used by SAX when resolving public id and system id. This must be called 1425 * before the first call to <code>parse()</code>. 1426 * 1427 * @param entityResolver a class that implement the <code>EntityResolver</code> interface. 1428 */ 1429 public void setEntityResolver( EntityResolver entityResolver ) 1430 { 1431 this.entityResolver = entityResolver; 1432 } 1433 1434 /** 1435 * Return the Entity Resolver used by the SAX parser. 1436 * 1437 * @return the Entity Resolver used by the SAX parser. 1438 */ 1439 public EntityResolver getEntityResolver() 1440 { 1441 return entityResolver; 1442 } 1443 1444 /** 1445 * {@inheritDoc} 1446 */ 1447 @Override 1448 public InputSource resolveEntity( String publicId, String systemId ) 1449 throws SAXException 1450 { 1451 if ( saxLog.isDebugEnabled() ) 1452 { 1453 saxLog.debug( "resolveEntity('" + publicId + "', '" + systemId + "')" ); 1454 } 1455 1456 if ( publicId != null ) 1457 { 1458 this.publicId = publicId; 1459 } 1460 1461 // Has this system identifier been registered? 1462 URL entityURL = null; 1463 if ( publicId != null ) 1464 { 1465 entityURL = entityValidator.get( publicId ); 1466 } 1467 1468 // Redirect the schema location to a local destination 1469 if ( entityURL == null && systemId != null ) 1470 { 1471 entityURL = entityValidator.get( systemId ); 1472 } 1473 1474 if ( entityURL == null ) 1475 { 1476 if ( systemId == null ) 1477 { 1478 // cannot resolve 1479 if ( log.isDebugEnabled() ) 1480 { 1481 log.debug( " Cannot resolve null entity, returning null InputSource" ); 1482 } 1483 return ( null ); 1484 1485 } 1486 // try to resolve using system ID 1487 if ( log.isDebugEnabled() ) 1488 { 1489 log.debug( " Trying to resolve using system ID '" + systemId + "'" ); 1490 } 1491 try 1492 { 1493 entityURL = new URL( systemId ); 1494 } 1495 catch ( MalformedURLException e ) 1496 { 1497 throw new IllegalArgumentException( "Malformed URL '" + systemId + "' : " + e.getMessage() ); 1498 } 1499 } 1500 1501 // Return an input source to our alternative URL 1502 if ( log.isDebugEnabled() ) 1503 { 1504 log.debug( " Resolving to alternate DTD '" + entityURL + "'" ); 1505 } 1506 1507 try 1508 { 1509 return createInputSourceFromURL( entityURL ); 1510 } 1511 catch ( Exception e ) 1512 { 1513 throw createSAXException( e ); 1514 } 1515 } 1516 1517 // ------------------------------------------------- ErrorHandler Methods 1518 1519 /** 1520 * {@inheritDoc} 1521 */ 1522 @Override 1523 public void error( SAXParseException exception ) 1524 throws SAXException 1525 { 1526 log.error( "Parse Error at line " + exception.getLineNumber() + " column " + exception.getColumnNumber() + ": " 1527 + exception.getMessage(), exception ); 1528 if ( errorHandler != null ) 1529 { 1530 errorHandler.error( exception ); 1531 } 1532 } 1533 1534 /** 1535 * {@inheritDoc} 1536 */ 1537 @Override 1538 public void fatalError( SAXParseException exception ) 1539 throws SAXException 1540 { 1541 log.error( "Parse Fatal Error at line " + exception.getLineNumber() + " column " + exception.getColumnNumber() 1542 + ": " + exception.getMessage(), exception ); 1543 if ( errorHandler != null ) 1544 { 1545 errorHandler.fatalError( exception ); 1546 } 1547 } 1548 1549 /** 1550 * {@inheritDoc} 1551 */ 1552 @Override 1553 public void warning( SAXParseException exception ) 1554 throws SAXException 1555 { 1556 if ( errorHandler != null ) 1557 { 1558 log.warn( "Parse Warning Error at line " + exception.getLineNumber() + " column " 1559 + exception.getColumnNumber() + ": " + exception.getMessage(), exception ); 1560 1561 errorHandler.warning( exception ); 1562 } 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 catch ( SAXException e ) 1651 { 1652 log.error( format( "An error occurred while parsing XML from '%s', see nested exceptions", systemId ), 1653 e ); 1654 throw e; 1655 } 1656 cleanup(); 1657 return this.<T> getRoot(); 1658 } 1659 1660 /** 1661 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1662 * 1663 * @param <T> The result type returned by the returned Future's {@code get} method 1664 * @param input Input source containing the XML data to be parsed 1665 * @return a Future that can be used to track when the parse has been fully processed. 1666 * @see Digester#parse(InputSource) 1667 * @since 3.1 1668 */ 1669 public <T> Future<T> asyncParse( final InputSource input ) 1670 { 1671 return asyncParse( new Callable<T>() 1672 { 1673 1674 public T call() 1675 throws Exception 1676 { 1677 return Digester.this.<T> parse( input ); 1678 } 1679 1680 } ); 1681 } 1682 1683 /** 1684 * Parse the content of the specified input stream using this Digester. Returns the root element from the object 1685 * stack (if any). 1686 * 1687 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1688 * @param input Input stream containing the XML data to be parsed 1689 * @return the root element from the object stack (if any) 1690 * @exception IOException if an input/output error occurs 1691 * @exception SAXException if a parsing exception occurs 1692 */ 1693 public <T> T parse( InputStream input ) 1694 throws IOException, SAXException 1695 { 1696 if ( input == null ) 1697 { 1698 throw new IllegalArgumentException( "InputStream to parse is null" ); 1699 } 1700 1701 return ( this.<T> parse( new InputSource( input ) ) ); 1702 } 1703 1704 /** 1705 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1706 * 1707 * @param <T> The result type returned by the returned Future's {@code get} method 1708 * @param input Input stream containing the XML data to be parsed 1709 * @return a Future that can be used to track when the parse has been fully processed. 1710 * @see Digester#parse(InputStream) 1711 * @since 3.1 1712 */ 1713 public <T> Future<T> asyncParse( final InputStream input ) 1714 { 1715 return asyncParse( new Callable<T>() 1716 { 1717 1718 public T call() 1719 throws Exception 1720 { 1721 return Digester.this.<T> parse( input ); 1722 } 1723 1724 } ); 1725 } 1726 1727 /** 1728 * Parse the content of the specified reader using this Digester. Returns the root element from the object stack (if 1729 * any). 1730 * 1731 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1732 * @param reader Reader containing the XML data to be parsed 1733 * @return the root element from the object stack (if any) 1734 * @exception IOException if an input/output error occurs 1735 * @exception SAXException if a parsing exception occurs 1736 */ 1737 public <T> T parse( Reader reader ) 1738 throws IOException, SAXException 1739 { 1740 if ( reader == null ) 1741 { 1742 throw new IllegalArgumentException( "Reader to parse is null" ); 1743 } 1744 1745 return ( this.<T> parse( new InputSource( reader ) ) ); 1746 } 1747 1748 /** 1749 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1750 * 1751 * @param <T> The result type returned by the returned Future's {@code get} method 1752 * @param reader Reader containing the XML data to be parsed 1753 * @return a Future that can be used to track when the parse has been fully processed. 1754 * @see Digester#parse(Reader) 1755 * @since 3.1 1756 */ 1757 public <T> Future<T> asyncParse( final Reader reader ) 1758 { 1759 return asyncParse( new Callable<T>() 1760 { 1761 1762 public T call() 1763 throws Exception 1764 { 1765 return Digester.this.<T> parse( reader ); 1766 } 1767 1768 } ); 1769 } 1770 1771 /** 1772 * Parse the content of the specified URI using this Digester. Returns the root element from the object stack (if 1773 * any). 1774 * 1775 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1776 * @param uri URI containing the XML data to be parsed 1777 * @return the root element from the object stack (if any) 1778 * @exception IOException if an input/output error occurs 1779 * @exception SAXException if a parsing exception occurs 1780 */ 1781 public <T> T parse( String uri ) 1782 throws IOException, SAXException 1783 { 1784 if ( uri == null ) 1785 { 1786 throw new IllegalArgumentException( "String URI to parse is null" ); 1787 } 1788 1789 return ( this.<T> parse( createInputSourceFromURL( uri ) ) ); 1790 } 1791 1792 /** 1793 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1794 * 1795 * @param <T> The result type returned by the returned Future's {@code get} method 1796 * @param uri URI containing the XML data to be parsed 1797 * @return a Future that can be used to track when the parse has been fully processed. 1798 * @see Digester#parse(String) 1799 * @since 3.1 1800 */ 1801 public <T> Future<T> asyncParse( final String uri ) 1802 { 1803 return asyncParse( new Callable<T>() 1804 { 1805 1806 public T call() 1807 throws Exception 1808 { 1809 return Digester.this.<T> parse( uri ); 1810 } 1811 1812 } ); 1813 } 1814 1815 /** 1816 * Parse the content of the specified URL using this Digester. Returns the root element from the object stack (if 1817 * any). 1818 * 1819 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1820 * @param url URL containing the XML data to be parsed 1821 * @return the root element from the object stack (if any) 1822 * @exception IOException if an input/output error occurs 1823 * @exception SAXException if a parsing exception occurs 1824 * @since 1.8 1825 */ 1826 public <T> T parse( URL url ) 1827 throws IOException, SAXException 1828 { 1829 if ( url == null ) 1830 { 1831 throw new IllegalArgumentException( "URL to parse is null" ); 1832 } 1833 1834 return ( this.<T> parse( createInputSourceFromURL( url ) ) ); 1835 } 1836 1837 /** 1838 * Creates a Callable instance that parse the content of the specified reader using this Digester. 1839 * 1840 * @param <T> The result type returned by the returned Future's {@code get} method 1841 * @param url URL containing the XML data to be parsed 1842 * @return a Future that can be used to track when the parse has been fully processed. 1843 * @see Digester#parse(URL) 1844 * @since 3.1 1845 */ 1846 public <T> Future<T> asyncParse( final URL url ) 1847 { 1848 return asyncParse( new Callable<T>() 1849 { 1850 1851 public T call() 1852 throws Exception 1853 { 1854 return Digester.this.<T> parse( url ); 1855 } 1856 1857 } ); 1858 } 1859 1860 /** 1861 * Execute the parse in async mode. 1862 * 1863 * @param <T> the type used to auto-cast the returned object to the assigned variable type 1864 * @param callable 1865 * @return a Future that can be used to track when the parse has been fully processed. 1866 * @since 3.1 1867 */ 1868 private <T> Future<T> asyncParse( Callable<T> callable ) 1869 { 1870 if ( executorService == null ) 1871 { 1872 throw new IllegalStateException( "ExecutorService not set" ); 1873 } 1874 1875 return executorService.submit( callable ); 1876 } 1877 1878 /** 1879 * <p> 1880 * Register the specified DTD URL for the specified public identifier. This must be called before the first call to 1881 * <code>parse()</code>. 1882 * </p> 1883 * <p> 1884 * <code>Digester</code> contains an internal <code>EntityResolver</code> implementation. This maps 1885 * <code>PUBLICID</code>'s to URLs (from which the resource will be loaded). A common use case for this method is to 1886 * register local URLs (possibly computed at runtime by a classloader) for DTDs. This allows the performance 1887 * advantage of using a local version without having to ensure every <code>SYSTEM</code> URI on every processed xml 1888 * document is local. This implementation provides only basic functionality. If more sophisticated features are 1889 * required, using {@link #setEntityResolver} to set a custom resolver is recommended. 1890 * </p> 1891 * <p> 1892 * <strong>Note:</strong> This method will have no effect when a custom <code>EntityResolver</code> has been set. 1893 * (Setting a custom <code>EntityResolver</code> overrides the internal implementation.) 1894 * </p> 1895 * 1896 * @param publicId Public identifier of the DTD to be resolved 1897 * @param entityURL The URL to use for reading this DTD 1898 * @since 1.8 1899 */ 1900 public void register( String publicId, URL entityURL ) 1901 { 1902 if ( log.isDebugEnabled() ) 1903 { 1904 log.debug( "register('" + publicId + "', '" + entityURL + "'" ); 1905 } 1906 entityValidator.put( publicId, entityURL ); 1907 } 1908 1909 /** 1910 * <p> 1911 * Convenience method that registers the string version of an entity URL instead of a URL version. 1912 * </p> 1913 * 1914 * @param publicId Public identifier of the entity to be resolved 1915 * @param entityURL The URL to use for reading this entity 1916 */ 1917 public void register( String publicId, String entityURL ) 1918 { 1919 if ( log.isDebugEnabled() ) 1920 { 1921 log.debug( "register('" + publicId + "', '" + entityURL + "'" ); 1922 } 1923 try 1924 { 1925 entityValidator.put( publicId, new URL( entityURL ) ); 1926 } 1927 catch ( MalformedURLException e ) 1928 { 1929 throw new IllegalArgumentException( "Malformed URL '" + entityURL + "' : " + e.getMessage() ); 1930 } 1931 } 1932 1933 /** 1934 * Convenience method that registers DTD URLs for the specified public identifiers. 1935 * 1936 * @param entityValidator The URLs of entityValidator that have been registered, keyed by the public 1937 * identifier that corresponds. 1938 * @since 3.0 1939 */ 1940 public void registerAll( Map<String, URL> entityValidator ) 1941 { 1942 this.entityValidator.putAll( entityValidator ); 1943 } 1944 1945 /** 1946 * <p> 1947 * <code>List</code> of <code>InputSource</code> instances created by a <code>createInputSourceFromURL()</code> 1948 * method call. These represent open input streams that need to be closed to avoid resource leaks, as well as 1949 * potentially locked JAR files on Windows. 1950 * </p> 1951 */ 1952 protected List<InputSource> inputSources = new ArrayList<InputSource>( 5 ); 1953 1954 /** 1955 * Given a URL, return an InputSource that reads from that URL. 1956 * <p> 1957 * Ideally this function would not be needed and code could just use <code>new InputSource(entityURL)</code>. 1958 * Unfortunately it appears that when the entityURL points to a file within a jar archive a caching mechanism inside 1959 * the InputSource implementation causes a file-handle to the jar file to remain open. On Windows systems this then 1960 * causes the jar archive file to be locked on disk ("in use") which makes it impossible to delete the jar file - 1961 * and that really stuffs up "undeploy" in webapps in particular. 1962 * <p> 1963 * In JDK1.4 and later, Apache XercesJ is used as the xml parser. The InputSource object provided is converted into 1964 * an XMLInputSource, and eventually passed to an instance of XMLDocumentScannerImpl to specify the source data to 1965 * be converted into tokens for the rest of the XMLReader code to handle. XMLDocumentScannerImpl calls 1966 * fEntityManager.startDocumentEntity(source), where fEntityManager is declared in ancestor class XMLScanner to be 1967 * an XMLEntityManager. In that class, if the input source stream is null, then: 1968 * 1969 * <pre> 1970 * URL location = new URL( expandedSystemId ); 1971 * URLConnection connect = location.openConnection(); 1972 * if ( connect instanceof HttpURLConnection ) 1973 * { 1974 * setHttpProperties( connect, xmlInputSource ); 1975 * } 1976 * stream = connect.getInputStream(); 1977 * </pre> 1978 * 1979 * This method pretty much duplicates the standard behaviour, except that it calls URLConnection.setUseCaches(false) 1980 * before opening the connection. 1981 * 1982 * @param url The URL has to be read 1983 * @return The InputSource that reads from the input URL 1984 * @throws IOException if any error occurs while reading the input URL 1985 * @since 1.8 1986 */ 1987 public InputSource createInputSourceFromURL( URL url ) 1988 throws IOException 1989 { 1990 URLConnection connection = url.openConnection(); 1991 connection.setUseCaches( false ); 1992 InputStream stream = connection.getInputStream(); 1993 InputSource source = new InputSource( stream ); 1994 source.setSystemId( url.toExternalForm() ); 1995 inputSources.add( source ); 1996 return source; 1997 } 1998 1999 /** 2000 * <p> 2001 * Convenience method that creates an <code>InputSource</code> from the string version of a URL. 2002 * </p> 2003 * 2004 * @param url URL for which to create an <code>InputSource</code> 2005 * @return The InputSource that reads from the input URL 2006 * @throws IOException if any error occurs while reading the input URL 2007 * @since 1.8 2008 */ 2009 public InputSource createInputSourceFromURL( String url ) 2010 throws IOException 2011 { 2012 return createInputSourceFromURL( new URL( url ) ); 2013 } 2014 2015 // --------------------------------------------------------- Rule Methods 2016 2017 /** 2018 * <p> 2019 * Register a new Rule matching the specified pattern. This method sets the <code>Digester</code> property on the 2020 * rule. 2021 * </p> 2022 * 2023 * @param pattern Element matching pattern 2024 * @param rule Rule to be registered 2025 */ 2026 public void addRule( String pattern, Rule rule ) 2027 { 2028 rule.setDigester( this ); 2029 getRules().add( pattern, rule ); 2030 } 2031 2032 /** 2033 * Register a set of Rule instances defined in a RuleSet. 2034 * 2035 * @param ruleSet The RuleSet instance to configure from 2036 */ 2037 public void addRuleSet( RuleSet ruleSet ) 2038 { 2039 String oldNamespaceURI = getRuleNamespaceURI(); 2040 String newNamespaceURI = ruleSet.getNamespaceURI(); 2041 if ( log.isDebugEnabled() ) 2042 { 2043 if ( newNamespaceURI == null ) 2044 { 2045 log.debug( "addRuleSet() with no namespace URI" ); 2046 } 2047 else 2048 { 2049 log.debug( "addRuleSet() with namespace URI " + newNamespaceURI ); 2050 } 2051 } 2052 setRuleNamespaceURI( newNamespaceURI ); 2053 ruleSet.addRuleInstances( this ); 2054 setRuleNamespaceURI( oldNamespaceURI ); 2055 } 2056 2057 /** 2058 * Add a "bean property setter" rule for the specified parameters. 2059 * 2060 * @param pattern Element matching pattern 2061 * @see BeanPropertySetterRule 2062 */ 2063 public void addBeanPropertySetter( String pattern ) 2064 { 2065 addRule( pattern, new BeanPropertySetterRule() ); 2066 } 2067 2068 /** 2069 * Add a "bean property setter" rule for the specified parameters. 2070 * 2071 * @param pattern Element matching pattern 2072 * @param propertyName Name of property to set 2073 * @see BeanPropertySetterRule 2074 */ 2075 public void addBeanPropertySetter( String pattern, String propertyName ) 2076 { 2077 addRule( pattern, new BeanPropertySetterRule( propertyName ) ); 2078 } 2079 2080 /** 2081 * Add an "call method" rule for a method which accepts no arguments. 2082 * 2083 * @param pattern Element matching pattern 2084 * @param methodName Method name to be called 2085 * @see CallMethodRule 2086 */ 2087 public void addCallMethod( String pattern, String methodName ) 2088 { 2089 addRule( pattern, new CallMethodRule( methodName ) ); 2090 } 2091 2092 /** 2093 * Add an "call method" rule for the specified parameters. 2094 * 2095 * @param pattern Element matching pattern 2096 * @param methodName Method name to be called 2097 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element) 2098 * @see CallMethodRule 2099 */ 2100 public void addCallMethod( String pattern, String methodName, int paramCount ) 2101 { 2102 addRule( pattern, new CallMethodRule( methodName, paramCount ) ); 2103 } 2104 2105 /** 2106 * Add an "call method" rule for the specified parameters. If <code>paramCount</code> is set to zero the rule will 2107 * use the body of the matched element as the single argument of the method, unless <code>paramTypes</code> is null 2108 * or empty, in this case the rule will call the specified method with no arguments. 2109 * 2110 * @param pattern Element matching pattern 2111 * @param methodName Method name to be called 2112 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element) 2113 * @param paramTypes Set of Java class names for the types of the expected parameters (if you wish to use a 2114 * primitive type, specify the corresonding Java wrapper class instead, such as 2115 * <code>java.lang.Boolean</code> for a <code>boolean</code> parameter) 2116 * @see CallMethodRule 2117 */ 2118 public void addCallMethod( String pattern, String methodName, int paramCount, String paramTypes[] ) 2119 { 2120 addRule( pattern, new CallMethodRule( methodName, paramCount, paramTypes ) ); 2121 } 2122 2123 /** 2124 * Add an "call method" rule for the specified parameters. If <code>paramCount</code> is set to zero the rule will 2125 * use the body of the matched element as the single argument of the method, unless <code>paramTypes</code> is null 2126 * or empty, in this case the rule will call the specified method with no arguments. 2127 * 2128 * @param pattern Element matching pattern 2129 * @param methodName Method name to be called 2130 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element) 2131 * @param paramTypes The Java class names of the arguments (if you wish to use a primitive type, specify the 2132 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a 2133 * <code>boolean</code> parameter) 2134 * @see CallMethodRule 2135 */ 2136 public void addCallMethod( String pattern, String methodName, int paramCount, Class<?> paramTypes[] ) 2137 { 2138 addRule( pattern, new CallMethodRule( methodName, paramCount, paramTypes ) ); 2139 } 2140 2141 /** 2142 * Add a "call parameter" rule for the specified parameters. 2143 * 2144 * @param pattern Element matching pattern 2145 * @param paramIndex Zero-relative parameter index to set (from the body of this element) 2146 * @see CallParamRule 2147 */ 2148 public void addCallParam( String pattern, int paramIndex ) 2149 { 2150 addRule( pattern, new CallParamRule( paramIndex ) ); 2151 } 2152 2153 /** 2154 * Add a "call parameter" rule for the specified parameters. 2155 * 2156 * @param pattern Element matching pattern 2157 * @param paramIndex Zero-relative parameter index to set (from the specified attribute) 2158 * @param attributeName Attribute whose value is used as the parameter value 2159 * @see CallParamRule 2160 */ 2161 public void addCallParam( String pattern, int paramIndex, String attributeName ) 2162 { 2163 addRule( pattern, new CallParamRule( paramIndex, attributeName ) ); 2164 } 2165 2166 /** 2167 * Add a "call parameter" rule. This will either take a parameter from the stack or from the current element body 2168 * text. 2169 * 2170 * @param pattern Element matching pattern 2171 * @param paramIndex The zero-relative parameter number 2172 * @param fromStack Should the call parameter be taken from the top of the stack? 2173 * @see CallParamRule 2174 */ 2175 public void addCallParam( String pattern, int paramIndex, boolean fromStack ) 2176 { 2177 addRule( pattern, new CallParamRule( paramIndex, fromStack ) ); 2178 } 2179 2180 /** 2181 * Add a "call parameter" rule that sets a parameter from the stack. This takes a parameter from the given position 2182 * on the stack. 2183 * 2184 * @param pattern Element matching pattern 2185 * @param paramIndex The zero-relative parameter number 2186 * @param stackIndex set the call parameter to the stackIndex'th object down the stack, where 0 is the top of the 2187 * stack, 1 the next element down and so on 2188 * @see CallMethodRule 2189 */ 2190 public void addCallParam( String pattern, int paramIndex, int stackIndex ) 2191 { 2192 addRule( pattern, new CallParamRule( paramIndex, stackIndex ) ); 2193 } 2194 2195 /** 2196 * Add a "call parameter" rule that sets a parameter from the current <code>Digester</code> matching path. This is 2197 * sometimes useful when using rules that support wildcards. 2198 * 2199 * @param pattern the pattern that this rule should match 2200 * @param paramIndex The zero-relative parameter number 2201 * @see CallMethodRule 2202 */ 2203 public void addCallParamPath( String pattern, int paramIndex ) 2204 { 2205 addRule( pattern, new PathCallParamRule( paramIndex ) ); 2206 } 2207 2208 /** 2209 * Add a "call parameter" rule that sets a parameter from a caller-provided object. This can be used to pass 2210 * constants such as strings to methods; it can also be used to pass mutable objects, providing ways for objects to 2211 * do things like "register" themselves with some shared object. 2212 * <p> 2213 * Note that when attempting to locate a matching method to invoke, the true type of the paramObj is used, so that 2214 * despite the paramObj being passed in here as type Object, the target method can declare its parameters as being 2215 * the true type of the object (or some ancestor type, according to the usual type-conversion rules). 2216 * 2217 * @param pattern Element matching pattern 2218 * @param paramIndex The zero-relative parameter number 2219 * @param paramObj Any arbitrary object to be passed to the target method. 2220 * @see CallMethodRule 2221 * @since 1.6 2222 */ 2223 public void addObjectParam( String pattern, int paramIndex, Object paramObj ) 2224 { 2225 addRule( pattern, new ObjectParamRule( paramIndex, paramObj ) ); 2226 } 2227 2228 /** 2229 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2230 * will be propagated. 2231 * 2232 * @param pattern Element matching pattern 2233 * @param className Java class name of the object creation factory class 2234 * @see FactoryCreateRule 2235 */ 2236 public void addFactoryCreate( String pattern, String className ) 2237 { 2238 addFactoryCreate( pattern, className, false ); 2239 } 2240 2241 /** 2242 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2243 * will be propagated. 2244 * 2245 * @param pattern Element matching pattern 2246 * @param clazz Java class of the object creation factory class 2247 * @see FactoryCreateRule 2248 */ 2249 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz ) 2250 { 2251 addFactoryCreate( pattern, clazz, false ); 2252 } 2253 2254 /** 2255 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2256 * will be propagated. 2257 * 2258 * @param pattern Element matching pattern 2259 * @param className Java class name of the object creation factory class 2260 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2261 * @see FactoryCreateRule 2262 */ 2263 public void addFactoryCreate( String pattern, String className, String attributeName ) 2264 { 2265 addFactoryCreate( pattern, className, attributeName, false ); 2266 } 2267 2268 /** 2269 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2270 * will be propagated. 2271 * 2272 * @param pattern Element matching pattern 2273 * @param clazz Java class of the object creation factory class 2274 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2275 * @see FactoryCreateRule 2276 */ 2277 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz, 2278 String attributeName ) 2279 { 2280 addFactoryCreate( pattern, clazz, attributeName, false ); 2281 } 2282 2283 /** 2284 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process 2285 * will be propagated. 2286 * 2287 * @param pattern Element matching pattern 2288 * @param creationFactory Previously instantiated ObjectCreationFactory to be utilized 2289 * @see FactoryCreateRule 2290 */ 2291 public void addFactoryCreate( String pattern, ObjectCreationFactory<?> creationFactory ) 2292 { 2293 addFactoryCreate( pattern, creationFactory, false ); 2294 } 2295 2296 /** 2297 * Add a "factory create" rule for the specified parameters. 2298 * 2299 * @param pattern Element matching pattern 2300 * @param className Java class name of the object creation factory class 2301 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2302 * ignored. 2303 * @see FactoryCreateRule 2304 */ 2305 public void addFactoryCreate( String pattern, String className, boolean ignoreCreateExceptions ) 2306 { 2307 addRule( pattern, new FactoryCreateRule( className, ignoreCreateExceptions ) ); 2308 } 2309 2310 /** 2311 * Add a "factory create" rule for the specified parameters. 2312 * 2313 * @param pattern Element matching pattern 2314 * @param clazz Java class of the object creation factory class 2315 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2316 * ignored. 2317 * @see FactoryCreateRule 2318 */ 2319 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz, 2320 boolean ignoreCreateExceptions ) 2321 { 2322 addRule( pattern, new FactoryCreateRule( clazz, ignoreCreateExceptions ) ); 2323 } 2324 2325 /** 2326 * Add a "factory create" rule for the specified parameters. 2327 * 2328 * @param pattern Element matching pattern 2329 * @param className Java class name of the object creation factory class 2330 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2331 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2332 * ignored. 2333 * @see FactoryCreateRule 2334 */ 2335 public void addFactoryCreate( String pattern, String className, String attributeName, 2336 boolean ignoreCreateExceptions ) 2337 { 2338 addRule( pattern, new FactoryCreateRule( className, attributeName, ignoreCreateExceptions ) ); 2339 } 2340 2341 /** 2342 * Add a "factory create" rule for the specified parameters. 2343 * 2344 * @param pattern Element matching pattern 2345 * @param clazz Java class of the object creation factory class 2346 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code> 2347 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2348 * ignored. 2349 * @see FactoryCreateRule 2350 */ 2351 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz, 2352 String attributeName, boolean ignoreCreateExceptions ) 2353 { 2354 addRule( pattern, new FactoryCreateRule( clazz, attributeName, ignoreCreateExceptions ) ); 2355 } 2356 2357 /** 2358 * Add a "factory create" rule for the specified parameters. 2359 * 2360 * @param pattern Element matching pattern 2361 * @param creationFactory Previously instantiated ObjectCreationFactory to be utilized 2362 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be 2363 * ignored. 2364 * @see FactoryCreateRule 2365 */ 2366 public void addFactoryCreate( String pattern, ObjectCreationFactory<?> creationFactory, 2367 boolean ignoreCreateExceptions ) 2368 { 2369 creationFactory.setDigester( this ); 2370 addRule( pattern, new FactoryCreateRule( creationFactory, ignoreCreateExceptions ) ); 2371 } 2372 2373 /** 2374 * Add an "object create" rule for the specified parameters. 2375 * 2376 * @param pattern Element matching pattern 2377 * @param className Java class name to be created 2378 * @see ObjectCreateRule 2379 */ 2380 public void addObjectCreate( String pattern, String className ) 2381 { 2382 addRule( pattern, new ObjectCreateRule( className ) ); 2383 } 2384 2385 /** 2386 * Add an "object create" rule for the specified parameters. 2387 * 2388 * @param pattern Element matching pattern 2389 * @param clazz Java class to be created 2390 * @see ObjectCreateRule 2391 */ 2392 public void addObjectCreate( String pattern, Class<?> clazz ) 2393 { 2394 addRule( pattern, new ObjectCreateRule( clazz ) ); 2395 } 2396 2397 /** 2398 * Add an "object create" rule for the specified parameters. 2399 * 2400 * @param pattern Element matching pattern 2401 * @param className Default Java class name to be created 2402 * @param attributeName Attribute name that optionally overrides the default Java class name to be created 2403 * @see ObjectCreateRule 2404 */ 2405 public void addObjectCreate( String pattern, String className, String attributeName ) 2406 { 2407 addRule( pattern, new ObjectCreateRule( className, attributeName ) ); 2408 } 2409 2410 /** 2411 * Add an "object create" rule for the specified parameters. 2412 * 2413 * @param pattern Element matching pattern 2414 * @param attributeName Attribute name that optionally overrides 2415 * @param clazz Default Java class to be created the default Java class name to be created 2416 * @see ObjectCreateRule 2417 */ 2418 public void addObjectCreate( String pattern, String attributeName, Class<?> clazz ) 2419 { 2420 addRule( pattern, new ObjectCreateRule( attributeName, clazz ) ); 2421 } 2422 2423 /** 2424 * Adds an {@link SetNestedPropertiesRule}. 2425 * 2426 * @param pattern register the rule with this pattern 2427 * @since 1.6 2428 */ 2429 public void addSetNestedProperties( String pattern ) 2430 { 2431 addRule( pattern, new SetNestedPropertiesRule() ); 2432 } 2433 2434 /** 2435 * Adds an {@link SetNestedPropertiesRule}. 2436 * 2437 * @param pattern register the rule with this pattern 2438 * @param elementName elment name that a property maps to 2439 * @param propertyName property name of the element mapped from 2440 * @since 1.6 2441 */ 2442 public void addSetNestedProperties( String pattern, String elementName, String propertyName ) 2443 { 2444 addRule( pattern, new SetNestedPropertiesRule( elementName, propertyName ) ); 2445 } 2446 2447 /** 2448 * Adds an {@link SetNestedPropertiesRule}. 2449 * 2450 * @param pattern register the rule with this pattern 2451 * @param elementNames elment names that (in order) map to properties 2452 * @param propertyNames property names that (in order) elements are mapped to 2453 * @since 1.6 2454 */ 2455 public void addSetNestedProperties( String pattern, String[] elementNames, String[] propertyNames ) 2456 { 2457 addRule( pattern, new SetNestedPropertiesRule( elementNames, propertyNames ) ); 2458 } 2459 2460 /** 2461 * Add a "set next" rule for the specified parameters. 2462 * 2463 * @param pattern Element matching pattern 2464 * @param methodName Method name to call on the parent element 2465 * @see SetNextRule 2466 */ 2467 public void addSetNext( String pattern, String methodName ) 2468 { 2469 addRule( pattern, new SetNextRule( methodName ) ); 2470 } 2471 2472 /** 2473 * Add a "set next" rule for the specified parameters. 2474 * 2475 * @param pattern Element matching pattern 2476 * @param methodName Method name to call on the parent element 2477 * @param paramType Java class name of the expected parameter type (if you wish to use a primitive type, specify the 2478 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a 2479 * <code>boolean</code> parameter) 2480 * @see SetNextRule 2481 */ 2482 public void addSetNext( String pattern, String methodName, String paramType ) 2483 { 2484 addRule( pattern, new SetNextRule( methodName, paramType ) ); 2485 } 2486 2487 /** 2488 * Add {@link SetRootRule} with the specified parameters. 2489 * 2490 * @param pattern Element matching pattern 2491 * @param methodName Method name to call on the root object 2492 * @see SetRootRule 2493 */ 2494 public void addSetRoot( String pattern, String methodName ) 2495 { 2496 addRule( pattern, new SetRootRule( methodName ) ); 2497 } 2498 2499 /** 2500 * Add {@link SetRootRule} with the specified parameters. 2501 * 2502 * @param pattern Element matching pattern 2503 * @param methodName Method name to call on the root object 2504 * @param paramType Java class name of the expected parameter type 2505 * @see SetRootRule 2506 */ 2507 public void addSetRoot( String pattern, String methodName, String paramType ) 2508 { 2509 addRule( pattern, new SetRootRule( methodName, paramType ) ); 2510 } 2511 2512 /** 2513 * Add a "set properties" rule for the specified parameters. 2514 * 2515 * @param pattern Element matching pattern 2516 * @see SetPropertiesRule 2517 */ 2518 public void addSetProperties( String pattern ) 2519 { 2520 addRule( pattern, new SetPropertiesRule() ); 2521 } 2522 2523 /** 2524 * Add a "set properties" rule with a single overridden parameter. See 2525 * {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)} 2526 * 2527 * @param pattern Element matching pattern 2528 * @param attributeName map this attribute 2529 * @param propertyName to this property 2530 * @see SetPropertiesRule 2531 */ 2532 public void addSetProperties( String pattern, String attributeName, String propertyName ) 2533 { 2534 addRule( pattern, new SetPropertiesRule( attributeName, propertyName ) ); 2535 } 2536 2537 /** 2538 * Add a "set properties" rule with overridden parameters. See 2539 * {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)} 2540 * 2541 * @param pattern Element matching pattern 2542 * @param attributeNames names of attributes with custom mappings 2543 * @param propertyNames property names these attributes map to 2544 * @see SetPropertiesRule 2545 */ 2546 public void addSetProperties( String pattern, String[] attributeNames, String[] propertyNames ) 2547 { 2548 addRule( pattern, new SetPropertiesRule( attributeNames, propertyNames ) ); 2549 } 2550 2551 /** 2552 * Add a "set property" rule for the specified parameters. 2553 * 2554 * @param pattern Element matching pattern 2555 * @param name Attribute name containing the property name to be set 2556 * @param value Attribute name containing the property value to set 2557 * @see SetPropertyRule 2558 */ 2559 public void addSetProperty( String pattern, String name, String value ) 2560 { 2561 addRule( pattern, new SetPropertyRule( name, value ) ); 2562 } 2563 2564 /** 2565 * Add a "set top" rule for the specified parameters. 2566 * 2567 * @param pattern Element matching pattern 2568 * @param methodName Method name to call on the parent element 2569 * @see SetTopRule 2570 */ 2571 public void addSetTop( String pattern, String methodName ) 2572 { 2573 addRule( pattern, new SetTopRule( methodName ) ); 2574 } 2575 2576 /** 2577 * Add a "set top" rule for the specified parameters. 2578 * 2579 * @param pattern Element matching pattern 2580 * @param methodName Method name to call on the parent element 2581 * @param paramType Java class name of the expected parameter type (if you wish to use a primitive type, specify the 2582 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a 2583 * <code>boolean</code> parameter) 2584 * @see SetTopRule 2585 */ 2586 public void addSetTop( String pattern, String methodName, String paramType ) 2587 { 2588 addRule( pattern, new SetTopRule( methodName, paramType ) ); 2589 } 2590 2591 // --------------------------------------------------- Object Stack Methods 2592 2593 /** 2594 * Clear the current contents of the default object stack, the param stack, all named stacks, and other internal 2595 * variables. 2596 * <p> 2597 * Calling this method <i>might</i> allow another document of the same type to be correctly parsed. However this 2598 * method was not intended for this purpose (just to tidy up memory usage). In general, a separate Digester object 2599 * should be created for each document to be parsed. 2600 * <p> 2601 * Note that this method is called automatically after a document has been successfully parsed by a Digester 2602 * instance. However it is not invoked automatically when a parse fails, so when reusing a Digester instance (which 2603 * is not recommended) this method <i>must</i> be called manually after a parse failure. 2604 */ 2605 public void clear() 2606 { 2607 match = ""; 2608 bodyTexts.clear(); 2609 params.clear(); 2610 publicId = null; 2611 stack.clear(); 2612 stacksByName.clear(); 2613 customContentHandler = null; 2614 } 2615 2616 /** 2617 * Return the top object on the stack without removing it. 2618 * 2619 * If there are no objects on the stack, return <code>null</code>. 2620 * 2621 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2622 * @return the top object on the stack without removing it. 2623 */ 2624 public <T> T peek() 2625 { 2626 try 2627 { 2628 return this.<T> npeSafeCast( stack.peek() ); 2629 } 2630 catch ( EmptyStackException e ) 2631 { 2632 log.warn( "Empty stack (returning null)" ); 2633 return ( null ); 2634 } 2635 } 2636 2637 /** 2638 * Return the n'th object down the stack, where 0 is the top element and [getCount()-1] is the bottom element. If 2639 * the specified index is out of range, return <code>null</code>. 2640 * 2641 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2642 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on. 2643 * @return the n'th object down the stack 2644 */ 2645 public <T> T peek( int n ) 2646 { 2647 int index = ( stack.size() - 1 ) - n; 2648 if ( index < 0 ) 2649 { 2650 log.warn( "Empty stack (returning null)" ); 2651 return ( null ); 2652 } 2653 try 2654 { 2655 return this.<T> npeSafeCast( stack.get( index ) ); 2656 } 2657 catch ( EmptyStackException e ) 2658 { 2659 log.warn( "Empty stack (returning null)" ); 2660 return ( null ); 2661 } 2662 } 2663 2664 /** 2665 * Pop the top object off of the stack, and return it. If there are no objects on the stack, return 2666 * <code>null</code>. 2667 * 2668 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2669 * @return the top object popped off of the stack 2670 */ 2671 public <T> T pop() 2672 { 2673 try 2674 { 2675 T popped = this.<T> npeSafeCast( stack.pop() ); 2676 if ( stackAction != null ) 2677 { 2678 popped = stackAction.onPop( this, null, popped ); 2679 } 2680 return popped; 2681 } 2682 catch ( EmptyStackException e ) 2683 { 2684 log.warn( "Empty stack (returning null)" ); 2685 return ( null ); 2686 } 2687 } 2688 2689 /** 2690 * Push a new object onto the top of the object stack. 2691 * 2692 * @param <T> any type of the pushed object 2693 * @param object The new object 2694 */ 2695 public <T> void push( T object ) 2696 { 2697 if ( stackAction != null ) 2698 { 2699 object = stackAction.onPush( this, null, object ); 2700 } 2701 2702 if ( stack.size() == 0 ) 2703 { 2704 root = object; 2705 } 2706 stack.push( object ); 2707 } 2708 2709 /** 2710 * Pushes the given object onto the stack with the given name. If no stack already exists with the given name then 2711 * one will be created. 2712 * 2713 * @param <T> any type of the pushed object 2714 * @param stackName the name of the stack onto which the object should be pushed 2715 * @param value the Object to be pushed onto the named stack. 2716 * @since 1.6 2717 */ 2718 public <T> void push( String stackName, T value ) 2719 { 2720 if ( stackAction != null ) 2721 { 2722 value = stackAction.onPush( this, stackName, value ); 2723 } 2724 2725 Stack<Object> namedStack = stacksByName.get( stackName ); 2726 if ( namedStack == null ) 2727 { 2728 namedStack = new Stack<Object>(); 2729 stacksByName.put( stackName, namedStack ); 2730 } 2731 namedStack.push( value ); 2732 } 2733 2734 /** 2735 * <p> 2736 * Pops (gets and removes) the top object from the stack with the given name. 2737 * </p> 2738 * <p> 2739 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2740 * </p> 2741 * 2742 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2743 * @param stackName the name of the stack from which the top value is to be popped. 2744 * @return the top <code>Object</code> on the stack or or null if the stack is either empty or has not been created 2745 * yet 2746 * @since 1.6 2747 */ 2748 public <T> T pop( String stackName ) 2749 { 2750 T result = null; 2751 Stack<Object> namedStack = stacksByName.get( stackName ); 2752 if ( namedStack == null ) 2753 { 2754 if ( log.isDebugEnabled() ) 2755 { 2756 log.debug( "Stack '" + stackName + "' is empty" ); 2757 } 2758 throw new EmptyStackException(); 2759 } 2760 2761 result = this.<T> npeSafeCast( namedStack.pop() ); 2762 2763 if ( stackAction != null ) 2764 { 2765 result = stackAction.onPop( this, stackName, result ); 2766 } 2767 2768 return result; 2769 } 2770 2771 /** 2772 * <p> 2773 * Gets the top object from the stack with the given name. This method does not remove the object from the stack. 2774 * </p> 2775 * <p> 2776 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2777 * </p> 2778 * 2779 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2780 * @param stackName the name of the stack to be peeked 2781 * @return the top <code>Object</code> on the stack or null if the stack is either empty or has not been created yet 2782 * @since 1.6 2783 */ 2784 public <T> T peek( String stackName ) 2785 { 2786 return this.<T> npeSafeCast( peek( stackName, 0 ) ); 2787 } 2788 2789 /** 2790 * <p> 2791 * Gets the top object from the stack with the given name. This method does not remove the object from the stack. 2792 * </p> 2793 * <p> 2794 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2795 * </p> 2796 * 2797 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2798 * @param stackName the name of the stack to be peeked 2799 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on. 2800 * @return the specified <code>Object</code> on the stack. 2801 * @since 1.6 2802 */ 2803 public <T> T peek( String stackName, int n ) 2804 { 2805 T result = null; 2806 Stack<Object> namedStack = stacksByName.get( stackName ); 2807 if ( namedStack == null ) 2808 { 2809 if ( log.isDebugEnabled() ) 2810 { 2811 log.debug( "Stack '" + stackName + "' is empty" ); 2812 } 2813 throw new EmptyStackException(); 2814 } 2815 2816 int index = ( namedStack.size() - 1 ) - n; 2817 if ( index < 0 ) 2818 { 2819 throw new EmptyStackException(); 2820 } 2821 result = this.<T> npeSafeCast( namedStack.get( index ) ); 2822 2823 return result; 2824 } 2825 2826 /** 2827 * <p> 2828 * Is the stack with the given name empty? 2829 * </p> 2830 * <p> 2831 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet. 2832 * </p> 2833 * 2834 * @param stackName the name of the stack whose emptiness should be evaluated 2835 * @return true if the given stack if empty 2836 * @since 1.6 2837 */ 2838 public boolean isEmpty( String stackName ) 2839 { 2840 boolean result = true; 2841 Stack<Object> namedStack = stacksByName.get( stackName ); 2842 if ( namedStack != null ) 2843 { 2844 result = namedStack.isEmpty(); 2845 } 2846 return result; 2847 } 2848 2849 /** 2850 * Returns the root element of the tree of objects created as a result of applying the rule objects to the input 2851 * XML. 2852 * <p> 2853 * If the digester stack was "primed" by explicitly pushing a root object onto the stack before parsing started, 2854 * then that root object is returned here. 2855 * <p> 2856 * Alternatively, if a Rule which creates an object (eg ObjectCreateRule) matched the root element of the xml, then 2857 * the object created will be returned here. 2858 * <p> 2859 * In other cases, the object most recently pushed onto an empty digester stack is returned. This would be a most 2860 * unusual use of digester, however; one of the previous configurations is much more likely. 2861 * <p> 2862 * Note that when using one of the Digester.parse methods, the return value from the parse method is exactly the 2863 * same as the return value from this method. However when the Digester is being used as a SAXContentHandler, no 2864 * such return value is available; in this case, this method allows you to access the root object that has been 2865 * created after parsing has completed. 2866 * 2867 * @param <T> the type used to auto-cast the returned object to the assigned variable type 2868 * @return the root object that has been created after parsing or null if the digester has not parsed any XML yet. 2869 */ 2870 public <T> T getRoot() 2871 { 2872 return this.<T> npeSafeCast( root ); 2873 } 2874 2875 /** 2876 * This method allows the "root" variable to be reset to null. 2877 * <p> 2878 * It is not considered safe for a digester instance to be reused to parse multiple xml documents. However if you 2879 * are determined to do so, then you should call both clear() and resetRoot() before each parse. 2880 * 2881 * @since 1.7 2882 */ 2883 public void resetRoot() 2884 { 2885 root = null; 2886 } 2887 2888 // ------------------------------------------------ Parameter Stack Methods 2889 2890 // ------------------------------------------------------ Protected Methods 2891 2892 /** 2893 * <p> 2894 * Clean up allocated resources after parsing is complete. The default method closes input streams that have been 2895 * created by Digester itself. If you override this method in a subclass, be sure to call 2896 * <code>super.cleanup()</code> to invoke this logic. 2897 * </p> 2898 * 2899 * @since 1.8 2900 */ 2901 protected void cleanup() 2902 { 2903 // If we created any InputSource objects in this instance, 2904 // they each have an input stream that should be closed 2905 for ( InputSource source : inputSources ) 2906 { 2907 try 2908 { 2909 source.getByteStream().close(); 2910 } 2911 catch ( IOException e ) 2912 { 2913 // Fall through so we get them all 2914 if ( log.isWarnEnabled() ) 2915 { 2916 log.warn( format( "An error occurred while closing resource %s (%s)", 2917 source.getPublicId(), 2918 source.getSystemId() ), e ); 2919 } 2920 } 2921 } 2922 inputSources.clear(); 2923 } 2924 2925 /** 2926 * <p> 2927 * Provide a hook for lazy configuration of this <code>Digester</code> instance. The default implementation does 2928 * nothing, but subclasses can override as needed. 2929 * </p> 2930 * <p> 2931 * <strong>Note</strong> This method may be called more than once. Once only initialization code should be placed in 2932 * {@link #initialize} or the code should take responsibility by checking and setting the {@link #configured} flag. 2933 * </p> 2934 */ 2935 protected void configure() 2936 { 2937 // Do not configure more than once 2938 if ( configured ) 2939 { 2940 return; 2941 } 2942 2943 // Perform lazy configuration as needed 2944 initialize(); // call hook method for subclasses that want to be initialized once only 2945 // Nothing else required by default 2946 2947 // Set the configuration flag to avoid repeating 2948 configured = true; 2949 } 2950 2951 /** 2952 * Checks the Digester instance has been configured. 2953 * 2954 * @return true, if the Digester instance has been configured, false otherwise 2955 * @since 3.0 2956 */ 2957 public boolean isConfigured() 2958 { 2959 return configured; 2960 } 2961 2962 /** 2963 * <p> 2964 * Provides a hook for lazy initialization of this <code>Digester</code> instance. The default implementation does 2965 * nothing, but subclasses can override as needed. Digester (by default) only calls this method once. 2966 * </p> 2967 * <p> 2968 * <strong>Note</strong> This method will be called by {@link #configure} only when the {@link #configured} flag is 2969 * false. Subclasses that override <code>configure</code> or who set <code>configured</code> may find that this 2970 * method may be called more than once. 2971 * </p> 2972 * 2973 * @since 1.6 2974 */ 2975 protected void initialize() 2976 { 2977 // Perform lazy initialization as needed 2978 // Nothing required by default 2979 } 2980 2981 // -------------------------------------------------------- Package Methods 2982 2983 /** 2984 * Return the set of DTD URL registrations, keyed by public identifier. NOTE: the returned map is in read-only mode. 2985 * 2986 * @return the read-only Map of DTD URL registrations. 2987 */ 2988 Map<String, URL> getRegistrations() 2989 { 2990 return Collections.unmodifiableMap( entityValidator ); 2991 } 2992 2993 /** 2994 * <p> 2995 * Return the top object on the parameters stack without removing it. If there are no objects on the stack, return 2996 * <code>null</code>. 2997 * </p> 2998 * <p> 2999 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3000 * </p> 3001 * 3002 * @return the top object on the parameters stack without removing it. 3003 */ 3004 public Object[] peekParams() 3005 { 3006 try 3007 { 3008 return ( params.peek() ); 3009 } 3010 catch ( EmptyStackException e ) 3011 { 3012 log.warn( "Empty stack (returning null)" ); 3013 return ( null ); 3014 } 3015 } 3016 3017 /** 3018 * <p> 3019 * Return the n'th object down the parameters stack, where 0 is the top element and [getCount()-1] is the bottom 3020 * element. If the specified index is out of range, return <code>null</code>. 3021 * </p> 3022 * <p> 3023 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3024 * </p> 3025 * 3026 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on. 3027 * @return the n'th object down the parameters stack 3028 */ 3029 public Object[] peekParams( int n ) 3030 { 3031 int index = ( params.size() - 1 ) - n; 3032 if ( index < 0 ) 3033 { 3034 log.warn( "Empty stack (returning null)" ); 3035 return ( null ); 3036 } 3037 try 3038 { 3039 return ( params.get( index ) ); 3040 } 3041 catch ( EmptyStackException e ) 3042 { 3043 log.warn( "Empty stack (returning null)" ); 3044 return ( null ); 3045 } 3046 } 3047 3048 /** 3049 * <p> 3050 * Pop the top object off of the parameters stack, and return it. If there are no objects on the stack, return 3051 * <code>null</code>. 3052 * </p> 3053 * <p> 3054 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3055 * </p> 3056 * 3057 * @return the top object popped off of the parameters stack 3058 */ 3059 public Object[] popParams() 3060 { 3061 try 3062 { 3063 if ( log.isTraceEnabled() ) 3064 { 3065 log.trace( "Popping params" ); 3066 } 3067 return ( params.pop() ); 3068 } 3069 catch ( EmptyStackException e ) 3070 { 3071 log.warn( "Empty stack (returning null)" ); 3072 return ( null ); 3073 } 3074 } 3075 3076 /** 3077 * <p> 3078 * Push a new object onto the top of the parameters stack. 3079 * </p> 3080 * <p> 3081 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}. 3082 * </p> 3083 * 3084 * @param object The new object 3085 */ 3086 public void pushParams( Object... object ) 3087 { 3088 if ( log.isTraceEnabled() ) 3089 { 3090 log.trace( "Pushing params" ); 3091 } 3092 params.push( object ); 3093 } 3094 3095 /** 3096 * Create a SAX exception which also understands about the location in the digester file where the exception occurs 3097 * 3098 * @param message the custom SAX exception message 3099 * @param e the exception cause 3100 * @return the new SAX exception 3101 */ 3102 public SAXException createSAXException( String message, Exception e ) 3103 { 3104 if ( ( e != null ) && ( e instanceof InvocationTargetException ) ) 3105 { 3106 Throwable t = ( (InvocationTargetException) e ).getTargetException(); 3107 if ( ( t != null ) && ( t instanceof Exception ) ) 3108 { 3109 e = (Exception) t; 3110 } 3111 } 3112 if ( locator != null ) 3113 { 3114 String error = 3115 "Error at line " + locator.getLineNumber() + " char " + locator.getColumnNumber() + ": " + message; 3116 if ( e != null ) 3117 { 3118 return new SAXParseException( error, locator, e ); 3119 } 3120 return new SAXParseException( error, locator ); 3121 } 3122 log.error( "No Locator!" ); 3123 if ( e != null ) 3124 { 3125 return new SAXException( message, e ); 3126 } 3127 return new SAXException( message ); 3128 } 3129 3130 /** 3131 * Create a SAX exception which also understands about the location in the digester file where the exception occurs 3132 * 3133 * @param e the exception cause 3134 * @return the new SAX exception 3135 */ 3136 public SAXException createSAXException( Exception e ) 3137 { 3138 if ( e instanceof InvocationTargetException ) 3139 { 3140 Throwable t = ( (InvocationTargetException) e ).getTargetException(); 3141 if ( ( t != null ) && ( t instanceof Exception ) ) 3142 { 3143 e = (Exception) t; 3144 } 3145 } 3146 return createSAXException( e.getMessage(), e ); 3147 } 3148 3149 /** 3150 * Create a SAX exception which also understands about the location in the digester file where the exception occurs 3151 * 3152 * @param message the custom SAX exception message 3153 * @return the new SAX exception 3154 */ 3155 public SAXException createSAXException( String message ) 3156 { 3157 return createSAXException( message, null ); 3158 } 3159 3160 /** 3161 * Helps casting the input object to given type, avoiding NPEs. 3162 * 3163 * @since 3.0 3164 * @param <T> the type the input object has to be cast. 3165 * @param obj the object has to be cast. 3166 * @return the casted object, if input object is not null, null otherwise. 3167 */ 3168 private <T> T npeSafeCast( Object obj ) 3169 { 3170 if ( obj == null ) 3171 { 3172 return null; 3173 } 3174 3175 @SuppressWarnings( "unchecked" ) 3176 T result = (T) obj; 3177 return result; 3178 } 3179 3180 }