1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.configuration2.builder.fluent; 18 19 import java.lang.reflect.InvocationHandler; 20 import java.lang.reflect.Method; 21 import java.lang.reflect.Proxy; 22 23 import org.apache.commons.configuration2.builder.BasicBuilderParameters; 24 import org.apache.commons.configuration2.builder.BuilderParameters; 25 import org.apache.commons.configuration2.builder.DatabaseBuilderParametersImpl; 26 import org.apache.commons.configuration2.builder.DefaultParametersHandler; 27 import org.apache.commons.configuration2.builder.DefaultParametersManager; 28 import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl; 29 import org.apache.commons.configuration2.builder.HierarchicalBuilderParametersImpl; 30 import org.apache.commons.configuration2.builder.INIBuilderParametersImpl; 31 import org.apache.commons.configuration2.builder.JndiBuilderParametersImpl; 32 import org.apache.commons.configuration2.builder.PropertiesBuilderParametersImpl; 33 import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl; 34 import org.apache.commons.configuration2.builder.combined.CombinedBuilderParametersImpl; 35 import org.apache.commons.configuration2.builder.combined.MultiFileBuilderParametersImpl; 36 37 //@formatter:off 38 /** 39 * A convenience class for creating parameter objects for initializing configuration builder objects. 40 * <p> 41 * For setting initialization properties of new configuration objects, a number of specialized parameter classes exists. 42 * These classes use inheritance to organize the properties they support in a logic way. For instance, parameters for 43 * file-based configurations also support the basic properties common to all configuration implementations, parameters 44 * for XML configurations also include file-based and basic properties, etc. 45 * </p> 46 * <p> 47 * When constructing a configuration builder, an easy-to-use fluent API is desired to define specific properties for the 48 * configuration to be created. However, the inheritance structure of the parameter classes makes it surprisingly 49 * difficult to provide such an API. This class comes to rescue by defining a set of methods for the creation of 50 * interface-based parameter objects offering a truly fluent API. The methods provided can be called directly when 51 * setting up a configuration builder as shown in the following example code fragment: 52 * </p> 53 * <pre> 54 * Parameters params = new Parameters(); 55 * configurationBuilder.configure(params.fileBased() 56 * .setThrowExceptionOnMissing(true) 57 * .setEncoding("UTF-8") 58 * .setListDelimiter('#') 59 * .setFileName("test.xml")); 60 * </pre> 61 * <p> 62 * Using this class it is not only possible to create new parameters objects but also to initialize the newly created 63 * objects with default values. This is via the associated {@link DefaultParametersManager} object. Such an object can 64 * be passed to the constructor, or a new (uninitialized) instance is created. There are convenience methods for 65 * interacting with the associated {@code DefaultParametersManager}, namely to register or remove 66 * {@link DefaultParametersHandler} objects. On all newly created parameters objects the handlers registered at the 67 * associated {@code DefaultParametersHandler} are automatically applied. 68 * </p> 69 * <p> 70 * Implementation note: This class is thread-safe. 71 * </p> 72 * 73 * @since 2.0 74 */ 75 //@formatter:off 76 public final class Parameters { 77 /** The manager for default handlers. */ 78 private final DefaultParametersManager defaultParametersManager; 79 80 /** 81 * Creates a new instance of {@code Parameters}. A new, uninitialized {@link DefaultParametersManager} is created. 82 */ 83 public Parameters() { 84 this(null); 85 } 86 87 /** 88 * Creates a new instance of {@code Parameters} and initializes it with the given {@code DefaultParametersManager}. 89 * Because {@code DefaultParametersManager} is thread-safe, it makes sense to share a single instance between multiple 90 * {@code Parameters} objects; that way the same initialization is performed on newly created parameters objects. 91 * 92 * @param manager the {@code DefaultParametersHandler} (may be <b>null</b>, then a new default instance is created) 93 */ 94 public Parameters(final DefaultParametersManager manager) { 95 defaultParametersManager = manager != null ? manager : new DefaultParametersManager(); 96 } 97 98 /** 99 * 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 }