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