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