001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.configuration2.builder;
018
019import java.lang.reflect.InvocationHandler;
020import java.lang.reflect.Method;
021import java.lang.reflect.Proxy;
022
023import org.apache.commons.configuration2.ConfigurationUtils;
024import org.apache.commons.configuration2.ImmutableConfiguration;
025import org.apache.commons.configuration2.event.EventSource;
026
027/**
028 * <p>
029 * A class that allows the creation of configuration objects wrapping a
030 * {@link ConfigurationBuilder}.
031 * </p>
032 * <p>
033 * Using this class special {@code ImmutableConfiguration} proxies can be created that
034 * delegate all method invocations to another {@code ImmutableConfiguration} obtained
035 * from a {@code ConfigurationBuilder}. For instance, if there is a
036 * configuration {@code c} wrapping the builder {@code builder}, the call
037 * {@code c.getString(myKey)} is transformed to
038 * {@code builder.getConfiguration().getString(myKey)}.
039 * </p>
040 * <p>
041 * There are multiple use cases for such a constellation. One example is that
042 * client code can continue working with {@code ImmutableConfiguration} objects while
043 * under the hood builders are used. Another example is that dynamic
044 * configurations can be realized in a transparent way: a client holds a single
045 * configuration (proxy) object, but the underlying builder may return a
046 * different data object on each call.
047 * </p>
048 *
049 * @since 2.0
050 */
051public class BuilderConfigurationWrapperFactory
052{
053    /** The current {@code EventSourceSupport} value. */
054    private final EventSourceSupport eventSourceSupport;
055
056    /**
057     * Creates a new instance of {@code BuilderConfigurationWrapperFactory} and
058     * sets the property for supporting the {@code EventSource} interface.
059     *
060     * @param evSrcSupport the level of {@code EventSource} support
061     */
062    public BuilderConfigurationWrapperFactory(final EventSourceSupport evSrcSupport)
063    {
064        eventSourceSupport = evSrcSupport;
065    }
066
067    /**
068     * Creates a new instance of {@code BuilderConfigurationWrapperFactory}
069     * setting the default {@code EventSourceSupport} <em>NONE</em>.
070     */
071    public BuilderConfigurationWrapperFactory()
072    {
073        this(EventSourceSupport.NONE);
074    }
075
076    /**
077     * Creates a wrapper {@code ImmutableConfiguration} on top of the specified
078     * {@code ConfigurationBuilder}. This implementation delegates to
079     * {@link #createBuilderConfigurationWrapper(Class, ConfigurationBuilder, EventSourceSupport)}
080     * .
081     *
082     * @param <T> the type of the configuration objects returned by this method
083     * @param ifcClass the class of the configuration objects returned by this
084     *        method; this must be an interface class and must not be
085     *        <b>null</b>
086     * @param builder the wrapped {@code ConfigurationBuilder} (must not be
087     *        <b>null</b>)
088     * @return the wrapper configuration
089     * @throws IllegalArgumentException if a required parameter is missing
090     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error
091     *         occurs when creating the result {@code ImmutableConfiguration}
092     */
093    public <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(
094            final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder)
095    {
096        return createBuilderConfigurationWrapper(ifcClass, builder,
097                getEventSourceSupport());
098    }
099
100    /**
101     * Returns the level of {@code EventSource} support used when generating
102     * {@code ImmutableConfiguration} objects.
103     *
104     * @return the level of {@code EventSource} support
105     */
106    public EventSourceSupport getEventSourceSupport()
107    {
108        return eventSourceSupport;
109    }
110
111    /**
112     * Returns a {@code ImmutableConfiguration} object which wraps the specified
113     * {@code ConfigurationBuilder}. Each access of the configuration is
114     * delegated to a corresponding call on the {@code ImmutableConfiguration} object
115     * managed by the builder. This is a convenience method which allows
116     * creating wrapper configurations without having to instantiate this class.
117     *
118     * @param <T> the type of the configuration objects returned by this method
119     * @param ifcClass the class of the configuration objects returned by this
120     *        method; this must be an interface class and must not be
121     *        <b>null</b>
122     * @param builder the wrapped {@code ConfigurationBuilder} (must not be
123     *        <b>null</b>)
124     * @param evSrcSupport the level of {@code EventSource} support
125     * @return the wrapper configuration
126     * @throws IllegalArgumentException if a required parameter is missing
127     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error
128     *         occurs when creating the result {@code ImmutableConfiguration}
129     */
130    public static <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(
131            final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder,
132            final EventSourceSupport evSrcSupport)
133    {
134        if (ifcClass == null)
135        {
136            throw new IllegalArgumentException(
137                    "Interface class must not be null!");
138        }
139        if (builder == null)
140        {
141            throw new IllegalArgumentException("Builder must not be null!");
142        }
143
144        return ifcClass.cast(Proxy.newProxyInstance(
145                BuilderConfigurationWrapperFactory.class.getClassLoader(),
146                fetchSupportedInterfaces(ifcClass, evSrcSupport),
147                new BuilderConfigurationWrapperInvocationHandler(builder,
148                        evSrcSupport)));
149    }
150
151    /**
152     * Returns an array with the classes the generated proxy has to support.
153     *
154     * @param ifcClass the class of the configuration objects returned by this
155     *        method; this must be an interface class and must not be
156     *        <b>null</b>
157     * @param evSrcSupport the level of {@code EventSource} support
158     * @return an array with the interface classes to implement
159     */
160    private static Class<?>[] fetchSupportedInterfaces(final Class<?> ifcClass,
161            final EventSourceSupport evSrcSupport)
162    {
163        if (EventSourceSupport.NONE == evSrcSupport)
164        {
165            return new Class<?>[] {
166                ifcClass
167            };
168        }
169
170        final Class<?>[] result = new Class<?>[2];
171        result[0] = EventSource.class;
172        result[1] = ifcClass;
173        return result;
174    }
175
176    /**
177     * <p>
178     * An enumeration class with different options for supporting the
179     * {@code EventSource} interface in generated {@code ImmutableConfiguration} proxies.
180     * </p>
181     * <p>
182     * Using literals of this class it is possible to specify that a
183     * {@code ImmutableConfiguration} object returned by
184     * {@code BuilderConfigurationWrapperFactory} also implements the
185     * {@code EventSource} interface and how this implementation should work.
186     * See the documentation of the single constants for more details.
187     * </p>
188     */
189    public enum EventSourceSupport
190    {
191        /**
192         * No support of the {@code EventSource} interface. If this option is
193         * set, {@code ImmutableConfiguration} objects generated by
194         * {@code BuilderConfigurationWrapperFactory} do not implement the
195         * {@code EventSource} interface.
196         */
197        NONE,
198
199        /**
200         * Dummy support of the {@code EventSource} interface. This option
201         * causes {@code ImmutableConfiguration} objects generated by
202         * {@code BuilderConfigurationWrapperFactory} to implement the
203         * {@code EventSource} interface, however, this implementation consists
204         * only of empty dummy methods without real functionality.
205         */
206        DUMMY,
207
208        /**
209         * {@code EventSource} support is implemented by delegating to the
210         * associated {@code ConfigurationBuilder} object. If this option is
211         * used, generated {@code ImmutableConfiguration} objects provide a fully
212         * functional implementation of {@code EventSource} by delegating to the
213         * builder. Because the {@code ConfigurationBuilder} interface extends
214         * {@code EventSource} this delegation is always possible.
215         */
216        BUILDER
217    }
218
219    /**
220     * A specialized {@code InvocationHandler} implementation for wrapper
221     * configurations. Here the logic of accessing a wrapped builder is
222     * implemented.
223     */
224    private static class BuilderConfigurationWrapperInvocationHandler implements
225            InvocationHandler
226    {
227        /** The wrapped builder. */
228        private final ConfigurationBuilder<? extends ImmutableConfiguration> builder;
229
230        /** The level of {@code EventSource} support. */
231        private final EventSourceSupport eventSourceSupport;
232
233        /**
234         * Creates a new instance of
235         * {@code BuilderConfigurationWrapperInvocationHandler}.
236         *
237         * @param wrappedBuilder the wrapped builder
238         * @param evSrcSupport the level of {@code EventSource} support
239         */
240        public BuilderConfigurationWrapperInvocationHandler(
241                final ConfigurationBuilder<? extends ImmutableConfiguration> wrappedBuilder,
242                final EventSourceSupport evSrcSupport)
243        {
244            builder = wrappedBuilder;
245            eventSourceSupport = evSrcSupport;
246        }
247
248        /**
249         * Handles method invocations. This implementation handles methods of
250         * two different interfaces:
251         * <ul>
252         * <li>Methods from the {@code EventSource} interface are handled
253         * according to the current support level.</li>
254         * <li>Other method calls are delegated to the builder's configuration
255         * object.</li>
256         * </ul>
257         *
258         * @param proxy the proxy object
259         * @param method the method to be invoked
260         * @param args method arguments
261         * @return the return value of the method
262         * @throws Throwable if an error occurs
263         */
264        @Override
265        public Object invoke(final Object proxy, final Method method, final Object[] args)
266                throws Throwable
267        {
268            if (EventSource.class.equals(method.getDeclaringClass()))
269            {
270                return handleEventSourceInvocation(method, args);
271            }
272            return handleConfigurationInvocation(method, args);
273        }
274
275        /**
276         * Handles a method invocation on the associated builder's configuration
277         * object.
278         *
279         * @param method the method to be invoked
280         * @param args method arguments
281         * @return the return value of the method
282         * @throws Exception if an error occurs
283         */
284        private Object handleConfigurationInvocation(final Method method,
285                final Object[] args) throws Exception
286        {
287            return method.invoke(builder.getConfiguration(), args);
288        }
289
290        /**
291         * Handles a method invocation on the {@code EventSource} interface.
292         * This method evaluates the current {@code EventSourceSupport} object
293         * in order to find the appropriate target for the invocation.
294         *
295         * @param method the method to be invoked
296         * @param args method arguments
297         * @return the return value of the method
298         * @throws Exception if an error occurs
299         */
300        private Object handleEventSourceInvocation(final Method method, final Object... args)
301                throws Exception
302        {
303            final Object target =
304                    EventSourceSupport.DUMMY == eventSourceSupport ? ConfigurationUtils
305                            .asEventSource(this, true) : builder;
306            return method.invoke(target, args);
307        }
308    }
309}