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 * @version $Id: Parameters.java 1785035 2017-03-01 20:58:32Z oheger $ 084 * @since 2.0 085 */ 086public final class Parameters 087{ 088 /** The manager for default handlers. */ 089 private final DefaultParametersManager defaultParametersManager; 090 091 /** 092 * Creates a new instance of {@code Parameters}. A new, uninitialized 093 * {@link DefaultParametersManager} is created. 094 */ 095 public Parameters() 096 { 097 this(null); 098 } 099 100 /** 101 * Creates a new instance of {@code Parameters} and initializes it with the 102 * given {@code DefaultParametersManager}. Because 103 * {@code DefaultParametersManager} is thread-safe, it makes sense to share 104 * a single instance between multiple {@code Parameters} objects; that way 105 * the same initialization is performed on newly created parameters objects. 106 * 107 * @param manager the {@code DefaultParametersHandler} (may be <b>null</b>, 108 * then a new default instance is created) 109 */ 110 public Parameters(DefaultParametersManager manager) 111 { 112 defaultParametersManager = 113 (manager != null) ? manager : new DefaultParametersManager(); 114 } 115 116 /** 117 * Returns the {@code DefaultParametersManager} associated with this object. 118 * 119 * @return the {@code DefaultParametersManager} 120 */ 121 public DefaultParametersManager getDefaultParametersManager() 122 { 123 return defaultParametersManager; 124 } 125 126 /** 127 * Registers the specified {@code DefaultParametersHandler} object for the 128 * given parameters class. This is a convenience method which just delegates 129 * to the associated {@code DefaultParametersManager}. 130 * 131 * @param <T> the type of the parameters supported by this handler 132 * @param paramsClass the parameters class supported by this handler (must 133 * not be <b>null</b>) 134 * @param handler the {@code DefaultParametersHandler} to be registered 135 * (must not be <b>null</b>) 136 * @throws IllegalArgumentException if a required parameter is missing 137 * @see DefaultParametersManager 138 */ 139 public <T> void registerDefaultsHandler(Class<T> paramsClass, 140 DefaultParametersHandler<? super T> handler) 141 { 142 getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler); 143 } 144 145 /** 146 * Registers the specified {@code DefaultParametersHandler} object for the 147 * given parameters class and start class in the inheritance hierarchy. This 148 * is a convenience method which just delegates to the associated 149 * {@code DefaultParametersManager}. 150 * 151 * @param <T> the type of the parameters supported by this handler 152 * @param paramsClass the parameters class supported by this handler (must 153 * not be <b>null</b>) 154 * @param handler the {@code DefaultParametersHandler} to be registered 155 * (must not be <b>null</b>) 156 * @param startClass an optional start class in the hierarchy of parameter 157 * objects for which this handler should be applied 158 * @throws IllegalArgumentException if a required parameter is missing 159 */ 160 public <T> void registerDefaultsHandler(Class<T> paramsClass, 161 DefaultParametersHandler<? super T> handler, Class<?> startClass) 162 { 163 getDefaultParametersManager().registerDefaultsHandler(paramsClass, 164 handler, startClass); 165 } 166 167 /** 168 * Creates a new instance of a parameters object for basic configuration 169 * properties. 170 * 171 * @return the new parameters object 172 */ 173 public BasicBuilderParameters basic() 174 { 175 return new BasicBuilderParameters(); 176 } 177 178 /** 179 * Creates a new instance of a parameters object for file-based 180 * configuration properties. 181 * 182 * @return the new parameters object 183 */ 184 public FileBasedBuilderParameters fileBased() 185 { 186 return createParametersProxy(new FileBasedBuilderParametersImpl(), 187 FileBasedBuilderParameters.class); 188 } 189 190 /** 191 * Creates a new instance of a parameters object for combined configuration 192 * builder properties. 193 * 194 * @return the new parameters object 195 */ 196 public CombinedBuilderParameters combined() 197 { 198 return createParametersProxy(new CombinedBuilderParametersImpl(), 199 CombinedBuilderParameters.class); 200 } 201 202 /** 203 * Creates a new instance of a parameters object for JNDI configurations. 204 * 205 * @return the new parameters object 206 */ 207 public JndiBuilderParameters jndi() 208 { 209 return createParametersProxy(new JndiBuilderParametersImpl(), 210 JndiBuilderParameters.class); 211 } 212 213 /** 214 * Creates a new instance of a parameters object for hierarchical 215 * configurations. 216 * 217 * @return the new parameters object 218 */ 219 public HierarchicalBuilderParameters hierarchical() 220 { 221 return createParametersProxy(new HierarchicalBuilderParametersImpl(), 222 HierarchicalBuilderParameters.class, 223 FileBasedBuilderParameters.class); 224 } 225 226 /** 227 * Creates a new instance of a parameters object for XML configurations. 228 * 229 * @return the new parameters object 230 */ 231 public XMLBuilderParameters xml() 232 { 233 return createParametersProxy(new XMLBuilderParametersImpl(), 234 XMLBuilderParameters.class, FileBasedBuilderParameters.class, 235 HierarchicalBuilderParameters.class); 236 } 237 238 /** 239 * Creates a new instance of a parameters object for properties 240 * configurations. 241 * 242 * @return the new parameters object 243 */ 244 public PropertiesBuilderParameters properties() 245 { 246 return createParametersProxy(new PropertiesBuilderParametersImpl(), 247 PropertiesBuilderParameters.class, 248 FileBasedBuilderParameters.class); 249 } 250 251 /** 252 * Creates a new instance of a parameters object for a builder for multiple 253 * file-based configurations. 254 * 255 * @return the new parameters object 256 */ 257 public MultiFileBuilderParameters multiFile() 258 { 259 return createParametersProxy(new MultiFileBuilderParametersImpl(), 260 MultiFileBuilderParameters.class); 261 } 262 263 /** 264 * Creates a new instance of a parameters object for database 265 * configurations. 266 * 267 * @return the new parameters object 268 */ 269 public DatabaseBuilderParameters database() 270 { 271 return createParametersProxy(new DatabaseBuilderParametersImpl(), 272 DatabaseBuilderParameters.class); 273 } 274 275 /** 276 * Creates a new instance of a parameters object for INI configurations. 277 * 278 * @return the new parameters object 279 */ 280 public INIBuilderParameters ini() 281 { 282 return createParametersProxy(new INIBuilderParametersImpl(), 283 INIBuilderParameters.class, FileBasedBuilderParameters.class, 284 HierarchicalBuilderParameters.class); 285 } 286 287 /** 288 * Creates a proxy object for a given parameters interface based on the 289 * given implementation object. The newly created object is initialized 290 * with default values if there are matching {@link DefaultParametersHandler} 291 * objects. 292 * 293 * @param <T> the type of the parameters interface 294 * @param target the implementing target object 295 * @param ifcClass the interface class 296 * @param superIfcs an array with additional interface classes to be 297 * implemented 298 * @return the proxy object 299 */ 300 private <T> T createParametersProxy(Object target, Class<T> ifcClass, 301 Class<?>... superIfcs) 302 { 303 Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length]; 304 ifcClasses[0] = ifcClass; 305 System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length); 306 Object obj = 307 Proxy.newProxyInstance(Parameters.class.getClassLoader(), 308 ifcClasses, new ParametersIfcInvocationHandler(target)); 309 getDefaultParametersManager().initializeParameters( 310 (BuilderParameters) obj); 311 return ifcClass.cast(obj); 312 } 313 314 /** 315 * A specialized {@code InvocationHandler} implementation which maps the 316 * methods of a parameters interface to an implementation of the 317 * corresponding property interfaces. The parameters interface is a union of 318 * multiple property interfaces. The wrapped object implements all of these, 319 * but not the union interface. Therefore, a reflection-based approach is 320 * required. A special handling is required for the method of the 321 * {@code BuilderParameters} interface because here no fluent return value 322 * is used. 323 */ 324 private static class ParametersIfcInvocationHandler implements 325 InvocationHandler 326 { 327 /** The target object of reflection calls. */ 328 private final Object target; 329 330 /** 331 * Creates a new instance of {@code ParametersIfcInvocationHandler} and 332 * sets the wrapped parameters object. 333 * 334 * @param targetObj the target object for reflection calls 335 */ 336 public ParametersIfcInvocationHandler(Object targetObj) 337 { 338 target = targetObj; 339 } 340 341 /** 342 * {@inheritDoc} This implementation delegates method invocations to the 343 * target object and handles the return value correctly. 344 */ 345 @Override 346 public Object invoke(Object proxy, Method method, Object[] args) 347 throws Throwable 348 { 349 Object result = method.invoke(target, args); 350 return isFluentResult(method) ? proxy : result; 351 } 352 353 /** 354 * Checks whether the specified method belongs to an interface which 355 * requires fluent result values. 356 * 357 * @param method the method to be checked 358 * @return a flag whether the method's result should be handled as a 359 * fluent result value 360 */ 361 private static boolean isFluentResult(Method method) 362 { 363 Class<?> declaringClass = method.getDeclaringClass(); 364 return declaringClass.isInterface() 365 && !declaringClass.equals(BuilderParameters.class); 366 } 367 } 368}