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