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