001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.configuration2.builder.combined;
018
019import java.net.URL;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.stream.Collectors;
030
031import org.apache.commons.configuration2.CombinedConfiguration;
032import org.apache.commons.configuration2.Configuration;
033import org.apache.commons.configuration2.ConfigurationLookup;
034import org.apache.commons.configuration2.HierarchicalConfiguration;
035import org.apache.commons.configuration2.SystemConfiguration;
036import org.apache.commons.configuration2.XMLConfiguration;
037import org.apache.commons.configuration2.beanutils.BeanDeclaration;
038import org.apache.commons.configuration2.beanutils.BeanHelper;
039import org.apache.commons.configuration2.beanutils.CombinedBeanDeclaration;
040import org.apache.commons.configuration2.beanutils.XMLBeanDeclaration;
041import org.apache.commons.configuration2.builder.BasicBuilderParameters;
042import org.apache.commons.configuration2.builder.BasicConfigurationBuilder;
043import org.apache.commons.configuration2.builder.BuilderParameters;
044import org.apache.commons.configuration2.builder.ConfigurationBuilder;
045import org.apache.commons.configuration2.builder.ConfigurationBuilderEvent;
046import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
047import org.apache.commons.configuration2.builder.FileBasedBuilderProperties;
048import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
049import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
050import org.apache.commons.configuration2.builder.XMLBuilderProperties;
051import org.apache.commons.configuration2.event.EventListener;
052import org.apache.commons.configuration2.ex.ConfigurationException;
053import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
054import org.apache.commons.configuration2.interpol.Lookup;
055import org.apache.commons.configuration2.io.FileSystem;
056import org.apache.commons.configuration2.resolver.CatalogResolver;
057import org.apache.commons.configuration2.tree.DefaultExpressionEngineSymbols;
058import org.apache.commons.configuration2.tree.OverrideCombiner;
059import org.apache.commons.configuration2.tree.UnionCombiner;
060import org.xml.sax.EntityResolver;
061
062/**
063 * <p>
064 * A specialized {@code ConfigurationBuilder} implementation that creates a {@link CombinedConfiguration} from multiple
065 * configuration sources defined by an XML-based <em>configuration definition file</em>.
066 * </p>
067 * <p>
068 * This class provides an easy and flexible means for loading multiple configuration sources and combining the results
069 * into a single configuration object. The sources to be loaded are defined in an XML document that can contain certain
070 * tags representing the different supported configuration classes. If such a tag is found, a corresponding
071 * {@code ConfigurationBuilder} class is instantiated and initialized using the classes of the {@code beanutils} package
072 * (namely {@link org.apache.commons.configuration2.beanutils.XMLBeanDeclaration XMLBeanDeclaration} will be used to
073 * extract the configuration's initialization parameters, which allows for complex initialization scenarios).
074 * </p>
075 * <p>
076 * It is also possible to add custom tags to the configuration definition file. For this purpose an implementation of
077 * {@link CombinedConfigurationBuilderProvider} has to be created which is responsible for the creation of a
078 * {@code ConfigurationBuilder} associated with the custom tag. An instance of this class has to be registered at the
079 * {@link CombinedBuilderParametersImpl} object which is used to initialize this {@code CombinedConfigurationBuilder}.
080 * This provider will then be called when the corresponding custom tag is detected. For many default configuration
081 * classes providers are already registered.
082 * </p>
083 * <p>
084 * The configuration definition file has the following basic structure:
085 * </p>
086 *
087 * <pre>
088 * &lt;configuration systemProperties="properties file name"&gt;
089 *   &lt;header&gt;
090 *     &lt;!-- Optional meta information about the combined configuration --&gt;
091 *   &lt;/header&gt;
092 *   &lt;override&gt;
093 *     &lt;!-- Declarations for override configurations --&gt;
094 *   &lt;/override&gt;
095 *   &lt;additional&gt;
096 *     &lt;!-- Declarations for union configurations --&gt;
097 *   &lt;/additional&gt;
098 * &lt;/configuration&gt;
099 * </pre>
100 *
101 * <p>
102 * The name of the root element (here {@code configuration}) is arbitrary. The optional {@code systemProperties}
103 * attribute identifies the path to a property file containing properties that should be added to the system properties.
104 * If specified on the root element, the system properties are set before the rest of the configuration is processed.
105 * </p>
106 * <p>
107 * There are two sections (both of them are optional) for declaring <em>override</em> and <em>additional</em>
108 * configurations. Configurations in the former section are evaluated in the order of their declaration, and properties
109 * of configurations declared earlier hide those of configurations declared later. Configurations in the latter section
110 * are combined to a union configuration, i.e. all of their properties are added to a large hierarchical configuration.
111 * Configuration declarations that occur as direct children of the root element are treated as override declarations.
112 * </p>
113 * <p>
114 * Each configuration declaration consists of a tag whose name is associated with a
115 * {@code CombinedConfigurationBuilderProvider}. This can be one of the predefined tags like {@code properties}, or
116 * {@code xml}, or a custom tag, for which a configuration builder provider was registered (as described above).
117 * Attributes and sub elements with specific initialization parameters can be added. There are some reserved attributes
118 * with a special meaning that can be used in every configuration declaration:
119 * </p>
120 * <table border="1">
121 * <caption>Standard attributes for configuration declarations</caption>
122 * <tr>
123 * <th>Attribute</th>
124 * <th>Meaning</th>
125 * </tr>
126 * <tr>
127 * <td>{@code config-name}</td>
128 * <td>Allows specifying a name for this configuration. This name can be used to obtain a reference to the configuration
129 * from the resulting combined configuration (see below). It can also be passed to the {@link #getNamedBuilder(String)}
130 * method.</td>
131 * </tr>
132 * <tr>
133 * <td>{@code config-at}</td>
134 * <td>With this attribute an optional prefix can be specified for the properties of the corresponding
135 * configuration.</td>
136 * </tr>
137 * <tr>
138 * <td>{@code config-optional}</td>
139 * <td>Declares a configuration source as optional. This means that errors that occur when creating the configuration
140 * are ignored.</td>
141 * </tr>
142 * <tr>
143 * <td>{@code config-reload}</td>
144 * <td>Many configuration sources support a reloading mechanism. For those sources it is possible to enable reloading by
145 * providing this attribute with a value of <strong>true</strong>.</td>
146 * </tr>
147 * </table>
148 * <p>
149 * The optional <em>header</em> section can contain some meta data about the created configuration itself. For instance,
150 * it is possible to set further properties of the {@code NodeCombiner} objects used for constructing the resulting
151 * configuration.
152 * </p>
153 * <p>
154 * The default configuration object returned by this builder is an instance of the {@link CombinedConfiguration} class.
155 * This allows for convenient access to the configuration objects maintained by the combined configuration (e.g. for
156 * updates of single configuration objects). It has also the advantage that the properties stored in all declared
157 * configuration objects are collected and transformed into a single hierarchical structure, which can be accessed using
158 * different expression engines. The actual {@code CombinedConfiguration} implementation can be overridden by specifying
159 * the class in the <em>config-class</em> attribute of the result element.
160 * </p>
161 * <p>
162 * A custom EntityResolver can be used for all XMLConfigurations by adding
163 * </p>
164 *
165 * <pre>
166 * &lt;entity-resolver config-class="EntityResolver fully qualified class name"&gt;
167 * </pre>
168 *
169 * <p>
170 * A specific CatalogResolver can be specified for all XMLConfiguration sources by adding
171 * </p>
172 *
173 * <pre>
174 * &lt;entity-resolver catalogFiles="comma separated list of catalog files"&gt;
175 * </pre>
176 *
177 * <p>
178 * Additional ConfigurationProviders can be added by configuring them in the <em>header</em> section.
179 * </p>
180 *
181 * <pre>
182 * &lt;providers&gt;
183 *   &lt;provider config-tag="tag name" config-class="provider fully qualified class name"/&gt;
184 * &lt;/providers&gt;
185 * </pre>
186 *
187 * <p>
188 * Additional variable resolvers can be added by configuring them in the <em>header</em> section.
189 * </p>
190 *
191 * <pre>
192 * &lt;lookups&gt;
193 *   &lt;lookup config-prefix="prefix" config-class="StrLookup fully qualified class name"/&gt;
194 * &lt;/lookups&gt;
195 * </pre>
196 *
197 * <p>
198 * All declared override configurations are directly added to the resulting combined configuration. If they are given
199 * names (using the {@code config-name} attribute), they can directly be accessed using the
200 * {@code getConfiguration(String)} method of {@code CombinedConfiguration}. The additional configurations are
201 * altogether added to another combined configuration, which uses a union combiner. Then this union configuration is
202 * added to the resulting combined configuration under the name defined by the {@code ADDITIONAL_NAME} constant. The
203 * {@link #getNamedBuilder(String)} method can be used to access the {@code ConfigurationBuilder} objects for all
204 * configuration sources which have been assigned a name; care has to be taken that these names are unique.
205 * </p>
206 *
207 * @since 1.3
208 */
209public class CombinedConfigurationBuilder extends BasicConfigurationBuilder<CombinedConfiguration> {
210    /**
211     * Constant for the name of the additional configuration. If the configuration definition file contains an
212     * {@code additional} section, a special union configuration is created and added under this name to the resulting
213     * combined configuration.
214     */
215    public static final String ADDITIONAL_NAME = CombinedConfigurationBuilder.class.getName() + "/ADDITIONAL_CONFIG";
216
217    /** Constant for the name of the configuration bean factory. */
218    static final String CONFIG_BEAN_FACTORY_NAME = CombinedConfigurationBuilder.class.getName() + ".CONFIG_BEAN_FACTORY_NAME";
219
220    /** Constant for the reserved name attribute. */
221    static final String ATTR_NAME = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "name"
222        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
223
224    /** Constant for the name of the at attribute. */
225    static final String ATTR_ATNAME = "at";
226
227    /** Constant for the reserved at attribute. */
228    static final String ATTR_AT_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_ATNAME
229        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
230
231    /** Constant for the at attribute without the reserved prefix. */
232    static final String ATTR_AT = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_ATNAME + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
233
234    /** Constant for the name of the optional attribute. */
235    static final String ATTR_OPTIONALNAME = "optional";
236
237    /** Constant for the reserved optional attribute. */
238    static final String ATTR_OPTIONAL_RES = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + ATTR_OPTIONALNAME
239        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
240
241    /** Constant for the optional attribute without the reserved prefix. */
242    static final String ATTR_OPTIONAL = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + ATTR_OPTIONALNAME
243        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
244
245    /** Constant for the forceCreate attribute. */
246    static final String ATTR_FORCECREATE = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "forceCreate"
247        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
248
249    /** Constant for the reload attribute. */
250    static final String ATTR_RELOAD = DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_START + XMLBeanDeclaration.RESERVED_PREFIX + "reload"
251        + DefaultExpressionEngineSymbols.DEFAULT_ATTRIBUTE_END;
252
253    /**
254     * Constant for the tag attribute for providers.
255     */
256    static final String KEY_SYSTEM_PROPS = "[@systemProperties]";
257
258    /** Constant for the name of the header section. */
259    static final String SEC_HEADER = "header";
260
261    /** Constant for an expression that selects the union configurations. */
262    static final String KEY_UNION = "additional";
263
264    /** An array with the names of top level configuration sections. */
265    static final String[] CONFIG_SECTIONS = {"additional", "override", SEC_HEADER};
266
267    /**
268     * Constant for an expression that selects override configurations in the override section.
269     */
270    static final String KEY_OVERRIDE = "override";
271
272    /**
273     * Constant for the key that points to the list nodes definition of the override combiner.
274     */
275    static final String KEY_OVERRIDE_LIST = SEC_HEADER + ".combiner.override.list-nodes.node";
276
277    /**
278     * Constant for the key that points to the list nodes definition of the additional combiner.
279     */
280    static final String KEY_ADDITIONAL_LIST = SEC_HEADER + ".combiner.additional.list-nodes.node";
281
282    /**
283     * Constant for the key for defining providers in the configuration file.
284     */
285    static final String KEY_CONFIGURATION_PROVIDERS = SEC_HEADER + ".providers.provider";
286
287    /**
288     * Constant for the tag attribute for providers.
289     */
290    static final String KEY_PROVIDER_KEY = XMLBeanDeclaration.ATTR_PREFIX + "tag]";
291
292    /**
293     * Constant for the key for defining variable resolvers
294     */
295    static final String KEY_CONFIGURATION_LOOKUPS = SEC_HEADER + ".lookups.lookup";
296
297    /**
298     * Constant for the key for defining entity resolvers
299     */
300    static final String KEY_ENTITY_RESOLVER = SEC_HEADER + ".entity-resolver";
301
302    /**
303     * Constant for the prefix attribute for lookups.
304     */
305    static final String KEY_LOOKUP_KEY = XMLBeanDeclaration.ATTR_PREFIX + "prefix]";
306
307    /**
308     * Constant for the FileSystem.
309     */
310    static final String FILE_SYSTEM = SEC_HEADER + ".fileSystem";
311
312    /**
313     * Constant for the key of the result declaration. This key can point to a bean declaration, which defines properties of
314     * the resulting combined configuration.
315     */
316    static final String KEY_RESULT = SEC_HEADER + ".result";
317
318    /** Constant for the key of the combiner in the result declaration. */
319    static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
320
321    /** Constant for the XML file extension. */
322    static final String EXT_XML = "xml";
323
324    /** Constant for the basic configuration builder class. */
325    private static final String BASIC_BUILDER = "org.apache.commons.configuration2.builder.BasicConfigurationBuilder";
326
327    /** Constant for the file-based configuration builder class. */
328    private static final String FILE_BUILDER = "org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder";
329
330    /** Constant for the reloading file-based configuration builder class. */
331    private static final String RELOADING_BUILDER = "org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder";
332
333    /** Constant for the name of the file-based builder parameters class. */
334    private static final String FILE_PARAMS = "org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl";
335
336    /** Constant for the provider for properties files. */
337    private static final ConfigurationBuilderProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
338        "org.apache.commons.configuration2.XMLPropertiesConfiguration", "org.apache.commons.configuration2.PropertiesConfiguration", EXT_XML,
339        Collections.singletonList(FILE_PARAMS));
340
341    /** Constant for the provider for XML files. */
342    private static final ConfigurationBuilderProvider XML_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
343        "org.apache.commons.configuration2.XMLConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.XMLBuilderParametersImpl"));
344
345    /** Constant for the provider for JNDI sources. */
346    private static final BaseConfigurationBuilderProvider JNDI_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
347        "org.apache.commons.configuration2.JNDIConfiguration",
348        Collections.singletonList("org.apache.commons.configuration2.builder.JndiBuilderParametersImpl"));
349
350    /** Constant for the provider for system properties. */
351    private static final BaseConfigurationBuilderProvider SYSTEM_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
352        "org.apache.commons.configuration2.SystemConfiguration", Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters"));
353
354    /** Constant for the provider for ini files. */
355    private static final BaseConfigurationBuilderProvider INI_PROVIDER = new BaseConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
356        "org.apache.commons.configuration2.INIConfiguration", Collections.singletonList(FILE_PARAMS));
357
358    /** Constant for the provider for environment properties. */
359    private static final BaseConfigurationBuilderProvider ENV_PROVIDER = new BaseConfigurationBuilderProvider(BASIC_BUILDER, null,
360        "org.apache.commons.configuration2.EnvironmentConfiguration",
361        Collections.singletonList("org.apache.commons.configuration2.builder.BasicBuilderParameters"));
362
363    /** Constant for the provider for plist files. */
364    private static final BaseConfigurationBuilderProvider PLIST_PROVIDER = new FileExtensionConfigurationBuilderProvider(FILE_BUILDER, RELOADING_BUILDER,
365        "org.apache.commons.configuration2.plist.XMLPropertyListConfiguration", "org.apache.commons.configuration2.plist.PropertyListConfiguration", EXT_XML,
366        Collections.singletonList(FILE_PARAMS));
367
368    /** Constant for the provider for configuration definition files. */
369    private static final BaseConfigurationBuilderProvider COMBINED_PROVIDER = new CombinedConfigurationBuilderProvider();
370
371    /** Constant for the provider for multiple XML configurations. */
372    private static final MultiFileConfigurationBuilderProvider MULTI_XML_PROVIDER = new MultiFileConfigurationBuilderProvider(
373        "org.apache.commons.configuration2.XMLConfiguration", "org.apache.commons.configuration2.builder.XMLBuilderParametersImpl");
374
375    /** An array with the names of the default tags. */
376    private static final String[] DEFAULT_TAGS = {"properties", "xml", "hierarchicalXml", "plist", "ini", "system", "env", "jndi", "configuration",
377        "multiFile"};
378
379    /** An array with the providers for the default tags. */
380    private static final ConfigurationBuilderProvider[] DEFAULT_PROVIDERS = {PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, PLIST_PROVIDER, INI_PROVIDER,
381        SYSTEM_PROVIDER, ENV_PROVIDER, JNDI_PROVIDER, COMBINED_PROVIDER, MULTI_XML_PROVIDER};
382
383    /** A map with the default configuration builder providers. */
384    private static final Map<String, ConfigurationBuilderProvider> DEFAULT_PROVIDERS_MAP;
385
386    /** The builder for the definition configuration. */
387    private ConfigurationBuilder<? extends HierarchicalConfiguration<?>> definitionBuilder;
388
389    /** Stores temporarily the configuration with the builder definitions. */
390    private HierarchicalConfiguration<?> definitionConfiguration;
391
392    /** The object with data about configuration sources. */
393    private ConfigurationSourceData sourceData;
394
395    /** Stores the current parameters object. */
396    private CombinedBuilderParametersImpl currentParameters;
397
398    /** The current XML parameters object. */
399    private XMLBuilderParametersImpl currentXMLParameters;
400
401    /** The configuration that is currently constructed. */
402    private CombinedConfiguration currentConfiguration;
403
404    /**
405     * A {@code ConfigurationInterpolator} to be used as parent for all child configurations to enable cross-source
406     * interpolation.
407     */
408    private ConfigurationInterpolator parentInterpolator;
409
410    /**
411     * Creates a new instance of {@code CombinedConfigurationBuilder}. No parameters are set.
412     */
413    public CombinedConfigurationBuilder() {
414        super(CombinedConfiguration.class);
415    }
416
417    /**
418     *
419     * Creates a new instance of {@code CombinedConfigurationBuilder} and sets the specified initialization parameters.
420     *
421     * @param params a map with initialization parameters
422     */
423    public CombinedConfigurationBuilder(final Map<String, Object> params) {
424        super(CombinedConfiguration.class, params);
425    }
426
427    /**
428     *
429     * Creates a new instance of {@code CombinedConfigurationBuilder} and sets the specified initialization parameters and
430     * the <em>allowFailOnInit</em> flag.
431     *
432     * @param params a map with initialization parameters
433     * @param allowFailOnInit the <em>allowFailOnInit</em> flag
434     */
435    public CombinedConfigurationBuilder(final Map<String, Object> params, final boolean allowFailOnInit) {
436        super(CombinedConfiguration.class, params, allowFailOnInit);
437    }
438
439    /**
440     * Gets the {@code ConfigurationBuilder} which creates the definition configuration.
441     *
442     * @return the builder for the definition configuration
443     * @throws ConfigurationException if an error occurs
444     */
445    public synchronized ConfigurationBuilder<? extends HierarchicalConfiguration<?>> getDefinitionBuilder() throws ConfigurationException {
446        if (definitionBuilder == null) {
447            definitionBuilder = setupDefinitionBuilder(getParameters());
448            addDefinitionBuilderChangeListener(definitionBuilder);
449        }
450        return definitionBuilder;
451    }
452
453    /**
454     * {@inheritDoc} This method is overridden to adapt the return type.
455     */
456    @Override
457    public CombinedConfigurationBuilder configure(final BuilderParameters... params) {
458        super.configure(params);
459        return this;
460    }
461
462    /**
463     * <p>
464     * Gets the configuration builder with the given name. With this method a builder of a child configuration which was
465     * given a name in the configuration definition file can be accessed directly.
466     * </p>
467     * <p>
468     * <strong>Important note:</strong> This method only returns a meaningful result after the result configuration has been
469     * created by calling {@code getConfiguration()}. If called before, always an exception is thrown.
470     * </p>
471     *
472     * @param name the name of the builder in question
473     * @return the child configuration builder with this name
474     * @throws ConfigurationException if information about named builders is not yet available or no builder with this name
475     *         exists
476     */
477    public synchronized ConfigurationBuilder<? extends Configuration> getNamedBuilder(final String name) throws ConfigurationException {
478        if (sourceData == null) {
479            throw new ConfigurationException("Information about child builders" + " has not been setup yet! Call getConfiguration() first.");
480        }
481        final ConfigurationBuilder<? extends Configuration> builder = sourceData.getNamedBuilder(name);
482        if (builder == null) {
483            throw new ConfigurationException("Builder cannot be resolved: " + name);
484        }
485        return builder;
486    }
487
488    /**
489     * <p>
490     * Returns a set with the names of all child configuration builders. A tag defining a configuration source in the
491     * configuration definition file can have the {@code config-name} attribute. If this attribute is present, the
492     * corresponding builder is assigned this name and can be directly accessed through the {@link #getNamedBuilder(String)}
493     * method. This method returns a collection with all available builder names.
494     * </p>
495     * <p>
496     * <strong>Important note:</strong> This method only returns a meaningful result after the result configuration has been
497     * created by calling {@code getConfiguration()}. If called before, always an empty set is returned.
498     * </p>
499     *
500     * @return a set with the names of all builders
501     */
502    public synchronized Set<String> builderNames() {
503        if (sourceData == null) {
504            return Collections.emptySet();
505        }
506        return Collections.unmodifiableSet(sourceData.builderNames());
507    }
508
509    /**
510     * {@inheritDoc} This implementation resets some specific internal state of this builder.
511     */
512    @Override
513    public synchronized void resetParameters() {
514        super.resetParameters();
515        definitionBuilder = null;
516        definitionConfiguration = null;
517        currentParameters = null;
518        currentXMLParameters = null;
519
520        if (sourceData != null) {
521            sourceData.cleanUp();
522            sourceData = null;
523        }
524    }
525
526    /**
527     * Obtains the {@code ConfigurationBuilder} object which provides access to the configuration containing the definition
528     * of the combined configuration to create. If a definition builder is defined in the parameters, it is used. Otherwise,
529     * we check whether the combined builder parameters object contains a parameters object for the definition builder. If
530     * this is the case, a builder for an {@code XMLConfiguration} is created and configured with this object. As a last
531     * resort, it is looked for a {@link FileBasedBuilderParametersImpl} object in the properties. If found, also a XML
532     * configuration builder is created which loads this file. Note: This method is called from a synchronized block.
533     *
534     * @param params the current parameters for this builder
535     * @return the builder for the definition configuration
536     * @throws ConfigurationException if an error occurs
537     */
538    protected ConfigurationBuilder<? extends HierarchicalConfiguration<?>> setupDefinitionBuilder(final Map<String, Object> params)
539        throws ConfigurationException {
540        final CombinedBuilderParametersImpl cbParams = CombinedBuilderParametersImpl.fromParameters(params);
541        if (cbParams != null) {
542            final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder = cbParams.getDefinitionBuilder();
543            if (defBuilder != null) {
544                return defBuilder;
545            }
546
547            if (cbParams.getDefinitionBuilderParameters() != null) {
548                return createXMLDefinitionBuilder(cbParams.getDefinitionBuilderParameters());
549            }
550        }
551
552        final BuilderParameters fileParams = FileBasedBuilderParametersImpl.fromParameters(params);
553        if (fileParams != null) {
554            return createXMLDefinitionBuilder(fileParams);
555        }
556
557        throw new ConfigurationException("No builder for configuration definition specified!");
558    }
559
560    /**
561     * Creates a default builder for the definition configuration and initializes it with a parameters object. This method
562     * is called if no definition builder is defined in this builder's parameters. This implementation creates a default
563     * file-based builder which produces an {@code XMLConfiguration}; it expects a corresponding file specification. Note:
564     * This method is called in a synchronized block.
565     *
566     * @param builderParams the parameters object for the builder
567     * @return the standard builder for the definition configuration
568     */
569    protected ConfigurationBuilder<? extends HierarchicalConfiguration<?>> createXMLDefinitionBuilder(final BuilderParameters builderParams) {
570        return new FileBasedConfigurationBuilder<>(XMLConfiguration.class).configure(builderParams);
571    }
572
573    /**
574     * Gets the configuration containing the definition of the combined configuration to be created. This method only
575     * returns a defined result during construction of the result configuration. The definition configuration is obtained
576     * from the definition builder at first access and then stored temporarily to ensure that during result construction
577     * always the same configuration instance is used. (Otherwise, it would be possible that the definition builder returns
578     * a different instance when queried multiple times.)
579     *
580     * @return the definition configuration
581     * @throws ConfigurationException if an error occurs
582     */
583    protected HierarchicalConfiguration<?> getDefinitionConfiguration() throws ConfigurationException {
584        if (definitionConfiguration == null) {
585            definitionConfiguration = getDefinitionBuilder().getConfiguration();
586        }
587        return definitionConfiguration;
588    }
589
590    /**
591     * Gets a collection with the builders for all child configuration sources. This method can be used by derived
592     * classes providing additional functionality on top of the declared configuration sources. It only returns a defined
593     * value during construction of the result configuration instance.
594     *
595     * @return a collection with the builders for child configuration sources
596     */
597    protected synchronized Collection<ConfigurationBuilder<? extends Configuration>> getChildBuilders() {
598        return sourceData.getChildBuilders();
599    }
600
601    /**
602     * {@inheritDoc} This implementation evaluates the {@code result} property of the definition configuration. It creates a
603     * combined bean declaration with both the properties specified in the definition file and the properties defined as
604     * initialization parameters.
605     */
606    @Override
607    protected BeanDeclaration createResultDeclaration(final Map<String, Object> params) throws ConfigurationException {
608        final BeanDeclaration paramsDecl = super.createResultDeclaration(params);
609        final XMLBeanDeclaration resultDecl = new XMLBeanDeclaration(getDefinitionConfiguration(), KEY_RESULT, true, CombinedConfiguration.class.getName());
610        return new CombinedBeanDeclaration(resultDecl, paramsDecl);
611    }
612
613    /**
614     * {@inheritDoc} This implementation processes the definition configuration in order to
615     * <ul>
616     * <li>initialize the resulting {@code CombinedConfiguration}</li>
617     * <li>determine the builders for all configuration sources</li>
618     * <li>populate the resulting {@code CombinedConfiguration}</li>
619     * </ul>
620     */
621    @Override
622    protected void initResultInstance(final CombinedConfiguration result) throws ConfigurationException {
623        super.initResultInstance(result);
624
625        currentConfiguration = result;
626        final HierarchicalConfiguration<?> config = getDefinitionConfiguration();
627        if (config.getMaxIndex(KEY_COMBINER) < 0) {
628            // No combiner defined => set default
629            result.setNodeCombiner(new OverrideCombiner());
630        }
631
632        setUpCurrentParameters();
633        initNodeCombinerListNodes(result, config, KEY_OVERRIDE_LIST);
634        registerConfiguredProviders(config);
635        setUpCurrentXMLParameters();
636        currentXMLParameters.setFileSystem(initFileSystem(config));
637        initSystemProperties(config, getBasePath());
638        registerConfiguredLookups(config, result);
639        configureEntityResolver(config, currentXMLParameters);
640        setUpParentInterpolator(currentConfiguration, config);
641
642        final ConfigurationSourceData data = getSourceData();
643        final boolean createBuilders = data.getChildBuilders().isEmpty();
644        final List<ConfigurationBuilder<? extends Configuration>> overrideBuilders = data.createAndAddConfigurations(result, data.getOverrideSources(),
645            data.overrideBuilders);
646        if (createBuilders) {
647            data.overrideBuilders.addAll(overrideBuilders);
648        }
649        if (!data.getUnionSources().isEmpty()) {
650            final CombinedConfiguration addConfig = createAdditionalsConfiguration(result);
651            result.addConfiguration(addConfig, ADDITIONAL_NAME);
652            initNodeCombinerListNodes(addConfig, config, KEY_ADDITIONAL_LIST);
653            final List<ConfigurationBuilder<? extends Configuration>> unionBuilders = data.createAndAddConfigurations(addConfig, data.unionDeclarations,
654                data.unionBuilders);
655            if (createBuilders) {
656                data.unionBuilders.addAll(unionBuilders);
657            }
658        }
659
660        result.isEmpty(); // this sets up the node structure
661        currentConfiguration = null;
662    }
663
664    /**
665     * Creates the {@code CombinedConfiguration} for the configuration sources in the {@code &lt;additional&gt;} section.
666     * This method is called when the builder constructs the final configuration. It creates a new
667     * {@code CombinedConfiguration} and initializes some properties from the result configuration.
668     *
669     * @param resultConfig the result configuration (this is the configuration that will be returned by the builder)
670     * @return the {@code CombinedConfiguration} for the additional configuration sources
671     * @since 1.7
672     */
673    protected CombinedConfiguration createAdditionalsConfiguration(final CombinedConfiguration resultConfig) {
674        final CombinedConfiguration addConfig = new CombinedConfiguration(new UnionCombiner());
675        addConfig.setListDelimiterHandler(resultConfig.getListDelimiterHandler());
676        return addConfig;
677    }
678
679    /**
680     * Processes custom {@link Lookup} objects that might be declared in the definition configuration. Each {@code Lookup}
681     * object is registered at the definition configuration and at the result configuration. It is also added to all child
682     * configurations added to the resulting combined configuration.
683     *
684     * @param defConfig the definition configuration
685     * @param resultConfig the resulting configuration
686     * @throws ConfigurationException if an error occurs
687     */
688    protected void registerConfiguredLookups(final HierarchicalConfiguration<?> defConfig, final Configuration resultConfig) throws ConfigurationException {
689        final Map<String, Lookup> lookups = defConfig.configurationsAt(KEY_CONFIGURATION_LOOKUPS).stream().collect(
690                Collectors.toMap(config -> config.getString(KEY_LOOKUP_KEY), config -> (Lookup) fetchBeanHelper().createBean(new XMLBeanDeclaration(config))));
691
692        if (!lookups.isEmpty()) {
693            final ConfigurationInterpolator defCI = defConfig.getInterpolator();
694            if (defCI != null) {
695                defCI.registerLookups(lookups);
696            }
697            resultConfig.getInterpolator().registerLookups(lookups);
698        }
699    }
700
701    /**
702     * Creates and initializes a default {@code FileSystem} if the definition configuration contains a corresponding
703     * declaration. The file system returned by this method is used as default for all file-based child configuration
704     * sources.
705     *
706     * @param config the definition configuration
707     * @return the default {@code FileSystem} (may be <b>null</b>)
708     * @throws ConfigurationException if an error occurs
709     */
710    protected FileSystem initFileSystem(final HierarchicalConfiguration<?> config) throws ConfigurationException {
711        if (config.getMaxIndex(FILE_SYSTEM) == 0) {
712            final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, FILE_SYSTEM);
713            return (FileSystem) fetchBeanHelper().createBean(decl);
714        }
715        return null;
716    }
717
718    /**
719     * Handles a file with system properties that may be defined in the definition configuration. If such property file is
720     * configured, all of its properties are added to the system properties.
721     *
722     * @param config the definition configuration
723     * @param basePath the base path defined for this builder (may be <b>null</b>)
724     * @throws ConfigurationException if an error occurs.
725     */
726    protected void initSystemProperties(final HierarchicalConfiguration<?> config, final String basePath) throws ConfigurationException {
727        final String fileName = config.getString(KEY_SYSTEM_PROPS);
728        if (fileName != null) {
729            try {
730                SystemConfiguration.setSystemProperties(basePath, fileName);
731            } catch (final Exception ex) {
732                throw new ConfigurationException("Error setting system properties from " + fileName, ex);
733            }
734        }
735    }
736
737    /**
738     * Creates and initializes a default {@code EntityResolver} if the definition configuration contains a corresponding
739     * declaration.
740     *
741     * @param config the definition configuration
742     * @param xmlParams the (already partly initialized) object with XML parameters; here the new resolver is to be stored
743     * @throws ConfigurationException if an error occurs
744     */
745    protected void configureEntityResolver(final HierarchicalConfiguration<?> config, final XMLBuilderParametersImpl xmlParams) throws ConfigurationException {
746        if (config.getMaxIndex(KEY_ENTITY_RESOLVER) == 0) {
747            final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY_ENTITY_RESOLVER, true);
748            final EntityResolver resolver = (EntityResolver) fetchBeanHelper().createBean(decl, CatalogResolver.class);
749            final FileSystem fileSystem = xmlParams.getFileHandler().getFileSystem();
750            if (fileSystem != null) {
751                BeanHelper.setProperty(resolver, "fileSystem", fileSystem);
752            }
753            final String basePath = xmlParams.getFileHandler().getBasePath();
754            if (basePath != null) {
755                BeanHelper.setProperty(resolver, "baseDir", basePath);
756            }
757            final ConfigurationInterpolator ci = new ConfigurationInterpolator();
758            ci.registerLookups(fetchPrefixLookups());
759            BeanHelper.setProperty(resolver, "interpolator", ci);
760
761            xmlParams.setEntityResolver(resolver);
762        }
763    }
764
765    /**
766     * Returns the {@code ConfigurationBuilderProvider} for the given tag. This method is called during creation of the
767     * result configuration. (It is not allowed to call it at another point of time; result is then unpredictable!) It
768     * supports all default providers and custom providers added through the parameters object as well.
769     *
770     * @param tagName the name of the tag
771     * @return the provider that was registered for this tag or <b>null</b> if there is none
772     */
773    protected ConfigurationBuilderProvider providerForTag(final String tagName) {
774        return currentParameters.providerForTag(tagName);
775    }
776
777    /**
778     * Initializes a parameters object for a child builder. This combined configuration builder has a bunch of properties
779     * which may be inherited by child configurations, e.g. the base path, the file system, etc. While processing the
780     * builders for child configurations, this method is called for each parameters object for a child builder. It
781     * initializes some properties of the passed in parameters objects which are derived from this parent builder.
782     *
783     * @param params the parameters object to be initialized
784     */
785    protected void initChildBuilderParameters(final BuilderParameters params) {
786        initDefaultChildParameters(params);
787
788        if (params instanceof BasicBuilderParameters) {
789            initChildBasicParameters((BasicBuilderParameters) params);
790        }
791        if (params instanceof XMLBuilderProperties<?>) {
792            initChildXMLParameters((XMLBuilderProperties<?>) params);
793        }
794        if (params instanceof FileBasedBuilderProperties<?>) {
795            initChildFileBasedParameters((FileBasedBuilderProperties<?>) params);
796        }
797        if (params instanceof CombinedBuilderParametersImpl) {
798            initChildCombinedParameters((CombinedBuilderParametersImpl) params);
799        }
800    }
801
802    /**
803     * Initializes the event listeners of the specified builder from this object. This method is used to inherit all
804     * listeners from a parent builder.
805     *
806     * @param dest the destination builder object which is to be initialized
807     */
808    void initChildEventListeners(final BasicConfigurationBuilder<? extends Configuration> dest) {
809        copyEventListeners(dest);
810    }
811
812    /**
813     * Gets the configuration object that is currently constructed. This method can be called during construction of the
814     * result configuration. It is intended for internal usage, e.g. some specialized builder providers need access to this
815     * configuration to perform advanced initialization.
816     *
817     * @return the configuration that us currently under construction
818     */
819    CombinedConfiguration getConfigurationUnderConstruction() {
820        return currentConfiguration;
821    }
822
823    /**
824     * Initializes a bean using the current {@code BeanHelper}. This is needed by builder providers when the configuration
825     * objects for sub builders are constructed.
826     *
827     * @param bean the bean to be initialized
828     * @param decl the {@code BeanDeclaration}
829     */
830    void initBean(final Object bean, final BeanDeclaration decl) {
831        fetchBeanHelper().initBean(bean, decl);
832    }
833
834    /**
835     * Initializes the current parameters object. This object has either been passed at builder configuration time or it is
836     * newly created. In any case, it is manipulated during result creation.
837     */
838    private void setUpCurrentParameters() {
839        currentParameters = CombinedBuilderParametersImpl.fromParameters(getParameters(), true);
840        currentParameters.registerMissingProviders(DEFAULT_PROVIDERS_MAP);
841    }
842
843    /**
844     * Sets up an XML parameters object which is used to store properties related to XML and file-based configurations
845     * during creation of the result configuration. The properties stored in this object can be inherited to child
846     * configurations.
847     *
848     * @throws ConfigurationException if an error occurs
849     */
850    private void setUpCurrentXMLParameters() throws ConfigurationException {
851        currentXMLParameters = new XMLBuilderParametersImpl();
852        initDefaultBasePath();
853    }
854
855    /**
856     * Sets up a parent {@code ConfigurationInterpolator} object. This object has a default {@link Lookup} querying the
857     * resulting combined configuration. Thus interpolation works globally across all configuration sources.
858     *
859     * @param resultConfig the result configuration
860     * @param defConfig the definition configuration
861     */
862    private void setUpParentInterpolator(final Configuration resultConfig, final Configuration defConfig) {
863        parentInterpolator = new ConfigurationInterpolator();
864        parentInterpolator.addDefaultLookup(new ConfigurationLookup(resultConfig));
865        final ConfigurationInterpolator defInterpolator = defConfig.getInterpolator();
866        if (defInterpolator != null) {
867            defInterpolator.setParentInterpolator(parentInterpolator);
868        }
869    }
870
871    /**
872     * Initializes the default base path for all file-based child configuration sources. The base path can be explicitly
873     * defined in the parameters of this builder. Otherwise, if the definition builder is a file-based builder, it is
874     * obtained from there.
875     *
876     * @throws ConfigurationException if an error occurs
877     */
878    private void initDefaultBasePath() throws ConfigurationException {
879        assert currentParameters != null : "Current parameters undefined!";
880        if (currentParameters.getBasePath() != null) {
881            currentXMLParameters.setBasePath(currentParameters.getBasePath());
882        } else {
883            final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder = getDefinitionBuilder();
884            if (defBuilder instanceof FileBasedConfigurationBuilder) {
885                @SuppressWarnings("rawtypes")
886                final FileBasedConfigurationBuilder fileBuilder = (FileBasedConfigurationBuilder) defBuilder;
887                final URL url = fileBuilder.getFileHandler().getURL();
888                currentXMLParameters.setBasePath(url != null ? url.toExternalForm() : fileBuilder.getFileHandler().getBasePath());
889            }
890        }
891    }
892
893    /**
894     * Executes the {@link org.apache.commons.configuration2.builder.DefaultParametersManager DefaultParametersManager}
895     * stored in the current parameters on the passed in parameters object. If default handlers have been registered for
896     * this type of parameters, an initialization is now performed. This method is called before the parameters object is
897     * initialized from the configuration definition file. So default values can be overridden later with concrete property
898     * definitions.
899     *
900     * @param params the parameters to be initialized
901     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when copying properties
902     */
903    private void initDefaultChildParameters(final BuilderParameters params) {
904        currentParameters.getChildDefaultParametersManager().initializeParameters(params);
905    }
906
907    /**
908     * Initializes basic builder parameters for a child configuration with default settings set for this builder. This
909     * implementation ensures that all {@code Lookup} objects are propagated to child configurations and interpolation is
910     * setup correctly.
911     *
912     * @param params the parameters object
913     */
914    private void initChildBasicParameters(final BasicBuilderParameters params) {
915        params.setPrefixLookups(fetchPrefixLookups());
916        params.setParentInterpolator(parentInterpolator);
917        if (currentParameters.isInheritSettings()) {
918            params.inheritFrom(getParameters());
919        }
920    }
921
922    /**
923     * Initializes a parameters object for a file-based configuration with properties already set for this parent builder.
924     * This method handles properties like a default file system or a base path.
925     *
926     * @param params the parameters object
927     */
928    private void initChildFileBasedParameters(final FileBasedBuilderProperties<?> params) {
929        params.setBasePath(getBasePath());
930        params.setFileSystem(currentXMLParameters.getFileHandler().getFileSystem());
931    }
932
933    /**
934     * Initializes a parameters object for an XML configuration with properties already set for this parent builder.
935     *
936     * @param params the parameters object
937     */
938    private void initChildXMLParameters(final XMLBuilderProperties<?> params) {
939        params.setEntityResolver(currentXMLParameters.getEntityResolver());
940    }
941
942    /**
943     * Initializes a parameters object for a combined configuration builder with properties already set for this parent
944     * builder. This implementation deals only with a subset of properties. Other properties are already handled by the
945     * specialized builder provider.
946     *
947     * @param params the parameters object
948     */
949    private void initChildCombinedParameters(final CombinedBuilderParametersImpl params) {
950        params.registerMissingProviders(currentParameters);
951        params.setBasePath(getBasePath());
952    }
953
954    /**
955     * Obtains the data object for the configuration sources and the corresponding builders. This object is created on first
956     * access and reset when the definition builder sends a change event. This method is called in a synchronized block.
957     *
958     * @return the object with information about configuration sources
959     * @throws ConfigurationException if an error occurs
960     */
961    private ConfigurationSourceData getSourceData() throws ConfigurationException {
962        if (sourceData == null) {
963            if (currentParameters == null) {
964                setUpCurrentParameters();
965                setUpCurrentXMLParameters();
966            }
967            sourceData = createSourceData();
968        }
969        return sourceData;
970    }
971
972    /**
973     * Creates the data object for configuration sources and the corresponding builders.
974     *
975     * @return the newly created data object
976     * @throws ConfigurationException if an error occurs
977     */
978    private ConfigurationSourceData createSourceData() throws ConfigurationException {
979        final ConfigurationSourceData result = new ConfigurationSourceData();
980        result.initFromDefinitionConfiguration(getDefinitionConfiguration());
981        return result;
982    }
983
984    /**
985     * Gets the current base path of this configuration builder. This is used for instance by all file-based child
986     * configurations.
987     *
988     * @return the base path
989     */
990    private String getBasePath() {
991        return currentXMLParameters.getFileHandler().getBasePath();
992    }
993
994    /**
995     * Registers providers defined in the configuration.
996     *
997     * @param defConfig the definition configuration
998     */
999    private void registerConfiguredProviders(final HierarchicalConfiguration<?> defConfig) {
1000        defConfig.configurationsAt(KEY_CONFIGURATION_PROVIDERS).forEach(config -> {
1001            final XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
1002            final String key = config.getString(KEY_PROVIDER_KEY);
1003            currentParameters.registerProvider(key, (ConfigurationBuilderProvider) fetchBeanHelper().createBean(decl));
1004        });
1005    }
1006
1007    /**
1008     * Adds a listener at the given definition builder which resets this builder when a reset of the definition builder
1009     * happens. This way it is ensured that this builder produces a new combined configuration when its definition
1010     * configuration changes.
1011     *
1012     * @param defBuilder the definition builder
1013     */
1014    private void addDefinitionBuilderChangeListener(final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> defBuilder) {
1015        defBuilder.addEventListener(ConfigurationBuilderEvent.RESET, event -> {
1016            synchronized (this) {
1017                reset();
1018                definitionBuilder = defBuilder;
1019            }
1020        });
1021    }
1022
1023    /**
1024     * Returns a map with the current prefix lookup objects. This map is obtained from the {@code ConfigurationInterpolator}
1025     * of the configuration under construction.
1026     *
1027     * @return the map with current prefix lookups (may be <b>null</b>)
1028     */
1029    private Map<String, ? extends Lookup> fetchPrefixLookups() {
1030        final CombinedConfiguration cc = getConfigurationUnderConstruction();
1031        return cc != null ? cc.getInterpolator().getLookups() : null;
1032    }
1033
1034    /**
1035     * Creates {@code ConfigurationDeclaration} objects for the specified configurations.
1036     *
1037     * @param configs the list with configurations
1038     * @return a collection with corresponding declarations
1039     */
1040    private Collection<ConfigurationDeclaration> createDeclarations(final Collection<? extends HierarchicalConfiguration<?>> configs) {
1041        return configs.stream().map(c -> new ConfigurationDeclaration(this, c)).collect(Collectors.toList());
1042    }
1043
1044    /**
1045     * Initializes the list nodes of the node combiner for the given combined configuration. This information can be set in
1046     * the header section of the configuration definition file for both the override and the union combiners.
1047     *
1048     * @param cc the combined configuration to initialize
1049     * @param defConfig the definition configuration
1050     * @param key the key for the list nodes
1051     */
1052    private static void initNodeCombinerListNodes(final CombinedConfiguration cc, final HierarchicalConfiguration<?> defConfig, final String key) {
1053        defConfig.getList(key).forEach(listNode -> cc.getNodeCombiner().addListNode((String) listNode));
1054    }
1055
1056    /**
1057     * Creates the map with the default configuration builder providers.
1058     *
1059     * @return the map with default providers
1060     */
1061    private static Map<String, ConfigurationBuilderProvider> createDefaultProviders() {
1062        final Map<String, ConfigurationBuilderProvider> providers = new HashMap<>();
1063        for (int i = 0; i < DEFAULT_TAGS.length; i++) {
1064            providers.put(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
1065        }
1066        return providers;
1067    }
1068
1069    static {
1070        DEFAULT_PROVIDERS_MAP = createDefaultProviders();
1071    }
1072
1073    /**
1074     * A data class for storing information about all configuration sources defined for a combined builder.
1075     */
1076    private final class ConfigurationSourceData {
1077        /** A list with data for all builders for override configurations. */
1078        private final List<ConfigurationDeclaration> overrideDeclarations;
1079
1080        /** A list with data for all builders for union configurations. */
1081        private final List<ConfigurationDeclaration> unionDeclarations;
1082
1083        /** A list with the builders for override configurations. */
1084        private final List<ConfigurationBuilder<? extends Configuration>> overrideBuilders;
1085
1086        /** A list with the builders for union configurations. */
1087        private final List<ConfigurationBuilder<? extends Configuration>> unionBuilders;
1088
1089        /** A map for direct access to a builder by its name. */
1090        private final Map<String, ConfigurationBuilder<? extends Configuration>> namedBuilders;
1091
1092        /** A collection with all child builders. */
1093        private final Collection<ConfigurationBuilder<? extends Configuration>> allBuilders;
1094
1095        /** A listener for reacting on changes of sub builders. */
1096        private final EventListener<ConfigurationBuilderEvent> changeListener;
1097
1098        /**
1099         * Creates a new instance of {@code ConfigurationSourceData}.
1100         */
1101        public ConfigurationSourceData() {
1102            overrideDeclarations = new ArrayList<>();
1103            unionDeclarations = new ArrayList<>();
1104            overrideBuilders = new ArrayList<>();
1105            unionBuilders = new ArrayList<>();
1106            namedBuilders = new HashMap<>();
1107            allBuilders = new LinkedList<>();
1108            changeListener = createBuilderChangeListener();
1109        }
1110
1111        /**
1112         * Initializes this object from the specified definition configuration.
1113         *
1114         * @param config the definition configuration
1115         * @throws ConfigurationException if an error occurs
1116         */
1117        public void initFromDefinitionConfiguration(final HierarchicalConfiguration<?> config) throws ConfigurationException {
1118            overrideDeclarations.addAll(createDeclarations(fetchTopLevelOverrideConfigs(config)));
1119            overrideDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_OVERRIDE)));
1120            unionDeclarations.addAll(createDeclarations(config.childConfigurationsAt(KEY_UNION)));
1121        }
1122
1123        /**
1124         * Processes the declaration of configuration builder providers, creates the corresponding builder if necessary, obtains
1125         * configurations, and adds them to the specified result configuration.
1126         *
1127         * @param ccResult the result configuration
1128         * @param srcDecl the collection with the declarations of configuration sources to process
1129         * @return a list with configuration builders
1130         * @throws ConfigurationException if an error occurs
1131         */
1132        public List<ConfigurationBuilder<? extends Configuration>> createAndAddConfigurations(final CombinedConfiguration ccResult,
1133            final List<ConfigurationDeclaration> srcDecl, final List<ConfigurationBuilder<? extends Configuration>> builders) throws ConfigurationException {
1134            final boolean createBuilders = builders.isEmpty();
1135            final List<ConfigurationBuilder<? extends Configuration>> newBuilders;
1136            if (createBuilders) {
1137                newBuilders = new ArrayList<>(srcDecl.size());
1138            } else {
1139                newBuilders = builders;
1140            }
1141
1142            for (int i = 0; i < srcDecl.size(); i++) {
1143                final ConfigurationBuilder<? extends Configuration> b;
1144                if (createBuilders) {
1145                    b = createConfigurationBuilder(srcDecl.get(i));
1146                    newBuilders.add(b);
1147                } else {
1148                    b = builders.get(i);
1149                }
1150                addChildConfiguration(ccResult, srcDecl.get(i), b);
1151            }
1152
1153            return newBuilders;
1154        }
1155
1156        /**
1157         * Frees resources used by this object and performs clean up. This method is called when the owning builder is reset.
1158         */
1159        public void cleanUp() {
1160            getChildBuilders().forEach(b -> b.removeEventListener(ConfigurationBuilderEvent.RESET, changeListener));
1161            namedBuilders.clear();
1162        }
1163
1164        /**
1165         * Gets a collection containing the builders for all child configuration sources.
1166         *
1167         * @return the child configuration builders
1168         */
1169        public Collection<ConfigurationBuilder<? extends Configuration>> getChildBuilders() {
1170            return allBuilders;
1171        }
1172
1173        /**
1174         * Gets a collection with all configuration source declarations defined in the override section.
1175         *
1176         * @return the override configuration builders
1177         */
1178        public List<ConfigurationDeclaration> getOverrideSources() {
1179            return overrideDeclarations;
1180        }
1181
1182        /**
1183         * Gets a collection with all configuration source declarations defined in the union section.
1184         *
1185         * @return the union configuration builders
1186         */
1187        public List<ConfigurationDeclaration> getUnionSources() {
1188            return unionDeclarations;
1189        }
1190
1191        /**
1192         * Gets the {@code ConfigurationBuilder} with the given name. If no such builder is defined in the definition
1193         * configuration, result is <b>null</b>.
1194         *
1195         * @param name the name of the builder in question
1196         * @return the builder with this name or <b>null</b>
1197         */
1198        public ConfigurationBuilder<? extends Configuration> getNamedBuilder(final String name) {
1199            return namedBuilders.get(name);
1200        }
1201
1202        /**
1203         * Returns a set with the names of all known named builders.
1204         *
1205         * @return the names of the available sub builders
1206         */
1207        public Set<String> builderNames() {
1208            return namedBuilders.keySet();
1209        }
1210
1211        /**
1212         * Creates a configuration builder based on a source declaration in the definition configuration.
1213         *
1214         * @param decl the current {@code ConfigurationDeclaration}
1215         * @return the newly created builder
1216         * @throws ConfigurationException if an error occurs
1217         */
1218        private ConfigurationBuilder<? extends Configuration> createConfigurationBuilder(final ConfigurationDeclaration decl) throws ConfigurationException {
1219            final ConfigurationBuilderProvider provider = providerForTag(decl.getConfiguration().getRootElementName());
1220            if (provider == null) {
1221                throw new ConfigurationException("Unsupported configuration source: " + decl.getConfiguration().getRootElementName());
1222            }
1223
1224            final ConfigurationBuilder<? extends Configuration> builder = provider.getConfigurationBuilder(decl);
1225            if (decl.getName() != null) {
1226                namedBuilders.put(decl.getName(), builder);
1227            }
1228            allBuilders.add(builder);
1229            builder.addEventListener(ConfigurationBuilderEvent.RESET, changeListener);
1230            return builder;
1231        }
1232
1233        /**
1234         * Creates a new configuration using the specified builder and adds it to the resulting combined configuration.
1235         *
1236         * @param ccResult the resulting combined configuration
1237         * @param decl the current {@code ConfigurationDeclaration}
1238         * @param builder the configuration builder
1239         * @throws ConfigurationException if an error occurs
1240         */
1241        private void addChildConfiguration(final CombinedConfiguration ccResult, final ConfigurationDeclaration decl,
1242            final ConfigurationBuilder<? extends Configuration> builder) throws ConfigurationException {
1243            try {
1244                ccResult.addConfiguration(builder.getConfiguration(), decl.getName(), decl.getAt());
1245            } catch (final ConfigurationException cex) {
1246                // ignore exceptions for optional configurations
1247                if (!decl.isOptional()) {
1248                    throw cex;
1249                }
1250            }
1251        }
1252
1253        /**
1254         * Creates a listener for builder change events. This listener is registered at all builders for child configurations.
1255         */
1256        private EventListener<ConfigurationBuilderEvent> createBuilderChangeListener() {
1257            return event -> resetResult();
1258        }
1259
1260        /**
1261         * Finds the override configurations that are defined as top level elements in the configuration definition file. This
1262         * method fetches the child elements of the root node and removes the nodes that represent other configuration sections.
1263         * The remaining nodes are treated as definitions for override configurations.
1264         *
1265         * @param config the definition configuration
1266         * @return a list with sub configurations for the top level override configurations
1267         */
1268        private List<? extends HierarchicalConfiguration<?>> fetchTopLevelOverrideConfigs(final HierarchicalConfiguration<?> config) {
1269
1270            final List<? extends HierarchicalConfiguration<?>> configs = config.childConfigurationsAt(null);
1271            for (final Iterator<? extends HierarchicalConfiguration<?>> it = configs.iterator(); it.hasNext();) {
1272                final String nodeName = it.next().getRootElementName();
1273                for (final String element : CONFIG_SECTIONS) {
1274                    if (element.equals(nodeName)) {
1275                        it.remove();
1276                        break;
1277                    }
1278                }
1279            }
1280            return configs;
1281        }
1282    }
1283}