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}