View Javadoc

1   /*
2    * Copyright 2001-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.configuration;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.net.URL;
23  import java.util.Collection;
24  import java.util.Iterator;
25  import java.util.LinkedList;
26  import java.util.Stack;
27  
28  import org.apache.commons.configuration.plist.PropertyListConfiguration;
29  import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
30  import org.apache.commons.digester.AbstractObjectCreationFactory;
31  import org.apache.commons.digester.Digester;
32  import org.apache.commons.digester.ObjectCreationFactory;
33  import org.apache.commons.digester.xmlrules.DigesterLoader;
34  import org.apache.commons.lang.StringUtils;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.xml.sax.Attributes;
38  import org.xml.sax.SAXException;
39  
40  /***
41   * Factory class to create a CompositeConfiguration from a .xml file using
42   * Digester.  By default it can handle the Configurations from commons-
43   * configuration.  If you need to add your own, then you can pass in your own
44   * digester rules to use.  It is also namespace aware, by providing a
45   * digesterRuleNamespaceURI.
46   *
47   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
48   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
49   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
50   * @version $Id: ConfigurationFactory.java 295090 2005-10-05 19:36:15Z oheger $
51   */
52  public class ConfigurationFactory
53  {
54      /*** Constant for the root element in the info file.*/
55      private static final String SEC_ROOT = "configuration/";
56  
57      /*** Constant for the override section.*/
58      private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
59  
60      /*** Constant for the additional section.*/
61      private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
62  
63      /*** Constant for the optional attribute.*/
64      private static final String ATTR_OPTIONAL = "optional";
65  
66      /*** Constant for the fileName attribute.*/
67      private static final String ATTR_FILENAME = "fileName";
68  
69      /*** Constant for the default base path (points to actual directory).*/
70      private static final String DEF_BASE_PATH = ".";
71  
72      /*** static logger */
73      private static Log log = LogFactory.getLog(ConfigurationFactory.class);
74  
75      /*** The XML file with the details about the configuration to load */
76      private String configurationFileName;
77  
78      /*** The URL to the XML file with the details about the configuration to load. */
79      private URL configurationURL;
80  
81      /***
82       * The implicit base path for included files. This path is determined by
83       * the configuration to load and used unless no other base path was
84       * explicitely specified.
85       */
86      private String implicitBasePath;
87  
88      /*** The basePath to prefix file paths for file based property files. */
89      private String basePath;
90  
91      /*** URL for xml digester rules file */
92      private URL digesterRules;
93  
94      /*** The digester namespace to parse */
95      private String digesterRuleNamespaceURI;
96  
97      /***
98       * Constructor
99       */
100     public ConfigurationFactory()
101     {
102         setBasePath(DEF_BASE_PATH);
103     }
104     /***
105      * Constructor with ConfigurationFile Name passed
106      *
107      * @param configurationFileName The path to the configuration file
108      */
109     public ConfigurationFactory(String configurationFileName)
110     {
111         setConfigurationFileName(configurationFileName);
112     }
113 
114     /***
115      * Return the configuration provided by this factory. It loads the
116      * configuration file which is a XML description of the actual
117      * configurations to load. It can contain various different types of
118      * configuration, e.g. Properties, XML and JNDI.
119      *
120      * @return A Configuration object
121      * @throws ConfigurationException A generic exception that we had trouble during the
122      * loading of the configuration data.
123      */
124     public Configuration getConfiguration() throws ConfigurationException
125     {
126         Digester digester;
127         InputStream input = null;
128         ConfigurationBuilder builder = new ConfigurationBuilder();
129         URL url = getConfigurationURL();
130         try
131         {
132             if (url == null)
133             {
134                 url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
135             }
136             input = url.openStream();
137         }
138         catch (Exception e)
139         {
140             log.error("Exception caught opening stream to URL", e);
141             throw new ConfigurationException("Exception caught opening stream to URL", e);
142         }
143 
144         if (getDigesterRules() == null)
145         {
146             digester = new Digester();
147             configureNamespace(digester);
148             initDefaultDigesterRules(digester);
149         }
150         else
151         {
152             digester = DigesterLoader.createDigester(getDigesterRules());
153             // This might already be too late. As far as I can see, the namespace
154             // awareness must be configured before the digester rules are loaded.
155             configureNamespace(digester);
156         }
157 
158         // Configure digester to always enable the context class loader
159         digester.setUseContextClassLoader(true);
160         // Put the composite builder object below all of the other objects.
161         digester.push(builder);
162         // Parse the input stream to configure our mappings
163         try
164         {
165             digester.parse(input);
166             input.close();
167         }
168         catch (SAXException saxe)
169         {
170             log.error("SAX Exception caught", saxe);
171             throw new ConfigurationException("SAX Exception caught", saxe);
172         }
173         catch (IOException ioe)
174         {
175             log.error("IO Exception caught", ioe);
176             throw new ConfigurationException("IO Exception caught", ioe);
177         }
178         return builder.getConfiguration();
179     }
180 
181     /***
182      * Returns the configurationFile.
183      *
184      * @return The name of the configuration file. Can be null.
185      */
186     public String getConfigurationFileName()
187     {
188         return configurationFileName;
189     }
190 
191     /***
192      * Sets the configurationFile.
193      *
194      * @param configurationFileName  The name of the configurationFile to use.
195      */
196     public void setConfigurationFileName(String configurationFileName)
197     {
198         File file = new File(configurationFileName).getAbsoluteFile();
199         this.configurationFileName = file.getName();
200         implicitBasePath = file.getParent();
201     }
202 
203     /***
204      * Returns the URL of the configuration file to be loaded.
205      *
206      * @return the URL of the configuration to load
207      */
208     public URL getConfigurationURL()
209     {
210         return configurationURL;
211     }
212 
213     /***
214      * Sets the URL of the configuration to load. This configuration can be
215      * either specified by a file name or by a URL.
216      *
217      * @param url the URL of the configuration to load
218      */
219     public void setConfigurationURL(URL url)
220     {
221         configurationURL = url;
222         implicitBasePath = url.toString();
223     }
224 
225     /***
226      * Returns the digesterRules.
227      *
228      * @return URL
229      */
230     public URL getDigesterRules()
231     {
232         return digesterRules;
233     }
234 
235     /***
236      * Sets the digesterRules.
237      *
238      * @param digesterRules The digesterRules to set
239      */
240     public void setDigesterRules(URL digesterRules)
241     {
242         this.digesterRules = digesterRules;
243     }
244 
245     /***
246      * Initializes the parsing rules for the default digester
247      *
248      * This allows the Configuration Factory to understand the
249      * default types: Properties, XML and JNDI. Two special sections are
250      * introduced: <code>&lt;override&gt;</code> and
251      * <code>&lt;additional&gt;</code>.
252      *
253      * @param digester The digester to configure
254      */
255     protected void initDefaultDigesterRules(Digester digester)
256     {
257         initDigesterSectionRules(digester, SEC_ROOT, false);
258         initDigesterSectionRules(digester, SEC_OVERRIDE, false);
259         initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
260     }
261 
262     /***
263      * Sets up digester rules for a specified section of the configuration
264      * info file.
265      *
266      * @param digester the current digester instance
267      * @param matchString specifies the section
268      * @param additional a flag if rules for the additional section are to be
269      * added
270      */
271     protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
272     {
273         setupDigesterInstance(
274             digester,
275             matchString + "properties",
276             new PropertiesConfigurationFactory(),
277             null,
278             additional);
279 
280         setupDigesterInstance(
281             digester,
282             matchString + "plist",
283             new PropertyListConfigurationFactory(),
284             null,
285             additional);
286 
287         setupDigesterInstance(
288             digester,
289             matchString + "xml",
290             new FileConfigurationFactory(XMLConfiguration.class),
291             null,
292             additional);
293 
294         setupDigesterInstance(
295             digester,
296             matchString + "hierarchicalXml",
297             new FileConfigurationFactory(XMLConfiguration.class),
298             null,
299             additional);
300 
301         setupDigesterInstance(
302             digester,
303             matchString + "jndi",
304             new JNDIConfigurationFactory(),
305             null,
306             additional);
307 
308         setupDigesterInstance(
309             digester,
310             matchString + "system",
311             new SystemConfigurationFactory(),
312             null,
313             additional);
314     }
315 
316     /***
317      * Sets up digester rules for a configuration to be loaded.
318      *
319      * @param digester the current digester
320      * @param matchString the pattern to match with this rule
321      * @param factory an ObjectCreationFactory instance to use for creating new
322      * objects
323      * @param method the name of a method to be called or <b>null</b> for none
324      * @param additional a flag if rules for the additional section are to be
325      * added
326      */
327     protected void setupDigesterInstance(
328             Digester digester,
329             String matchString,
330             ObjectCreationFactory factory,
331             String method,
332             boolean additional)
333     {
334         if (additional)
335         {
336             setupUnionRules(digester, matchString);
337         }
338 
339         digester.addFactoryCreate(matchString, factory);
340         digester.addSetProperties(matchString);
341 
342         if (method != null)
343         {
344             digester.addCallMethod(matchString, method);
345         }
346 
347         digester.addSetNext(matchString, "addConfiguration", Configuration.class.getName());
348     }
349 
350     /***
351      * Sets up rules for configurations in the additional section.
352      *
353      * @param digester the current digester
354      * @param matchString the pattern to match with this rule
355      */
356     protected void setupUnionRules(Digester digester, String matchString)
357     {
358         digester.addObjectCreate(matchString,
359         AdditionalConfigurationData.class);
360         digester.addSetProperties(matchString);
361         digester.addSetNext(matchString, "addAdditionalConfig",
362         AdditionalConfigurationData.class.getName());
363     }
364 
365     /***
366      * Returns the digesterRuleNamespaceURI.
367      *
368      * @return A String with the digesterRuleNamespaceURI.
369      */
370     public String getDigesterRuleNamespaceURI()
371     {
372         return digesterRuleNamespaceURI;
373     }
374 
375     /***
376      * Sets the digesterRuleNamespaceURI.
377      *
378      * @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
379      */
380     public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
381     {
382         this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
383     }
384 
385     /***
386      * Configure the current digester to be namespace aware and to have
387      * a Configuration object to which all of the other configurations
388      * should be added
389      *
390      * @param digester The Digester to configure
391      */
392     private void configureNamespace(Digester digester)
393     {
394         if (getDigesterRuleNamespaceURI() != null)
395         {
396             digester.setNamespaceAware(true);
397             digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
398         }
399         else
400         {
401             digester.setNamespaceAware(false);
402         }
403         digester.setValidating(false);
404     }
405 
406     /***
407      * Returns the Base path from which this Configuration Factory operates.
408      * This is never null. If you set the BasePath to null, then a base path
409      * according to the configuration to load is returned.
410      *
411      * @return The base Path of this configuration factory.
412      */
413     public String getBasePath()
414     {
415         String path = StringUtils.isEmpty(basePath)
416                 || DEF_BASE_PATH.equals(basePath) ? implicitBasePath : basePath;
417         return StringUtils.isEmpty(path) ? DEF_BASE_PATH : path;
418     }
419 
420     /***
421      * Sets the basePath for all file references from this Configuration Factory.
422      * Normally a base path need not to be set because it is determined by
423      * the location of the configuration file to load. All relative pathes in
424      * this file are resolved relative to this file. Setting a base path makes
425      * sense if such relative pathes should be otherwise resolved, e.g. if
426      * the configuration file is loaded from the class path and all sub
427      * configurations it refers to are stored in a special config directory.
428      *
429      * @param basePath The new basePath to set.
430      */
431     public void setBasePath(String basePath)
432     {
433         this.basePath = basePath;
434     }
435 
436     /***
437      * A base class for digester factory classes. This base class maintains
438      * a default class for the objects to be created.
439      * There will be sub classes for specific configuration implementations.
440      */
441     public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
442     {
443         /*** Actual class to use. */
444         private Class clazz;
445 
446         /***
447          * Creates a new instance of <code>DigesterConfigurationFactory</code>.
448          *
449          * @param clazz the class which we should instantiate
450          */
451         public DigesterConfigurationFactory(Class clazz)
452         {
453             this.clazz = clazz;
454         }
455 
456         /***
457          * Creates an instance of the specified class.
458          *
459          * @param attribs the attributes (ignored)
460          * @return the new object
461          * @throws Exception if object creation fails
462          */
463         public Object createObject(Attributes attribs) throws Exception
464         {
465             return clazz.newInstance();
466         }
467     }
468 
469     /***
470      * A tiny inner class that allows the Configuration Factory to
471      * let the digester construct FileConfiguration objects
472      * that already have the correct base Path set.
473      *
474      */
475     public class FileConfigurationFactory extends DigesterConfigurationFactory
476     {
477         /***
478          * C'tor
479          *
480          * @param clazz The class which we should instantiate.
481          */
482         public FileConfigurationFactory(Class clazz)
483         {
484             super(clazz);
485         }
486 
487         /***
488          * Gets called by the digester.
489          *
490          * @param attributes the actual attributes
491          * @return the new object
492          * @throws Exception Couldn't instantiate the requested object.
493          */
494         public Object createObject(Attributes attributes) throws Exception
495         {
496             FileConfiguration conf = createConfiguration(attributes);
497             conf.setBasePath(getBasePath());
498             conf.setFileName(attributes.getValue(ATTR_FILENAME));
499             try
500             {
501                 log.info("Trying to load configuration " + conf.getFileName());
502                 conf.load();
503             }
504             catch (ConfigurationException cex)
505             {
506                 if (attributes.getValue(ATTR_OPTIONAL) != null
507                         && PropertyConverter.toBoolean(attributes.getValue(ATTR_OPTIONAL)).booleanValue())
508                 {
509                     log.warn("Could not load optional configuration " + conf.getFileName());
510                 }
511                 else
512                 {
513                     throw cex;
514                 }
515             }
516             return conf;
517         }
518 
519         /***
520          * Creates the object, a <code>FileConfiguration</code>.
521          *
522          * @param attributes the actual attributes
523          * @return the file configuration
524          * @throws Exception if the object could not be created
525          */
526         protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
527         {
528             return (FileConfiguration) super.createObject(attributes);
529         }
530     }
531 
532     /***
533      * A factory that returns an XMLPropertiesConfiguration for .xml files
534      * and a PropertiesConfiguration for the others.
535      *
536      * @since 1.2
537      */
538     public class PropertiesConfigurationFactory extends FileConfigurationFactory
539     {
540         /***
541          * Creates a new instance of <code>PropertiesConfigurationFactory</code>.
542          */
543         public PropertiesConfigurationFactory()
544         {
545             super(null);
546         }
547 
548         /***
549          * Creates the new configuration object. Based on the file name
550          * provided in the attributes either a <code>PropertiesConfiguration</code>
551          * or a <code>XMLPropertiesConfiguration</code> object will be
552          * returned.
553          *
554          * @param attributes the attributes
555          * @return the new configuration object
556          * @throws Exception if an error occurs
557          */
558         protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
559         {
560             String filename = attributes.getValue(ATTR_FILENAME);
561 
562             if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
563             {
564                 return new XMLPropertiesConfiguration();
565             }
566             else
567             {
568                 return new PropertiesConfiguration();
569             }
570         }
571     }
572 
573     /***
574      * A factory that returns an XMLPropertyListConfiguration for .xml files
575      * and a PropertyListConfiguration for the others.
576      *
577      * @since 1.2
578      */
579     public class PropertyListConfigurationFactory extends FileConfigurationFactory
580     {
581         /***
582          * Creates a new instance of <code>PropertyListConfigurationFactory</code>.
583          */
584         public PropertyListConfigurationFactory()
585         {
586             super(null);
587         }
588 
589         /***
590          * Creates the new configuration object. Based on the file name
591          * provided in the attributes either a <code>XMLPropertyListConfiguration</code>
592          * or a <code>PropertyListConfiguration</code> object will be
593          * returned.
594          *
595          * @param attributes the attributes
596          * @return the new configuration object
597          * @throws Exception if an error occurs
598          */
599         protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
600         {
601             String filename = attributes.getValue(ATTR_FILENAME);
602 
603             if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
604             {
605                 return new XMLPropertyListConfiguration();
606             }
607             else
608             {
609                 return new PropertyListConfiguration();
610             }
611         }
612     }
613 
614     /***
615      * A tiny inner class that allows the Configuration Factory to
616      * let the digester construct JNDIConfiguration objects.
617      */
618     private class JNDIConfigurationFactory extends DigesterConfigurationFactory
619     {
620         /***
621          * Creates a new instance of <code>JNDIConfigurationFactory</code>.
622          */
623         public JNDIConfigurationFactory()
624         {
625             super(JNDIConfiguration.class);
626         }
627     }
628 
629     /***
630      * A tiny inner class that allows the Configuration Factory to
631      * let the digester construct SystemConfiguration objects.
632      */
633     private class SystemConfigurationFactory extends DigesterConfigurationFactory
634     {
635         /***
636          * Creates a new instance of <code>SystemConfigurationFactory</code>.
637          */
638         public SystemConfigurationFactory()
639         {
640             super(SystemConfiguration.class);
641         }
642     }
643 
644     /***
645      * A simple data class that holds all information about a configuration
646      * from the <code>&lt;additional&gt;</code> section.
647      */
648     public static class AdditionalConfigurationData
649     {
650         /*** Stores the configuration object.*/
651         private Configuration configuration;
652 
653         /*** Stores the location of this configuration in the global tree.*/
654         private String at;
655 
656         /***
657          * Returns the value of the <code>at</code> attribute.
658          *
659          * @return the at attribute
660          */
661         public String getAt()
662         {
663             return at;
664         }
665 
666         /***
667          * Sets the value of the <code>at</code> attribute.
668          *
669          * @param string the attribute value
670          */
671         public void setAt(String string)
672         {
673             at = string;
674         }
675 
676         /***
677          * Returns the configuration object.
678          *
679          * @return the configuration
680          */
681         public Configuration getConfiguration()
682         {
683             return configuration;
684         }
685 
686         /***
687          * Sets the configuration object. Note: Normally this method should be
688          * named <code>setConfiguration()</code>, but the name
689          * <code>addConfiguration()</code> is required by some of the digester
690          * rules.
691          *
692          * @param config the configuration to set
693          */
694         public void addConfiguration(Configuration config)
695         {
696             configuration = config;
697         }
698     }
699 
700     /***
701      * An internally used helper class for constructing the composite
702      * configuration object.
703      */
704     public static class ConfigurationBuilder
705     {
706         /*** Stores the composite configuration.*/
707         private CompositeConfiguration config;
708 
709         /*** Stores a collection with the configs from the additional section.*/
710         private Collection additionalConfigs;
711 
712         /***
713          * Creates a new instance of <code>ConfigurationBuilder</code>.
714          */
715         public ConfigurationBuilder()
716         {
717             config = new CompositeConfiguration();
718             additionalConfigs = new LinkedList();
719         }
720 
721         /***
722          * Adds a new configuration to this object. This method is called by
723          * Digester.
724          *
725          * @param conf the configuration to be added
726          */
727         public void addConfiguration(Configuration conf)
728         {
729             config.addConfiguration(conf);
730         }
731 
732         /***
733          * Adds information about an additional configuration. This method is
734          * called by Digester.
735          *
736          * @param data the data about the additional configuration
737          */
738         public void addAdditionalConfig(AdditionalConfigurationData data)
739         {
740             additionalConfigs.add(data);
741         }
742 
743         /***
744          * Returns the final composite configuration.
745          *
746          * @return the final configuration object
747          */
748         public CompositeConfiguration getConfiguration()
749         {
750             if (!additionalConfigs.isEmpty())
751             {
752                 Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
753                 if (unionConfig != null)
754                 {
755                     addConfiguration(unionConfig);
756                 }
757                 additionalConfigs.clear();
758             }
759 
760             return config;
761         }
762 
763         /***
764          * Creates a configuration object with the union of all properties
765          * defined in the <code>&lt;additional&gt;</code> section. This
766          * implementation returns a <code>HierarchicalConfiguration</code>
767          * object.
768          *
769          * @param configs a collection with
770          * <code>AdditionalConfigurationData</code> objects
771          * @return the union configuration (can be <b>null</b>)
772          */
773         protected Configuration createAdditionalConfiguration(Collection configs)
774         {
775             HierarchicalConfiguration result = new HierarchicalConfiguration();
776 
777             for (Iterator it = configs.iterator(); it.hasNext();)
778             {
779                 AdditionalConfigurationData cdata =
780                 (AdditionalConfigurationData) it.next();
781                 result.addNodes(cdata.getAt(),
782                 createRootNode(cdata).getChildren());
783             }
784 
785             return result.isEmpty() ? null : result;
786         }
787 
788         /***
789          * Creates a configuration root node for the specified configuration.
790          *
791          * @param cdata the configuration data object
792          * @return a root node for this configuration
793          */
794         private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
795         {
796             if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
797             {
798                 // we can directly use this configuration's root node
799                 return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
800             }
801             else
802             {
803                 // transform configuration to a hierarchical root node
804                 HierarchicalConfigurationNodeConverter conv =
805                 new HierarchicalConfigurationNodeConverter();
806                 conv.process(cdata.getConfiguration());
807                 return conv.getRootNode();
808             }
809         }
810     }
811 
812     /***
813      * A specialized <code>HierarchicalConfigurationConverter</code> class
814      * that creates a <code>HierarchicalConfiguration</code> root node from
815      * an arbitrary <code>Configuration</code> object. This class is used to
816      * add additional configuration objects to the hierarchical configuration
817      * managed by the <code>ConfigurationBuilder</code>.
818      */
819     static class HierarchicalConfigurationNodeConverter extends HierarchicalConfigurationConverter
820     {
821         /*** A stack for constructing the hierarchy.*/
822         private Stack nodes;
823 
824         /*** Stores the root node.*/
825         private HierarchicalConfiguration.Node root;
826 
827         /***
828          * Default constructor.
829          */
830         public HierarchicalConfigurationNodeConverter()
831         {
832             nodes = new Stack();
833             root = new HierarchicalConfiguration.Node();
834             nodes.push(root);
835         }
836 
837         /***
838          * Callback for an element start event. Creates a new node and adds
839          * it to the actual parent.
840          *
841          * @param name the name of the new node
842          * @param value the node's value
843          */
844         protected void elementStart(String name, Object value)
845         {
846             HierarchicalConfiguration.Node parent = (HierarchicalConfiguration.Node) nodes.peek();
847             HierarchicalConfiguration.Node child = new HierarchicalConfiguration.Node(name);
848             if (value != null)
849             {
850                 child.setValue(value);
851             }
852             parent.addChild(child);
853             nodes.push(child);
854         }
855 
856         /***
857          * Callback for an element end event. Clears the stack.
858          *
859          * @param name the name of the element
860          */
861         protected void elementEnd(String name)
862         {
863             nodes.pop();
864         }
865 
866         /***
867          * Returns the constructed root node.
868          *
869          * @return the root node
870          */
871         public HierarchicalConfiguration.Node getRootNode()
872         {
873             return root;
874         }
875     }
876 }