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    *     http://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       * A specialized {@code InvocationHandler} implementation which maps the methods of a parameters interface to an
79       * implementation of the corresponding property interfaces. The parameters interface is a union of multiple property
80       * interfaces. The wrapped object implements all of these, but not the union interface. Therefore, a reflection-based
81       * approach is required. A special handling is required for the method of the {@code BuilderParameters} interface
82       * because here no fluent return value is used.
83       */
84      private static final class ParametersIfcInvocationHandler implements InvocationHandler {
85          /**
86           * Checks whether the specified method belongs to an interface which requires fluent result values.
87           *
88           * @param method the method to be checked
89           * @return a flag whether the method's result should be handled as a fluent result value
90           */
91          private static boolean isFluentResult(final Method method) {
92              final Class<?> declaringClass = method.getDeclaringClass();
93              return declaringClass.isInterface() && !declaringClass.equals(BuilderParameters.class);
94          }
95  
96          /** The target object of reflection calls. */
97          private final Object target;
98  
99          /**
100          * Creates a new instance of {@code ParametersIfcInvocationHandler} and sets the wrapped parameters object.
101          *
102          * @param targetObj the target object for reflection calls
103          */
104         public ParametersIfcInvocationHandler(final Object targetObj) {
105             target = targetObj;
106         }
107 
108         /**
109          * {@inheritDoc} This implementation delegates method invocations to the target object and handles the return value
110          * correctly.
111          */
112         @Override
113         public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
114             final Object result = method.invoke(target, args);
115             return isFluentResult(method) ? proxy : result;
116         }
117     }
118 
119     /** The manager for default handlers. */
120     private final DefaultParametersManager defaultParametersManager;
121 
122     /**
123      * Creates a new instance of {@code Parameters}. A new, uninitialized {@link DefaultParametersManager} is created.
124      */
125     public Parameters() {
126         this(null);
127     }
128 
129     /**
130      * Creates a new instance of {@code Parameters} and initializes it with the given {@code DefaultParametersManager}.
131      * Because {@code DefaultParametersManager} is thread-safe, it makes sense to share a single instance between multiple
132      * {@code Parameters} objects; that way the same initialization is performed on newly created parameters objects.
133      *
134      * @param manager the {@code DefaultParametersHandler} (may be <strong>null</strong>, then a new default instance is created)
135      */
136     public Parameters(final DefaultParametersManager manager) {
137         defaultParametersManager = manager != null ? manager : new DefaultParametersManager();
138     }
139 
140     /**
141      * Creates a new instance of a parameters object for basic configuration properties.
142      *
143      * @return the new parameters object
144      */
145     public BasicBuilderParameters basic() {
146         return new BasicBuilderParameters();
147     }
148 
149     /**
150      * Creates a new instance of a parameters object for combined configuration builder properties.
151      *
152      * @return the new parameters object
153      */
154     public CombinedBuilderParameters combined() {
155         return createParametersProxy(new CombinedBuilderParametersImpl(), CombinedBuilderParameters.class);
156     }
157 
158     /**
159      * Creates a proxy object for a given parameters interface based on the given implementation object. The newly created
160      * object is initialized with default values if there are matching {@link DefaultParametersHandler} objects.
161      *
162      * @param <T> the type of the parameters interface
163      * @param target the implementing target object
164      * @param ifcClass the interface class
165      * @param superIfcs an array with additional interface classes to be implemented
166      * @return the proxy object
167      */
168     private <T> T createParametersProxy(final Object target, final Class<T> ifcClass, final Class<?>... superIfcs) {
169         final Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length];
170         ifcClasses[0] = ifcClass;
171         System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length);
172         final Object obj = Proxy.newProxyInstance(Parameters.class.getClassLoader(), ifcClasses, new ParametersIfcInvocationHandler(target));
173         getDefaultParametersManager().initializeParameters((BuilderParameters) obj);
174         return ifcClass.cast(obj);
175     }
176 
177     /**
178      * Creates a new instance of a parameters object for database configurations.
179      *
180      * @return the new parameters object
181      */
182     public DatabaseBuilderParameters database() {
183         return createParametersProxy(new DatabaseBuilderParametersImpl(), DatabaseBuilderParameters.class);
184     }
185 
186     /**
187      * Creates a new instance of a parameters object for file-based configuration properties.
188      *
189      * @return the new parameters object
190      */
191     public FileBasedBuilderParameters fileBased() {
192         return createParametersProxy(new FileBasedBuilderParametersImpl(), FileBasedBuilderParameters.class);
193     }
194 
195     /**
196      * Gets the {@code DefaultParametersManager} associated with this object.
197      *
198      * @return the {@code DefaultParametersManager}
199      */
200     public DefaultParametersManager getDefaultParametersManager() {
201         return defaultParametersManager;
202     }
203 
204     /**
205      * Creates a new instance of a parameters object for hierarchical configurations.
206      *
207      * @return the new parameters object
208      */
209     public HierarchicalBuilderParameters hierarchical() {
210         return createParametersProxy(new HierarchicalBuilderParametersImpl(), HierarchicalBuilderParameters.class, FileBasedBuilderParameters.class);
211     }
212 
213     /**
214      * Creates a new instance of a parameters object for INI configurations.
215      *
216      * @return the new parameters object
217      */
218     public INIBuilderParameters ini() {
219         return createParametersProxy(new INIBuilderParametersImpl(), INIBuilderParameters.class, FileBasedBuilderParameters.class,
220             HierarchicalBuilderParameters.class);
221     }
222 
223     /**
224      * Creates a new instance of a parameters object for JNDI configurations.
225      *
226      * @return the new parameters object
227      */
228     public JndiBuilderParameters jndi() {
229         return createParametersProxy(new JndiBuilderParametersImpl(), JndiBuilderParameters.class);
230     }
231 
232     /**
233      * Creates a new instance of a parameters object for a builder for multiple file-based configurations.
234      *
235      * @return the new parameters object
236      */
237     public MultiFileBuilderParameters multiFile() {
238         return createParametersProxy(new MultiFileBuilderParametersImpl(), MultiFileBuilderParameters.class);
239     }
240 
241     /**
242      * Creates a new instance of a parameters object for properties configurations.
243      *
244      * @return the new parameters object
245      */
246     public PropertiesBuilderParameters properties() {
247         return createParametersProxy(new PropertiesBuilderParametersImpl(), PropertiesBuilderParameters.class, FileBasedBuilderParameters.class);
248     }
249 
250     /**
251      * Registers the specified {@code DefaultParametersHandler} object for the given parameters class. This is a convenience
252      * method which just delegates to the associated {@code DefaultParametersManager}.
253      *
254      * @param <T> the type of the parameters supported by this handler
255      * @param paramsClass the parameters class supported by this handler (must not be <strong>null</strong>)
256      * @param handler the {@code DefaultParametersHandler} to be registered (must not be <strong>null</strong>)
257      * @throws IllegalArgumentException if a required parameter is missing
258      * @see DefaultParametersManager
259      */
260     public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler) {
261         getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler);
262     }
263 
264     /**
265      * Registers the specified {@code DefaultParametersHandler} object for the given parameters class and start class in the
266      * inheritance hierarchy. This is a convenience method which just delegates to the associated
267      * {@code DefaultParametersManager}.
268      *
269      * @param <T> the type of the parameters supported by this handler
270      * @param paramsClass the parameters class supported by this handler (must not be <strong>null</strong>)
271      * @param handler the {@code DefaultParametersHandler} to be registered (must not be <strong>null</strong>)
272      * @param startClass an optional start class in the hierarchy of parameter objects for which this handler should be
273      *        applied
274      * @throws IllegalArgumentException if a required parameter is missing
275      */
276     public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler, final Class<?> startClass) {
277         getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler, startClass);
278     }
279 
280     /**
281      * Creates a new instance of a parameters object for XML configurations.
282      *
283      * @return the new parameters object
284      */
285     public XMLBuilderParameters xml() {
286         return createParametersProxy(new XMLBuilderParametersImpl(), XMLBuilderParameters.class, FileBasedBuilderParameters.class,
287             HierarchicalBuilderParameters.class);
288     }
289 }