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