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