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}