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