    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 * @since 2.0
    084 */
    085public final class Parameters
    086{
    087    /** The manager for default handlers. */
    088    private final DefaultParametersManager defaultParametersManager;
    089
    090    /**
    091     * Creates a new instance of {@code Parameters}. A new, uninitialized
    092     * {@link DefaultParametersManager} is created.
    093     */
    094    public Parameters()
    095    {
    096        this(null);
    097    }
    098
    099    /**
    100     * Creates a new instance of {@code Parameters} and initializes it with the
    101     * given {@code DefaultParametersManager}. Because
    102     * {@code DefaultParametersManager} is thread-safe, it makes sense to share
    103     * a single instance between multiple {@code Parameters} objects; that way
    104     * the same initialization is performed on newly created parameters objects.
    105     *
    106     * @param manager the {@code DefaultParametersHandler} (may be <b>null</b>,
    107     *        then a new default instance is created)
    108     */
    109    public Parameters(final DefaultParametersManager manager)
    110    {
    111        defaultParametersManager =
    112                manager != null ? manager : new DefaultParametersManager();
    113    }
    114
    115    /**
    116     * Returns the {@code DefaultParametersManager} associated with this object.
    117     *
    118     * @return the {@code DefaultParametersManager}
    119     */
    120    public DefaultParametersManager getDefaultParametersManager()
    121    {
    122        return defaultParametersManager;
    123    }
    124
    125    /**
    126     * Registers the specified {@code DefaultParametersHandler} object for the
    127     * given parameters class. This is a convenience method which just delegates
    128     * to the associated {@code DefaultParametersManager}.
    129     *
    130     * @param <T> the type of the parameters supported by this handler
    131     * @param paramsClass the parameters class supported by this handler (must
    132     *        not be <b>null</b>)
    133     * @param handler the {@code DefaultParametersHandler} to be registered
    134     *        (must not be <b>null</b>)
    135     * @throws IllegalArgumentException if a required parameter is missing
    136     * @see DefaultParametersManager
    137     */
    138    public <T> void registerDefaultsHandler(final Class<T> paramsClass,
    139            final DefaultParametersHandler<? super T> handler)
    140    {
    141        getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler);
    142    }
    143
    144    /**
    145     * Registers the specified {@code DefaultParametersHandler} object for the
    146     * given parameters class and start class in the inheritance hierarchy. This
    147     * is a convenience method which just delegates to the associated
    148     * {@code DefaultParametersManager}.
    149     *
    150     * @param <T> the type of the parameters supported by this handler
    151     * @param paramsClass the parameters class supported by this handler (must
    152     *        not be <b>null</b>)
    153     * @param handler the {@code DefaultParametersHandler} to be registered
    154     *        (must not be <b>null</b>)
    155     * @param startClass an optional start class in the hierarchy of parameter
    156     *        objects for which this handler should be applied
    157     * @throws IllegalArgumentException if a required parameter is missing
    158     */
    159    public <T> void registerDefaultsHandler(final Class<T> paramsClass,
    160            final DefaultParametersHandler<? super T> handler, final Class<?> startClass)
    161    {
    162        getDefaultParametersManager().registerDefaultsHandler(paramsClass,
    163                handler, startClass);
    164    }
    165
    166    /**
    167     * Creates a new instance of a parameters object for basic configuration
    168     * properties.
    169     *
    170     * @return the new parameters object
    171     */
    172    public BasicBuilderParameters basic()
    173    {
    174        return new BasicBuilderParameters();
    175    }
    176
    177    /**
    178     * Creates a new instance of a parameters object for file-based
    179     * configuration properties.
    180     *
    181     * @return the new parameters object
    182     */
    183    public FileBasedBuilderParameters fileBased()
    184    {
    185        return createParametersProxy(new FileBasedBuilderParametersImpl(),
    186                FileBasedBuilderParameters.class);
    187    }
    188
    189    /**
    190     * Creates a new instance of a parameters object for combined configuration
    191     * builder properties.
    192     *
    193     * @return the new parameters object
    194     */
    195    public CombinedBuilderParameters combined()
    196    {
    197        return createParametersProxy(new CombinedBuilderParametersImpl(),
    198                CombinedBuilderParameters.class);
    199    }
    200
    201    /**
    202     * Creates a new instance of a parameters object for JNDI configurations.
    203     *
    204     * @return the new parameters object
    205     */
    206    public JndiBuilderParameters jndi()
    207    {
    208        return createParametersProxy(new JndiBuilderParametersImpl(),
    209                JndiBuilderParameters.class);
    210    }
    211
    212    /**
    213     * Creates a new instance of a parameters object for hierarchical
    214     * configurations.
    215     *
    216     * @return the new parameters object
    217     */
    218    public HierarchicalBuilderParameters hierarchical()
    219    {
    220        return createParametersProxy(new HierarchicalBuilderParametersImpl(),
    221                HierarchicalBuilderParameters.class,
    222                FileBasedBuilderParameters.class);
    223    }
    224
    225    /**
    226     * Creates a new instance of a parameters object for XML configurations.
    227     *
    228     * @return the new parameters object
    229     */
    230    public XMLBuilderParameters xml()
    231    {
    232        return createParametersProxy(new XMLBuilderParametersImpl(),
    233                XMLBuilderParameters.class, FileBasedBuilderParameters.class,
    234                HierarchicalBuilderParameters.class);
    235    }
    236
    237    /**
    238     * Creates a new instance of a parameters object for properties
    239     * configurations.
    240     *
    241     * @return the new parameters object
    242     */
    243    public PropertiesBuilderParameters properties()
    244    {
    245        return createParametersProxy(new PropertiesBuilderParametersImpl(),
    246                PropertiesBuilderParameters.class,
    247                FileBasedBuilderParameters.class);
    248    }
    249
    250    /**
    251     * Creates a new instance of a parameters object for a builder for multiple
    252     * file-based configurations.
    253     *
    254     * @return the new parameters object
    255     */
    256    public MultiFileBuilderParameters multiFile()
    257    {
    258        return createParametersProxy(new MultiFileBuilderParametersImpl(),
    259                MultiFileBuilderParameters.class);
    260    }
    261
    262    /**
    263     * Creates a new instance of a parameters object for database
    264     * configurations.
    265     *
    266     * @return the new parameters object
    267     */
    268    public DatabaseBuilderParameters database()
    269    {
    270        return createParametersProxy(new DatabaseBuilderParametersImpl(),
    271                DatabaseBuilderParameters.class);
    272    }
    273
    274    /**
    275     * Creates a new instance of a parameters object for INI configurations.
    276     *
    277     * @return the new parameters object
    278     */
    279    public INIBuilderParameters ini()
    280    {
    281        return createParametersProxy(new INIBuilderParametersImpl(),
    282                INIBuilderParameters.class, FileBasedBuilderParameters.class,
    283                HierarchicalBuilderParameters.class);
    284    }
    285
    286    /**
    287     * Creates a proxy object for a given parameters interface based on the
    288     * given implementation object. The newly created object is initialized
    289     * with default values if there are matching {@link DefaultParametersHandler}
    290     * objects.
    291     *
    292     * @param <T> the type of the parameters interface
    293     * @param target the implementing target object
    294     * @param ifcClass the interface class
    295     * @param superIfcs an array with additional interface classes to be
    296     *        implemented
    297     * @return the proxy object
    298     */
    299    private <T> T createParametersProxy(final Object target, final Class<T> ifcClass,
    300            final Class<?>... superIfcs)
    301    {
    302        final Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length];
    303        ifcClasses[0] = ifcClass;
    304        System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length);
    305        final Object obj =
    306                Proxy.newProxyInstance(Parameters.class.getClassLoader(),
    307                        ifcClasses, new ParametersIfcInvocationHandler(target));
    308        getDefaultParametersManager().initializeParameters(
    309                (BuilderParameters) obj);
    310        return ifcClass.cast(obj);
    311    }
    312
    313    /**
    314     * A specialized {@code InvocationHandler} implementation which maps the
    315     * methods of a parameters interface to an implementation of the
    316     * corresponding property interfaces. The parameters interface is a union of
    317     * multiple property interfaces. The wrapped object implements all of these,
    318     * but not the union interface. Therefore, a reflection-based approach is
    319     * required. A special handling is required for the method of the
    320     * {@code BuilderParameters} interface because here no fluent return value
    321     * is used.
    322     */
    323    private static class ParametersIfcInvocationHandler implements
    324            InvocationHandler
    325    {
    326        /** The target object of reflection calls. */
    327        private final Object target;
    328
    329        /**
    330         * Creates a new instance of {@code ParametersIfcInvocationHandler} and
    331         * sets the wrapped parameters object.
    332         *
    333         * @param targetObj the target object for reflection calls
    334         */
    335        public ParametersIfcInvocationHandler(final Object targetObj)
    336        {
    337            target = targetObj;
    338        }
    339
    340        /**
    341         * {@inheritDoc} This implementation delegates method invocations to the
    342         * target object and handles the return value correctly.
    343         */
    344        @Override
    345        public Object invoke(final Object proxy, final Method method, final Object[] args)
    346                throws Throwable
    347        {
    348            final Object result = method.invoke(target, args);
    349            return isFluentResult(method) ? proxy : result;
    350        }
    351
    352        /**
    353         * Checks whether the specified method belongs to an interface which
    354         * requires fluent result values.
    355         *
    356         * @param method the method to be checked
    357         * @return a flag whether the method's result should be handled as a
    358         *         fluent result value
    359         */
    360        private static boolean isFluentResult(final Method method)
    361        {
    362            final Class<?> declaringClass = method.getDeclaringClass();
    363            return declaringClass.isInterface()
    364                    && !declaringClass.equals(BuilderParameters.class);
    365        }
    366    }
    367}