blob: 61c611ee0c972f86dfa419e6b682cda568d2d896 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration2.builder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.commons.configuration2.ConfigurationUtils;
import org.apache.commons.configuration2.ImmutableConfiguration;
import org.apache.commons.configuration2.event.EventSource;
/**
* <p>
* A class that allows the creation of configuration objects wrapping a
* {@link ConfigurationBuilder}.
* </p>
* <p>
* Using this class special {@code ImmutableConfiguration} proxies can be created that
* delegate all method invocations to another {@code ImmutableConfiguration} obtained
* from a {@code ConfigurationBuilder}. For instance, if there is a
* configuration {@code c} wrapping the builder {@code builder}, the call
* {@code c.getString(myKey)} is transformed to
* {@code builder.getConfiguration().getString(myKey)}.
* </p>
* <p>
* There are multiple use cases for such a constellation. One example is that
* client code can continue working with {@code ImmutableConfiguration} objects while
* under the hood builders are used. Another example is that dynamic
* configurations can be realized in a transparent way: a client holds a single
* configuration (proxy) object, but the underlying builder may return a
* different data object on each call.
* </p>
*
* @since 2.0
*/
public class BuilderConfigurationWrapperFactory
{
/** The current {@code EventSourceSupport} value. */
private final EventSourceSupport eventSourceSupport;
/**
* Creates a new instance of {@code BuilderConfigurationWrapperFactory} and
* sets the property for supporting the {@code EventSource} interface.
*
* @param evSrcSupport the level of {@code EventSource} support
*/
public BuilderConfigurationWrapperFactory(final EventSourceSupport evSrcSupport)
{
eventSourceSupport = evSrcSupport;
}
/**
* Creates a new instance of {@code BuilderConfigurationWrapperFactory}
* setting the default {@code EventSourceSupport} <em>NONE</em>.
*/
public BuilderConfigurationWrapperFactory()
{
this(EventSourceSupport.NONE);
}
/**
* Creates a wrapper {@code ImmutableConfiguration} on top of the specified
* {@code ConfigurationBuilder}. This implementation delegates to
* {@link #createBuilderConfigurationWrapper(Class, ConfigurationBuilder, EventSourceSupport)}
* .
*
* @param <T> the type of the configuration objects returned by this method
* @param ifcClass the class of the configuration objects returned by this
* method; this must be an interface class and must not be
* <b>null</b>
* @param builder the wrapped {@code ConfigurationBuilder} (must not be
* <b>null</b>)
* @return the wrapper configuration
* @throws IllegalArgumentException if a required parameter is missing
* @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error
* occurs when creating the result {@code ImmutableConfiguration}
*/
public <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(
final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder)
{
return createBuilderConfigurationWrapper(ifcClass, builder,
getEventSourceSupport());
}
/**
* Returns the level of {@code EventSource} support used when generating
* {@code ImmutableConfiguration} objects.
*
* @return the level of {@code EventSource} support
*/
public EventSourceSupport getEventSourceSupport()
{
return eventSourceSupport;
}
/**
* Returns a {@code ImmutableConfiguration} object which wraps the specified
* {@code ConfigurationBuilder}. Each access of the configuration is
* delegated to a corresponding call on the {@code ImmutableConfiguration} object
* managed by the builder. This is a convenience method which allows
* creating wrapper configurations without having to instantiate this class.
*
* @param <T> the type of the configuration objects returned by this method
* @param ifcClass the class of the configuration objects returned by this
* method; this must be an interface class and must not be
* <b>null</b>
* @param builder the wrapped {@code ConfigurationBuilder} (must not be
* <b>null</b>)
* @param evSrcSupport the level of {@code EventSource} support
* @return the wrapper configuration
* @throws IllegalArgumentException if a required parameter is missing
* @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error
* occurs when creating the result {@code ImmutableConfiguration}
*/
public static <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper(
final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder,
final EventSourceSupport evSrcSupport)
{
if (ifcClass == null)
{
throw new IllegalArgumentException(
"Interface class must not be null!");
}
if (builder == null)
{
throw new IllegalArgumentException("Builder must not be null!");
}
return ifcClass.cast(Proxy.newProxyInstance(
BuilderConfigurationWrapperFactory.class.getClassLoader(),
fetchSupportedInterfaces(ifcClass, evSrcSupport),
new BuilderConfigurationWrapperInvocationHandler(builder,
evSrcSupport)));
}
/**
* Returns an array with the classes the generated proxy has to support.
*
* @param ifcClass the class of the configuration objects returned by this
* method; this must be an interface class and must not be
* <b>null</b>
* @param evSrcSupport the level of {@code EventSource} support
* @return an array with the interface classes to implement
*/
private static Class<?>[] fetchSupportedInterfaces(final Class<?> ifcClass,
final EventSourceSupport evSrcSupport)
{
if (EventSourceSupport.NONE == evSrcSupport)
{
return new Class<?>[] {
ifcClass
};
}
final Class<?>[] result = new Class<?>[2];
result[0] = EventSource.class;
result[1] = ifcClass;
return result;
}
/**
* <p>
* An enumeration class with different options for supporting the
* {@code EventSource} interface in generated {@code ImmutableConfiguration} proxies.
* </p>
* <p>
* Using literals of this class it is possible to specify that a
* {@code ImmutableConfiguration} object returned by
* {@code BuilderConfigurationWrapperFactory} also implements the
* {@code EventSource} interface and how this implementation should work.
* See the documentation of the single constants for more details.
* </p>
*/
public enum EventSourceSupport
{
/**
* No support of the {@code EventSource} interface. If this option is
* set, {@code ImmutableConfiguration} objects generated by
* {@code BuilderConfigurationWrapperFactory} do not implement the
* {@code EventSource} interface.
*/
NONE,
/**
* Dummy support of the {@code EventSource} interface. This option
* causes {@code ImmutableConfiguration} objects generated by
* {@code BuilderConfigurationWrapperFactory} to implement the
* {@code EventSource} interface, however, this implementation consists
* only of empty dummy methods without real functionality.
*/
DUMMY,
/**
* {@code EventSource} support is implemented by delegating to the
* associated {@code ConfigurationBuilder} object. If this option is
* used, generated {@code ImmutableConfiguration} objects provide a fully
* functional implementation of {@code EventSource} by delegating to the
* builder. Because the {@code ConfigurationBuilder} interface extends
* {@code EventSource} this delegation is always possible.
*/
BUILDER
}
/**
* A specialized {@code InvocationHandler} implementation for wrapper
* configurations. Here the logic of accessing a wrapped builder is
* implemented.
*/
private static class BuilderConfigurationWrapperInvocationHandler implements
InvocationHandler
{
/** The wrapped builder. */
private final ConfigurationBuilder<? extends ImmutableConfiguration> builder;
/** The level of {@code EventSource} support. */
private final EventSourceSupport eventSourceSupport;
/**
* Creates a new instance of
* {@code BuilderConfigurationWrapperInvocationHandler}.
*
* @param wrappedBuilder the wrapped builder
* @param evSrcSupport the level of {@code EventSource} support
*/
public BuilderConfigurationWrapperInvocationHandler(
final ConfigurationBuilder<? extends ImmutableConfiguration> wrappedBuilder,
final EventSourceSupport evSrcSupport)
{
builder = wrappedBuilder;
eventSourceSupport = evSrcSupport;
}
/**
* Handles method invocations. This implementation handles methods of
* two different interfaces:
* <ul>
* <li>Methods from the {@code EventSource} interface are handled
* according to the current support level.</li>
* <li>Other method calls are delegated to the builder's configuration
* object.</li>
* </ul>
*
* @param proxy the proxy object
* @param method the method to be invoked
* @param args method arguments
* @return the return value of the method
* @throws Throwable if an error occurs
*/
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable
{
if (EventSource.class.equals(method.getDeclaringClass()))
{
return handleEventSourceInvocation(method, args);
}
return handleConfigurationInvocation(method, args);
}
/**
* Handles a method invocation on the associated builder's configuration
* object.
*
* @param method the method to be invoked
* @param args method arguments
* @return the return value of the method
* @throws Exception if an error occurs
*/
private Object handleConfigurationInvocation(final Method method,
final Object[] args) throws Exception
{
return method.invoke(builder.getConfiguration(), args);
}
/**
* Handles a method invocation on the {@code EventSource} interface.
* This method evaluates the current {@code EventSourceSupport} object
* in order to find the appropriate target for the invocation.
*
* @param method the method to be invoked
* @param args method arguments
* @return the return value of the method
* @throws Exception if an error occurs
*/
private Object handleEventSourceInvocation(final Method method, final Object[] args)
throws Exception
{
final Object target =
EventSourceSupport.DUMMY == eventSourceSupport ? ConfigurationUtils
.asEventSource(this, true) : builder;
return method.invoke(target, args);
}
}
}