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 * 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 }