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 1785035 2017-03-01 20:58:32Z oheger $
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(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(Class<T> paramsClass,
140            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(Class<T> paramsClass,
161            DefaultParametersHandler<? super T> handler, 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(Object target, Class<T> ifcClass,
301            Class<?>... superIfcs)
302    {
303        Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length];
304        ifcClasses[0] = ifcClass;
305        System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length);
306        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(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(Object proxy, Method method, Object[] args)
347                throws Throwable
348        {
349            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(Method method)
362        {
363            Class<?> declaringClass = method.getDeclaringClass();
364            return declaringClass.isInterface()
365                    && !declaringClass.equals(BuilderParameters.class);
366        }
367    }
368}