StaticProxy.java
1 |
/*
|
---|---|
2 |
** Module : StaticProxy.java
|
3 |
** Abstract : Utility class to create proxies which map interface methods to
|
4 |
** static methods.
|
5 |
**
|
6 |
** Copyright (c) 2013, Golden Code Development Corporation.
|
7 |
** ALL RIGHTS RESERVED. Use is subject to license terms.
|
8 |
**
|
9 |
** Golden Code Development Corporation
|
10 |
** CONFIDENTIAL
|
11 |
**
|
12 |
** -#- -I- --Date-- -----------------------Description------------------------
|
13 |
** 001 CA 20130112 Created initial version.
|
14 |
** 002 CA 20131015 Added helpers to query the static methods for a given proxy.
|
15 |
*/
|
16 |
|
17 |
package com.goldencode.proxy; |
18 |
|
19 |
import java.lang.reflect.*; |
20 |
import java.util.*; |
21 |
|
22 |
import com.goldencode.p2j.classloader.MultiClassLoader; |
23 |
|
24 |
/**
|
25 |
* Create a static proxy which maps all interface methods to static method
|
26 |
* in the specified classes.
|
27 |
* <p>
|
28 |
* To {@link #obtain(Class, Class[]) obtain a static proxy use }, specify the
|
29 |
* interface and a list of classes from where the static methods will be
|
30 |
* collected and mapped. If for an interface method an static method
|
31 |
* equivalent can not be found, then the proxy creation will fail with a
|
32 |
* {@link RuntimeException}.
|
33 |
* <p>
|
34 |
* All proxies are saved for the life-time of the application. If you need to
|
35 |
* remove a static proxy, use {@link #remove(Class)}. Else, the static proxy
|
36 |
* will be built on the first {@link #obtain(Class, Class[])} call, and any
|
37 |
* subsequent calls will return the cached proxy instance for that interface
|
38 |
* (from the {@link StaticProxy#staticProxies} map).
|
39 |
*/
|
40 |
public class StaticProxy |
41 |
implements InvocationHandler |
42 |
{ |
43 |
/** Cache of created static proxyies, by interface. */
|
44 |
private static Map<Class<?>, Object> staticProxies = |
45 |
new HashMap<Class<?>, Object>(); |
46 |
|
47 |
/** Map of interface methods to static equivalent. */
|
48 |
private final Map<Method, Method> im2sm = new HashMap<Method, Method>(); |
49 |
|
50 |
/** The interface for this static proxy. */
|
51 |
private final Class<?> ifc; |
52 |
|
53 |
/**
|
54 |
* Remove the static proxy for the given interface, if it exists.
|
55 |
*
|
56 |
* @param ifc
|
57 |
* The interface for which its static proxy needs to be removed.
|
58 |
*/
|
59 |
public static synchronized void remove(Class ifc) |
60 |
{ |
61 |
staticProxies.remove(ifc); |
62 |
} |
63 |
|
64 |
/**
|
65 |
* Check if the given instance is a static proxy. All following conditions must be met:
|
66 |
* <ol>
|
67 |
* <li>The instance's class must implement exactly one interface.</li>
|
68 |
* <li>The {@link #staticProxies} must have an entry for the implemented interface.</li>
|
69 |
* </ol>
|
70 |
*
|
71 |
* @param instance
|
72 |
* The instance to check.
|
73 |
*
|
74 |
* @return <code>true</code> if the instance is a static proxy.
|
75 |
*/
|
76 |
public static synchronized boolean isStaticProxy(Object instance) |
77 |
{ |
78 |
Class<?> cls = instance.getClass();
|
79 |
Class<?>[] ifcs = cls.getInterfaces(); |
80 |
Class<?> ifc = null; |
81 |
if (ifcs.length == 1) |
82 |
{ |
83 |
ifc = ifcs[0];
|
84 |
} |
85 |
|
86 |
return ifc != null && staticProxies.containsKey(ifc); |
87 |
} |
88 |
|
89 |
/**
|
90 |
* Get the static methods implementing the static proxy represented by the given instance. If
|
91 |
* the instance is not a static proxy, an empty list is returned.
|
92 |
*
|
93 |
* @param instance
|
94 |
* The proxy instance for which the static methods are needed.
|
95 |
*
|
96 |
* @return The list of static methods.
|
97 |
*/
|
98 |
public static List<Method> getStaticMethods(Object instance) |
99 |
{ |
100 |
List<Method> result = new ArrayList<Method>(); |
101 |
|
102 |
if (!isStaticProxy(instance))
|
103 |
{ |
104 |
// return an empty list; use StaticProxy.isStaticProxy to make sure this is a proxy
|
105 |
return result;
|
106 |
} |
107 |
|
108 |
StaticProxy proxy = (StaticProxy) Proxy.getInvocationHandler(instance);
|
109 |
|
110 |
result.addAll(proxy.im2sm.values()); |
111 |
|
112 |
return result;
|
113 |
} |
114 |
|
115 |
|
116 |
/**
|
117 |
* Get the static proxy instance for the given interface. If it does not
|
118 |
* exist, return <code>null</code>
|
119 |
*
|
120 |
* @param ifc
|
121 |
* The interface for which the proxy is needed.
|
122 |
*
|
123 |
* @return See above.
|
124 |
*/
|
125 |
public static synchronized <T> T obtain(Class<T> ifc) |
126 |
{ |
127 |
return (T) staticProxies.get(ifc);
|
128 |
} |
129 |
|
130 |
|
131 |
/**
|
132 |
* Get the static proxy instance for the given interface, if it was already
|
133 |
* created by a previous call. Else, create it first.
|
134 |
*
|
135 |
* @param ifc
|
136 |
* The proxy interface.
|
137 |
* @param staticClasses
|
138 |
* The classes which statically implement the proxy's methods.
|
139 |
*
|
140 |
* @return The static proxy instance.
|
141 |
*/
|
142 |
public static synchronized <T> T obtain(Class<T> ifc, |
143 |
Class<?>[] staticClasses) |
144 |
{ |
145 |
// for all static proxy there is only one instance
|
146 |
T proxy = (T) staticProxies.get(ifc); |
147 |
if (proxy == null) |
148 |
{ |
149 |
ClassLoader cl = MultiClassLoader.class.getClassLoader();
|
150 |
Class<?>[] ifaces = new Class<?>[] { ifc }; |
151 |
StaticProxy sp = new StaticProxy(ifc, staticClasses);
|
152 |
proxy = (T) Proxy.newProxyInstance(cl, ifaces, sp);
|
153 |
staticProxies.put(ifc, proxy); |
154 |
} |
155 |
|
156 |
return proxy;
|
157 |
} |
158 |
|
159 |
/**
|
160 |
* Create and initialize a new static proxy for the given interface and
|
161 |
* implementation classes.
|
162 |
* <p>
|
163 |
* If not all interface methods are implemented by the specified classes,
|
164 |
* a {@link RuntimeException} is thrown.
|
165 |
*
|
166 |
* @param ifc
|
167 |
* The proxy interface.
|
168 |
* @param staticClasses
|
169 |
* The classes which statically implement the proxy's methods.
|
170 |
*/
|
171 |
private StaticProxy(Class<?> ifc, Class<?>[] staticClasses) |
172 |
{ |
173 |
this.ifc = ifc;
|
174 |
init(staticClasses); |
175 |
} |
176 |
|
177 |
/**
|
178 |
* Invokes the specified method by determining its associated static
|
179 |
* implementation from the {@link #im2sm} map. If the method is
|
180 |
* {@link Object#equals(Object)}, then the proxy reference is compared to
|
181 |
* the references from <code>args[0]</code>; if the method is
|
182 |
* {@link Object#hashCode()}, then the {@link #ifc}'s hashcode is returned.
|
183 |
* if The method is not found in the {@link #im2sm} map, a
|
184 |
* {@link RuntimeException} is generated.
|
185 |
*
|
186 |
* @param proxy
|
187 |
* The proxy instance that the method was invoked on
|
188 |
* @param method
|
189 |
* The <code>Method</code> instance corresponding to
|
190 |
* the interface method invoked on the proxy instance.
|
191 |
* @param args
|
192 |
* An array of objects containing the values of the arguments
|
193 |
* passed in the method invocation on the proxy instance,
|
194 |
* or <code>null</code> if interface method takes no arguments.
|
195 |
*
|
196 |
* @return The value returned by the static method, depending on each
|
197 |
* API call.
|
198 |
*
|
199 |
* @throws Throwable
|
200 |
* If any exceptions are encounted during the static call.
|
201 |
*/
|
202 |
public Object invoke(Object proxy, Method method, Object[] args) |
203 |
throws Throwable |
204 |
{ |
205 |
if (Object.class == method.getDeclaringClass()) |
206 |
{ |
207 |
String name = method.getName();
|
208 |
if ("equals".equals(name)) |
209 |
{ |
210 |
return proxy == args[0]; |
211 |
} |
212 |
if ("hashCode".equals(name)) |
213 |
{ |
214 |
return ifc.hashCode();
|
215 |
} |
216 |
} |
217 |
|
218 |
Method mstatic = im2sm.get(method);
|
219 |
if (mstatic == null) |
220 |
{ |
221 |
throw new RuntimeException("Static method was not found for proxy" + |
222 |
" method " + method.toString() +
|
223 |
" in static proxy " +
|
224 |
proxy.getClass().getName()); |
225 |
} |
226 |
|
227 |
return mstatic.invoke(null, args); |
228 |
} |
229 |
|
230 |
/**
|
231 |
* Initialize this static proxy. If for an interface method there is more
|
232 |
* than one static method equivalent (or no static method) in the given
|
233 |
* classes, a {@link RuntimeException} is thrown.
|
234 |
*
|
235 |
* @param staticClasses
|
236 |
* The classes which statically implement the proxy's methods.
|
237 |
*/
|
238 |
private void init(Class<?>[] staticClasses) |
239 |
{ |
240 |
// method signature to static class method
|
241 |
Map<String, Method>[] s2cm = new HashMap[staticClasses.length]; |
242 |
for (int i = 0; i < staticClasses.length; i++) |
243 |
{ |
244 |
Method[] methods = staticClasses[i].getMethods(); |
245 |
s2cm[i] = new HashMap<String, Method>(); |
246 |
for (int j = 0; j < methods.length; j++) |
247 |
{ |
248 |
Method m = methods[j];
|
249 |
int mods = m.getModifiers();
|
250 |
if (!Modifier.isStatic(mods) || !Modifier.isPublic(mods)) |
251 |
{ |
252 |
continue;
|
253 |
} |
254 |
|
255 |
s2cm[i].put(getSignature(m), m); |
256 |
} |
257 |
} |
258 |
|
259 |
String ifcName = ifc.getName();
|
260 |
Method[] methods = ifc.getMethods(); |
261 |
for (int i = 0; i < methods.length; i++) |
262 |
{ |
263 |
Method im = methods[i];
|
264 |
|
265 |
String signature = getSignature(im);
|
266 |
Method mstatic = null; |
267 |
for (int j = 0; j < staticClasses.length; j++) |
268 |
{ |
269 |
Method m = s2cm[j].get(signature);
|
270 |
if (m != null) |
271 |
{ |
272 |
if (mstatic != null) |
273 |
{ |
274 |
String class1 = staticClasses[j].getName();
|
275 |
String class2 = mstatic.getDeclaringClass().getName();
|
276 |
final String msg = |
277 |
"Error creating static proxy for interface %s: " +
|
278 |
"ambigous static method %s (found in %s and %s).";
|
279 |
String err = String.format(msg, |
280 |
ifcName, |
281 |
signature, |
282 |
class1, |
283 |
class2); |
284 |
|
285 |
throw new RuntimeException(err); |
286 |
} |
287 |
|
288 |
mstatic = m; |
289 |
} |
290 |
} |
291 |
|
292 |
if (mstatic == null) |
293 |
{ |
294 |
final String msg = |
295 |
"Error creating static proxy for interface %s: " +
|
296 |
"static method %s was not found in the given classes.";
|
297 |
throw new RuntimeException(String.format(msg, ifcName, signature)); |
298 |
} |
299 |
|
300 |
im2sm.put(methods[i], mstatic); |
301 |
} |
302 |
} |
303 |
|
304 |
/**
|
305 |
* Get a string representation of the given method, including the return
|
306 |
* type, the method name and the type for all parameters.
|
307 |
*
|
308 |
* @param m
|
309 |
* The method for which the signature is neede.
|
310 |
*
|
311 |
* @return See above.
|
312 |
*/
|
313 |
private String getSignature(Method m) |
314 |
{ |
315 |
StringBuilder sb = new StringBuilder(); |
316 |
// emit return type
|
317 |
sb.append(m.getReturnType().getName()).append(" ");
|
318 |
|
319 |
// emit name
|
320 |
sb.append(m.getName()); |
321 |
// emit parameter types
|
322 |
sb.append("(");
|
323 |
for (int i = 0; i < m.getParameterTypes().length; i++) |
324 |
{ |
325 |
if (i > 0) |
326 |
{ |
327 |
sb.append(", ");
|
328 |
} |
329 |
|
330 |
sb.append(m.getParameterTypes()[i].getName()); |
331 |
} |
332 |
sb.append(")");
|
333 |
|
334 |
return sb.toString();
|
335 |
} |
336 |
|
337 |
public static void main(String[] args) |
338 |
{ |
339 |
Object o = obtain(Ai.class, new Class[] {Bi.class}); |
340 |
System.out.println(o.getClass().getInterfaces()[0].getName()); |
341 |
System.out.println(isStaticProxy(o));
|
342 |
System.out.println(getStaticMethods(o));
|
343 |
} |
344 |
|
345 |
public static interface Ai |
346 |
{ |
347 |
public void bla(); |
348 |
} |
349 |
|
350 |
public static class Bi |
351 |
{ |
352 |
public static void bla() {}; |
353 |
} |
354 |
} |