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