    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 *     https://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.fluent;
    018
    019import java.lang.reflect.InvocationHandler;
    020import java.lang.reflect.Method;
    021import java.lang.reflect.Proxy;
    022
    023import org.apache.commons.configuration2.builder.BasicBuilderParameters;
    024import org.apache.commons.configuration2.builder.BuilderParameters;
    025import org.apache.commons.configuration2.builder.DatabaseBuilderParametersImpl;
    026import org.apache.commons.configuration2.builder.DefaultParametersHandler;
    027import org.apache.commons.configuration2.builder.DefaultParametersManager;
    028import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
    029import org.apache.commons.configuration2.builder.HierarchicalBuilderParametersImpl;
    030import org.apache.commons.configuration2.builder.INIBuilderParametersImpl;
    031import org.apache.commons.configuration2.builder.JndiBuilderParametersImpl;
    032import org.apache.commons.configuration2.builder.PropertiesBuilderParametersImpl;
    033import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
    034import org.apache.commons.configuration2.builder.combined.CombinedBuilderParametersImpl;
    035import org.apache.commons.configuration2.builder.combined.MultiFileBuilderParametersImpl;
    036
    037//@formatter:off
    038/**
    039 * A convenience class for creating parameter objects for initializing configuration builder objects.
    040 * <p>
    041 * For setting initialization properties of new configuration objects, a number of specialized parameter classes exists.
    042 * These classes use inheritance to organize the properties they support in a logic way. For instance, parameters for
    043 * file-based configurations also support the basic properties common to all configuration implementations, parameters
    044 * for XML configurations also include file-based and basic properties, etc.
    045 * </p>
    046 * <p>
    047 * When constructing a configuration builder, an easy-to-use fluent API is desired to define specific properties for the
    048 * configuration to be created. However, the inheritance structure of the parameter classes makes it surprisingly
    049 * difficult to provide such an API. This class comes to rescue by defining a set of methods for the creation of
    050 * interface-based parameter objects offering a truly fluent API. The methods provided can be called directly when
    051 * setting up a configuration builder as shown in the following example code fragment:
    052 * </p>
    053 * <pre>
    054 * Parameters params = new Parameters();
    055 * configurationBuilder.configure(params.fileBased()
    056 *   .setThrowExceptionOnMissing(true)
    057 *   .setEncoding(&quot;UTF-8&quot;)
    058 *   .setListDelimiter('#')
    059 *   .setFileName(&quot;test.xml&quot;));
    060 * </pre>
    061 * <p>
    062 * Using this class it is not only possible to create new parameters objects but also to initialize the newly created
    063 * objects with default values. This is via the associated {@link DefaultParametersManager} object. Such an object can
    064 * be passed to the constructor, or a new (uninitialized) instance is created. There are convenience methods for
    065 * interacting with the associated {@code DefaultParametersManager}, namely to register or remove
    066 * {@link DefaultParametersHandler} objects. On all newly created parameters objects the handlers registered at the
    067 * associated {@code DefaultParametersHandler} are automatically applied.
    068 * </p>
    069 * <p>
    070 * Implementation note: This class is thread-safe.
    071 * </p>
    072 *
    073 * @since 2.0
    074 */
    075//@formatter:off
    076public final class Parameters {
    077
    078    /**
    079     * A specialized {@code InvocationHandler} implementation which maps the methods of a parameters interface to an
    080     * implementation of the corresponding property interfaces. The parameters interface is a union of multiple property
    081     * interfaces. The wrapped object implements all of these, but not the union interface. Therefore, a reflection-based
    082     * approach is required. A special handling is required for the method of the {@code BuilderParameters} interface
    083     * because here no fluent return value is used.
    084     */
    085    private static final class ParametersIfcInvocationHandler implements InvocationHandler {
    086
    087        /**
    088         * Checks whether the specified method belongs to an interface which requires fluent result values.
    089         *
    090         * @param method the method to be checked
    091         * @return a flag whether the method's result should be handled as a fluent result value
    092         */
    093        private static boolean isFluentResult(final Method method) {
    094            final Class<?> declaringClass = method.getDeclaringClass();
    095            return declaringClass.isInterface() && !declaringClass.equals(BuilderParameters.class);
    096        }
    097
    098        /** The target object of reflection calls. */
    099        private final Object target;
    100
    101        /**
    102         * Creates a new instance of {@code ParametersIfcInvocationHandler} and sets the wrapped parameters object.
    103         *
    104         * @param targetObj the target object for reflection calls
    105         */
    106        public ParametersIfcInvocationHandler(final Object targetObj) {
    107            target = targetObj;
    108        }
    109
    110        /**
    111         * {@inheritDoc} This implementation delegates method invocations to the target object and handles the return value
    112         * correctly.
    113         */
    114        @Override
    115        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
    116            final Object result = method.invoke(target, args);
    117            return isFluentResult(method) ? proxy : result;
    118        }
    119    }
    120
    121    /** The manager for default handlers. */
    122    private final DefaultParametersManager defaultParametersManager;
    123
    124    /**
    125     * Creates a new instance of {@code Parameters}. A new, uninitialized {@link DefaultParametersManager} is created.
    126     */
    127    public Parameters() {
    128        this(null);
    129    }
    130
    131    /**
    132     * Creates a new instance of {@code Parameters} and initializes it with the given {@code DefaultParametersManager}.
    133     * Because {@code DefaultParametersManager} is thread-safe, it makes sense to share a single instance between multiple
    134     * {@code Parameters} objects; that way the same initialization is performed on newly created parameters objects.
    135     *
    136     * @param manager the {@code DefaultParametersHandler} (may be <strong>null</strong>, then a new default instance is created)
    137     */
    138    public Parameters(final DefaultParametersManager manager) {
    139        defaultParametersManager = manager != null ? manager : new DefaultParametersManager();
    140    }
    141
    142    /**
    143     * Creates a new instance of a parameters object for basic configuration properties.
    144     *
    145     * @return the new parameters object
    146     */
    147    public BasicBuilderParameters basic() {
    148        return new BasicBuilderParameters();
    149    }
    150
    151    /**
    152     * Creates a new instance of a parameters object for combined configuration builder properties.
    153     *
    154     * @return the new parameters object
    155     */
    156    public CombinedBuilderParameters combined() {
    157        return createParametersProxy(new CombinedBuilderParametersImpl(), CombinedBuilderParameters.class);
    158    }
    159
    160    /**
    161     * Creates a proxy object for a given parameters interface based on the given implementation object. The newly created
    162     * object is initialized with default values if there are matching {@link DefaultParametersHandler} objects.
    163     *
    164     * @param <T> the type of the parameters interface
    165     * @param target the implementing target object
    166     * @param ifcClass the interface class
    167     * @param superIfcs an array with additional interface classes to be implemented
    168     * @return the proxy object
    169     */
    170    private <T> T createParametersProxy(final Object target, final Class<T> ifcClass, final Class<?>... superIfcs) {
    171        final Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length];
    172        ifcClasses[0] = ifcClass;
    173        System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length);
    174        final Object obj = Proxy.newProxyInstance(Parameters.class.getClassLoader(), ifcClasses, new ParametersIfcInvocationHandler(target));
    175        getDefaultParametersManager().initializeParameters((BuilderParameters) obj);
    176        return ifcClass.cast(obj);
    177    }
    178
    179    /**
    180     * Creates a new instance of a parameters object for database configurations.
    181     *
    182     * @return the new parameters object
    183     */
    184    public DatabaseBuilderParameters database() {
    185        return createParametersProxy(new DatabaseBuilderParametersImpl(), DatabaseBuilderParameters.class);
    186    }
    187
    188    /**
    189     * Creates a new instance of a parameters object for file-based configuration properties.
    190     *
    191     * @return the new parameters object
    192     */
    193    public FileBasedBuilderParameters fileBased() {
    194        return createParametersProxy(new FileBasedBuilderParametersImpl(), FileBasedBuilderParameters.class);
    195    }
    196
    197    /**
    198     * Gets the {@code DefaultParametersManager} associated with this object.
    199     *
    200     * @return the {@code DefaultParametersManager}
    201     */
    202    public DefaultParametersManager getDefaultParametersManager() {
    203        return defaultParametersManager;
    204    }
    205
    206    /**
    207     * Creates a new instance of a parameters object for hierarchical configurations.
    208     *
    209     * @return the new parameters object
    210     */
    211    public HierarchicalBuilderParameters hierarchical() {
    212        return createParametersProxy(new HierarchicalBuilderParametersImpl(), HierarchicalBuilderParameters.class, FileBasedBuilderParameters.class);
    213    }
    214
    215    /**
    216     * Creates a new instance of a parameters object for INI configurations.
    217     *
    218     * @return the new parameters object
    219     */
    220    public INIBuilderParameters ini() {
    221        return createParametersProxy(new INIBuilderParametersImpl(), INIBuilderParameters.class, FileBasedBuilderParameters.class,
    222            HierarchicalBuilderParameters.class);
    223    }
    224
    225    /**
    226     * Creates a new instance of a parameters object for JNDI configurations.
    227     *
    228     * @return the new parameters object
    229     */
    230    public JndiBuilderParameters jndi() {
    231        return createParametersProxy(new JndiBuilderParametersImpl(), JndiBuilderParameters.class);
    232    }
    233
    234    /**
    235     * Creates a new instance of a parameters object for a builder for multiple file-based configurations.
    236     *
    237     * @return the new parameters object
    238     */
    239    public MultiFileBuilderParameters multiFile() {
    240        return createParametersProxy(new MultiFileBuilderParametersImpl(), MultiFileBuilderParameters.class);
    241    }
    242
    243    /**
    244     * Creates a new instance of a parameters object for properties configurations.
    245     *
    246     * @return the new parameters object
    247     */
    248    public PropertiesBuilderParameters properties() {
    249        return createParametersProxy(new PropertiesBuilderParametersImpl(), PropertiesBuilderParameters.class, FileBasedBuilderParameters.class);
    250    }
    251
    252    /**
    253     * Registers the specified {@code DefaultParametersHandler} object for the given parameters class. This is a convenience
    254     * method which just delegates to the associated {@code DefaultParametersManager}.
    255     *
    256     * @param <T> the type of the parameters supported by this handler
    257     * @param paramsClass the parameters class supported by this handler (must not be <strong>null</strong>)
    258     * @param handler the {@code DefaultParametersHandler} to be registered (must not be <strong>null</strong>)
    259     * @throws IllegalArgumentException if a required parameter is missing
    260     * @see DefaultParametersManager
    261     */
    262    public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler) {
    263        getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler);
    264    }
    265
    266    /**
    267     * Registers the specified {@code DefaultParametersHandler} object for the given parameters class and start class in the
    268     * inheritance hierarchy. This is a convenience method which just delegates to the associated
    269     * {@code DefaultParametersManager}.
    270     *
    271     * @param <T> the type of the parameters supported by this handler
    272     * @param paramsClass the parameters class supported by this handler (must not be <strong>null</strong>)
    273     * @param handler the {@code DefaultParametersHandler} to be registered (must not be <strong>null</strong>)
    274     * @param startClass an optional start class in the hierarchy of parameter objects for which this handler should be
    275     *        applied
    276     * @throws IllegalArgumentException if a required parameter is missing
    277     */
    278    public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler, final Class<?> startClass) {
    279        getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler, startClass);
    280    }
    281
    282    /**
    283     * Creates a new instance of a parameters object for XML configurations.
    284     *
    285     * @return the new parameters object
    286     */
    287    public XMLBuilderParameters xml() {
    288        return createParametersProxy(new XMLBuilderParametersImpl(), XMLBuilderParameters.class, FileBasedBuilderParameters.class,
    289            HierarchicalBuilderParameters.class);
    290    }
    291}