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