View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.builder.fluent;
18  
19  import java.lang.reflect.InvocationHandler;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Proxy;
22  
23  import org.apache.commons.configuration2.builder.BasicBuilderParameters;
24  import org.apache.commons.configuration2.builder.BuilderParameters;
25  import org.apache.commons.configuration2.builder.DatabaseBuilderParametersImpl;
26  import org.apache.commons.configuration2.builder.DefaultParametersHandler;
27  import org.apache.commons.configuration2.builder.DefaultParametersManager;
28  import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
29  import org.apache.commons.configuration2.builder.HierarchicalBuilderParametersImpl;
30  import org.apache.commons.configuration2.builder.INIBuilderParametersImpl;
31  import org.apache.commons.configuration2.builder.JndiBuilderParametersImpl;
32  import org.apache.commons.configuration2.builder.PropertiesBuilderParametersImpl;
33  import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
34  import org.apache.commons.configuration2.builder.combined.CombinedBuilderParametersImpl;
35  import org.apache.commons.configuration2.builder.combined.MultiFileBuilderParametersImpl;
36  
37  //@formatter:off
38  /**
39   * A convenience class for creating parameter objects for initializing configuration builder objects.
40   * <p>
41   * For setting initialization properties of new configuration objects, a number of specialized parameter classes exists.
42   * These classes use inheritance to organize the properties they support in a logic way. For instance, parameters for
43   * file-based configurations also support the basic properties common to all configuration implementations, parameters
44   * for XML configurations also include file-based and basic properties, etc.
45   * </p>
46   * <p>
47   * When constructing a configuration builder, an easy-to-use fluent API is desired to define specific properties for the
48   * configuration to be created. However, the inheritance structure of the parameter classes makes it surprisingly
49   * difficult to provide such an API. This class comes to rescue by defining a set of methods for the creation of
50   * interface-based parameter objects offering a truly fluent API. The methods provided can be called directly when
51   * setting up a configuration builder as shown in the following example code fragment:
52   * </p>
53   * <pre>
54   * Parameters params = new Parameters();
55   * configurationBuilder.configure(params.fileBased()
56   *   .setThrowExceptionOnMissing(true)
57   *   .setEncoding(&quot;UTF-8&quot;)
58   *   .setListDelimiter('#')
59   *   .setFileName(&quot;test.xml&quot;));
60   * </pre>
61   * <p>
62   * Using this class it is not only possible to create new parameters objects but also to initialize the newly created
63   * objects with default values. This is via the associated {@link DefaultParametersManager} object. Such an object can
64   * be passed to the constructor, or a new (uninitialized) instance is created. There are convenience methods for
65   * interacting with the associated {@code DefaultParametersManager}, namely to register or remove
66   * {@link DefaultParametersHandler} objects. On all newly created parameters objects the handlers registered at the
67   * associated {@code DefaultParametersHandler} are automatically applied.
68   * </p>
69   * <p>
70   * Implementation note: This class is thread-safe.
71   * </p>
72   *
73   * @since 2.0
74   */
75  //@formatter:off
76  public final class Parameters {
77  
78      /**
79       * A specialized {@code InvocationHandler} implementation which maps the methods of a parameters interface to an
80       * implementation of the corresponding property interfaces. The parameters interface is a union of multiple property
81       * interfaces. The wrapped object implements all of these, but not the union interface. Therefore, a reflection-based
82       * approach is required. A special handling is required for the method of the {@code BuilderParameters} interface
83       * because here no fluent return value is used.
84       */
85      private static final class ParametersIfcInvocationHandler implements InvocationHandler {
86  
87          /**
88           * Checks whether the specified method belongs to an interface which requires fluent result values.
89           *
90           * @param method the method to be checked
91           * @return a flag whether the method's result should be handled as a fluent result value
92           */
93          private static boolean isFluentResult(final Method method) {
94              final Class<?> declaringClass = method.getDeclaringClass();
95              return declaringClass.isInterface() && !declaringClass.equals(BuilderParameters.class);
96          }
97  
98          /** The target object of reflection calls. */
99          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 }