    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}