blob: cf6ee0a9337b82b3f6af2db4d4d73c6b15794c05 [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.fluent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.commons.configuration2.builder.BasicBuilderParameters;
import org.apache.commons.configuration2.builder.BuilderParameters;
import org.apache.commons.configuration2.builder.DatabaseBuilderParametersImpl;
import org.apache.commons.configuration2.builder.DefaultParametersHandler;
import org.apache.commons.configuration2.builder.DefaultParametersManager;
import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
import org.apache.commons.configuration2.builder.HierarchicalBuilderParametersImpl;
import org.apache.commons.configuration2.builder.INIBuilderParametersImpl;
import org.apache.commons.configuration2.builder.JndiBuilderParametersImpl;
import org.apache.commons.configuration2.builder.PropertiesBuilderParametersImpl;
import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
import org.apache.commons.configuration2.builder.combined.CombinedBuilderParametersImpl;
import org.apache.commons.configuration2.builder.combined.MultiFileBuilderParametersImpl;
/**
* <p>
* A convenience class for creating parameter objects for initializing
* configuration builder objects.
* </p>
* <p>
* For setting initialization properties of new configuration objects, a number
* of specialized parameter classes exists. These classes use inheritance to
* organize the properties they support in a logic way. For instance, parameters
* for file-based configurations also support the basic properties common to all
* configuration implementations, parameters for XML configurations also include
* file-based and basic properties, etc.
* </p>
* <p>
* When constructing a configuration builder, an easy-to-use fluent API is
* desired to define specific properties for the configuration to be created.
* However, the inheritance structure of the parameter classes makes it
* surprisingly difficult to provide such an API. This class comes to rescue by
* defining a set of methods for the creation of interface-based parameter
* objects offering a truly fluent API. The methods provided can be called
* directly when setting up a configuration builder as shown in the following
* example code fragment:
* </p>
*
* <pre>
* Parameters params = new Parameters();
* configurationBuilder.configure(params.fileBased()
* .setThrowExceptionOnMissing(true).setEncoding(&quot;UTF-8&quot;)
* .setListDelimiter('#').setFileName(&quot;test.xml&quot;));
* </pre>
*
* <p>
* Using this class it is not only possible to create new parameters objects but
* also to initialize the newly created objects with default values. This is
* via the associated {@link DefaultParametersManager} object. Such an object
* can be passed to the constructor, or a new (uninitialized) instance is
* created. There are convenience methods for interacting with the associated
* {@code DefaultParametersManager}, namely to register or remove
* {@link DefaultParametersHandler} objects. On all newly created parameters
* objects the handlers registered at the associated {@code DefaultParametersHandler}
* are automatically applied.
* </p>
* <p>
* Implementation note: This class is thread-safe.
* </p>
*
* @since 2.0
*/
public final class Parameters
{
/** The manager for default handlers. */
private final DefaultParametersManager defaultParametersManager;
/**
* Creates a new instance of {@code Parameters}. A new, uninitialized
* {@link DefaultParametersManager} is created.
*/
public Parameters()
{
this(null);
}
/**
* Creates a new instance of {@code Parameters} and initializes it with the
* given {@code DefaultParametersManager}. Because
* {@code DefaultParametersManager} is thread-safe, it makes sense to share
* a single instance between multiple {@code Parameters} objects; that way
* the same initialization is performed on newly created parameters objects.
*
* @param manager the {@code DefaultParametersHandler} (may be <b>null</b>,
* then a new default instance is created)
*/
public Parameters(final DefaultParametersManager manager)
{
defaultParametersManager =
manager != null ? manager : new DefaultParametersManager();
}
/**
* Returns the {@code DefaultParametersManager} associated with this object.
*
* @return the {@code DefaultParametersManager}
*/
public DefaultParametersManager getDefaultParametersManager()
{
return defaultParametersManager;
}
/**
* Registers the specified {@code DefaultParametersHandler} object for the
* given parameters class. This is a convenience method which just delegates
* to the associated {@code DefaultParametersManager}.
*
* @param <T> the type of the parameters supported by this handler
* @param paramsClass the parameters class supported by this handler (must
* not be <b>null</b>)
* @param handler the {@code DefaultParametersHandler} to be registered
* (must not be <b>null</b>)
* @throws IllegalArgumentException if a required parameter is missing
* @see DefaultParametersManager
*/
public <T> void registerDefaultsHandler(final Class<T> paramsClass,
final DefaultParametersHandler<? super T> handler)
{
getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler);
}
/**
* Registers the specified {@code DefaultParametersHandler} object for the
* given parameters class and start class in the inheritance hierarchy. This
* is a convenience method which just delegates to the associated
* {@code DefaultParametersManager}.
*
* @param <T> the type of the parameters supported by this handler
* @param paramsClass the parameters class supported by this handler (must
* not be <b>null</b>)
* @param handler the {@code DefaultParametersHandler} to be registered
* (must not be <b>null</b>)
* @param startClass an optional start class in the hierarchy of parameter
* objects for which this handler should be applied
* @throws IllegalArgumentException if a required parameter is missing
*/
public <T> void registerDefaultsHandler(final Class<T> paramsClass,
final DefaultParametersHandler<? super T> handler, final Class<?> startClass)
{
getDefaultParametersManager().registerDefaultsHandler(paramsClass,
handler, startClass);
}
/**
* Creates a new instance of a parameters object for basic configuration
* properties.
*
* @return the new parameters object
*/
public BasicBuilderParameters basic()
{
return new BasicBuilderParameters();
}
/**
* Creates a new instance of a parameters object for file-based
* configuration properties.
*
* @return the new parameters object
*/
public FileBasedBuilderParameters fileBased()
{
return createParametersProxy(new FileBasedBuilderParametersImpl(),
FileBasedBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for combined configuration
* builder properties.
*
* @return the new parameters object
*/
public CombinedBuilderParameters combined()
{
return createParametersProxy(new CombinedBuilderParametersImpl(),
CombinedBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for JNDI configurations.
*
* @return the new parameters object
*/
public JndiBuilderParameters jndi()
{
return createParametersProxy(new JndiBuilderParametersImpl(),
JndiBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for hierarchical
* configurations.
*
* @return the new parameters object
*/
public HierarchicalBuilderParameters hierarchical()
{
return createParametersProxy(new HierarchicalBuilderParametersImpl(),
HierarchicalBuilderParameters.class,
FileBasedBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for XML configurations.
*
* @return the new parameters object
*/
public XMLBuilderParameters xml()
{
return createParametersProxy(new XMLBuilderParametersImpl(),
XMLBuilderParameters.class, FileBasedBuilderParameters.class,
HierarchicalBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for properties
* configurations.
*
* @return the new parameters object
*/
public PropertiesBuilderParameters properties()
{
return createParametersProxy(new PropertiesBuilderParametersImpl(),
PropertiesBuilderParameters.class,
FileBasedBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for a builder for multiple
* file-based configurations.
*
* @return the new parameters object
*/
public MultiFileBuilderParameters multiFile()
{
return createParametersProxy(new MultiFileBuilderParametersImpl(),
MultiFileBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for database
* configurations.
*
* @return the new parameters object
*/
public DatabaseBuilderParameters database()
{
return createParametersProxy(new DatabaseBuilderParametersImpl(),
DatabaseBuilderParameters.class);
}
/**
* Creates a new instance of a parameters object for INI configurations.
*
* @return the new parameters object
*/
public INIBuilderParameters ini()
{
return createParametersProxy(new INIBuilderParametersImpl(),
INIBuilderParameters.class, FileBasedBuilderParameters.class,
HierarchicalBuilderParameters.class);
}
/**
* Creates a proxy object for a given parameters interface based on the
* given implementation object. The newly created object is initialized
* with default values if there are matching {@link DefaultParametersHandler}
* objects.
*
* @param <T> the type of the parameters interface
* @param target the implementing target object
* @param ifcClass the interface class
* @param superIfcs an array with additional interface classes to be
* implemented
* @return the proxy object
*/
private <T> T createParametersProxy(final Object target, final Class<T> ifcClass,
final Class<?>... superIfcs)
{
final Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length];
ifcClasses[0] = ifcClass;
System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length);
final Object obj =
Proxy.newProxyInstance(Parameters.class.getClassLoader(),
ifcClasses, new ParametersIfcInvocationHandler(target));
getDefaultParametersManager().initializeParameters(
(BuilderParameters) obj);
return ifcClass.cast(obj);
}
/**
* A specialized {@code InvocationHandler} implementation which maps the
* methods of a parameters interface to an implementation of the
* corresponding property interfaces. The parameters interface is a union of
* multiple property interfaces. The wrapped object implements all of these,
* but not the union interface. Therefore, a reflection-based approach is
* required. A special handling is required for the method of the
* {@code BuilderParameters} interface because here no fluent return value
* is used.
*/
private static class ParametersIfcInvocationHandler implements
InvocationHandler
{
/** The target object of reflection calls. */
private final Object target;
/**
* Creates a new instance of {@code ParametersIfcInvocationHandler} and
* sets the wrapped parameters object.
*
* @param targetObj the target object for reflection calls
*/
public ParametersIfcInvocationHandler(final Object targetObj)
{
target = targetObj;
}
/**
* {@inheritDoc} This implementation delegates method invocations to the
* target object and handles the return value correctly.
*/
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable
{
final Object result = method.invoke(target, args);
return isFluentResult(method) ? proxy : result;
}
/**
* Checks whether the specified method belongs to an interface which
* requires fluent result values.
*
* @param method the method to be checked
* @return a flag whether the method's result should be handled as a
* fluent result value
*/
private static boolean isFluentResult(final Method method)
{
final Class<?> declaringClass = method.getDeclaringClass();
return declaringClass.isInterface()
&& !declaringClass.equals(BuilderParameters.class);
}
}
}