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