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