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