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 /** 78 * A specialized {@code InvocationHandler} implementation which maps the methods of a parameters interface to an 79 * implementation of the corresponding property interfaces. The parameters interface is a union of multiple property 80 * interfaces. The wrapped object implements all of these, but not the union interface. Therefore, a reflection-based 81 * approach is required. A special handling is required for the method of the {@code BuilderParameters} interface 82 * because here no fluent return value is used. 83 */ 84 private static final class ParametersIfcInvocationHandler implements InvocationHandler { 85 /** 86 * Checks whether the specified method belongs to an interface which requires fluent result values. 87 * 88 * @param method the method to be checked 89 * @return a flag whether the method's result should be handled as a fluent result value 90 */ 91 private static boolean isFluentResult(final Method method) { 92 final Class<?> declaringClass = method.getDeclaringClass(); 93 return declaringClass.isInterface() && !declaringClass.equals(BuilderParameters.class); 94 } 95 96 /** The target object of reflection calls. */ 97 private final Object target; 98 99 /** 100 * Creates a new instance of {@code ParametersIfcInvocationHandler} and sets the wrapped parameters object. 101 * 102 * @param targetObj the target object for reflection calls 103 */ 104 public ParametersIfcInvocationHandler(final Object targetObj) { 105 target = targetObj; 106 } 107 108 /** 109 * {@inheritDoc} This implementation delegates method invocations to the target object and handles the return value 110 * correctly. 111 */ 112 @Override 113 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 114 final Object result = method.invoke(target, args); 115 return isFluentResult(method) ? proxy : result; 116 } 117 } 118 119 /** The manager for default handlers. */ 120 private final DefaultParametersManager defaultParametersManager; 121 122 /** 123 * Creates a new instance of {@code Parameters}. A new, uninitialized {@link DefaultParametersManager} is created. 124 */ 125 public Parameters() { 126 this(null); 127 } 128 129 /** 130 * Creates a new instance of {@code Parameters} and initializes it with the given {@code DefaultParametersManager}. 131 * Because {@code DefaultParametersManager} is thread-safe, it makes sense to share a single instance between multiple 132 * {@code Parameters} objects; that way the same initialization is performed on newly created parameters objects. 133 * 134 * @param manager the {@code DefaultParametersHandler} (may be <strong>null</strong>, then a new default instance is created) 135 */ 136 public Parameters(final DefaultParametersManager manager) { 137 defaultParametersManager = manager != null ? manager : new DefaultParametersManager(); 138 } 139 140 /** 141 * Creates a new instance of a parameters object for basic configuration properties. 142 * 143 * @return the new parameters object 144 */ 145 public BasicBuilderParameters basic() { 146 return new BasicBuilderParameters(); 147 } 148 149 /** 150 * Creates a new instance of a parameters object for combined configuration builder properties. 151 * 152 * @return the new parameters object 153 */ 154 public CombinedBuilderParameters combined() { 155 return createParametersProxy(new CombinedBuilderParametersImpl(), CombinedBuilderParameters.class); 156 } 157 158 /** 159 * Creates a proxy object for a given parameters interface based on the given implementation object. The newly created 160 * object is initialized with default values if there are matching {@link DefaultParametersHandler} objects. 161 * 162 * @param <T> the type of the parameters interface 163 * @param target the implementing target object 164 * @param ifcClass the interface class 165 * @param superIfcs an array with additional interface classes to be implemented 166 * @return the proxy object 167 */ 168 private <T> T createParametersProxy(final Object target, final Class<T> ifcClass, final Class<?>... superIfcs) { 169 final Class<?>[] ifcClasses = new Class<?>[1 + superIfcs.length]; 170 ifcClasses[0] = ifcClass; 171 System.arraycopy(superIfcs, 0, ifcClasses, 1, superIfcs.length); 172 final Object obj = Proxy.newProxyInstance(Parameters.class.getClassLoader(), ifcClasses, new ParametersIfcInvocationHandler(target)); 173 getDefaultParametersManager().initializeParameters((BuilderParameters) obj); 174 return ifcClass.cast(obj); 175 } 176 177 /** 178 * Creates a new instance of a parameters object for database configurations. 179 * 180 * @return the new parameters object 181 */ 182 public DatabaseBuilderParameters database() { 183 return createParametersProxy(new DatabaseBuilderParametersImpl(), DatabaseBuilderParameters.class); 184 } 185 186 /** 187 * Creates a new instance of a parameters object for file-based configuration properties. 188 * 189 * @return the new parameters object 190 */ 191 public FileBasedBuilderParameters fileBased() { 192 return createParametersProxy(new FileBasedBuilderParametersImpl(), FileBasedBuilderParameters.class); 193 } 194 195 /** 196 * Gets the {@code DefaultParametersManager} associated with this object. 197 * 198 * @return the {@code DefaultParametersManager} 199 */ 200 public DefaultParametersManager getDefaultParametersManager() { 201 return defaultParametersManager; 202 } 203 204 /** 205 * Creates a new instance of a parameters object for hierarchical configurations. 206 * 207 * @return the new parameters object 208 */ 209 public HierarchicalBuilderParameters hierarchical() { 210 return createParametersProxy(new HierarchicalBuilderParametersImpl(), HierarchicalBuilderParameters.class, FileBasedBuilderParameters.class); 211 } 212 213 /** 214 * Creates a new instance of a parameters object for INI configurations. 215 * 216 * @return the new parameters object 217 */ 218 public INIBuilderParameters ini() { 219 return createParametersProxy(new INIBuilderParametersImpl(), INIBuilderParameters.class, FileBasedBuilderParameters.class, 220 HierarchicalBuilderParameters.class); 221 } 222 223 /** 224 * Creates a new instance of a parameters object for JNDI configurations. 225 * 226 * @return the new parameters object 227 */ 228 public JndiBuilderParameters jndi() { 229 return createParametersProxy(new JndiBuilderParametersImpl(), JndiBuilderParameters.class); 230 } 231 232 /** 233 * Creates a new instance of a parameters object for a builder for multiple file-based configurations. 234 * 235 * @return the new parameters object 236 */ 237 public MultiFileBuilderParameters multiFile() { 238 return createParametersProxy(new MultiFileBuilderParametersImpl(), MultiFileBuilderParameters.class); 239 } 240 241 /** 242 * Creates a new instance of a parameters object for properties configurations. 243 * 244 * @return the new parameters object 245 */ 246 public PropertiesBuilderParameters properties() { 247 return createParametersProxy(new PropertiesBuilderParametersImpl(), PropertiesBuilderParameters.class, FileBasedBuilderParameters.class); 248 } 249 250 /** 251 * Registers the specified {@code DefaultParametersHandler} object for the given parameters class. This is a convenience 252 * method which just delegates to the associated {@code DefaultParametersManager}. 253 * 254 * @param <T> the type of the parameters supported by this handler 255 * @param paramsClass the parameters class supported by this handler (must not be <strong>null</strong>) 256 * @param handler the {@code DefaultParametersHandler} to be registered (must not be <strong>null</strong>) 257 * @throws IllegalArgumentException if a required parameter is missing 258 * @see DefaultParametersManager 259 */ 260 public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler) { 261 getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler); 262 } 263 264 /** 265 * Registers the specified {@code DefaultParametersHandler} object for the given parameters class and start class in the 266 * inheritance hierarchy. This is a convenience method which just delegates to the associated 267 * {@code DefaultParametersManager}. 268 * 269 * @param <T> the type of the parameters supported by this handler 270 * @param paramsClass the parameters class supported by this handler (must not be <strong>null</strong>) 271 * @param handler the {@code DefaultParametersHandler} to be registered (must not be <strong>null</strong>) 272 * @param startClass an optional start class in the hierarchy of parameter objects for which this handler should be 273 * applied 274 * @throws IllegalArgumentException if a required parameter is missing 275 */ 276 public <T> void registerDefaultsHandler(final Class<T> paramsClass, final DefaultParametersHandler<? super T> handler, final Class<?> startClass) { 277 getDefaultParametersManager().registerDefaultsHandler(paramsClass, handler, startClass); 278 } 279 280 /** 281 * Creates a new instance of a parameters object for XML configurations. 282 * 283 * @return the new parameters object 284 */ 285 public XMLBuilderParameters xml() { 286 return createParametersProxy(new XMLBuilderParametersImpl(), XMLBuilderParameters.class, FileBasedBuilderParameters.class, 287 HierarchicalBuilderParameters.class); 288 } 289 }