View Javadoc
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 }