Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.configuration2.builder.combined; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Map; import org.apache.commons.configuration2.Configuration; import org.apache.commons.configuration2.ConfigurationUtils; import org.apache.commons.configuration2.builder.BasicConfigurationBuilder; import org.apache.commons.configuration2.builder.BuilderParameters; import org.apache.commons.configuration2.builder.ConfigurationBuilder; import org.apache.commons.configuration2.ex.ConfigurationException; /** * <p> * A fully-functional, reflection-based implementation of the * {@code ConfigurationBuilderProvider} interface which can deal with the * default tags defining configuration sources. * </p> * <p> * An instance of this class is initialized with the names of the * {@code ConfigurationBuilder} class used by this provider and the concrete * {@code Configuration} class. The {@code ConfigurationBuilder} class must be * derived from {@link BasicConfigurationBuilder}. When asked for the builder * object, an instance of the builder class is created and initialized from the * bean declaration associated with the current configuration source. * </p> * <p> * {@code ConfigurationBuilder} objects are configured using parameter objects. * When declaring configuration sources in XML it should not be necessary to * define the single parameter objects. Rather, simple and complex properties * are set in the typical way of a bean declaration (i.e. as attributes of the * current XML element or as child elements). This class creates all supported * parameter objects (whose names also must be provided at construction time) * and takes care that their properties are initialized according to the current * bean declaration. * </p> * <p> * The use of reflection to create builder instances allows a generic * implementation supporting many concrete builder classes. Another reason for * this approach is that builder classes are only loaded if actually needed. * Some specialized {@code Configuration} implementations require specific * external dependencies which should not be mandatory for the use of * {@code CombinedConfigurationBuilder}. Because such classes are lazily loaded, * an application only has to include the dependencies it actually uses. * </p> * * @version $Id$ * @since 2.0 */ public class BaseConfigurationBuilderProvider implements ConfigurationBuilderProvider { /** The types of the constructor parameters for a basic builder. */ private static final Class<?>[] CTOR_PARAM_TYPES = { Class.class, Map.class, Boolean.TYPE }; /** The name of the builder class. */ private final String builderClass; /** The name of a builder class with reloading support. */ private final String reloadingBuilderClass; /** Stores the name of the configuration class to be created. */ private final String configurationClass; /** A collection with the names of parameter classes. */ private final Collection<String> parameterClasses; /** * Creates a new instance of {@code BaseConfigurationBuilderProvider} and * initializes all its properties. * * @param bldrCls the name of the builder class (must not be <b>null</b>) * @param reloadBldrCls the name of a builder class to be used if reloading * support is required (<b>null</b> if reloading is not supported) * @param configCls the name of the configuration class (must not be * <b>null</b>) * @param paramCls a collection with the names of parameters classes * @throws IllegalArgumentException if a required parameter is missing */ public BaseConfigurationBuilderProvider(final String bldrCls, final String reloadBldrCls, final String configCls, final Collection<String> paramCls) { if (bldrCls == null) { throw new IllegalArgumentException("Builder class must not be null!"); } if (configCls == null) { throw new IllegalArgumentException("Configuration class must not be null!"); } builderClass = bldrCls; reloadingBuilderClass = reloadBldrCls; configurationClass = configCls; parameterClasses = initParameterClasses(paramCls); } /** * Returns the name of the class of the builder created by this provider. * * @return the builder class */ public String getBuilderClass() { return builderClass; } /** * Returns the name of the class of the builder created by this provider if * the reload flag is set. If this method returns <b>null</b>, reloading * builders are not supported by this provider. * * @return the reloading builder class */ public String getReloadingBuilderClass() { return reloadingBuilderClass; } /** * Returns the name of the configuration class created by the builder * produced by this provider. * * @return the configuration class */ public String getConfigurationClass() { return configurationClass; } /** * Returns an unmodifiable collection with the names of parameter classes * supported by this provider. * * @return the parameter classes */ public Collection<String> getParameterClasses() { return parameterClasses; } /** * {@inheritDoc} This implementation delegates to some protected methods to * create a new builder instance using reflection and to configure it with * parameter values defined by the passed in {@code BeanDeclaration}. */ @Override public ConfigurationBuilder<? extends Configuration> getConfigurationBuilder( final ConfigurationDeclaration decl) throws ConfigurationException { try { final Collection<BuilderParameters> params = createParameterObjects(); initializeParameterObjects(decl, params); final BasicConfigurationBuilder<? extends Configuration> builder = createBuilder(decl, params); configureBuilder(builder, decl, params); return builder; } catch (final ConfigurationException cex) { throw cex; } catch (final Exception ex) { throw new ConfigurationException(ex); } } /** * Determines the <em>allowFailOnInit</em> flag for the newly created * builder based on the given {@code ConfigurationDeclaration}. Some * combinations of flags in the declaration say that a configuration source * is optional, but an empty instance should be created if its creation * fail. * * @param decl the current {@code ConfigurationDeclaration} * @return the value of the <em>allowFailOnInit</em> flag */ protected boolean isAllowFailOnInit(final ConfigurationDeclaration decl) { return decl.isOptional() && decl.isForceCreate(); } /** * Creates a collection of parameter objects to be used for configuring the * builder. This method creates instances of the parameter classes passed to * the constructor. * * @return a collection with parameter objects for the builder * @throws Exception if an error occurs while creating parameter objects via * reflection */ protected Collection<BuilderParameters> createParameterObjects() throws Exception { final Collection<BuilderParameters> params = new ArrayList<>(getParameterClasses().size()); for (final String paramcls : getParameterClasses()) { params.add(createParameterObject(paramcls)); } return params; } /** * Initializes the parameter objects with data stored in the current bean * declaration. This method is called before the newly created builder * instance is configured with the parameter objects. It maps attributes of * the bean declaration to properties of parameter objects. In addition, * it invokes the parent {@code CombinedConfigurationBuilder} so that the * parameters object can inherit properties already defined for this * builder. * * @param decl the current {@code ConfigurationDeclaration} * @param params the collection with (uninitialized) parameter objects * @throws Exception if an error occurs */ protected void initializeParameterObjects(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws Exception { inheritParentBuilderProperties(decl, params); final MultiWrapDynaBean wrapBean = new MultiWrapDynaBean(params); decl.getConfigurationBuilder().initBean(wrapBean, decl); } /** * Passes all parameter objects to the parent * {@code CombinedConfigurationBuilder} so that properties already defined * for the parent builder can be added. This method is called before the * parameter objects are initialized from the definition configuration. This * way properties from the parent builder are inherited, but can be * overridden for child configurations. * * @param decl the current {@code ConfigurationDeclaration} * @param params the collection with (uninitialized) parameter objects */ protected void inheritParentBuilderProperties(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) { for (final BuilderParameters p : params) { decl.getConfigurationBuilder().initChildBuilderParameters(p); } } /** * Creates a new, uninitialized instance of the builder class managed by * this provider. This implementation determines the builder class to be * used by delegating to {@code determineBuilderClass()}. It then calls the * constructor expecting the configuration class, the map with properties, * and the<em>allowFailOnInit</em> flag. * * @param decl the current {@code ConfigurationDeclaration} * @param params initialization parameters for the new builder object * @return the newly created builder instance * @throws Exception if an error occurs */ protected BasicConfigurationBuilder<? extends Configuration> createBuilder(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws Exception { final Class<?> bldCls = ConfigurationUtils.loadClass(determineBuilderClass(decl)); final Class<?> configCls = ConfigurationUtils.loadClass(determineConfigurationClass(decl, params)); final Constructor<?> ctor = bldCls.getConstructor(CTOR_PARAM_TYPES); // ? extends Configuration is the minimum constraint @SuppressWarnings("unchecked") final BasicConfigurationBuilder<? extends Configuration> builder = (BasicConfigurationBuilder<? extends Configuration>) ctor .newInstance(configCls, null, isAllowFailOnInit(decl)); return builder; } /** * Configures a newly created builder instance with its initialization * parameters. This method is called after a new instance was created using * reflection. This implementation passes the parameter objects to the * builder's {@code configure()} method. * * @param builder the builder to be initialized * @param decl the current {@code ConfigurationDeclaration} * @param params the collection with initialization parameter objects * @throws Exception if an error occurs */ protected void configureBuilder(final BasicConfigurationBuilder<? extends Configuration> builder, final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws Exception { builder.configure(params.toArray(new BuilderParameters[params.size()])); } /** * Determines the name of the class to be used for a new builder instance. * This implementation selects between the normal and the reloading builder * class, based on the passed in {@code ConfigurationDeclaration}. If a * reloading builder is desired, but this provider has no reloading support, * an exception is thrown. * * @param decl the current {@code ConfigurationDeclaration} * @return the name of the builder class * @throws ConfigurationException if the builder class cannot be determined */ protected String determineBuilderClass(final ConfigurationDeclaration decl) throws ConfigurationException { if (decl.isReload()) { if (getReloadingBuilderClass() == null) { throw new ConfigurationException("No support for reloading for builder class " + getBuilderClass()); } return getReloadingBuilderClass(); } return getBuilderClass(); } /** * Determines the name of the configuration class produced by the builder. * This method is called when obtaining the arguments for invoking the * constructor of the builder class. This implementation just returns the * pre-configured configuration class name. Derived classes may determine * this class name dynamically based on the passed in parameters. * * @param decl the current {@code ConfigurationDeclaration} * @param params the collection with parameter objects * @return the name of the builder's result configuration class * @throws ConfigurationException if an error occurs */ protected String determineConfigurationClass(final ConfigurationDeclaration decl, final Collection<BuilderParameters> params) throws ConfigurationException { return getConfigurationClass(); } /** * Creates an instance of a parameter class using reflection. * * @param paramcls the parameter class * @return the newly created instance * @throws Exception if an error occurs */ private static BuilderParameters createParameterObject(final String paramcls) throws Exception { final Class<?> cls = ConfigurationUtils.loadClass(paramcls); final BuilderParameters p = (BuilderParameters) cls.newInstance(); return p; } /** * Creates a new, unmodifiable collection for the parameter classes. * * @param paramCls the collection with parameter classes passed to the * constructor * @return the collection to be stored */ private static Collection<String> initParameterClasses(final Collection<String> paramCls) { if (paramCls == null) { return Collections.emptySet(); } return Collections.unmodifiableCollection(new ArrayList<>(paramCls)); } }