Project

General

Profile

StaticProxy.java

Constantin Asofiei, 10/15/2013 02:50 AM

Download (11.6 KB)

 
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
}