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