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