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