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.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.LinkedList;
024import java.util.Map;
025
026import org.apache.commons.configuration2.ConfigurationUtils;
027import org.apache.commons.configuration2.HierarchicalConfiguration;
028import org.apache.commons.configuration2.builder.BasicBuilderParameters;
029import org.apache.commons.configuration2.builder.BuilderParameters;
030import org.apache.commons.configuration2.builder.ConfigurationBuilder;
031import org.apache.commons.configuration2.builder.DefaultParametersHandler;
032import org.apache.commons.configuration2.builder.DefaultParametersManager;
033
034/**
035 * <p>
036 * A specialized parameters object for a {@link CombinedConfigurationBuilder}.
037 * </p>
038 * <p>
039 * This class defines methods for setting properties for customizing a builder for combined configurations. Note that
040 * some of these properties can also be set in the configuration definition file. If this is the case, the settings in
041 * the definition file override the content of this object.
042 * </p>
043 * <p>
044 * This class is not thread-safe. It is intended that an instance is constructed and initialized by a single thread
045 * during configuration of a {@code ConfigurationBuilder}.
046 * </p>
047 *
048 * @since 2.0
049 */
050public class CombinedBuilderParametersImpl extends BasicBuilderParameters implements CombinedBuilderProperties<CombinedBuilderParametersImpl> {
051    /** Constant for the key in the parameters map used by this class. */
052    private static final String PARAM_KEY = RESERVED_PARAMETER_PREFIX + CombinedBuilderParametersImpl.class.getName();
053
054    /** The definition configuration builder. */
055    private ConfigurationBuilder<? extends HierarchicalConfiguration<?>> definitionBuilder;
056
057    /** A parameters object for the definition configuration builder. */
058    private BuilderParameters definitionBuilderParameters;
059
060    /** A map with registered configuration builder providers. */
061    private final Map<String, ConfigurationBuilderProvider> providers;
062
063    /** A list with default parameters for child configuration sources. */
064    private final Collection<BuilderParameters> childParameters;
065
066    /** The manager for default handlers. */
067    private DefaultParametersManager childDefaultParametersManager;
068
069    /** The base path for configuration sources to be loaded. */
070    private String basePath;
071
072    /** A flag whether settings should be inherited by child builders. */
073    private boolean inheritSettings;
074
075    /**
076     * Creates a new instance of {@code CombinedBuilderParametersImpl}.
077     */
078    public CombinedBuilderParametersImpl() {
079        providers = new HashMap<>();
080        childParameters = new LinkedList<>();
081        inheritSettings = true;
082    }
083
084    /**
085     * Looks up an instance of this class in the specified parameters map. This is equivalent to
086     * {@code fromParameters(params, false);}
087     *
088     * @param params the map with parameters (must not be <b>null</b>
089     * @return the instance obtained from the map or <b>null</b>
090     * @throws NullPointerException if the map is <b>null</b>
091     */
092    public static CombinedBuilderParametersImpl fromParameters(final Map<String, ?> params) {
093        return fromParameters(params, false);
094    }
095
096    /**
097     * Looks up an instance of this class in the specified parameters map and optionally creates a new one if none is found.
098     * This method can be used to obtain an instance of this class which has been stored in a parameters map. It is
099     * compatible with the {@code getParameters()} method.
100     *
101     * @param params the map with parameters (must not be <b>null</b>
102     * @param createIfMissing determines the behavior if no instance is found in the map; if <b>true</b>, a new instance
103     *        with default settings is created; if <b>false</b>, <b>null</b> is returned
104     * @return the instance obtained from the map or <b>null</b>
105     * @throws NullPointerException if the map is <b>null</b>
106     */
107    public static CombinedBuilderParametersImpl fromParameters(final Map<String, ?> params, final boolean createIfMissing) {
108        CombinedBuilderParametersImpl result = (CombinedBuilderParametersImpl) params.get(PARAM_KEY);
109        if (result == null && createIfMissing) {
110            result = new CombinedBuilderParametersImpl();
111        }
112        return result;
113    }
114
115    /**
116     * {@inheritDoc} This implementation additionally copies some properties defined by this class.
117     */
118    @Override
119    public void inheritFrom(final Map<String, ?> source) {
120        super.inheritFrom(source);
121
122        final CombinedBuilderParametersImpl srcParams = fromParameters(source);
123        if (srcParams != null) {
124            setChildDefaultParametersManager(srcParams.getChildDefaultParametersManager());
125            setInheritSettings(srcParams.isInheritSettings());
126        }
127    }
128
129    /**
130     * Returns the current value of the flag that controls whether the settings of the parent combined configuration builder
131     * should be inherited by its child configurations.
132     *
133     * @return the flag whether settings should be inherited by child configurations
134     */
135    public boolean isInheritSettings() {
136        return inheritSettings;
137    }
138
139    @Override
140    public CombinedBuilderParametersImpl setInheritSettings(final boolean inheritSettings) {
141        this.inheritSettings = inheritSettings;
142        return this;
143    }
144
145    /**
146     * Returns the {@code ConfigurationBuilder} object for obtaining the definition configuration.
147     *
148     * @return the definition {@code ConfigurationBuilder}
149     */
150    public ConfigurationBuilder<? extends HierarchicalConfiguration<?>> getDefinitionBuilder() {
151        return definitionBuilder;
152    }
153
154    /**
155     * Sets the {@code ConfigurationBuilder} for the definition configuration. This is the configuration which contains the
156     * configuration sources that form the combined configuration.
157     *
158     * @param builder the definition {@code ConfigurationBuilder}
159     * @return a reference to this object for method chaining
160     */
161    @Override
162    public CombinedBuilderParametersImpl setDefinitionBuilder(final ConfigurationBuilder<? extends HierarchicalConfiguration<?>> builder) {
163        definitionBuilder = builder;
164        return this;
165    }
166
167    /**
168     * Registers the given {@code ConfigurationBuilderProvider} for the specified tag name. This means that whenever this
169     * tag is encountered in a configuration definition file, the corresponding builder provider is invoked.
170     *
171     * @param tagName the name of the tag (must not be <b>null</b>)
172     * @param provider the {@code ConfigurationBuilderProvider} (must not be <b>null</b>)
173     * @return a reference to this object for method chaining
174     * @throws IllegalArgumentException if a required parameter is missing
175     */
176    @Override
177    public CombinedBuilderParametersImpl registerProvider(final String tagName, final ConfigurationBuilderProvider provider) {
178        if (tagName == null) {
179            throw new IllegalArgumentException("Tag name must not be null!");
180        }
181        if (provider == null) {
182            throw new IllegalArgumentException("Provider must not be null!");
183        }
184
185        providers.put(tagName, provider);
186        return this;
187    }
188
189    /**
190     * Registers all {@code ConfigurationBuilderProvider}s in the given map to this object which have not yet been
191     * registered. This method is mainly used for internal purposes: a {@code CombinedConfigurationBuilder} takes the
192     * providers contained in a parameters object and adds all standard providers. This way it is possible to override a
193     * standard provider by registering a provider object for the same tag name at the parameters object.
194     *
195     * @param providers a map with tag names and corresponding providers (must not be <b>null</b> or contain <b>null</b>
196     *        entries)
197     * @return a reference to this object for method chaining
198     * @throws IllegalArgumentException if the map with providers is <b>null</b> or contains <b>null</b> entries
199     */
200    public CombinedBuilderParametersImpl registerMissingProviders(final Map<String, ConfigurationBuilderProvider> providers) {
201        if (providers == null) {
202            throw new IllegalArgumentException("Map with providers must not be null!");
203        }
204
205        for (final Map.Entry<String, ConfigurationBuilderProvider> e : providers.entrySet()) {
206            if (!this.providers.containsKey(e.getKey())) {
207                registerProvider(e.getKey(), e.getValue());
208            }
209        }
210        return this;
211    }
212
213    /**
214     * Registers all {@code ConfigurationBuilderProvider}s in the given parameters object which have not yet been
215     * registered. This method works like the method with the same name, but the map with providers is obtained from the
216     * passed in parameters object.
217     *
218     * @param params the parameters object from which to copy providers(must not be <b>null</b>)
219     * @return a reference to this object for method chaining
220     * @throws IllegalArgumentException if the source parameters object is <b>null</b>
221     */
222    public CombinedBuilderParametersImpl registerMissingProviders(final CombinedBuilderParametersImpl params) {
223        if (params == null) {
224            throw new IllegalArgumentException("Source parameters must not be null!");
225        }
226        return registerMissingProviders(params.getProviders());
227    }
228
229    /**
230     * Returns an (unmodifiable) map with the currently registered {@code ConfigurationBuilderProvider} objects.
231     *
232     * @return the map with {@code ConfigurationBuilderProvider} objects (the keys are the tag names)
233     */
234    public Map<String, ConfigurationBuilderProvider> getProviders() {
235        return Collections.unmodifiableMap(providers);
236    }
237
238    /**
239     * Returns the {@code ConfigurationBuilderProvider} which is registered for the specified tag name or <b>null</b> if
240     * there is no registration for this tag.
241     *
242     * @param tagName the tag name
243     * @return the provider registered for this tag or <b>null</b>
244     */
245    public ConfigurationBuilderProvider providerForTag(final String tagName) {
246        return providers.get(tagName);
247    }
248
249    /**
250     * Returns the base path for relative names of configuration sources. Result may be <b>null</b> if no base path has been
251     * set.
252     *
253     * @return the base path for resolving relative file names
254     */
255    public String getBasePath() {
256        return basePath;
257    }
258
259    /**
260     * Sets the base path for this combined configuration builder. Normally it it not necessary to set the base path
261     * explicitly. Per default, relative file names of configuration sources are resolved based on the location of the
262     * definition file. If this is not desired or if the definition configuration is loaded by a different means, the base
263     * path for relative file names can be specified using this method.
264     *
265     * @param path the base path for resolving relative file names
266     * @return a reference to this object for method chaining
267     */
268    @Override
269    public CombinedBuilderParametersImpl setBasePath(final String path) {
270        basePath = path;
271        return this;
272    }
273
274    /**
275     * Returns the parameters object for the definition configuration builder if present.
276     *
277     * @return the parameters object for the definition configuration builder or <b>null</b>
278     */
279    public BuilderParameters getDefinitionBuilderParameters() {
280        return definitionBuilderParameters;
281    }
282
283    /**
284     * Sets the parameters object for the definition configuration builder. This property is evaluated only if the
285     * definition configuration builder is not set explicitly (using the {@link #setDefinitionBuilder(ConfigurationBuilder)}
286     * method). In this case, a builder for an XML configuration is created and configured with this parameters object.
287     *
288     * @param params the parameters object for the definition configuration builder
289     * @return a reference to this object for method chaining
290     */
291    @Override
292    public CombinedBuilderParametersImpl setDefinitionBuilderParameters(final BuilderParameters params) {
293        definitionBuilderParameters = params;
294        return this;
295    }
296
297    /**
298     * Returns a collection with default parameter objects for child configuration sources. This collection contains the
299     * same objects (in the same order) that were passed to {@code addChildParameters()}. The returned collection is a
300     * defensive copy; it can be modified, but this has no effect on the parameters stored in this object.
301     *
302     * @return a map with default parameters for child sources
303     */
304    public Collection<? extends BuilderParameters> getDefaultChildParameters() {
305        return new ArrayList<>(childParameters);
306    }
307
308    /**
309     * Returns the {@code DefaultParametersManager} object for initializing parameter objects for child configuration
310     * sources. This method never returns <b>null</b>. If no manager was set, a new instance is created right now.
311     *
312     * @return the {@code DefaultParametersManager} for child configuration sources
313     */
314    public DefaultParametersManager getChildDefaultParametersManager() {
315        if (childDefaultParametersManager == null) {
316            childDefaultParametersManager = new DefaultParametersManager();
317        }
318        return childDefaultParametersManager;
319    }
320
321    /**
322     * {@inheritDoc} This implementation stores the passed in manager object. An already existing manager object (either
323     * explicitly set or created on demand) is overridden. This also removes all default handlers registered before!
324     */
325    @Override
326    public CombinedBuilderParametersImpl setChildDefaultParametersManager(final DefaultParametersManager manager) {
327        childDefaultParametersManager = manager;
328        return this;
329    }
330
331    /**
332     * {@inheritDoc} This implementation registers the passed in handler at an internal {@link DefaultParametersManager}
333     * instance. If none was set, a new instance is created now.
334     */
335    @Override
336    public <D> CombinedBuilderParametersImpl registerChildDefaultsHandler(final Class<D> paramClass, final DefaultParametersHandler<? super D> handler) {
337        getChildDefaultParametersManager().registerDefaultsHandler(paramClass, handler);
338        return this;
339    }
340
341    /**
342     * {@inheritDoc} This implementation registers the passed in handler at an internal {@link DefaultParametersManager}
343     * instance. If none was set, a new instance is created now.
344     */
345    @Override
346    public <D> CombinedBuilderParametersImpl registerChildDefaultsHandler(final Class<D> paramClass, final DefaultParametersHandler<? super D> handler,
347        final Class<?> startClass) {
348        getChildDefaultParametersManager().registerDefaultsHandler(paramClass, handler, startClass);
349        return this;
350    }
351
352    /**
353     * {@inheritDoc} This implementation returns a map which contains this object itself under a specific key. The static
354     * {@code fromParameters()} method can be used to extract an instance from a parameters map.
355     */
356    @Override
357    public Map<String, Object> getParameters() {
358        final Map<String, Object> params = super.getParameters();
359        params.put(PARAM_KEY, this);
360        return params;
361    }
362
363    /**
364     * {@inheritDoc} This implementation also clones the parameters object for the definition builder if possible.
365     */
366    @Override
367    public CombinedBuilderParametersImpl clone() {
368        final CombinedBuilderParametersImpl copy = (CombinedBuilderParametersImpl) super.clone();
369        copy.setDefinitionBuilderParameters((BuilderParameters) ConfigurationUtils.cloneIfPossible(getDefinitionBuilderParameters()));
370        return copy;
371    }
372}