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