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