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 * @version $Id: BuilderConfigurationWrapperFactory.java 1842194 2018-09-27 22:24:23Z ggregory $
050 * @since 2.0
051 */
052public class BuilderConfigurationWrapperFactory
053{
054    /** The current {@code EventSourceSupport} value. */
055    private final EventSourceSupport eventSourceSupport;
056
057    /**
058     * Creates a new instance of {@code BuilderConfigurationWrapperFactory} and
059     * sets the property for supporting the {@code EventSource} interface.
060     *
061     * @param evSrcSupport the level of {@code EventSource} support
062     */
063    public BuilderConfigurationWrapperFactory(final EventSourceSupport evSrcSupport)
064    {
065        eventSourceSupport = evSrcSupport;
066    }
067
068    /**
069     * Creates a new instance of {@code BuilderConfigurationWrapperFactory}
070     * setting the default {@code EventSourceSupport} <em>NONE</em>.
071     */
072    public BuilderConfigurationWrapperFactory()
073    {
074        this(EventSourceSupport.NONE);
075    }
076
077    /**
078     * Creates a wrapper {@code ImmutableConfiguration} on top of the specified
079     * {@code ConfigurationBuilder}. This implementation delegates to
080     * {@link #createBuilderConfigurationWrapper(Class, ConfigurationBuilder, EventSourceSupport)}
081     * .
082     *
083     * @param <T> the type of the configuration objects returned by this method
084     * @param ifcClass the class of the configuration objects returned by this
085     *        method; this must be an interface class and must not be
086     *        <b>null</b>
087     * @param builder the wrapped {@code ConfigurationBuilder} (must not be
088     *        <b>null</b>)
089     * @return the wrapper configuration
090     * @throws IllegalArgumentException if a required parameter is missing
091     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error
092     *         occurs when creating the result {@code ImmutableConfiguration}
093     */
094    public <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(
095            final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder)
096    {
097        return createBuilderConfigurationWrapper(ifcClass, builder,
098                getEventSourceSupport());
099    }
100
101    /**
102     * Returns the level of {@code EventSource} support used when generating
103     * {@code ImmutableConfiguration} objects.
104     *
105     * @return the level of {@code EventSource} support
106     */
107    public EventSourceSupport getEventSourceSupport()
108    {
109        return eventSourceSupport;
110    }
111
112    /**
113     * Returns a {@code ImmutableConfiguration} object which wraps the specified
114     * {@code ConfigurationBuilder}. Each access of the configuration is
115     * delegated to a corresponding call on the {@code ImmutableConfiguration} object
116     * managed by the builder. This is a convenience method which allows
117     * creating wrapper configurations without having to instantiate this class.
118     *
119     * @param <T> the type of the configuration objects returned by this method
120     * @param ifcClass the class of the configuration objects returned by this
121     *        method; this must be an interface class and must not be
122     *        <b>null</b>
123     * @param builder the wrapped {@code ConfigurationBuilder} (must not be
124     *        <b>null</b>)
125     * @param evSrcSupport the level of {@code EventSource} support
126     * @return the wrapper configuration
127     * @throws IllegalArgumentException if a required parameter is missing
128     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error
129     *         occurs when creating the result {@code ImmutableConfiguration}
130     */
131    public static <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(
132            final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder,
133            final EventSourceSupport evSrcSupport)
134    {
135        if (ifcClass == null)
136        {
137            throw new IllegalArgumentException(
138                    "Interface class must not be null!");
139        }
140        if (builder == null)
141        {
142            throw new IllegalArgumentException("Builder must not be null!");
143        }
144
145        return ifcClass.cast(Proxy.newProxyInstance(
146                BuilderConfigurationWrapperFactory.class.getClassLoader(),
147                fetchSupportedInterfaces(ifcClass, evSrcSupport),
148                new BuilderConfigurationWrapperInvocationHandler(builder,
149                        evSrcSupport)));
150    }
151
152    /**
153     * Returns an array with the classes the generated proxy has to support.
154     *
155     * @param ifcClass the class of the configuration objects returned by this
156     *        method; this must be an interface class and must not be
157     *        <b>null</b>
158     * @param evSrcSupport the level of {@code EventSource} support
159     * @return an array with the interface classes to implement
160     */
161    private static Class<?>[] fetchSupportedInterfaces(final Class<?> ifcClass,
162            final EventSourceSupport evSrcSupport)
163    {
164        if (EventSourceSupport.NONE == evSrcSupport)
165        {
166            return new Class<?>[] {
167                ifcClass
168            };
169        }
170
171        final Class<?>[] result = new Class<?>[2];
172        result[0] = EventSource.class;
173        result[1] = ifcClass;
174        return result;
175    }
176
177    /**
178     * <p>
179     * An enumeration class with different options for supporting the
180     * {@code EventSource} interface in generated {@code ImmutableConfiguration} proxies.
181     * </p>
182     * <p>
183     * Using literals of this class it is possible to specify that a
184     * {@code ImmutableConfiguration} object returned by
185     * {@code BuilderConfigurationWrapperFactory} also implements the
186     * {@code EventSource} interface and how this implementation should work.
187     * See the documentation of the single constants for more details.
188     * </p>
189     */
190    public enum EventSourceSupport
191    {
192        /**
193         * No support of the {@code EventSource} interface. If this option is
194         * set, {@code ImmutableConfiguration} objects generated by
195         * {@code BuilderConfigurationWrapperFactory} do not implement the
196         * {@code EventSource} interface.
197         */
198        NONE,
199
200        /**
201         * Dummy support of the {@code EventSource} interface. This option
202         * causes {@code ImmutableConfiguration} objects generated by
203         * {@code BuilderConfigurationWrapperFactory} to implement the
204         * {@code EventSource} interface, however, this implementation consists
205         * only of empty dummy methods without real functionality.
206         */
207        DUMMY,
208
209        /**
210         * {@code EventSource} support is implemented by delegating to the
211         * associated {@code ConfigurationBuilder} object. If this option is
212         * used, generated {@code ImmutableConfiguration} objects provide a fully
213         * functional implementation of {@code EventSource} by delegating to the
214         * builder. Because the {@code ConfigurationBuilder} interface extends
215         * {@code EventSource} this delegation is always possible.
216         */
217        BUILDER
218    }
219
220    /**
221     * A specialized {@code InvocationHandler} implementation for wrapper
222     * configurations. Here the logic of accessing a wrapped builder is
223     * implemented.
224     */
225    private static class BuilderConfigurationWrapperInvocationHandler implements
226            InvocationHandler
227    {
228        /** The wrapped builder. */
229        private final ConfigurationBuilder<? extends ImmutableConfiguration> builder;
230
231        /** The level of {@code EventSource} support. */
232        private final EventSourceSupport eventSourceSupport;
233
234        /**
235         * Creates a new instance of
236         * {@code BuilderConfigurationWrapperInvocationHandler}.
237         *
238         * @param wrappedBuilder the wrapped builder
239         * @param evSrcSupport the level of {@code EventSource} support
240         */
241        public BuilderConfigurationWrapperInvocationHandler(
242                final ConfigurationBuilder<? extends ImmutableConfiguration> wrappedBuilder,
243                final EventSourceSupport evSrcSupport)
244        {
245            builder = wrappedBuilder;
246            eventSourceSupport = evSrcSupport;
247        }
248
249        /**
250         * Handles method invocations. This implementation handles methods of
251         * two different interfaces:
252         * <ul>
253         * <li>Methods from the {@code EventSource} interface are handled
254         * according to the current support level.</li>
255         * <li>Other method calls are delegated to the builder's configuration
256         * object.</li>
257         * </ul>
258         *
259         * @param proxy the proxy object
260         * @param method the method to be invoked
261         * @param args method arguments
262         * @return the return value of the method
263         * @throws Throwable if an error occurs
264         */
265        @Override
266        public Object invoke(final Object proxy, final Method method, final Object[] args)
267                throws Throwable
268        {
269            if (EventSource.class.equals(method.getDeclaringClass()))
270            {
271                return handleEventSourceInvocation(method, args);
272            }
273            return handleConfigurationInvocation(method, args);
274        }
275
276        /**
277         * Handles a method invocation on the associated builder's configuration
278         * object.
279         *
280         * @param method the method to be invoked
281         * @param args method arguments
282         * @return the return value of the method
283         * @throws Exception if an error occurs
284         */
285        private Object handleConfigurationInvocation(final Method method,
286                final Object[] args) throws Exception
287        {
288            return method.invoke(builder.getConfiguration(), args);
289        }
290
291        /**
292         * Handles a method invocation on the {@code EventSource} interface.
293         * This method evaluates the current {@code EventSourceSupport} object
294         * in order to find the appropriate target for the invocation.
295         *
296         * @param method the method to be invoked
297         * @param args method arguments
298         * @return the return value of the method
299         * @throws Exception if an error occurs
300         */
301        private Object handleEventSourceInvocation(final Method method, final Object[] args)
302                throws Exception
303        {
304            final Object target =
305                    (EventSourceSupport.DUMMY == eventSourceSupport) ? ConfigurationUtils
306                            .asEventSource(this, true) : builder;
307            return method.invoke(target, args);
308        }
309    }
310}