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 }