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("UTF-8")
58 * .setListDelimiter('#')
59 * .setFileName("test.xml"));
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 }