    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}