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;
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.ConfigurationUtils;
24 import org.apache.commons.configuration2.ImmutableConfiguration;
25 import org.apache.commons.configuration2.event.EventSource;
26 import org.apache.commons.configuration2.ex.ConfigurationException;
27
28 /**
29 * <p>
30 * A class that allows the creation of configuration objects wrapping a {@link ConfigurationBuilder}.
31 * </p>
32 * <p>
33 * Using this class special {@code ImmutableConfiguration} proxies can be created that delegate all method invocations
34 * to another {@code ImmutableConfiguration} obtained from a {@code ConfigurationBuilder}. For instance, if there is a
35 * configuration {@code c} wrapping the builder {@code builder}, the call {@code c.getString(myKey)} is transformed to
36 * {@code builder.getConfiguration().getString(myKey)}.
37 * </p>
38 * <p>
39 * There are multiple use cases for such a constellation. One example is that client code can continue working with
40 * {@code ImmutableConfiguration} objects while under the hood builders are used. Another example is that dynamic
41 * configurations can be realized in a transparent way: a client holds a single configuration (proxy) object, but the
42 * underlying builder may return a different data object on each call.
43 * </p>
44 *
45 * @since 2.0
46 */
47 public class BuilderConfigurationWrapperFactory {
48
49 /**
50 * A specialized {@code InvocationHandler} implementation for wrapper configurations. Here the logic of accessing a
51 * wrapped builder is implemented.
52 */
53 private static final class BuilderConfigurationWrapperInvocationHandler implements InvocationHandler {
54
55 /** The wrapped builder. */
56 private final ConfigurationBuilder<? extends ImmutableConfiguration> builder;
57
58 /** The level of {@code EventSource} support. */
59 private final EventSourceSupport eventSourceSupport;
60
61 /**
62 * Creates a new instance of {@code BuilderConfigurationWrapperInvocationHandler}.
63 *
64 * @param wrappedBuilder the wrapped builder
65 * @param evSrcSupport the level of {@code EventSource} support
66 */
67 public BuilderConfigurationWrapperInvocationHandler(final ConfigurationBuilder<? extends ImmutableConfiguration> wrappedBuilder,
68 final EventSourceSupport evSrcSupport) {
69 builder = wrappedBuilder;
70 eventSourceSupport = evSrcSupport;
71 }
72
73 /**
74 * Handles a method invocation on the associated builder's configuration object.
75 *
76 * @param method the method to be invoked
77 * @param args method arguments
78 * @return the return value of the method
79 * @throws Exception if an error occurs
80 */
81 private Object handleConfigurationInvocation(final Method method, final Object[] args) throws ReflectiveOperationException, ConfigurationException {
82 return method.invoke(builder.getConfiguration(), args);
83 }
84
85 /**
86 * Handles a method invocation on the {@code EventSource} interface. This method evaluates the current
87 * {@code EventSourceSupport} object in order to find the appropriate target for the invocation.
88 *
89 * @param method the method to be invoked
90 * @param args method arguments
91 * @return the return value of the method
92 * @throws ReflectiveOperationException if an error occurs
93 */
94 private Object handleEventSourceInvocation(final Method method, final Object... args) throws ReflectiveOperationException {
95 return method.invoke(EventSourceSupport.DUMMY == eventSourceSupport ? ConfigurationUtils.asEventSource(this, true) : builder, args);
96 }
97
98 /**
99 * Handles method invocations. This implementation handles methods of two different interfaces:
100 * <ul>
101 * <li>Methods from the {@code EventSource} interface are handled according to the current support level.</li>
102 * <li>Other method calls are delegated to the builder's configuration object.</li>
103 * </ul>
104 *
105 * @param proxy the proxy object
106 * @param method the method to be invoked
107 * @param args method arguments
108 * @return the return value of the method
109 * @throws ReflectiveOperationException if an error occurs
110 * @throws ConfigurationException if an error occurs
111 */
112 @Override
113 public Object invoke(final Object proxy, final Method method, final Object[] args) throws ReflectiveOperationException, ConfigurationException {
114 return EventSource.class.equals(method.getDeclaringClass()) ? handleEventSourceInvocation(method, args)
115 : handleConfigurationInvocation(method, args);
116 }
117 }
118
119 /**
120 * <p>
121 * An enumeration class with different options for supporting the {@code EventSource} interface in generated
122 * {@code ImmutableConfiguration} proxies.
123 * </p>
124 * <p>
125 * Using literals of this class it is possible to specify that a {@code ImmutableConfiguration} object returned by
126 * {@code BuilderConfigurationWrapperFactory} also implements the {@code EventSource} interface and how this
127 * implementation should work. See the documentation of the single constants for more details.
128 * </p>
129 */
130 public enum EventSourceSupport {
131 /**
132 * No support of the {@code EventSource} interface. If this option is set, {@code ImmutableConfiguration} objects
133 * generated by {@code BuilderConfigurationWrapperFactory} do not implement the {@code EventSource} interface.
134 */
135 NONE,
136
137 /**
138 * Dummy support of the {@code EventSource} interface. This option causes {@code ImmutableConfiguration} objects
139 * generated by {@code BuilderConfigurationWrapperFactory} to implement the {@code EventSource} interface, however, this
140 * implementation consists only of empty dummy methods without real functionality.
141 */
142 DUMMY,
143
144 /**
145 * {@code EventSource} support is implemented by delegating to the associated {@code ConfigurationBuilder} object. If
146 * this option is used, generated {@code ImmutableConfiguration} objects provide a fully functional implementation of
147 * {@code EventSource} by delegating to the builder. Because the {@code ConfigurationBuilder} interface extends
148 * {@code EventSource} this delegation is always possible.
149 */
150 BUILDER
151 }
152
153 /**
154 * Creates a {@code ImmutableConfiguration} object which wraps the specified {@code ConfigurationBuilder}. Each access
155 * of the configuration is delegated to a corresponding call on the {@code ImmutableConfiguration} object managed by the
156 * builder. This is a convenience method which allows creating wrapper configurations without having to instantiate this
157 * class.
158 *
159 * @param <T> the type of the configuration objects returned by this method
160 * @param ifcClass the class of the configuration objects returned by this method; this must be an interface class and
161 * must not be <strong>null</strong>
162 * @param builder the wrapped {@code ConfigurationBuilder} (must not be <strong>null</strong>)
163 * @param evSrcSupport the level of {@code EventSource} support
164 * @return the wrapper configuration
165 * @throws IllegalArgumentException if a required parameter is missing
166 * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when creating the
167 * result {@code ImmutableConfiguration}
168 */
169 public static <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(final Class<T> ifcClass,
170 final ConfigurationBuilder<? extends T> builder, final EventSourceSupport evSrcSupport) {
171 if (ifcClass == null) {
172 throw new IllegalArgumentException("Interface class must not be null!");
173 }
174 if (builder == null) {
175 throw new IllegalArgumentException("Builder must not be null!");
176 }
177
178 return ifcClass.cast(Proxy.newProxyInstance(BuilderConfigurationWrapperFactory.class.getClassLoader(), getSupportedInterfaces(ifcClass, evSrcSupport),
179 new BuilderConfigurationWrapperInvocationHandler(builder, evSrcSupport)));
180 }
181
182 /**
183 * Gets an array with the classes the generated proxy has to support.
184 *
185 * @param ifcClass the class of the configuration objects returned by this method; this must be an interface class and
186 * must not be <strong>null</strong>
187 * @param evSrcSupport the level of {@code EventSource} support
188 * @return an array with the interface classes to implement
189 */
190 private static Class<?>[] getSupportedInterfaces(final Class<?> ifcClass, final EventSourceSupport evSrcSupport) {
191 return EventSourceSupport.NONE == evSrcSupport ? new Class<?>[] {ifcClass} : new Class<?>[] {EventSource.class, ifcClass};
192 }
193
194 /** The current {@code EventSourceSupport} value. */
195 private final EventSourceSupport eventSourceSupport;
196
197 /**
198 * Creates a new instance of {@code BuilderConfigurationWrapperFactory} setting the default {@code EventSourceSupport}
199 * <em>NONE</em>.
200 */
201 public BuilderConfigurationWrapperFactory() {
202 this(EventSourceSupport.NONE);
203 }
204
205 /**
206 * Creates a new instance of {@code BuilderConfigurationWrapperFactory} and sets the property for supporting the
207 * {@code EventSource} interface.
208 *
209 * @param evSrcSupport the level of {@code EventSource} support
210 */
211 public BuilderConfigurationWrapperFactory(final EventSourceSupport evSrcSupport) {
212 eventSourceSupport = evSrcSupport;
213 }
214
215 /**
216 * Creates a wrapper {@code ImmutableConfiguration} on top of the specified {@code ConfigurationBuilder}. This
217 * implementation delegates to
218 * {@link #createBuilderConfigurationWrapper(Class, ConfigurationBuilder, EventSourceSupport)}.
219 *
220 * @param <T> the type of the configuration objects returned by this method
221 * @param ifcClass the class of the configuration objects returned by this method; this must be an interface class and
222 * must not be <strong>null</strong>
223 * @param builder the wrapped {@code ConfigurationBuilder} (must not be <strong>null</strong>)
224 * @return the wrapper configuration
225 * @throws IllegalArgumentException if a required parameter is missing
226 * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when creating the
227 * result {@code ImmutableConfiguration}
228 */
229 public <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder) {
230 return createBuilderConfigurationWrapper(ifcClass, builder, getEventSourceSupport());
231 }
232
233 /**
234 * Gets the level of {@code EventSource} support used when generating {@code ImmutableConfiguration} objects.
235 *
236 * @return the level of {@code EventSource} support
237 */
238 public EventSourceSupport getEventSourceSupport() {
239 return eventSourceSupport;
240 }
241 }