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