    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;
    026import org.apache.commons.configuration2.ex.ConfigurationException;
    027
    028/**
    029 * <p>
    030 * A class that allows the creation of configuration objects wrapping a {@link ConfigurationBuilder}.
    031 * </p>
    032 * <p>
    033 * Using this class special {@code ImmutableConfiguration} proxies can be created that delegate all method invocations
    034 * to another {@code ImmutableConfiguration} obtained from a {@code ConfigurationBuilder}. For instance, if there is a
    035 * configuration {@code c} wrapping the builder {@code builder}, the call {@code c.getString(myKey)} is transformed to
    036 * {@code builder.getConfiguration().getString(myKey)}.
    037 * </p>
    038 * <p>
    039 * There are multiple use cases for such a constellation. One example is that client code can continue working with
    040 * {@code ImmutableConfiguration} objects while under the hood builders are used. Another example is that dynamic
    041 * configurations can be realized in a transparent way: a client holds a single configuration (proxy) object, but the
    042 * underlying builder may return a different data object on each call.
    043 * </p>
    044 *
    045 * @since 2.0
    046 */
    047public class BuilderConfigurationWrapperFactory {
    048
    049    /** The current {@code EventSourceSupport} value. */
    050    private final EventSourceSupport eventSourceSupport;
    051
    052    /**
    053     * Creates a new instance of {@code BuilderConfigurationWrapperFactory} and sets the property for supporting the
    054     * {@code EventSource} interface.
    055     *
    056     * @param evSrcSupport the level of {@code EventSource} support
    057     */
    058    public BuilderConfigurationWrapperFactory(final EventSourceSupport evSrcSupport) {
    059        eventSourceSupport = evSrcSupport;
    060    }
    061
    062    /**
    063     * Creates a new instance of {@code BuilderConfigurationWrapperFactory} setting the default {@code EventSourceSupport}
    064     * <em>NONE</em>.
    065     */
    066    public BuilderConfigurationWrapperFactory() {
    067        this(EventSourceSupport.NONE);
    068    }
    069
    070    /**
    071     * Creates a wrapper {@code ImmutableConfiguration} on top of the specified {@code ConfigurationBuilder}. This
    072     * implementation delegates to
    073     * {@link #createBuilderConfigurationWrapper(Class, ConfigurationBuilder, EventSourceSupport)} .
    074     *
    075     * @param <T> the type of the configuration objects returned by this method
    076     * @param ifcClass the class of the configuration objects returned by this method; this must be an interface class and
    077     *        must not be <b>null</b>
    078     * @param builder the wrapped {@code ConfigurationBuilder} (must not be <b>null</b>)
    079     * @return the wrapper configuration
    080     * @throws IllegalArgumentException if a required parameter is missing
    081     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when creating the
    082     *         result {@code ImmutableConfiguration}
    083     */
    084    public <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder) {
    085        return createBuilderConfigurationWrapper(ifcClass, builder, getEventSourceSupport());
    086    }
    087
    088    /**
    089     * Gets the level of {@code EventSource} support used when generating {@code ImmutableConfiguration} objects.
    090     *
    091     * @return the level of {@code EventSource} support
    092     */
    093    public EventSourceSupport getEventSourceSupport() {
    094        return eventSourceSupport;
    095    }
    096
    097    /**
    098     * Creates a {@code ImmutableConfiguration} object which wraps the specified {@code ConfigurationBuilder}. Each access
    099     * of the configuration is delegated to a corresponding call on the {@code ImmutableConfiguration} object managed by the
    100     * builder. This is a convenience method which allows creating wrapper configurations without having to instantiate this
    101     * class.
    102     *
    103     * @param <T> the type of the configuration objects returned by this method
    104     * @param ifcClass the class of the configuration objects returned by this method; this must be an interface class and
    105     *        must not be <b>null</b>
    106     * @param builder the wrapped {@code ConfigurationBuilder} (must not be <b>null</b>)
    107     * @param evSrcSupport the level of {@code EventSource} support
    108     * @return the wrapper configuration
    109     * @throws IllegalArgumentException if a required parameter is missing
    110     * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error occurs when creating the
    111     *         result {@code ImmutableConfiguration}
    112     */
    113    public static <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(final Class<T> ifcClass,
    114        final ConfigurationBuilder<? extends T> builder, final EventSourceSupport evSrcSupport) {
    115        if (ifcClass == null) {
    116            throw new IllegalArgumentException("Interface class must not be null!");
    117        }
    118        if (builder == null) {
    119            throw new IllegalArgumentException("Builder must not be null!");
    120        }
    121
    122        return ifcClass.cast(Proxy.newProxyInstance(BuilderConfigurationWrapperFactory.class.getClassLoader(), getSupportedInterfaces(ifcClass, evSrcSupport),
    123            new BuilderConfigurationWrapperInvocationHandler(builder, evSrcSupport)));
    124    }
    125
    126    /**
    127     * Gets an array with the classes the generated proxy has to support.
    128     *
    129     * @param ifcClass the class of the configuration objects returned by this method; this must be an interface class and
    130     *        must not be <b>null</b>
    131     * @param evSrcSupport the level of {@code EventSource} support
    132     * @return an array with the interface classes to implement
    133     */
    134    private static Class<?>[] getSupportedInterfaces(final Class<?> ifcClass, final EventSourceSupport evSrcSupport) {
    135        return EventSourceSupport.NONE == evSrcSupport ? new Class<?>[] {ifcClass} : new Class<?>[] {EventSource.class, ifcClass};
    136    }
    137
    138    /**
    139     * <p>
    140     * An enumeration class with different options for supporting the {@code EventSource} interface in generated
    141     * {@code ImmutableConfiguration} proxies.
    142     * </p>
    143     * <p>
    144     * Using literals of this class it is possible to specify that a {@code ImmutableConfiguration} object returned by
    145     * {@code BuilderConfigurationWrapperFactory} also implements the {@code EventSource} interface and how this
    146     * implementation should work. See the documentation of the single constants for more details.
    147     * </p>
    148     */
    149    public enum EventSourceSupport {
    150        /**
    151         * No support of the {@code EventSource} interface. If this option is set, {@code ImmutableConfiguration} objects
    152         * generated by {@code BuilderConfigurationWrapperFactory} do not implement the {@code EventSource} interface.
    153         */
    154        NONE,
    155
    156        /**
    157         * Dummy support of the {@code EventSource} interface. This option causes {@code ImmutableConfiguration} objects
    158         * generated by {@code BuilderConfigurationWrapperFactory} to implement the {@code EventSource} interface, however, this
    159         * implementation consists only of empty dummy methods without real functionality.
    160         */
    161        DUMMY,
    162
    163        /**
    164         * {@code EventSource} support is implemented by delegating to the associated {@code ConfigurationBuilder} object. If
    165         * this option is used, generated {@code ImmutableConfiguration} objects provide a fully functional implementation of
    166         * {@code EventSource} by delegating to the builder. Because the {@code ConfigurationBuilder} interface extends
    167         * {@code EventSource} this delegation is always possible.
    168         */
    169        BUILDER
    170    }
    171
    172    /**
    173     * A specialized {@code InvocationHandler} implementation for wrapper configurations. Here the logic of accessing a
    174     * wrapped builder is implemented.
    175     */
    176    private static final class BuilderConfigurationWrapperInvocationHandler implements InvocationHandler {
    177
    178        /** The wrapped builder. */
    179        private final ConfigurationBuilder<? extends ImmutableConfiguration> builder;
    180
    181        /** The level of {@code EventSource} support. */
    182        private final EventSourceSupport eventSourceSupport;
    183
    184        /**
    185         * Creates a new instance of {@code BuilderConfigurationWrapperInvocationHandler}.
    186         *
    187         * @param wrappedBuilder the wrapped builder
    188         * @param evSrcSupport the level of {@code EventSource} support
    189         */
    190        public BuilderConfigurationWrapperInvocationHandler(final ConfigurationBuilder<? extends ImmutableConfiguration> wrappedBuilder,
    191            final EventSourceSupport evSrcSupport) {
    192            builder = wrappedBuilder;
    193            eventSourceSupport = evSrcSupport;
    194        }
    195
    196        /**
    197         * Handles method invocations. This implementation handles methods of two different interfaces:
    198         * <ul>
    199         * <li>Methods from the {@code EventSource} interface are handled according to the current support level.</li>
    200         * <li>Other method calls are delegated to the builder's configuration object.</li>
    201         * </ul>
    202         *
    203         * @param proxy the proxy object
    204         * @param method the method to be invoked
    205         * @param args method arguments
    206         * @return the return value of the method
    207         * @throws ReflectiveOperationException if an error occurs
    208         * @throws ConfigurationException if an error occurs
    209         */
    210        @Override
    211        public Object invoke(final Object proxy, final Method method, final Object[] args) throws ReflectiveOperationException, ConfigurationException {
    212            return EventSource.class.equals(method.getDeclaringClass()) ? handleEventSourceInvocation(method, args)
    213                : handleConfigurationInvocation(method, args);
    214        }
    215
    216        /**
    217         * Handles a method invocation on the associated builder's configuration object.
    218         *
    219         * @param method the method to be invoked
    220         * @param args method arguments
    221         * @return the return value of the method
    222         * @throws Exception if an error occurs
    223         */
    224        private Object handleConfigurationInvocation(final Method method, final Object[] args) throws ReflectiveOperationException, ConfigurationException {
    225            return method.invoke(builder.getConfiguration(), args);
    226        }
    227
    228        /**
    229         * Handles a method invocation on the {@code EventSource} interface. This method evaluates the current
    230         * {@code EventSourceSupport} object in order to find the appropriate target for the invocation.
    231         *
    232         * @param method the method to be invoked
    233         * @param args method arguments
    234         * @return the return value of the method
    235         * @throws ReflectiveOperationException if an error occurs
    236         */
    237        private Object handleEventSourceInvocation(final Method method, final Object... args) throws ReflectiveOperationException {
    238            return method.invoke(EventSourceSupport.DUMMY == eventSourceSupport ? ConfigurationUtils.asEventSource(this, true) : builder, args);
    239        }
    240    }
    241}