001 package org.apache.commons.digester3;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import static java.lang.String.format;
023
024 import java.io.File;
025 import java.io.FileInputStream;
026 import java.io.IOException;
027 import java.io.InputStream;
028 import java.io.Reader;
029 import java.lang.reflect.InvocationTargetException;
030 import java.net.MalformedURLException;
031 import java.net.URL;
032 import java.net.URLConnection;
033 import java.util.ArrayList;
034 import java.util.Collections;
035 import java.util.EmptyStackException;
036 import java.util.HashMap;
037 import java.util.List;
038 import java.util.Map;
039 import java.util.Stack;
040 import java.util.concurrent.Callable;
041 import java.util.concurrent.ExecutorService;
042 import java.util.concurrent.Future;
043
044 import javax.xml.parsers.ParserConfigurationException;
045 import javax.xml.parsers.SAXParser;
046 import javax.xml.parsers.SAXParserFactory;
047 import javax.xml.validation.Schema;
048
049 import org.apache.commons.logging.Log;
050 import org.apache.commons.logging.LogFactory;
051 import org.xml.sax.Attributes;
052 import org.xml.sax.ContentHandler;
053 import org.xml.sax.EntityResolver;
054 import org.xml.sax.ErrorHandler;
055 import org.xml.sax.InputSource;
056 import org.xml.sax.Locator;
057 import org.xml.sax.SAXException;
058 import org.xml.sax.SAXNotRecognizedException;
059 import org.xml.sax.SAXNotSupportedException;
060 import org.xml.sax.SAXParseException;
061 import org.xml.sax.XMLReader;
062 import org.xml.sax.helpers.DefaultHandler;
063
064 /**
065 * <p>
066 * A <strong>Digester</strong> processes an XML input stream by matching a series of element nesting patterns to execute
067 * Rules that have been added prior to the start of parsing.
068 * </p>
069 * <p>
070 * See the <a href="package-summary.html#package_description">Digester Developer Guide</a> for more information.
071 * </p>
072 * <p>
073 * <strong>IMPLEMENTATION NOTE</strong> - A single Digester instance may only be used within the context of a single
074 * thread at a time, and a call to <code>parse()</code> must be completed before another can be initiated even from the
075 * same thread.
076 * </p>
077 * <p>
078 * A Digester instance should not be used for parsing more than one input document. The problem is that the Digester
079 * class has quite a few member variables whose values "evolve" as SAX events are received during a parse. When reusing
080 * the Digester instance, all these members must be reset back to their initial states before the second parse begins.
081 * The "clear()" method makes a stab at resetting these, but it is actually rather a difficult problem. If you are
082 * determined to reuse Digester instances, then at the least you should call the clear() method before each parse, and
083 * must call it if the Digester parse terminates due to an exception during a parse.
084 * </p>
085 * <p>
086 * <strong>LEGACY IMPLEMENTATION NOTE</strong> - When using the legacy XML schema support (instead of using the
087 * {@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
088 * make this class work with the legacy XML schema support.
089 * </p>
090 * <p>
091 * This package was inspired by the <code>XmlMapper</code> class that was part of Tomcat 3.0 and 3.1, but is organized
092 * somewhat differently.
093 * </p>
094 */
095 public class Digester
096 extends DefaultHandler
097 {
098
099 // --------------------------------------------------------- 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 *
779 * @param validating The new validating parser flag.
780 */
781 public void setValidating( boolean validating )
782 {
783 this.validating = validating;
784 }
785
786 /**
787 * Return the XMLReader to be used for parsing the input document.
788 *
789 * FIXME: there is a bug in JAXP/XERCES that prevent the use of a parser that contains a schema with a DTD.
790 *
791 * @return the XMLReader to be used for parsing the input document.
792 * @exception SAXException if no XMLReader can be instantiated
793 */
794 public XMLReader getXMLReader()
795 throws SAXException
796 {
797 if ( reader == null )
798 {
799 reader = getParser().getXMLReader();
800 }
801
802 reader.setDTDHandler( this );
803 reader.setContentHandler( this );
804
805 if ( entityResolver == null )
806 {
807 reader.setEntityResolver( this );
808 }
809 else
810 {
811 reader.setEntityResolver( entityResolver );
812 }
813
814 reader.setErrorHandler( this );
815 return reader;
816 }
817
818 /**
819 * Gets the <code>Substitutor</code> used to convert attributes and body text.
820 *
821 * @return the <code>Substitutor</code> used to convert attributes and body text,
822 * null if not substitutions are to be performed.
823 */
824 public Substitutor getSubstitutor()
825 {
826 return substitutor;
827 }
828
829 /**
830 * Sets the <code>Substitutor</code> to be used to convert attributes and body text.
831 *
832 * @param substitutor the Substitutor to be used to convert attributes and body text or null if not substitution of
833 * these values is to be performed.
834 */
835 public void setSubstitutor( Substitutor substitutor )
836 {
837 this.substitutor = substitutor;
838 }
839
840 /**
841 * returns the custom SAX ContentHandler where events are redirected.
842 *
843 * @return the custom SAX ContentHandler where events are redirected.
844 * @see #setCustomContentHandler(ContentHandler)
845 * @since 1.7
846 */
847 public ContentHandler getCustomContentHandler()
848 {
849 return customContentHandler;
850 }
851
852 /**
853 * Redirects (or cancels redirecting) of SAX ContentHandler events to an external object.
854 * <p>
855 * When this object's customContentHandler is non-null, any SAX events received from the parser will simply be
856 * passed on to the specified object instead of this object handling them. This allows Rule classes to take control
857 * of the SAX event stream for a while in order to do custom processing. Such a rule should save the old value
858 * before setting a new one, and restore the old value in order to resume normal digester processing.
859 * <p>
860 * An example of a Rule which needs this feature is NodeCreateRule.
861 * <p>
862 * Note that saving the old value is probably not needed as it should always be null; a custom rule that wants to
863 * take control could only have been called when there was no custom content handler. But it seems cleaner to
864 * properly save/restore the value and maybe some day this will come in useful.
865 * <p>
866 * Note also that this is not quite equivalent to
867 *
868 * <pre>
869 * digester.getXMLReader().setContentHandler( handler )
870 * </pre>
871 *
872 * for these reasons:
873 * <ul>
874 * <li>Some xml parsers don't like having setContentHandler called after parsing has started. The Aelfred parser is
875 * one example.</li>
876 * <li>Directing the events via the Digester object potentially allows us to log information about those SAX events
877 * at the digester level.</li>
878 * </ul>
879 *
880 * @param handler the custom SAX ContentHandler where events are redirected.
881 * @since 1.7
882 */
883 public void setCustomContentHandler( ContentHandler handler )
884 {
885 customContentHandler = handler;
886 }
887
888 /**
889 * Define a callback object which is invoked whenever an object is pushed onto a digester object stack,
890 * or popped off one.
891 *
892 * @param stackAction the callback object which is invoked whenever an object is pushed onto a digester
893 * object stack, or popped off one.
894 * @since 1.8
895 */
896 public void setStackAction( StackAction stackAction )
897 {
898 this.stackAction = stackAction;
899 }
900
901 /**
902 * Return the callback object which is invoked whenever an object is pushed onto a digester object stack,
903 * or popped off one.
904 *
905 * @return the callback object which is invoked whenever an object is pushed onto a digester object stack,
906 * or popped off one.
907 * @see #setStackAction(StackAction)
908 * @since 1.8
909 */
910 public StackAction getStackAction()
911 {
912 return stackAction;
913 }
914
915 /**
916 * Get the most current namespaces for all prefixes.
917 *
918 * @return Map A map with namespace prefixes as keys and most current namespace URIs for the corresponding prefixes
919 * as values
920 * @since 1.8
921 */
922 public Map<String, String> getCurrentNamespaces()
923 {
924 if ( !namespaceAware )
925 {
926 log.warn( "Digester is not namespace aware" );
927 }
928 Map<String, String> currentNamespaces = new HashMap<String, String>();
929 for ( Map.Entry<String, Stack<String>> nsEntry : namespaces.entrySet() )
930 {
931 try
932 {
933 currentNamespaces.put( nsEntry.getKey(), nsEntry.getValue().peek() );
934 }
935 catch ( RuntimeException e )
936 {
937 // rethrow, after logging
938 log.error( e.getMessage(), e );
939 throw e;
940 }
941 }
942 return currentNamespaces;
943 }
944
945 /**
946 * Returns the executor service used to run asynchronous parse method.
947 *
948 * @return the executor service used to run asynchronous parse method
949 * @since 3.1
950 */
951 public ExecutorService getExecutorService()
952 {
953 return executorService;
954 }
955
956 /**
957 * Sets the executor service to run asynchronous parse method.
958 *
959 * @param executorService the executor service to run asynchronous parse method
960 * @since 3.1
961 */
962 public void setExecutorService( ExecutorService executorService )
963 {
964 this.executorService = executorService;
965 }
966
967 // ------------------------------------------------- ContentHandler Methods
968
969 /**
970 * {@inheritDoc}
971 */
972 @Override
973 public void characters( char buffer[], int start, int length )
974 throws SAXException
975 {
976 if ( customContentHandler != null )
977 {
978 // forward calls instead of handling them here
979 customContentHandler.characters( buffer, start, length );
980 return;
981 }
982
983 if ( saxLog.isDebugEnabled() )
984 {
985 saxLog.debug( "characters(" + new String( buffer, start, length ) + ")" );
986 }
987
988 bodyText.append( buffer, start, length );
989 }
990
991 /**
992 * {@inheritDoc}
993 */
994 @Override
995 public void endDocument()
996 throws SAXException
997 {
998 if ( saxLog.isDebugEnabled() )
999 {
1000 if ( getCount() > 1 )
1001 {
1002 saxLog.debug( "endDocument(): " + getCount() + " elements left" );
1003 }
1004 else
1005 {
1006 saxLog.debug( "endDocument()" );
1007 }
1008 }
1009
1010 // Fire "finish" events for all defined rules
1011 for ( Rule rule : getRules().rules() )
1012 {
1013 try
1014 {
1015 rule.finish();
1016 }
1017 catch ( Exception e )
1018 {
1019 log.error( "Finish event threw exception", e );
1020 throw createSAXException( e );
1021 }
1022 catch ( Error e )
1023 {
1024 log.error( "Finish event threw error", e );
1025 throw e;
1026 }
1027 }
1028
1029 // Perform final cleanup
1030 clear();
1031 }
1032
1033 /**
1034 * {@inheritDoc}
1035 */
1036 @Override
1037 public void endElement( String namespaceURI, String localName, String qName )
1038 throws SAXException
1039 {
1040 if ( customContentHandler != null )
1041 {
1042 // forward calls instead of handling them here
1043 customContentHandler.endElement( namespaceURI, localName, qName );
1044 return;
1045 }
1046
1047 boolean debug = log.isDebugEnabled();
1048
1049 if ( debug )
1050 {
1051 if ( saxLog.isDebugEnabled() )
1052 {
1053 saxLog.debug( "endElement(" + namespaceURI + "," + localName + "," + qName + ")" );
1054 }
1055 log.debug( " match='" + match + "'" );
1056 log.debug( " bodyText='" + bodyText + "'" );
1057 }
1058
1059 // the actual element name is either in localName or qName, depending
1060 // on whether the parser is namespace aware
1061 String name = localName;
1062 if ( ( name == null ) || ( name.length() < 1 ) )
1063 {
1064 name = qName;
1065 }
1066
1067 // Fire "body" events for all relevant rules
1068 List<Rule> rules = matches.pop();
1069 if ( ( rules != null ) && ( rules.size() > 0 ) )
1070 {
1071 String bodyText = this.bodyText.toString();
1072 Substitutor substitutor = getSubstitutor();
1073 if ( substitutor != null )
1074 {
1075 bodyText = substitutor.substitute( bodyText );
1076 }
1077 for ( int i = 0; i < rules.size(); i++ )
1078 {
1079 try
1080 {
1081 Rule rule = rules.get( i );
1082 if ( debug )
1083 {
1084 log.debug( " Fire body() for " + rule );
1085 }
1086 rule.body( namespaceURI, name, bodyText );
1087 }
1088 catch ( Exception e )
1089 {
1090 log.error( "Body event threw exception", e );
1091 throw createSAXException( e );
1092 }
1093 catch ( Error e )
1094 {
1095 log.error( "Body event threw error", e );
1096 throw e;
1097 }
1098 }
1099 }
1100 else
1101 {
1102 if ( debug )
1103 {
1104 log.debug( " No rules found matching '" + match + "'." );
1105 }
1106 }
1107
1108 // Recover the body text from the surrounding element
1109 bodyText = bodyTexts.pop();
1110 if ( debug )
1111 {
1112 log.debug( " Popping body text '" + bodyText.toString() + "'" );
1113 }
1114
1115 // Fire "end" events for all relevant rules in reverse order
1116 if ( rules != null )
1117 {
1118 for ( int i = 0; i < rules.size(); i++ )
1119 {
1120 int j = ( rules.size() - i ) - 1;
1121 try
1122 {
1123 Rule rule = rules.get( j );
1124 if ( debug )
1125 {
1126 log.debug( " Fire end() for " + rule );
1127 }
1128 rule.end( namespaceURI, name );
1129 }
1130 catch ( Exception e )
1131 {
1132 log.error( "End event threw exception", e );
1133 throw createSAXException( e );
1134 }
1135 catch ( Error e )
1136 {
1137 log.error( "End event threw error", e );
1138 throw e;
1139 }
1140 }
1141 }
1142
1143 // Recover the previous match expression
1144 int slash = match.lastIndexOf( '/' );
1145 if ( slash >= 0 )
1146 {
1147 match = match.substring( 0, slash );
1148 }
1149 else
1150 {
1151 match = "";
1152 }
1153 }
1154
1155 /**
1156 * {@inheritDoc}
1157 */
1158 @Override
1159 public void endPrefixMapping( String prefix )
1160 throws SAXException
1161 {
1162 if ( saxLog.isDebugEnabled() )
1163 {
1164 saxLog.debug( "endPrefixMapping(" + prefix + ")" );
1165 }
1166
1167 // Deregister this prefix mapping
1168 Stack<String> stack = namespaces.get( prefix );
1169 if ( stack == null )
1170 {
1171 return;
1172 }
1173 try
1174 {
1175 stack.pop();
1176 if ( stack.empty() )
1177 {
1178 namespaces.remove( prefix );
1179 }
1180 }
1181 catch ( EmptyStackException e )
1182 {
1183 throw createSAXException( "endPrefixMapping popped too many times" );
1184 }
1185 }
1186
1187 /**
1188 * {@inheritDoc}
1189 */
1190 @Override
1191 public void ignorableWhitespace( char buffer[], int start, int len )
1192 throws SAXException
1193 {
1194 if ( saxLog.isDebugEnabled() )
1195 {
1196 saxLog.debug( "ignorableWhitespace(" + new String( buffer, start, len ) + ")" );
1197 }
1198
1199 // No processing required
1200 }
1201
1202 /**
1203 * {@inheritDoc}
1204 */
1205 @Override
1206 public void processingInstruction( String target, String data )
1207 throws SAXException
1208 {
1209 if ( customContentHandler != null )
1210 {
1211 // forward calls instead of handling them here
1212 customContentHandler.processingInstruction( target, data );
1213 return;
1214 }
1215
1216 if ( saxLog.isDebugEnabled() )
1217 {
1218 saxLog.debug( "processingInstruction('" + target + "','" + data + "')" );
1219 }
1220
1221 // No processing is required
1222 }
1223
1224 /**
1225 * Gets the document locator associated with our parser.
1226 *
1227 * @return the Locator supplied by the document parser
1228 */
1229 public Locator getDocumentLocator()
1230 {
1231 return locator;
1232 }
1233
1234 /**
1235 * {@inheritDoc}
1236 */
1237 @Override
1238 public void setDocumentLocator( Locator locator )
1239 {
1240 if ( saxLog.isDebugEnabled() )
1241 {
1242 saxLog.debug( "setDocumentLocator(" + locator + ")" );
1243 }
1244
1245 this.locator = locator;
1246 }
1247
1248 /**
1249 * {@inheritDoc}
1250 */
1251 @Override
1252 public void skippedEntity( String name )
1253 throws SAXException
1254 {
1255 if ( saxLog.isDebugEnabled() )
1256 {
1257 saxLog.debug( "skippedEntity(" + name + ")" );
1258 }
1259
1260 // No processing required
1261 }
1262
1263 /**
1264 * {@inheritDoc}
1265 */
1266 @Override
1267 public void startDocument()
1268 throws SAXException
1269 {
1270 if ( saxLog.isDebugEnabled() )
1271 {
1272 saxLog.debug( "startDocument()" );
1273 }
1274
1275 // ensure that the digester is properly configured, as
1276 // the digester could be used as a SAX ContentHandler
1277 // rather than via the parse() methods.
1278 configure();
1279 }
1280
1281 /**
1282 * {@inheritDoc}
1283 */
1284 @Override
1285 public void startElement( String namespaceURI, String localName, String qName, Attributes list )
1286 throws SAXException
1287 {
1288 boolean debug = log.isDebugEnabled();
1289
1290 if ( customContentHandler != null )
1291 {
1292 // forward calls instead of handling them here
1293 customContentHandler.startElement( namespaceURI, localName, qName, list );
1294 return;
1295 }
1296
1297 if ( saxLog.isDebugEnabled() )
1298 {
1299 saxLog.debug( "startElement(" + namespaceURI + "," + localName + "," + qName + ")" );
1300 }
1301
1302 // Save the body text accumulated for our surrounding element
1303 bodyTexts.push( bodyText );
1304 if ( debug )
1305 {
1306 log.debug( " Pushing body text '" + bodyText.toString() + "'" );
1307 }
1308 bodyText = new StringBuilder();
1309
1310 // the actual element name is either in localName or qName, depending
1311 // on whether the parser is namespace aware
1312 String name = localName;
1313 if ( ( name == null ) || ( name.length() < 1 ) )
1314 {
1315 name = qName;
1316 }
1317
1318 // Compute the current matching rule
1319 StringBuilder sb = new StringBuilder( match );
1320 if ( match.length() > 0 )
1321 {
1322 sb.append( '/' );
1323 }
1324 sb.append( name );
1325 match = sb.toString();
1326 if ( debug )
1327 {
1328 log.debug( " New match='" + match + "'" );
1329 }
1330
1331 // Fire "begin" events for all relevant rules
1332 List<Rule> rules = getRules().match( namespaceURI, match, localName, list );
1333 matches.push( rules );
1334 if ( ( rules != null ) && ( rules.size() > 0 ) )
1335 {
1336 Substitutor substitutor = getSubstitutor();
1337 if ( substitutor != null )
1338 {
1339 list = substitutor.substitute( list );
1340 }
1341 for ( int i = 0; i < rules.size(); i++ )
1342 {
1343 try
1344 {
1345 Rule rule = rules.get( i );
1346 if ( debug )
1347 {
1348 log.debug( " Fire begin() for " + rule );
1349 }
1350 rule.begin( namespaceURI, name, list );
1351 }
1352 catch ( Exception e )
1353 {
1354 log.error( "Begin event threw exception", e );
1355 throw createSAXException( e );
1356 }
1357 catch ( Error e )
1358 {
1359 log.error( "Begin event threw error", e );
1360 throw e;
1361 }
1362 }
1363 }
1364 else
1365 {
1366 if ( debug )
1367 {
1368 log.debug( " No rules found matching '" + match + "'." );
1369 }
1370 }
1371 }
1372
1373 /**
1374 * {@inheritDoc}
1375 */
1376 @Override
1377 public void startPrefixMapping( String prefix, String namespaceURI )
1378 throws SAXException
1379 {
1380 if ( saxLog.isDebugEnabled() )
1381 {
1382 saxLog.debug( "startPrefixMapping(" + prefix + "," + namespaceURI + ")" );
1383 }
1384
1385 // Register this prefix mapping
1386 Stack<String> stack = namespaces.get( prefix );
1387 if ( stack == null )
1388 {
1389 stack = new Stack<String>();
1390 namespaces.put( prefix, stack );
1391 }
1392 stack.push( namespaceURI );
1393 }
1394
1395 // ----------------------------------------------------- DTDHandler Methods
1396
1397 /**
1398 * {@inheritDoc}
1399 */
1400 @Override
1401 public void notationDecl( String name, String publicId, String systemId )
1402 {
1403 if ( saxLog.isDebugEnabled() )
1404 {
1405 saxLog.debug( "notationDecl(" + name + "," + publicId + "," + systemId + ")" );
1406 }
1407 }
1408
1409 /**
1410 * {@inheritDoc}
1411 */
1412 @Override
1413 public void unparsedEntityDecl( String name, String publicId, String systemId, String notation )
1414 {
1415 if ( saxLog.isDebugEnabled() )
1416 {
1417 saxLog.debug( "unparsedEntityDecl(" + name + "," + publicId + "," + systemId + "," + notation + ")" );
1418 }
1419 }
1420
1421 // ----------------------------------------------- EntityResolver Methods
1422
1423 /**
1424 * Set the <code>EntityResolver</code> used by SAX when resolving public id and system id. This must be called
1425 * before the first call to <code>parse()</code>.
1426 *
1427 * @param entityResolver a class that implement the <code>EntityResolver</code> interface.
1428 */
1429 public void setEntityResolver( EntityResolver entityResolver )
1430 {
1431 this.entityResolver = entityResolver;
1432 }
1433
1434 /**
1435 * Return the Entity Resolver used by the SAX parser.
1436 *
1437 * @return the Entity Resolver used by the SAX parser.
1438 */
1439 public EntityResolver getEntityResolver()
1440 {
1441 return entityResolver;
1442 }
1443
1444 /**
1445 * {@inheritDoc}
1446 */
1447 @Override
1448 public InputSource resolveEntity( String publicId, String systemId )
1449 throws SAXException
1450 {
1451 if ( saxLog.isDebugEnabled() )
1452 {
1453 saxLog.debug( "resolveEntity('" + publicId + "', '" + systemId + "')" );
1454 }
1455
1456 if ( publicId != null )
1457 {
1458 this.publicId = publicId;
1459 }
1460
1461 // Has this system identifier been registered?
1462 URL entityURL = null;
1463 if ( publicId != null )
1464 {
1465 entityURL = entityValidator.get( publicId );
1466 }
1467
1468 // Redirect the schema location to a local destination
1469 if ( entityURL == null && systemId != null )
1470 {
1471 entityURL = entityValidator.get( systemId );
1472 }
1473
1474 if ( entityURL == null )
1475 {
1476 if ( systemId == null )
1477 {
1478 // cannot resolve
1479 if ( log.isDebugEnabled() )
1480 {
1481 log.debug( " Cannot resolve null entity, returning null InputSource" );
1482 }
1483 return ( null );
1484
1485 }
1486 // try to resolve using system ID
1487 if ( log.isDebugEnabled() )
1488 {
1489 log.debug( " Trying to resolve using system ID '" + systemId + "'" );
1490 }
1491 try
1492 {
1493 entityURL = new URL( systemId );
1494 }
1495 catch ( MalformedURLException e )
1496 {
1497 throw new IllegalArgumentException( "Malformed URL '" + systemId + "' : " + e.getMessage() );
1498 }
1499 }
1500
1501 // Return an input source to our alternative URL
1502 if ( log.isDebugEnabled() )
1503 {
1504 log.debug( " Resolving to alternate DTD '" + entityURL + "'" );
1505 }
1506
1507 try
1508 {
1509 return createInputSourceFromURL( entityURL );
1510 }
1511 catch ( Exception e )
1512 {
1513 throw createSAXException( e );
1514 }
1515 }
1516
1517 // ------------------------------------------------- ErrorHandler Methods
1518
1519 /**
1520 * {@inheritDoc}
1521 */
1522 @Override
1523 public void error( SAXParseException exception )
1524 throws SAXException
1525 {
1526 log.error( "Parse Error at line " + exception.getLineNumber() + " column " + exception.getColumnNumber() + ": "
1527 + exception.getMessage(), exception );
1528 if ( errorHandler != null )
1529 {
1530 errorHandler.error( exception );
1531 }
1532 }
1533
1534 /**
1535 * {@inheritDoc}
1536 */
1537 @Override
1538 public void fatalError( SAXParseException exception )
1539 throws SAXException
1540 {
1541 log.error( "Parse Fatal Error at line " + exception.getLineNumber() + " column " + exception.getColumnNumber()
1542 + ": " + exception.getMessage(), exception );
1543 if ( errorHandler != null )
1544 {
1545 errorHandler.fatalError( exception );
1546 }
1547 }
1548
1549 /**
1550 * {@inheritDoc}
1551 */
1552 @Override
1553 public void warning( SAXParseException exception )
1554 throws SAXException
1555 {
1556 if ( errorHandler != null )
1557 {
1558 log.warn( "Parse Warning Error at line " + exception.getLineNumber() + " column "
1559 + exception.getColumnNumber() + ": " + exception.getMessage(), exception );
1560
1561 errorHandler.warning( exception );
1562 }
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 catch ( SAXException e )
1651 {
1652 log.error( format( "An error occurred while parsing XML from '%s', see nested exceptions", systemId ),
1653 e );
1654 throw e;
1655 }
1656 cleanup();
1657 return this.<T> getRoot();
1658 }
1659
1660 /**
1661 * Creates a Callable instance that parse the content of the specified reader using this Digester.
1662 *
1663 * @param <T> The result type returned by the returned Future's {@code get} method
1664 * @param input Input source containing the XML data to be parsed
1665 * @return a Future that can be used to track when the parse has been fully processed.
1666 * @see Digester#parse(InputSource)
1667 * @since 3.1
1668 */
1669 public <T> Future<T> asyncParse( final InputSource input )
1670 {
1671 return asyncParse( new Callable<T>()
1672 {
1673
1674 public T call()
1675 throws Exception
1676 {
1677 return Digester.this.<T> parse( input );
1678 }
1679
1680 } );
1681 }
1682
1683 /**
1684 * Parse the content of the specified input stream using this Digester. Returns the root element from the object
1685 * stack (if any).
1686 *
1687 * @param <T> the type used to auto-cast the returned object to the assigned variable type
1688 * @param input Input stream containing the XML data to be parsed
1689 * @return the root element from the object stack (if any)
1690 * @exception IOException if an input/output error occurs
1691 * @exception SAXException if a parsing exception occurs
1692 */
1693 public <T> T parse( InputStream input )
1694 throws IOException, SAXException
1695 {
1696 if ( input == null )
1697 {
1698 throw new IllegalArgumentException( "InputStream to parse is null" );
1699 }
1700
1701 return ( this.<T> parse( new InputSource( input ) ) );
1702 }
1703
1704 /**
1705 * Creates a Callable instance that parse the content of the specified reader using this Digester.
1706 *
1707 * @param <T> The result type returned by the returned Future's {@code get} method
1708 * @param input Input stream containing the XML data to be parsed
1709 * @return a Future that can be used to track when the parse has been fully processed.
1710 * @see Digester#parse(InputStream)
1711 * @since 3.1
1712 */
1713 public <T> Future<T> asyncParse( final InputStream input )
1714 {
1715 return asyncParse( new Callable<T>()
1716 {
1717
1718 public T call()
1719 throws Exception
1720 {
1721 return Digester.this.<T> parse( input );
1722 }
1723
1724 } );
1725 }
1726
1727 /**
1728 * Parse the content of the specified reader using this Digester. Returns the root element from the object stack (if
1729 * any).
1730 *
1731 * @param <T> the type used to auto-cast the returned object to the assigned variable type
1732 * @param reader Reader containing the XML data to be parsed
1733 * @return the root element from the object stack (if any)
1734 * @exception IOException if an input/output error occurs
1735 * @exception SAXException if a parsing exception occurs
1736 */
1737 public <T> T parse( Reader reader )
1738 throws IOException, SAXException
1739 {
1740 if ( reader == null )
1741 {
1742 throw new IllegalArgumentException( "Reader to parse is null" );
1743 }
1744
1745 return ( this.<T> parse( new InputSource( reader ) ) );
1746 }
1747
1748 /**
1749 * Creates a Callable instance that parse the content of the specified reader using this Digester.
1750 *
1751 * @param <T> The result type returned by the returned Future's {@code get} method
1752 * @param reader Reader containing the XML data to be parsed
1753 * @return a Future that can be used to track when the parse has been fully processed.
1754 * @see Digester#parse(Reader)
1755 * @since 3.1
1756 */
1757 public <T> Future<T> asyncParse( final Reader reader )
1758 {
1759 return asyncParse( new Callable<T>()
1760 {
1761
1762 public T call()
1763 throws Exception
1764 {
1765 return Digester.this.<T> parse( reader );
1766 }
1767
1768 } );
1769 }
1770
1771 /**
1772 * Parse the content of the specified URI using this Digester. Returns the root element from the object stack (if
1773 * any).
1774 *
1775 * @param <T> the type used to auto-cast the returned object to the assigned variable type
1776 * @param uri URI containing the XML data to be parsed
1777 * @return the root element from the object stack (if any)
1778 * @exception IOException if an input/output error occurs
1779 * @exception SAXException if a parsing exception occurs
1780 */
1781 public <T> T parse( String uri )
1782 throws IOException, SAXException
1783 {
1784 if ( uri == null )
1785 {
1786 throw new IllegalArgumentException( "String URI to parse is null" );
1787 }
1788
1789 return ( this.<T> parse( createInputSourceFromURL( uri ) ) );
1790 }
1791
1792 /**
1793 * Creates a Callable instance that parse the content of the specified reader using this Digester.
1794 *
1795 * @param <T> The result type returned by the returned Future's {@code get} method
1796 * @param uri URI containing the XML data to be parsed
1797 * @return a Future that can be used to track when the parse has been fully processed.
1798 * @see Digester#parse(String)
1799 * @since 3.1
1800 */
1801 public <T> Future<T> asyncParse( final String uri )
1802 {
1803 return asyncParse( new Callable<T>()
1804 {
1805
1806 public T call()
1807 throws Exception
1808 {
1809 return Digester.this.<T> parse( uri );
1810 }
1811
1812 } );
1813 }
1814
1815 /**
1816 * Parse the content of the specified URL using this Digester. Returns the root element from the object stack (if
1817 * any).
1818 *
1819 * @param <T> the type used to auto-cast the returned object to the assigned variable type
1820 * @param url URL containing the XML data to be parsed
1821 * @return the root element from the object stack (if any)
1822 * @exception IOException if an input/output error occurs
1823 * @exception SAXException if a parsing exception occurs
1824 * @since 1.8
1825 */
1826 public <T> T parse( URL url )
1827 throws IOException, SAXException
1828 {
1829 if ( url == null )
1830 {
1831 throw new IllegalArgumentException( "URL to parse is null" );
1832 }
1833
1834 return ( this.<T> parse( createInputSourceFromURL( url ) ) );
1835 }
1836
1837 /**
1838 * Creates a Callable instance that parse the content of the specified reader using this Digester.
1839 *
1840 * @param <T> The result type returned by the returned Future's {@code get} method
1841 * @param url URL containing the XML data to be parsed
1842 * @return a Future that can be used to track when the parse has been fully processed.
1843 * @see Digester#parse(URL)
1844 * @since 3.1
1845 */
1846 public <T> Future<T> asyncParse( final URL url )
1847 {
1848 return asyncParse( new Callable<T>()
1849 {
1850
1851 public T call()
1852 throws Exception
1853 {
1854 return Digester.this.<T> parse( url );
1855 }
1856
1857 } );
1858 }
1859
1860 /**
1861 * Execute the parse in async mode.
1862 *
1863 * @param <T> the type used to auto-cast the returned object to the assigned variable type
1864 * @param callable
1865 * @return a Future that can be used to track when the parse has been fully processed.
1866 * @since 3.1
1867 */
1868 private <T> Future<T> asyncParse( Callable<T> callable )
1869 {
1870 if ( executorService == null )
1871 {
1872 throw new IllegalStateException( "ExecutorService not set" );
1873 }
1874
1875 return executorService.submit( callable );
1876 }
1877
1878 /**
1879 * <p>
1880 * Register the specified DTD URL for the specified public identifier. This must be called before the first call to
1881 * <code>parse()</code>.
1882 * </p>
1883 * <p>
1884 * <code>Digester</code> contains an internal <code>EntityResolver</code> implementation. This maps
1885 * <code>PUBLICID</code>'s to URLs (from which the resource will be loaded). A common use case for this method is to
1886 * register local URLs (possibly computed at runtime by a classloader) for DTDs. This allows the performance
1887 * advantage of using a local version without having to ensure every <code>SYSTEM</code> URI on every processed xml
1888 * document is local. This implementation provides only basic functionality. If more sophisticated features are
1889 * required, using {@link #setEntityResolver} to set a custom resolver is recommended.
1890 * </p>
1891 * <p>
1892 * <strong>Note:</strong> This method will have no effect when a custom <code>EntityResolver</code> has been set.
1893 * (Setting a custom <code>EntityResolver</code> overrides the internal implementation.)
1894 * </p>
1895 *
1896 * @param publicId Public identifier of the DTD to be resolved
1897 * @param entityURL The URL to use for reading this DTD
1898 * @since 1.8
1899 */
1900 public void register( String publicId, URL entityURL )
1901 {
1902 if ( log.isDebugEnabled() )
1903 {
1904 log.debug( "register('" + publicId + "', '" + entityURL + "'" );
1905 }
1906 entityValidator.put( publicId, entityURL );
1907 }
1908
1909 /**
1910 * <p>
1911 * Convenience method that registers the string version of an entity URL instead of a URL version.
1912 * </p>
1913 *
1914 * @param publicId Public identifier of the entity to be resolved
1915 * @param entityURL The URL to use for reading this entity
1916 */
1917 public void register( String publicId, String entityURL )
1918 {
1919 if ( log.isDebugEnabled() )
1920 {
1921 log.debug( "register('" + publicId + "', '" + entityURL + "'" );
1922 }
1923 try
1924 {
1925 entityValidator.put( publicId, new URL( entityURL ) );
1926 }
1927 catch ( MalformedURLException e )
1928 {
1929 throw new IllegalArgumentException( "Malformed URL '" + entityURL + "' : " + e.getMessage() );
1930 }
1931 }
1932
1933 /**
1934 * Convenience method that registers DTD URLs for the specified public identifiers.
1935 *
1936 * @param entityValidator The URLs of entityValidator that have been registered, keyed by the public
1937 * identifier that corresponds.
1938 * @since 3.0
1939 */
1940 public void registerAll( Map<String, URL> entityValidator )
1941 {
1942 this.entityValidator.putAll( entityValidator );
1943 }
1944
1945 /**
1946 * <p>
1947 * <code>List</code> of <code>InputSource</code> instances created by a <code>createInputSourceFromURL()</code>
1948 * method call. These represent open input streams that need to be closed to avoid resource leaks, as well as
1949 * potentially locked JAR files on Windows.
1950 * </p>
1951 */
1952 protected List<InputSource> inputSources = new ArrayList<InputSource>( 5 );
1953
1954 /**
1955 * Given a URL, return an InputSource that reads from that URL.
1956 * <p>
1957 * Ideally this function would not be needed and code could just use <code>new InputSource(entityURL)</code>.
1958 * Unfortunately it appears that when the entityURL points to a file within a jar archive a caching mechanism inside
1959 * the InputSource implementation causes a file-handle to the jar file to remain open. On Windows systems this then
1960 * causes the jar archive file to be locked on disk ("in use") which makes it impossible to delete the jar file -
1961 * and that really stuffs up "undeploy" in webapps in particular.
1962 * <p>
1963 * In JDK1.4 and later, Apache XercesJ is used as the xml parser. The InputSource object provided is converted into
1964 * an XMLInputSource, and eventually passed to an instance of XMLDocumentScannerImpl to specify the source data to
1965 * be converted into tokens for the rest of the XMLReader code to handle. XMLDocumentScannerImpl calls
1966 * fEntityManager.startDocumentEntity(source), where fEntityManager is declared in ancestor class XMLScanner to be
1967 * an XMLEntityManager. In that class, if the input source stream is null, then:
1968 *
1969 * <pre>
1970 * URL location = new URL( expandedSystemId );
1971 * URLConnection connect = location.openConnection();
1972 * if ( connect instanceof HttpURLConnection )
1973 * {
1974 * setHttpProperties( connect, xmlInputSource );
1975 * }
1976 * stream = connect.getInputStream();
1977 * </pre>
1978 *
1979 * This method pretty much duplicates the standard behaviour, except that it calls URLConnection.setUseCaches(false)
1980 * before opening the connection.
1981 *
1982 * @param url The URL has to be read
1983 * @return The InputSource that reads from the input URL
1984 * @throws IOException if any error occurs while reading the input URL
1985 * @since 1.8
1986 */
1987 public InputSource createInputSourceFromURL( URL url )
1988 throws IOException
1989 {
1990 URLConnection connection = url.openConnection();
1991 connection.setUseCaches( false );
1992 InputStream stream = connection.getInputStream();
1993 InputSource source = new InputSource( stream );
1994 source.setSystemId( url.toExternalForm() );
1995 inputSources.add( source );
1996 return source;
1997 }
1998
1999 /**
2000 * <p>
2001 * Convenience method that creates an <code>InputSource</code> from the string version of a URL.
2002 * </p>
2003 *
2004 * @param url URL for which to create an <code>InputSource</code>
2005 * @return The InputSource that reads from the input URL
2006 * @throws IOException if any error occurs while reading the input URL
2007 * @since 1.8
2008 */
2009 public InputSource createInputSourceFromURL( String url )
2010 throws IOException
2011 {
2012 return createInputSourceFromURL( new URL( url ) );
2013 }
2014
2015 // --------------------------------------------------------- Rule Methods
2016
2017 /**
2018 * <p>
2019 * Register a new Rule matching the specified pattern. This method sets the <code>Digester</code> property on the
2020 * rule.
2021 * </p>
2022 *
2023 * @param pattern Element matching pattern
2024 * @param rule Rule to be registered
2025 */
2026 public void addRule( String pattern, Rule rule )
2027 {
2028 rule.setDigester( this );
2029 getRules().add( pattern, rule );
2030 }
2031
2032 /**
2033 * Register a set of Rule instances defined in a RuleSet.
2034 *
2035 * @param ruleSet The RuleSet instance to configure from
2036 */
2037 public void addRuleSet( RuleSet ruleSet )
2038 {
2039 String oldNamespaceURI = getRuleNamespaceURI();
2040 String newNamespaceURI = ruleSet.getNamespaceURI();
2041 if ( log.isDebugEnabled() )
2042 {
2043 if ( newNamespaceURI == null )
2044 {
2045 log.debug( "addRuleSet() with no namespace URI" );
2046 }
2047 else
2048 {
2049 log.debug( "addRuleSet() with namespace URI " + newNamespaceURI );
2050 }
2051 }
2052 setRuleNamespaceURI( newNamespaceURI );
2053 ruleSet.addRuleInstances( this );
2054 setRuleNamespaceURI( oldNamespaceURI );
2055 }
2056
2057 /**
2058 * Add a "bean property setter" rule for the specified parameters.
2059 *
2060 * @param pattern Element matching pattern
2061 * @see BeanPropertySetterRule
2062 */
2063 public void addBeanPropertySetter( String pattern )
2064 {
2065 addRule( pattern, new BeanPropertySetterRule() );
2066 }
2067
2068 /**
2069 * Add a "bean property setter" rule for the specified parameters.
2070 *
2071 * @param pattern Element matching pattern
2072 * @param propertyName Name of property to set
2073 * @see BeanPropertySetterRule
2074 */
2075 public void addBeanPropertySetter( String pattern, String propertyName )
2076 {
2077 addRule( pattern, new BeanPropertySetterRule( propertyName ) );
2078 }
2079
2080 /**
2081 * Add an "call method" rule for a method which accepts no arguments.
2082 *
2083 * @param pattern Element matching pattern
2084 * @param methodName Method name to be called
2085 * @see CallMethodRule
2086 */
2087 public void addCallMethod( String pattern, String methodName )
2088 {
2089 addRule( pattern, new CallMethodRule( methodName ) );
2090 }
2091
2092 /**
2093 * Add an "call method" rule for the specified parameters.
2094 *
2095 * @param pattern Element matching pattern
2096 * @param methodName Method name to be called
2097 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element)
2098 * @see CallMethodRule
2099 */
2100 public void addCallMethod( String pattern, String methodName, int paramCount )
2101 {
2102 addRule( pattern, new CallMethodRule( methodName, paramCount ) );
2103 }
2104
2105 /**
2106 * Add an "call method" rule for the specified parameters. If <code>paramCount</code> is set to zero the rule will
2107 * use the body of the matched element as the single argument of the method, unless <code>paramTypes</code> is null
2108 * or empty, in this case the rule will call the specified method with no arguments.
2109 *
2110 * @param pattern Element matching pattern
2111 * @param methodName Method name to be called
2112 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element)
2113 * @param paramTypes Set of Java class names for the types of the expected parameters (if you wish to use a
2114 * primitive type, specify the corresonding Java wrapper class instead, such as
2115 * <code>java.lang.Boolean</code> for a <code>boolean</code> parameter)
2116 * @see CallMethodRule
2117 */
2118 public void addCallMethod( String pattern, String methodName, int paramCount, String paramTypes[] )
2119 {
2120 addRule( pattern, new CallMethodRule( methodName, paramCount, paramTypes ) );
2121 }
2122
2123 /**
2124 * Add an "call method" rule for the specified parameters. If <code>paramCount</code> is set to zero the rule will
2125 * use the body of the matched element as the single argument of the method, unless <code>paramTypes</code> is null
2126 * or empty, in this case the rule will call the specified method with no arguments.
2127 *
2128 * @param pattern Element matching pattern
2129 * @param methodName Method name to be called
2130 * @param paramCount Number of expected parameters (or zero for a single parameter from the body of this element)
2131 * @param paramTypes The Java class names of the arguments (if you wish to use a primitive type, specify the
2132 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
2133 * <code>boolean</code> parameter)
2134 * @see CallMethodRule
2135 */
2136 public void addCallMethod( String pattern, String methodName, int paramCount, Class<?> paramTypes[] )
2137 {
2138 addRule( pattern, new CallMethodRule( methodName, paramCount, paramTypes ) );
2139 }
2140
2141 /**
2142 * Add a "call parameter" rule for the specified parameters.
2143 *
2144 * @param pattern Element matching pattern
2145 * @param paramIndex Zero-relative parameter index to set (from the body of this element)
2146 * @see CallParamRule
2147 */
2148 public void addCallParam( String pattern, int paramIndex )
2149 {
2150 addRule( pattern, new CallParamRule( paramIndex ) );
2151 }
2152
2153 /**
2154 * Add a "call parameter" rule for the specified parameters.
2155 *
2156 * @param pattern Element matching pattern
2157 * @param paramIndex Zero-relative parameter index to set (from the specified attribute)
2158 * @param attributeName Attribute whose value is used as the parameter value
2159 * @see CallParamRule
2160 */
2161 public void addCallParam( String pattern, int paramIndex, String attributeName )
2162 {
2163 addRule( pattern, new CallParamRule( paramIndex, attributeName ) );
2164 }
2165
2166 /**
2167 * Add a "call parameter" rule. This will either take a parameter from the stack or from the current element body
2168 * text.
2169 *
2170 * @param pattern Element matching pattern
2171 * @param paramIndex The zero-relative parameter number
2172 * @param fromStack Should the call parameter be taken from the top of the stack?
2173 * @see CallParamRule
2174 */
2175 public void addCallParam( String pattern, int paramIndex, boolean fromStack )
2176 {
2177 addRule( pattern, new CallParamRule( paramIndex, fromStack ) );
2178 }
2179
2180 /**
2181 * Add a "call parameter" rule that sets a parameter from the stack. This takes a parameter from the given position
2182 * on the stack.
2183 *
2184 * @param pattern Element matching pattern
2185 * @param paramIndex The zero-relative parameter number
2186 * @param stackIndex set the call parameter to the stackIndex'th object down the stack, where 0 is the top of the
2187 * stack, 1 the next element down and so on
2188 * @see CallMethodRule
2189 */
2190 public void addCallParam( String pattern, int paramIndex, int stackIndex )
2191 {
2192 addRule( pattern, new CallParamRule( paramIndex, stackIndex ) );
2193 }
2194
2195 /**
2196 * Add a "call parameter" rule that sets a parameter from the current <code>Digester</code> matching path. This is
2197 * sometimes useful when using rules that support wildcards.
2198 *
2199 * @param pattern the pattern that this rule should match
2200 * @param paramIndex The zero-relative parameter number
2201 * @see CallMethodRule
2202 */
2203 public void addCallParamPath( String pattern, int paramIndex )
2204 {
2205 addRule( pattern, new PathCallParamRule( paramIndex ) );
2206 }
2207
2208 /**
2209 * Add a "call parameter" rule that sets a parameter from a caller-provided object. This can be used to pass
2210 * constants such as strings to methods; it can also be used to pass mutable objects, providing ways for objects to
2211 * do things like "register" themselves with some shared object.
2212 * <p>
2213 * Note that when attempting to locate a matching method to invoke, the true type of the paramObj is used, so that
2214 * despite the paramObj being passed in here as type Object, the target method can declare its parameters as being
2215 * the true type of the object (or some ancestor type, according to the usual type-conversion rules).
2216 *
2217 * @param pattern Element matching pattern
2218 * @param paramIndex The zero-relative parameter number
2219 * @param paramObj Any arbitrary object to be passed to the target method.
2220 * @see CallMethodRule
2221 * @since 1.6
2222 */
2223 public void addObjectParam( String pattern, int paramIndex, Object paramObj )
2224 {
2225 addRule( pattern, new ObjectParamRule( paramIndex, paramObj ) );
2226 }
2227
2228 /**
2229 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process
2230 * will be propagated.
2231 *
2232 * @param pattern Element matching pattern
2233 * @param className Java class name of the object creation factory class
2234 * @see FactoryCreateRule
2235 */
2236 public void addFactoryCreate( String pattern, String className )
2237 {
2238 addFactoryCreate( pattern, className, false );
2239 }
2240
2241 /**
2242 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process
2243 * will be propagated.
2244 *
2245 * @param pattern Element matching pattern
2246 * @param clazz Java class of the object creation factory class
2247 * @see FactoryCreateRule
2248 */
2249 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz )
2250 {
2251 addFactoryCreate( pattern, clazz, false );
2252 }
2253
2254 /**
2255 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process
2256 * will be propagated.
2257 *
2258 * @param pattern Element matching pattern
2259 * @param className Java class name of the object creation factory class
2260 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code>
2261 * @see FactoryCreateRule
2262 */
2263 public void addFactoryCreate( String pattern, String className, String attributeName )
2264 {
2265 addFactoryCreate( pattern, className, attributeName, false );
2266 }
2267
2268 /**
2269 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process
2270 * will be propagated.
2271 *
2272 * @param pattern Element matching pattern
2273 * @param clazz Java class of the object creation factory class
2274 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code>
2275 * @see FactoryCreateRule
2276 */
2277 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz,
2278 String attributeName )
2279 {
2280 addFactoryCreate( pattern, clazz, attributeName, false );
2281 }
2282
2283 /**
2284 * Add a "factory create" rule for the specified parameters. Exceptions thrown during the object creation process
2285 * will be propagated.
2286 *
2287 * @param pattern Element matching pattern
2288 * @param creationFactory Previously instantiated ObjectCreationFactory to be utilized
2289 * @see FactoryCreateRule
2290 */
2291 public void addFactoryCreate( String pattern, ObjectCreationFactory<?> creationFactory )
2292 {
2293 addFactoryCreate( pattern, creationFactory, false );
2294 }
2295
2296 /**
2297 * Add a "factory create" rule for the specified parameters.
2298 *
2299 * @param pattern Element matching pattern
2300 * @param className Java class name of the object creation factory class
2301 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be
2302 * ignored.
2303 * @see FactoryCreateRule
2304 */
2305 public void addFactoryCreate( String pattern, String className, boolean ignoreCreateExceptions )
2306 {
2307 addRule( pattern, new FactoryCreateRule( className, ignoreCreateExceptions ) );
2308 }
2309
2310 /**
2311 * Add a "factory create" rule for the specified parameters.
2312 *
2313 * @param pattern Element matching pattern
2314 * @param clazz Java class of the object creation factory class
2315 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be
2316 * ignored.
2317 * @see FactoryCreateRule
2318 */
2319 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz,
2320 boolean ignoreCreateExceptions )
2321 {
2322 addRule( pattern, new FactoryCreateRule( clazz, ignoreCreateExceptions ) );
2323 }
2324
2325 /**
2326 * Add a "factory create" rule for the specified parameters.
2327 *
2328 * @param pattern Element matching pattern
2329 * @param className Java class name of the object creation factory class
2330 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code>
2331 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be
2332 * ignored.
2333 * @see FactoryCreateRule
2334 */
2335 public void addFactoryCreate( String pattern, String className, String attributeName,
2336 boolean ignoreCreateExceptions )
2337 {
2338 addRule( pattern, new FactoryCreateRule( className, attributeName, ignoreCreateExceptions ) );
2339 }
2340
2341 /**
2342 * Add a "factory create" rule for the specified parameters.
2343 *
2344 * @param pattern Element matching pattern
2345 * @param clazz Java class of the object creation factory class
2346 * @param attributeName Attribute name which, if present, overrides the value specified by <code>className</code>
2347 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be
2348 * ignored.
2349 * @see FactoryCreateRule
2350 */
2351 public void addFactoryCreate( String pattern, Class<? extends ObjectCreationFactory<?>> clazz,
2352 String attributeName, boolean ignoreCreateExceptions )
2353 {
2354 addRule( pattern, new FactoryCreateRule( clazz, attributeName, ignoreCreateExceptions ) );
2355 }
2356
2357 /**
2358 * Add a "factory create" rule for the specified parameters.
2359 *
2360 * @param pattern Element matching pattern
2361 * @param creationFactory Previously instantiated ObjectCreationFactory to be utilized
2362 * @param ignoreCreateExceptions when <code>true</code> any exceptions thrown during object creation will be
2363 * ignored.
2364 * @see FactoryCreateRule
2365 */
2366 public void addFactoryCreate( String pattern, ObjectCreationFactory<?> creationFactory,
2367 boolean ignoreCreateExceptions )
2368 {
2369 creationFactory.setDigester( this );
2370 addRule( pattern, new FactoryCreateRule( creationFactory, ignoreCreateExceptions ) );
2371 }
2372
2373 /**
2374 * Add an "object create" rule for the specified parameters.
2375 *
2376 * @param pattern Element matching pattern
2377 * @param className Java class name to be created
2378 * @see ObjectCreateRule
2379 */
2380 public void addObjectCreate( String pattern, String className )
2381 {
2382 addRule( pattern, new ObjectCreateRule( className ) );
2383 }
2384
2385 /**
2386 * Add an "object create" rule for the specified parameters.
2387 *
2388 * @param pattern Element matching pattern
2389 * @param clazz Java class to be created
2390 * @see ObjectCreateRule
2391 */
2392 public void addObjectCreate( String pattern, Class<?> clazz )
2393 {
2394 addRule( pattern, new ObjectCreateRule( clazz ) );
2395 }
2396
2397 /**
2398 * Add an "object create" rule for the specified parameters.
2399 *
2400 * @param pattern Element matching pattern
2401 * @param className Default Java class name to be created
2402 * @param attributeName Attribute name that optionally overrides the default Java class name to be created
2403 * @see ObjectCreateRule
2404 */
2405 public void addObjectCreate( String pattern, String className, String attributeName )
2406 {
2407 addRule( pattern, new ObjectCreateRule( className, attributeName ) );
2408 }
2409
2410 /**
2411 * Add an "object create" rule for the specified parameters.
2412 *
2413 * @param pattern Element matching pattern
2414 * @param attributeName Attribute name that optionally overrides
2415 * @param clazz Default Java class to be created the default Java class name to be created
2416 * @see ObjectCreateRule
2417 */
2418 public void addObjectCreate( String pattern, String attributeName, Class<?> clazz )
2419 {
2420 addRule( pattern, new ObjectCreateRule( attributeName, clazz ) );
2421 }
2422
2423 /**
2424 * Adds an {@link SetNestedPropertiesRule}.
2425 *
2426 * @param pattern register the rule with this pattern
2427 * @since 1.6
2428 */
2429 public void addSetNestedProperties( String pattern )
2430 {
2431 addRule( pattern, new SetNestedPropertiesRule() );
2432 }
2433
2434 /**
2435 * Adds an {@link SetNestedPropertiesRule}.
2436 *
2437 * @param pattern register the rule with this pattern
2438 * @param elementName elment name that a property maps to
2439 * @param propertyName property name of the element mapped from
2440 * @since 1.6
2441 */
2442 public void addSetNestedProperties( String pattern, String elementName, String propertyName )
2443 {
2444 addRule( pattern, new SetNestedPropertiesRule( elementName, propertyName ) );
2445 }
2446
2447 /**
2448 * Adds an {@link SetNestedPropertiesRule}.
2449 *
2450 * @param pattern register the rule with this pattern
2451 * @param elementNames elment names that (in order) map to properties
2452 * @param propertyNames property names that (in order) elements are mapped to
2453 * @since 1.6
2454 */
2455 public void addSetNestedProperties( String pattern, String[] elementNames, String[] propertyNames )
2456 {
2457 addRule( pattern, new SetNestedPropertiesRule( elementNames, propertyNames ) );
2458 }
2459
2460 /**
2461 * Add a "set next" rule for the specified parameters.
2462 *
2463 * @param pattern Element matching pattern
2464 * @param methodName Method name to call on the parent element
2465 * @see SetNextRule
2466 */
2467 public void addSetNext( String pattern, String methodName )
2468 {
2469 addRule( pattern, new SetNextRule( methodName ) );
2470 }
2471
2472 /**
2473 * Add a "set next" rule for the specified parameters.
2474 *
2475 * @param pattern Element matching pattern
2476 * @param methodName Method name to call on the parent element
2477 * @param paramType Java class name of the expected parameter type (if you wish to use a primitive type, specify the
2478 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
2479 * <code>boolean</code> parameter)
2480 * @see SetNextRule
2481 */
2482 public void addSetNext( String pattern, String methodName, String paramType )
2483 {
2484 addRule( pattern, new SetNextRule( methodName, paramType ) );
2485 }
2486
2487 /**
2488 * Add {@link SetRootRule} with the specified parameters.
2489 *
2490 * @param pattern Element matching pattern
2491 * @param methodName Method name to call on the root object
2492 * @see SetRootRule
2493 */
2494 public void addSetRoot( String pattern, String methodName )
2495 {
2496 addRule( pattern, new SetRootRule( methodName ) );
2497 }
2498
2499 /**
2500 * Add {@link SetRootRule} with the specified parameters.
2501 *
2502 * @param pattern Element matching pattern
2503 * @param methodName Method name to call on the root object
2504 * @param paramType Java class name of the expected parameter type
2505 * @see SetRootRule
2506 */
2507 public void addSetRoot( String pattern, String methodName, String paramType )
2508 {
2509 addRule( pattern, new SetRootRule( methodName, paramType ) );
2510 }
2511
2512 /**
2513 * Add a "set properties" rule for the specified parameters.
2514 *
2515 * @param pattern Element matching pattern
2516 * @see SetPropertiesRule
2517 */
2518 public void addSetProperties( String pattern )
2519 {
2520 addRule( pattern, new SetPropertiesRule() );
2521 }
2522
2523 /**
2524 * Add a "set properties" rule with a single overridden parameter. See
2525 * {@link SetPropertiesRule#SetPropertiesRule(String attributeName, String propertyName)}
2526 *
2527 * @param pattern Element matching pattern
2528 * @param attributeName map this attribute
2529 * @param propertyName to this property
2530 * @see SetPropertiesRule
2531 */
2532 public void addSetProperties( String pattern, String attributeName, String propertyName )
2533 {
2534 addRule( pattern, new SetPropertiesRule( attributeName, propertyName ) );
2535 }
2536
2537 /**
2538 * Add a "set properties" rule with overridden parameters. See
2539 * {@link SetPropertiesRule#SetPropertiesRule(String [] attributeNames, String [] propertyNames)}
2540 *
2541 * @param pattern Element matching pattern
2542 * @param attributeNames names of attributes with custom mappings
2543 * @param propertyNames property names these attributes map to
2544 * @see SetPropertiesRule
2545 */
2546 public void addSetProperties( String pattern, String[] attributeNames, String[] propertyNames )
2547 {
2548 addRule( pattern, new SetPropertiesRule( attributeNames, propertyNames ) );
2549 }
2550
2551 /**
2552 * Add a "set property" rule for the specified parameters.
2553 *
2554 * @param pattern Element matching pattern
2555 * @param name Attribute name containing the property name to be set
2556 * @param value Attribute name containing the property value to set
2557 * @see SetPropertyRule
2558 */
2559 public void addSetProperty( String pattern, String name, String value )
2560 {
2561 addRule( pattern, new SetPropertyRule( name, value ) );
2562 }
2563
2564 /**
2565 * Add a "set top" rule for the specified parameters.
2566 *
2567 * @param pattern Element matching pattern
2568 * @param methodName Method name to call on the parent element
2569 * @see SetTopRule
2570 */
2571 public void addSetTop( String pattern, String methodName )
2572 {
2573 addRule( pattern, new SetTopRule( methodName ) );
2574 }
2575
2576 /**
2577 * Add a "set top" rule for the specified parameters.
2578 *
2579 * @param pattern Element matching pattern
2580 * @param methodName Method name to call on the parent element
2581 * @param paramType Java class name of the expected parameter type (if you wish to use a primitive type, specify the
2582 * corresonding Java wrapper class instead, such as <code>java.lang.Boolean</code> for a
2583 * <code>boolean</code> parameter)
2584 * @see SetTopRule
2585 */
2586 public void addSetTop( String pattern, String methodName, String paramType )
2587 {
2588 addRule( pattern, new SetTopRule( methodName, paramType ) );
2589 }
2590
2591 // --------------------------------------------------- Object Stack Methods
2592
2593 /**
2594 * Clear the current contents of the default object stack, the param stack, all named stacks, and other internal
2595 * variables.
2596 * <p>
2597 * Calling this method <i>might</i> allow another document of the same type to be correctly parsed. However this
2598 * method was not intended for this purpose (just to tidy up memory usage). In general, a separate Digester object
2599 * should be created for each document to be parsed.
2600 * <p>
2601 * Note that this method is called automatically after a document has been successfully parsed by a Digester
2602 * instance. However it is not invoked automatically when a parse fails, so when reusing a Digester instance (which
2603 * is not recommended) this method <i>must</i> be called manually after a parse failure.
2604 */
2605 public void clear()
2606 {
2607 match = "";
2608 bodyTexts.clear();
2609 params.clear();
2610 publicId = null;
2611 stack.clear();
2612 stacksByName.clear();
2613 customContentHandler = null;
2614 }
2615
2616 /**
2617 * Return the top object on the stack without removing it.
2618 *
2619 * If there are no objects on the stack, return <code>null</code>.
2620 *
2621 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2622 * @return the top object on the stack without removing it.
2623 */
2624 public <T> T peek()
2625 {
2626 try
2627 {
2628 return this.<T> npeSafeCast( stack.peek() );
2629 }
2630 catch ( EmptyStackException e )
2631 {
2632 log.warn( "Empty stack (returning null)" );
2633 return ( null );
2634 }
2635 }
2636
2637 /**
2638 * Return the n'th object down the stack, where 0 is the top element and [getCount()-1] is the bottom element. If
2639 * the specified index is out of range, return <code>null</code>.
2640 *
2641 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2642 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on.
2643 * @return the n'th object down the stack
2644 */
2645 public <T> T peek( int n )
2646 {
2647 int index = ( stack.size() - 1 ) - n;
2648 if ( index < 0 )
2649 {
2650 log.warn( "Empty stack (returning null)" );
2651 return ( null );
2652 }
2653 try
2654 {
2655 return this.<T> npeSafeCast( stack.get( index ) );
2656 }
2657 catch ( EmptyStackException e )
2658 {
2659 log.warn( "Empty stack (returning null)" );
2660 return ( null );
2661 }
2662 }
2663
2664 /**
2665 * Pop the top object off of the stack, and return it. If there are no objects on the stack, return
2666 * <code>null</code>.
2667 *
2668 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2669 * @return the top object popped off of the stack
2670 */
2671 public <T> T pop()
2672 {
2673 try
2674 {
2675 T popped = this.<T> npeSafeCast( stack.pop() );
2676 if ( stackAction != null )
2677 {
2678 popped = stackAction.onPop( this, null, popped );
2679 }
2680 return popped;
2681 }
2682 catch ( EmptyStackException e )
2683 {
2684 log.warn( "Empty stack (returning null)" );
2685 return ( null );
2686 }
2687 }
2688
2689 /**
2690 * Push a new object onto the top of the object stack.
2691 *
2692 * @param <T> any type of the pushed object
2693 * @param object The new object
2694 */
2695 public <T> void push( T object )
2696 {
2697 if ( stackAction != null )
2698 {
2699 object = stackAction.onPush( this, null, object );
2700 }
2701
2702 if ( stack.size() == 0 )
2703 {
2704 root = object;
2705 }
2706 stack.push( object );
2707 }
2708
2709 /**
2710 * Pushes the given object onto the stack with the given name. If no stack already exists with the given name then
2711 * one will be created.
2712 *
2713 * @param <T> any type of the pushed object
2714 * @param stackName the name of the stack onto which the object should be pushed
2715 * @param value the Object to be pushed onto the named stack.
2716 * @since 1.6
2717 */
2718 public <T> void push( String stackName, T value )
2719 {
2720 if ( stackAction != null )
2721 {
2722 value = stackAction.onPush( this, stackName, value );
2723 }
2724
2725 Stack<Object> namedStack = stacksByName.get( stackName );
2726 if ( namedStack == null )
2727 {
2728 namedStack = new Stack<Object>();
2729 stacksByName.put( stackName, namedStack );
2730 }
2731 namedStack.push( value );
2732 }
2733
2734 /**
2735 * <p>
2736 * Pops (gets and removes) the top object from the stack with the given name.
2737 * </p>
2738 * <p>
2739 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet.
2740 * </p>
2741 *
2742 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2743 * @param stackName the name of the stack from which the top value is to be popped.
2744 * @return the top <code>Object</code> on the stack or or null if the stack is either empty or has not been created
2745 * yet
2746 * @since 1.6
2747 */
2748 public <T> T pop( String stackName )
2749 {
2750 T result = null;
2751 Stack<Object> namedStack = stacksByName.get( stackName );
2752 if ( namedStack == null )
2753 {
2754 if ( log.isDebugEnabled() )
2755 {
2756 log.debug( "Stack '" + stackName + "' is empty" );
2757 }
2758 throw new EmptyStackException();
2759 }
2760
2761 result = this.<T> npeSafeCast( namedStack.pop() );
2762
2763 if ( stackAction != null )
2764 {
2765 result = stackAction.onPop( this, stackName, result );
2766 }
2767
2768 return result;
2769 }
2770
2771 /**
2772 * <p>
2773 * Gets the top object from the stack with the given name. This method does not remove the object from the stack.
2774 * </p>
2775 * <p>
2776 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet.
2777 * </p>
2778 *
2779 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2780 * @param stackName the name of the stack to be peeked
2781 * @return the top <code>Object</code> on the stack or null if the stack is either empty or has not been created yet
2782 * @since 1.6
2783 */
2784 public <T> T peek( String stackName )
2785 {
2786 return this.<T> npeSafeCast( peek( stackName, 0 ) );
2787 }
2788
2789 /**
2790 * <p>
2791 * Gets the top object from the stack with the given name. This method does not remove the object from the stack.
2792 * </p>
2793 * <p>
2794 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet.
2795 * </p>
2796 *
2797 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2798 * @param stackName the name of the stack to be peeked
2799 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on.
2800 * @return the specified <code>Object</code> on the stack.
2801 * @since 1.6
2802 */
2803 public <T> T peek( String stackName, int n )
2804 {
2805 T result = null;
2806 Stack<Object> namedStack = stacksByName.get( stackName );
2807 if ( namedStack == null )
2808 {
2809 if ( log.isDebugEnabled() )
2810 {
2811 log.debug( "Stack '" + stackName + "' is empty" );
2812 }
2813 throw new EmptyStackException();
2814 }
2815
2816 int index = ( namedStack.size() - 1 ) - n;
2817 if ( index < 0 )
2818 {
2819 throw new EmptyStackException();
2820 }
2821 result = this.<T> npeSafeCast( namedStack.get( index ) );
2822
2823 return result;
2824 }
2825
2826 /**
2827 * <p>
2828 * Is the stack with the given name empty?
2829 * </p>
2830 * <p>
2831 * <strong>Note:</strong> a stack is considered empty if no objects have been pushed onto it yet.
2832 * </p>
2833 *
2834 * @param stackName the name of the stack whose emptiness should be evaluated
2835 * @return true if the given stack if empty
2836 * @since 1.6
2837 */
2838 public boolean isEmpty( String stackName )
2839 {
2840 boolean result = true;
2841 Stack<Object> namedStack = stacksByName.get( stackName );
2842 if ( namedStack != null )
2843 {
2844 result = namedStack.isEmpty();
2845 }
2846 return result;
2847 }
2848
2849 /**
2850 * Returns the root element of the tree of objects created as a result of applying the rule objects to the input
2851 * XML.
2852 * <p>
2853 * If the digester stack was "primed" by explicitly pushing a root object onto the stack before parsing started,
2854 * then that root object is returned here.
2855 * <p>
2856 * Alternatively, if a Rule which creates an object (eg ObjectCreateRule) matched the root element of the xml, then
2857 * the object created will be returned here.
2858 * <p>
2859 * In other cases, the object most recently pushed onto an empty digester stack is returned. This would be a most
2860 * unusual use of digester, however; one of the previous configurations is much more likely.
2861 * <p>
2862 * Note that when using one of the Digester.parse methods, the return value from the parse method is exactly the
2863 * same as the return value from this method. However when the Digester is being used as a SAXContentHandler, no
2864 * such return value is available; in this case, this method allows you to access the root object that has been
2865 * created after parsing has completed.
2866 *
2867 * @param <T> the type used to auto-cast the returned object to the assigned variable type
2868 * @return the root object that has been created after parsing or null if the digester has not parsed any XML yet.
2869 */
2870 public <T> T getRoot()
2871 {
2872 return this.<T> npeSafeCast( root );
2873 }
2874
2875 /**
2876 * This method allows the "root" variable to be reset to null.
2877 * <p>
2878 * It is not considered safe for a digester instance to be reused to parse multiple xml documents. However if you
2879 * are determined to do so, then you should call both clear() and resetRoot() before each parse.
2880 *
2881 * @since 1.7
2882 */
2883 public void resetRoot()
2884 {
2885 root = null;
2886 }
2887
2888 // ------------------------------------------------ Parameter Stack Methods
2889
2890 // ------------------------------------------------------ Protected Methods
2891
2892 /**
2893 * <p>
2894 * Clean up allocated resources after parsing is complete. The default method closes input streams that have been
2895 * created by Digester itself. If you override this method in a subclass, be sure to call
2896 * <code>super.cleanup()</code> to invoke this logic.
2897 * </p>
2898 *
2899 * @since 1.8
2900 */
2901 protected void cleanup()
2902 {
2903 // If we created any InputSource objects in this instance,
2904 // they each have an input stream that should be closed
2905 for ( InputSource source : inputSources )
2906 {
2907 try
2908 {
2909 source.getByteStream().close();
2910 }
2911 catch ( IOException e )
2912 {
2913 // Fall through so we get them all
2914 if ( log.isWarnEnabled() )
2915 {
2916 log.warn( format( "An error occurred while closing resource %s (%s)",
2917 source.getPublicId(),
2918 source.getSystemId() ), e );
2919 }
2920 }
2921 }
2922 inputSources.clear();
2923 }
2924
2925 /**
2926 * <p>
2927 * Provide a hook for lazy configuration of this <code>Digester</code> instance. The default implementation does
2928 * nothing, but subclasses can override as needed.
2929 * </p>
2930 * <p>
2931 * <strong>Note</strong> This method may be called more than once. Once only initialization code should be placed in
2932 * {@link #initialize} or the code should take responsibility by checking and setting the {@link #configured} flag.
2933 * </p>
2934 */
2935 protected void configure()
2936 {
2937 // Do not configure more than once
2938 if ( configured )
2939 {
2940 return;
2941 }
2942
2943 // Perform lazy configuration as needed
2944 initialize(); // call hook method for subclasses that want to be initialized once only
2945 // Nothing else required by default
2946
2947 // Set the configuration flag to avoid repeating
2948 configured = true;
2949 }
2950
2951 /**
2952 * Checks the Digester instance has been configured.
2953 *
2954 * @return true, if the Digester instance has been configured, false otherwise
2955 * @since 3.0
2956 */
2957 public boolean isConfigured()
2958 {
2959 return configured;
2960 }
2961
2962 /**
2963 * <p>
2964 * Provides a hook for lazy initialization of this <code>Digester</code> instance. The default implementation does
2965 * nothing, but subclasses can override as needed. Digester (by default) only calls this method once.
2966 * </p>
2967 * <p>
2968 * <strong>Note</strong> This method will be called by {@link #configure} only when the {@link #configured} flag is
2969 * false. Subclasses that override <code>configure</code> or who set <code>configured</code> may find that this
2970 * method may be called more than once.
2971 * </p>
2972 *
2973 * @since 1.6
2974 */
2975 protected void initialize()
2976 {
2977 // Perform lazy initialization as needed
2978 // Nothing required by default
2979 }
2980
2981 // -------------------------------------------------------- Package Methods
2982
2983 /**
2984 * Return the set of DTD URL registrations, keyed by public identifier. NOTE: the returned map is in read-only mode.
2985 *
2986 * @return the read-only Map of DTD URL registrations.
2987 */
2988 Map<String, URL> getRegistrations()
2989 {
2990 return Collections.unmodifiableMap( entityValidator );
2991 }
2992
2993 /**
2994 * <p>
2995 * Return the top object on the parameters stack without removing it. If there are no objects on the stack, return
2996 * <code>null</code>.
2997 * </p>
2998 * <p>
2999 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}.
3000 * </p>
3001 *
3002 * @return the top object on the parameters stack without removing it.
3003 */
3004 public Object[] peekParams()
3005 {
3006 try
3007 {
3008 return ( params.peek() );
3009 }
3010 catch ( EmptyStackException e )
3011 {
3012 log.warn( "Empty stack (returning null)" );
3013 return ( null );
3014 }
3015 }
3016
3017 /**
3018 * <p>
3019 * Return the n'th object down the parameters stack, where 0 is the top element and [getCount()-1] is the bottom
3020 * element. If the specified index is out of range, return <code>null</code>.
3021 * </p>
3022 * <p>
3023 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}.
3024 * </p>
3025 *
3026 * @param n Index of the desired element, where 0 is the top of the stack, 1 is the next element down, and so on.
3027 * @return the n'th object down the parameters stack
3028 */
3029 public Object[] peekParams( int n )
3030 {
3031 int index = ( params.size() - 1 ) - n;
3032 if ( index < 0 )
3033 {
3034 log.warn( "Empty stack (returning null)" );
3035 return ( null );
3036 }
3037 try
3038 {
3039 return ( params.get( index ) );
3040 }
3041 catch ( EmptyStackException e )
3042 {
3043 log.warn( "Empty stack (returning null)" );
3044 return ( null );
3045 }
3046 }
3047
3048 /**
3049 * <p>
3050 * Pop the top object off of the parameters stack, and return it. If there are no objects on the stack, return
3051 * <code>null</code>.
3052 * </p>
3053 * <p>
3054 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}.
3055 * </p>
3056 *
3057 * @return the top object popped off of the parameters stack
3058 */
3059 public Object[] popParams()
3060 {
3061 try
3062 {
3063 if ( log.isTraceEnabled() )
3064 {
3065 log.trace( "Popping params" );
3066 }
3067 return ( params.pop() );
3068 }
3069 catch ( EmptyStackException e )
3070 {
3071 log.warn( "Empty stack (returning null)" );
3072 return ( null );
3073 }
3074 }
3075
3076 /**
3077 * <p>
3078 * Push a new object onto the top of the parameters stack.
3079 * </p>
3080 * <p>
3081 * The parameters stack is used to store <code>CallMethodRule</code> parameters. See {@link #params}.
3082 * </p>
3083 *
3084 * @param object The new object
3085 */
3086 public void pushParams( Object... object )
3087 {
3088 if ( log.isTraceEnabled() )
3089 {
3090 log.trace( "Pushing params" );
3091 }
3092 params.push( object );
3093 }
3094
3095 /**
3096 * Create a SAX exception which also understands about the location in the digester file where the exception occurs
3097 *
3098 * @param message the custom SAX exception message
3099 * @param e the exception cause
3100 * @return the new SAX exception
3101 */
3102 public SAXException createSAXException( String message, Exception e )
3103 {
3104 if ( ( e != null ) && ( e instanceof InvocationTargetException ) )
3105 {
3106 Throwable t = ( (InvocationTargetException) e ).getTargetException();
3107 if ( ( t != null ) && ( t instanceof Exception ) )
3108 {
3109 e = (Exception) t;
3110 }
3111 }
3112 if ( locator != null )
3113 {
3114 String error =
3115 "Error at line " + locator.getLineNumber() + " char " + locator.getColumnNumber() + ": " + message;
3116 if ( e != null )
3117 {
3118 return new SAXParseException( error, locator, e );
3119 }
3120 return new SAXParseException( error, locator );
3121 }
3122 log.error( "No Locator!" );
3123 if ( e != null )
3124 {
3125 return new SAXException( message, e );
3126 }
3127 return new SAXException( message );
3128 }
3129
3130 /**
3131 * Create a SAX exception which also understands about the location in the digester file where the exception occurs
3132 *
3133 * @param e the exception cause
3134 * @return the new SAX exception
3135 */
3136 public SAXException createSAXException( Exception e )
3137 {
3138 if ( e instanceof InvocationTargetException )
3139 {
3140 Throwable t = ( (InvocationTargetException) e ).getTargetException();
3141 if ( ( t != null ) && ( t instanceof Exception ) )
3142 {
3143 e = (Exception) t;
3144 }
3145 }
3146 return createSAXException( e.getMessage(), e );
3147 }
3148
3149 /**
3150 * Create a SAX exception which also understands about the location in the digester file where the exception occurs
3151 *
3152 * @param message the custom SAX exception message
3153 * @return the new SAX exception
3154 */
3155 public SAXException createSAXException( String message )
3156 {
3157 return createSAXException( message, null );
3158 }
3159
3160 /**
3161 * Helps casting the input object to given type, avoiding NPEs.
3162 *
3163 * @since 3.0
3164 * @param <T> the type the input object has to be cast.
3165 * @param obj the object has to be cast.
3166 * @return the casted object, if input object is not null, null otherwise.
3167 */
3168 private <T> T npeSafeCast( Object obj )
3169 {
3170 if ( obj == null )
3171 {
3172 return null;
3173 }
3174
3175 @SuppressWarnings( "unchecked" )
3176 T result = (T) obj;
3177 return result;
3178 }
3179
3180 }