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.fluent; 018 019import java.lang.reflect.InvocationHandler; 020import java.lang.reflect.Method; 021import java.lang.reflect.Proxy; 022 023import org.apache.commons.configuration2.builder.BasicBuilderParameters; 024import org.apache.commons.configuration2.builder.BuilderParameters; 025import org.apache.commons.configuration2.builder.DatabaseBuilderParametersImpl; 026import org.apache.commons.configuration2.builder.DefaultParametersHandler; 027import org.apache.commons.configuration2.builder.DefaultParametersManager; 028import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl; 029import org.apache.commons.configuration2.builder.HierarchicalBuilderParametersImpl; 030import org.apache.commons.configuration2.builder.INIBuilderParametersImpl; 031import org.apache.commons.configuration2.builder.JndiBuilderParametersImpl; 032import org.apache.commons.configuration2.builder.PropertiesBuilderParametersImpl; 033import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl; 034import org.apache.commons.configuration2.builder.combined.CombinedBuilderParametersImpl; 035import org.apache.commons.configuration2.builder.combined.MultiFileBuilderParametersImpl; 036 037//@formatter:off 038/** 039 * A convenience class for creating parameter objects for initializing configuration builder objects. 040 * <p> 041 * For setting initialization properties of new configuration objects, a number of specialized parameter classes exists. 042 * These classes use inheritance to organize the properties they support in a logic way. For instance, parameters for 043 * file-based configurations also support the basic properties common to all configuration implementations, parameters 044 * for XML configurations also include file-based and basic properties, etc. 045 * </p> 046 * <p> 047 * When constructing a configuration builder, an easy-to-use fluent API is desired to define specific properties for the 048 * configuration to be created. However, the inheritance structure of the parameter classes makes it surprisingly 049 * difficult to provide such an API. This class comes to rescue by defining a set of methods for the creation of 050 * interface-based parameter objects offering a truly fluent API. The methods provided can be called directly when 051 * setting up a configuration builder as shown in the following example code fragment: 052 * </p> 053 * <pre> 054 * Parameters params = new Parameters(); 055 * configurationBuilder.configure(params.fileBased() 056 * .setThrowExceptionOnMissing(true) 057 * .setEncoding("UTF-8") 058 * .setListDelimiter('#') 059 * .setFileName("test.xml")); 060 * </pre> 061 * <p> 062 * Using this class it is not only possible to create new parameters objects but also to initialize the newly created 063 * objects with default values. This is via the associated {@link DefaultParametersManager} object. Such an object can 064 * be passed to the constructor, or a new (uninitialized) instance is created. There are convenience methods for 065 * interacting with the associated {@code DefaultParametersManager}, namely to register or remove 066 * {@link DefaultParametersHandler} objects. On all newly created parameters objects the handlers registered at the 067 * associated {@code DefaultParametersHandler} are automatically applied. 068 * </p> 069 * <p> 070 * Implementation note: This class is thread-safe. 071 * </p> 072 * 073 * @since 2.0 074 */ 075//@formatter:off 076public final class Parameters { 077 /** The manager for default handlers. */ 078 private final DefaultParametersManager defaultParametersManager; 079 080 /** 081 * Creates a new instance of {@code Parameters}. A new, uninitialized {@link DefaultParametersManager} is created. 082 */ 083 public Parameters() { 084 this(null); 085 } 086 087 /** 088 * Creates a new instance of {@code Parameters} and initializes it with the given {@code DefaultParametersManager}. 089 * Because {@code DefaultParametersManager} is thread-safe, it makes sense to share a single instance between multiple 090 * {@code Parameters} objects; that way the same initialization is performed on newly created parameters objects. 091 * 092 * @param manager the {@code DefaultParametersHandler} (may be <b>null</b>, then a new default instance is created) 093 */ 094 public Parameters(final DefaultParametersManager manager) { 095 defaultParametersManager = manager != null ? manager : new DefaultParametersManager(); 096 } 097 098 /** 099 * Gets the {@code DefaultParametersManager} associated with this object. 100 * 101 * @return the {@code DefaultParametersManager} 102 */ 103 public DefaultParametersManager getDefaultParametersManager() { 104 return defaultParametersManager; 105 } 106 107 /** 108 * Registers the specified {@code DefaultParametersHandler} object for the given parameters class. This is a convenience 109 * method which just delegates to the associated {@code DefaultParametersManager}. 110 * 111 * @param <T> the type of the parameters supported by this handler 112 * @param paramsClass the parameters class supported by this handler (must not be <b>null</b>) 113 * @param handler the {@code DefaultParametersHandler} to be registered (must not be <b>null</b>) 114 * @throws IllegalArgumentException if a required parameter is missing 115 * @see DefaultParametersManager 116 */ 117 public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler) { 118 getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler); 119 } 120 121 /** 122 * Registers the specified {@code DefaultParametersHandler} object for the given parameters class and start class in the 123 * inheritance hierarchy. This is a convenience method which just delegates to the associated 124 * {@code DefaultParametersManager}. 125 * 126 * @param <T> the type of the parameters supported by this handler 127 * @param paramsClass the parameters class supported by this handler (must not be <b>null</b>) 128 * @param handler the {@code DefaultParametersHandler} to be registered (must not be <b>null</b>) 129 * @param startClass an optional start class in the hierarchy of parameter objects for which this handler should be 130 * applied 131 * @throws IllegalArgumentException if a required parameter is missing 132 */ 133 public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler, final Class<?> startClass) { 134 getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler, startClass); 135 } 136 137 /** 138 * Creates a new instance of a parameters object for basic configuration properties. 139 * 140 * @return the new parameters object 141 */ 142 public BasicBuilderParameters basic() { 143 return new BasicBuilderParameters(); 144 } 145 146 /** 147 * Creates a new instance of a parameters object for file-based configuration properties. 148 * 149 * @return the new parameters object 150 */ 151 public FileBasedBuilderParameters fileBased() { 152 return createParametersProxy(new FileBasedBuilderParametersImpl(), FileBasedBuilderParameters.class); 153 } 154 155 /** 156 * Creates a new instance of a parameters object for combined configuration builder properties. 157 * 158 * @return the new parameters object 159 */ 160 public CombinedBuilderParameters combined() { 161 return createParametersProxy(new CombinedBuilderParametersImpl(), CombinedBuilderParameters.class); 162 } 163 164 /** 165 * Creates a new instance of a parameters object for JNDI configurations. 166 * 167 * @return the new parameters object 168 */ 169 public JndiBuilderParameters jndi() { 170 return createParametersProxy(new JndiBuilderParametersImpl(), JndiBuilderParameters.class); 171 } 172 173 /** 174 * Creates a new instance of a parameters object for hierarchical configurations. 175 * 176 * @return the new parameters object 177 */ 178 public HierarchicalBuilderParameters hierarchical() { 179 return createParametersProxy(new HierarchicalBuilderParametersImpl(), HierarchicalBuilderParameters.class, FileBasedBuilderParameters.class); 180 } 181 182 /** 183 * Creates a new instance of a parameters object for XML configurations. 184 * 185 * @return the new parameters object 186 */ 187 public XMLBuilderParameters xml() { 188 return createParametersProxy(new XMLBuilderParametersImpl(), XMLBuilderParameters.class, FileBasedBuilderParameters.class, 189 HierarchicalBuilderParameters.class); 190 } 191 192 /** 193 * Creates a new instance of a parameters object for properties configurations. 194 * 195 * @return the new parameters object 196 */ 197 public PropertiesBuilderParameters properties() { 198 return createParametersProxy(new PropertiesBuilderParametersImpl(), PropertiesBuilderParameters.class, FileBasedBuilderParameters.class); 199 } 200 201 /** 202 * Creates a new instance of a parameters object for a builder for multiple file-based configurations. 203 * 204 * @return the new parameters object 205 */ 206 public MultiFileBuilderParameters multiFile() { 207 return createParametersProxy(new MultiFileBuilderParametersImpl(), MultiFileBuilderParameters.class); 208 } 209 210 /** 211 * Creates a new instance of a parameters object for database configurations. 212 * 213 * @return the new parameters object 214 */ 215 public DatabaseBuilderParameters database() { 216 return createParametersProxy(new DatabaseBuilderParametersImpl(), DatabaseBuilderParameters.class); 217 } 218 219 /** 220 * Creates a new instance of a parameters object for INI configurations. 221 * 222 * @return the new parameters object 223 */ 224 public INIBuilderParameters ini() { 225 return createParametersProxy(new INIBuilderParametersImpl(), INIBuilderParameters.class, FileBasedBuilderParameters.class, 226 HierarchicalBuilderParameters.class); 227 } 228 229 /** 230 * Creates a proxy object for a given parameters interface based on the given implementation object. The newly created 231 * object is initialized with default values if there are matching {@link DefaultParametersHandler} objects. 232 * 233 * @param <T> the type of the parameters interface 234 * @param target the implementing target object 235 * @param ifcClass the interface class 236 * @param superIfcs an array with additional interface classes to be implemented 237 * @return the proxy object 238 */ 239 private <T> T createParametersProxy(final Object target, final Class<T> ifcClass, final Class<?>... superIfcs) { 240 final Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length]; 241 ifcClasses[0] = ifcClass; 242 System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length); 243 final Object obj = Proxy.newProxyInstance(Parameters.class.getClassLoader(), ifcClasses, new ParametersIfcInvocationHandler(target)); 244 getDefaultParametersManager().initializeParameters((BuilderParameters) obj); 245 return ifcClass.cast(obj); 246 } 247 248 /** 249 * A specialized {@code InvocationHandler} implementation which maps the methods of a parameters interface to an 250 * implementation of the corresponding property interfaces. The parameters interface is a union of multiple property 251 * interfaces. The wrapped object implements all of these, but not the union interface. Therefore, a reflection-based 252 * approach is required. A special handling is required for the method of the {@code BuilderParameters} interface 253 * because here no fluent return value is used. 254 */ 255 private static final class ParametersIfcInvocationHandler implements InvocationHandler { 256 /** The target object of reflection calls. */ 257 private final Object target; 258 259 /** 260 * Creates a new instance of {@code ParametersIfcInvocationHandler} and sets the wrapped parameters object. 261 * 262 * @param targetObj the target object for reflection calls 263 */ 264 public ParametersIfcInvocationHandler(final Object targetObj) { 265 target = targetObj; 266 } 267 268 /** 269 * {@inheritDoc} This implementation delegates method invocations to the target object and handles the return value 270 * correctly. 271 */ 272 @Override 273 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 274 final Object result = method.invoke(target, args); 275 return isFluentResult(method) ? proxy : result; 276 } 277 278 /** 279 * Checks whether the specified method belongs to an interface which requires fluent result values. 280 * 281 * @param method the method to be checked 282 * @return a flag whether the method's result should be handled as a fluent result value 283 */ 284 private static boolean isFluentResult(final Method method) { 285 final Class<?> declaringClass = method.getDeclaringClass(); 286 return declaringClass.isInterface() && !declaringClass.equals(BuilderParameters.class); 287 } 288 } 289}