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; 018 019import java.lang.reflect.InvocationHandler; 020import java.lang.reflect.Method; 021import java.lang.reflect.Proxy; 022 023import org.apache.commons.configuration2.ConfigurationUtils; 024import org.apache.commons.configuration2.ImmutableConfiguration; 025import org.apache.commons.configuration2.event.EventSource; 026 027/** 028 * <p> 029 * A class that allows the creation of configuration objects wrapping a 030 * {@link ConfigurationBuilder}. 031 * </p> 032 * <p> 033 * Using this class special {@code ImmutableConfiguration} proxies can be created that 034 * delegate all method invocations to another {@code ImmutableConfiguration} obtained 035 * from a {@code ConfigurationBuilder}. For instance, if there is a 036 * configuration {@code c} wrapping the builder {@code builder}, the call 037 * {@code c.getString(myKey)} is transformed to 038 * {@code builder.getConfiguration().getString(myKey)}. 039 * </p> 040 * <p> 041 * There are multiple use cases for such a constellation. One example is that 042 * client code can continue working with {@code ImmutableConfiguration} objects while 043 * under the hood builders are used. Another example is that dynamic 044 * configurations can be realized in a transparent way: a client holds a single 045 * configuration (proxy) object, but the underlying builder may return a 046 * different data object on each call. 047 * </p> 048 * 049 * @since 2.0 050 */ 051public class BuilderConfigurationWrapperFactory 052{ 053 /** The current {@code EventSourceSupport} value. */ 054 private final EventSourceSupport eventSourceSupport; 055 056 /** 057 * Creates a new instance of {@code BuilderConfigurationWrapperFactory} and 058 * sets the property for supporting the {@code EventSource} interface. 059 * 060 * @param evSrcSupport the level of {@code EventSource} support 061 */ 062 public BuilderConfigurationWrapperFactory(final EventSourceSupport evSrcSupport) 063 { 064 eventSourceSupport = evSrcSupport; 065 } 066 067 /** 068 * Creates a new instance of {@code BuilderConfigurationWrapperFactory} 069 * setting the default {@code EventSourceSupport} <em>NONE</em>. 070 */ 071 public BuilderConfigurationWrapperFactory() 072 { 073 this(EventSourceSupport.NONE); 074 } 075 076 /** 077 * Creates a wrapper {@code ImmutableConfiguration} on top of the specified 078 * {@code ConfigurationBuilder}. This implementation delegates to 079 * {@link #createBuilderConfigurationWrapper(Class, ConfigurationBuilder, EventSourceSupport)} 080 * . 081 * 082 * @param <T> the type of the configuration objects returned by this method 083 * @param ifcClass the class of the configuration objects returned by this 084 * method; this must be an interface class and must not be 085 * <b>null</b> 086 * @param builder the wrapped {@code ConfigurationBuilder} (must not be 087 * <b>null</b>) 088 * @return the wrapper configuration 089 * @throws IllegalArgumentException if a required parameter is missing 090 * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error 091 * occurs when creating the result {@code ImmutableConfiguration} 092 */ 093 public <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper( 094 final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder) 095 { 096 return createBuilderConfigurationWrapper(ifcClass, builder, 097 getEventSourceSupport()); 098 } 099 100 /** 101 * Returns the level of {@code EventSource} support used when generating 102 * {@code ImmutableConfiguration} objects. 103 * 104 * @return the level of {@code EventSource} support 105 */ 106 public EventSourceSupport getEventSourceSupport() 107 { 108 return eventSourceSupport; 109 } 110 111 /** 112 * Returns a {@code ImmutableConfiguration} object which wraps the specified 113 * {@code ConfigurationBuilder}. Each access of the configuration is 114 * delegated to a corresponding call on the {@code ImmutableConfiguration} object 115 * managed by the builder. This is a convenience method which allows 116 * creating wrapper configurations without having to instantiate this class. 117 * 118 * @param <T> the type of the configuration objects returned by this method 119 * @param ifcClass the class of the configuration objects returned by this 120 * method; this must be an interface class and must not be 121 * <b>null</b> 122 * @param builder the wrapped {@code ConfigurationBuilder} (must not be 123 * <b>null</b>) 124 * @param evSrcSupport the level of {@code EventSource} support 125 * @return the wrapper configuration 126 * @throws IllegalArgumentException if a required parameter is missing 127 * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if an error 128 * occurs when creating the result {@code ImmutableConfiguration} 129 */ 130 public static <T extends ImmutableConfiguration> T createBuilderConfigurationWrapper( 131 final Class<T> ifcClass, final ConfigurationBuilder<? extends T> builder, 132 final EventSourceSupport evSrcSupport) 133 { 134 if (ifcClass == null) 135 { 136 throw new IllegalArgumentException( 137 "Interface class must not be null!"); 138 } 139 if (builder == null) 140 { 141 throw new IllegalArgumentException("Builder must not be null!"); 142 } 143 144 return ifcClass.cast(Proxy.newProxyInstance( 145 BuilderConfigurationWrapperFactory.class.getClassLoader(), 146 fetchSupportedInterfaces(ifcClass, evSrcSupport), 147 new BuilderConfigurationWrapperInvocationHandler(builder, 148 evSrcSupport))); 149 } 150 151 /** 152 * Returns an array with the classes the generated proxy has to support. 153 * 154 * @param ifcClass the class of the configuration objects returned by this 155 * method; this must be an interface class and must not be 156 * <b>null</b> 157 * @param evSrcSupport the level of {@code EventSource} support 158 * @return an array with the interface classes to implement 159 */ 160 private static Class<?>[] fetchSupportedInterfaces(final Class<?> ifcClass, 161 final EventSourceSupport evSrcSupport) 162 { 163 if (EventSourceSupport.NONE == evSrcSupport) 164 { 165 return new Class<?>[] { 166 ifcClass 167 }; 168 } 169 170 final Class<?>[] result = new Class<?>[2]; 171 result[0] = EventSource.class; 172 result[1] = ifcClass; 173 return result; 174 } 175 176 /** 177 * <p> 178 * An enumeration class with different options for supporting the 179 * {@code EventSource} interface in generated {@code ImmutableConfiguration} proxies. 180 * </p> 181 * <p> 182 * Using literals of this class it is possible to specify that a 183 * {@code ImmutableConfiguration} object returned by 184 * {@code BuilderConfigurationWrapperFactory} also implements the 185 * {@code EventSource} interface and how this implementation should work. 186 * See the documentation of the single constants for more details. 187 * </p> 188 */ 189 public enum EventSourceSupport 190 { 191 /** 192 * No support of the {@code EventSource} interface. If this option is 193 * set, {@code ImmutableConfiguration} objects generated by 194 * {@code BuilderConfigurationWrapperFactory} do not implement the 195 * {@code EventSource} interface. 196 */ 197 NONE, 198 199 /** 200 * Dummy support of the {@code EventSource} interface. This option 201 * causes {@code ImmutableConfiguration} objects generated by 202 * {@code BuilderConfigurationWrapperFactory} to implement the 203 * {@code EventSource} interface, however, this implementation consists 204 * only of empty dummy methods without real functionality. 205 */ 206 DUMMY, 207 208 /** 209 * {@code EventSource} support is implemented by delegating to the 210 * associated {@code ConfigurationBuilder} object. If this option is 211 * used, generated {@code ImmutableConfiguration} objects provide a fully 212 * functional implementation of {@code EventSource} by delegating to the 213 * builder. Because the {@code ConfigurationBuilder} interface extends 214 * {@code EventSource} this delegation is always possible. 215 */ 216 BUILDER 217 } 218 219 /** 220 * A specialized {@code InvocationHandler} implementation for wrapper 221 * configurations. Here the logic of accessing a wrapped builder is 222 * implemented. 223 */ 224 private static class BuilderConfigurationWrapperInvocationHandler implements 225 InvocationHandler 226 { 227 /** The wrapped builder. */ 228 private final ConfigurationBuilder<? extends ImmutableConfiguration> builder; 229 230 /** The level of {@code EventSource} support. */ 231 private final EventSourceSupport eventSourceSupport; 232 233 /** 234 * Creates a new instance of 235 * {@code BuilderConfigurationWrapperInvocationHandler}. 236 * 237 * @param wrappedBuilder the wrapped builder 238 * @param evSrcSupport the level of {@code EventSource} support 239 */ 240 public BuilderConfigurationWrapperInvocationHandler( 241 final ConfigurationBuilder<? extends ImmutableConfiguration> wrappedBuilder, 242 final EventSourceSupport evSrcSupport) 243 { 244 builder = wrappedBuilder; 245 eventSourceSupport = evSrcSupport; 246 } 247 248 /** 249 * Handles method invocations. This implementation handles methods of 250 * two different interfaces: 251 * <ul> 252 * <li>Methods from the {@code EventSource} interface are handled 253 * according to the current support level.</li> 254 * <li>Other method calls are delegated to the builder's configuration 255 * object.</li> 256 * </ul> 257 * 258 * @param proxy the proxy object 259 * @param method the method to be invoked 260 * @param args method arguments 261 * @return the return value of the method 262 * @throws Throwable if an error occurs 263 */ 264 @Override 265 public Object invoke(final Object proxy, final Method method, final Object[] args) 266 throws Throwable 267 { 268 if (EventSource.class.equals(method.getDeclaringClass())) 269 { 270 return handleEventSourceInvocation(method, args); 271 } 272 return handleConfigurationInvocation(method, args); 273 } 274 275 /** 276 * Handles a method invocation on the associated builder's configuration 277 * object. 278 * 279 * @param method the method to be invoked 280 * @param args method arguments 281 * @return the return value of the method 282 * @throws Exception if an error occurs 283 */ 284 private Object handleConfigurationInvocation(final Method method, 285 final Object[] args) throws Exception 286 { 287 return method.invoke(builder.getConfiguration(), args); 288 } 289 290 /** 291 * Handles a method invocation on the {@code EventSource} interface. 292 * This method evaluates the current {@code EventSourceSupport} object 293 * in order to find the appropriate target for the invocation. 294 * 295 * @param method the method to be invoked 296 * @param args method arguments 297 * @return the return value of the method 298 * @throws Exception if an error occurs 299 */ 300 private Object handleEventSourceInvocation(final Method method, final Object... args) 301 throws Exception 302 { 303 final Object target = 304 EventSourceSupport.DUMMY == eventSourceSupport ? ConfigurationUtils 305 .asEventSource(this, true) : builder; 306 return method.invoke(target, args); 307 } 308 } 309}