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