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