1 /* Copyright (2007-2011) Schibsted ASA 2 * This file is part of Sesat Commons. 3 * 4 * Sesat Commons is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * Sesat Commons is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU Lesser General Public License for more details. 13 * 14 * You should have received a copy of the GNU Lesser General Public License 15 * along with Sesat Commons. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 package no.sesat.commons.reflect; 18 19 import java.lang.ref.Reference; 20 import java.lang.ref.WeakReference; 21 import java.lang.reflect.Constructor; 22 import java.lang.reflect.InvocationHandler; 23 import java.lang.reflect.InvocationTargetException; 24 import java.util.Arrays; 25 import java.util.HashSet; 26 import java.util.Map; 27 import java.util.Set; 28 import java.util.WeakHashMap; 29 import java.util.concurrent.ConcurrentHashMap; 30 import java.util.concurrent.locks.ReentrantReadWriteLock; 31 32 /** 33 * <code>java.lang.reflect.Proxy</code> provides static methods for creating dynamic proxy 34 * classes and instances, and it is also the superclass of all 35 * dynamic proxy classes created by those methods. 36 * 37 * This class provides faster concurrent caching of Proxies through ConcurrentHashMaps, 38 * It delegates back to java.lang.reflect.Proxy for 39 * the construction of new Proxies and the required synchronisation surrounding it. 40 * 41 * @author @author <a href="mailto:mick@wever.org">Mck Semb Wever</a> 42 * @version $Id$ 43 * @see java.lang.reflect.Proxy 44 */ 45 public final class ConcurrentProxy { 46 47 /** parameter types of a proxy class constructor */ 48 private static final Class[] constructorParams = {InvocationHandler.class}; 49 50 /** maps a class loader to the proxy class cache for that loader */ 51 private static final Map<ClassLoader,Map<Object,Reference<Class>>> loaderToCache 52 = new WeakHashMap<ClassLoader,Map<Object,Reference<Class>>>(); 53 54 private static final ReentrantReadWriteLock loaderToCacheLock = new ReentrantReadWriteLock(); 55 56 /** 57 * Prohibits instantiation. 58 */ 59 private ConcurrentProxy() { 60 } 61 62 /** 63 * Returns the <code>java.lang.Class</code> object for a proxy class 64 * given a class loader and an array of interfaces. The proxy class 65 * will be defined by the specified class loader and will implement 66 * all of the supplied interfaces. If a proxy class for the same 67 * permutation of interfaces has already been defined by the class 68 * loader, then the existing proxy class will be returned; otherwise, 69 * a proxy class for those interfaces will be generated dynamically 70 * and defined by the class loader. 71 * 72 * <p>There are several restrictions on the parameters that may be 73 * passed to <code>Proxy.getProxyClass</code>: 74 * 75 * <ul> 76 * <li>All of the <code>Class</code> objects in the 77 * <code>interfaces</code> array must represent interfaces, not 78 * classes or primitive types. 79 * 80 * <li>No two elements in the <code>interfaces</code> array may 81 * refer to identical <code>Class</code> objects. 82 * 83 * <li>All of the interface types must be visible by name through the 84 * specified class loader. In other words, for class loader 85 * <code>cl</code> and every interface <code>i</code>, the following 86 * expression must be true: 87 * <pre> 88 * Class.forName(i.getName(), false, cl) == i 89 * </pre> 90 * 91 * <li>All non-public interfaces must be in the same package; 92 * otherwise, it would not be possible for the proxy class to 93 * implement all of the interfaces, regardless of what package it is 94 * defined in. 95 * 96 * <li>For any set of member methods of the specified interfaces 97 * that have the same signature: 98 * <ul> 99 * <li>If the return type of any of the methods is a primitive 100 * type or void, then all of the methods must have that same 101 * return type. 102 * <li>Otherwise, one of the methods must have a return type that 103 * is assignable to all of the return types of the rest of the 104 * methods. 105 * </ul> 106 * 107 * <li>The resulting proxy class must not exceed any limits imposed 108 * on classes by the virtual machine. For example, the VM may limit 109 * the number of interfaces that a class may implement to 65535; in 110 * that case, the size of the <code>interfaces</code> array must not 111 * exceed 65535. 112 * </ul> 113 * 114 * <p>If any of these restrictions are violated, 115 * <code>Proxy.getProxyClass</code> will throw an 116 * <code>IllegalArgumentException</code>. If the <code>interfaces</code> 117 * array argument or any of its elements are <code>null</code>, a 118 * <code>NullPointerException</code> will be thrown. 119 * 120 * <p>Note that the order of the specified proxy interfaces is 121 * significant: two requests for a proxy class with the same combination 122 * of interfaces but in a different order will result in two distinct 123 * proxy classes. 124 * 125 * @param loader the class loader to define the proxy class 126 * @param interfaces the list of interfaces for the proxy class 127 * to implement 128 * @return a proxy class that is defined in the specified class loader 129 * and that implements the specified interfaces 130 * @throws IllegalArgumentException if any of the restrictions on the 131 * parameters that may be passed to <code>getProxyClass</code> 132 * are violated 133 * @throws NullPointerException if the <code>interfaces</code> array 134 * argument or any of its elements are <code>null</code> 135 */ 136 public static Class<?> getProxyClass(final ClassLoader loader, final Class<?>... interfaces) 137 throws IllegalArgumentException { 138 139 // --- 140 // start of copy from java.lang.reflect.Proxy 141 // --- 142 if (interfaces.length > 65535) { 143 throw new IllegalArgumentException("interface limit exceeded"); 144 } 145 146 Class proxyClass = null; 147 148 /* collect interface names to use as key for proxy class cache */ 149 String[] interfaceNames = new String[interfaces.length]; 150 151 Set<Class> interfaceSet = new HashSet<Class>(); // for detecting duplicates 152 for (int i = 0; i < interfaces.length; i++) { 153 /* 154 * Verify that the class loader resolves the name of this 155 * interface to the same Class object. 156 */ 157 String interfaceName = interfaces[i].getName(); 158 Class interfaceClass = null; 159 try { 160 interfaceClass = Class.forName(interfaceName, false, loader); 161 } 162 catch (ClassNotFoundException e) { 163 } 164 if (interfaceClass != interfaces[i]) { 165 throw new IllegalArgumentException(interfaces[i] + " is not visible from class loader"); 166 } 167 168 /* 169 * Verify that the Class object actually represents an 170 * interface. 171 */ 172 if (!interfaceClass.isInterface()) { 173 throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface"); 174 } 175 176 /* 177 * Verify that this interface is not a duplicate. 178 */ 179 if (interfaceSet.contains(interfaceClass)) { 180 throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName()); 181 } 182 interfaceSet.add(interfaceClass); 183 184 interfaceNames[i] = interfaceName; 185 } 186 187 /* 188 * Using string representations of the proxy interfaces as 189 * keys in the proxy class cache (instead of their Class 190 * objects) is sufficient because we require the proxy 191 * interfaces to be resolvable by name through the supplied 192 * class loader, and it has the advantage that using a string 193 * representation of a class makes for an implicit weak 194 * reference to the class. 195 */ 196 Object key = Arrays.asList(interfaceNames); 197 198 /* 199 * Find or create the proxy class cache for the class loader. 200 */ 201 Map<Object,Reference<Class>> cache; 202 try{ 203 loaderToCacheLock.readLock().lock(); 204 cache = loaderToCache.get(loader); 205 }finally{ 206 loaderToCacheLock.readLock().unlock(); 207 } 208 // window of opportunity here between locks that would result in duplicate put(..) call 209 if (cache == null) { 210 try{ 211 loaderToCacheLock.writeLock().lock(); 212 cache = new ConcurrentHashMap<Object,Reference<Class>>(); 213 loaderToCache.put(loader, cache); 214 }finally{ 215 loaderToCacheLock.writeLock().unlock(); 216 } 217 } 218 219 // --- 220 // end of copy from java.lang.reflect.Proxy 221 // --- 222 223 Object value = cache.get(key); 224 if (value instanceof Reference) { 225 proxyClass = (Class) ((Reference) value).get(); 226 } 227 if (proxyClass == null) { 228 // we haven't yet used it. delegate to the real Proxy class. 229 proxyClass = java.lang.reflect.Proxy.getProxyClass(loader, interfaces); 230 cache.put(key, new WeakReference<Class>(proxyClass)); 231 } 232 return proxyClass; 233 234 } 235 236 // --- 237 // start of copy from java.lang.reflect.Proxy 238 // --- 239 /** 240 * Returns an instance of a proxy class for the specified interfaces 241 * that dispatches method invocations to the specified invocation 242 * handler. This method is equivalent to: 243 * <pre> 244 * Proxy.getProxyClass(loader, interfaces). 245 * getConstructor(new Class[] { InvocationHandler.class }). 246 * newInstance(new Object[] { handler }); 247 * </pre> 248 * 249 * <p><code>Proxy.newProxyInstance</code> throws 250 * <code>IllegalArgumentException</code> for the same reasons that 251 * <code>Proxy.getProxyClass</code> does. 252 * 253 * @param loader the class loader to define the proxy class 254 * @param interfaces the list of interfaces for the proxy class 255 * to implement 256 * @param h the invocation handler to dispatch method invocations to 257 * @return a proxy instance with the specified invocation handler of a 258 * proxy class that is defined by the specified class loader 259 * and that implements the specified interfaces 260 * @throws IllegalArgumentException if any of the restrictions on the 261 * parameters that may be passed to <code>getProxyClass</code> 262 * are violated 263 * @throws NullPointerException if the <code>interfaces</code> array 264 * argument or any of its elements are <code>null</code>, or 265 * if the invocation handler, <code>h</code>, is 266 * <code>null</code> 267 */ 268 public static Object newProxyInstance( 269 final ClassLoader loader, 270 final Class<?>[] interfaces, 271 final InvocationHandler h) throws IllegalArgumentException { 272 273 if (h == null) { 274 throw new NullPointerException(); 275 } 276 277 /* 278 * Look up or generate the designated proxy class. 279 */ 280 Class<?> cl = getProxyClass(loader, interfaces); 281 282 /* 283 * Invoke its constructor with the designated invocation handler. 284 */ 285 try { 286 Constructor<?> cons = cl.getConstructor(constructorParams); 287 return (Object) cons.newInstance(new Object[] { h }); 288 } 289 catch (NoSuchMethodException e) { 290 throw new InternalError(e.toString()); 291 } 292 catch (IllegalAccessException e) { 293 throw new InternalError(e.toString()); 294 } 295 catch (InstantiationException e) { 296 throw new InternalError(e.toString()); 297 } 298 catch (InvocationTargetException e) { 299 throw new InternalError(e.toString()); 300 } 301 } 302 // --- 303 // end of copy from java.lang.reflect.Proxy 304 // --- 305 306 }