View Javadoc

1   package com.atlassian.core.test.util;
2   
3   import java.lang.reflect.*;
4   import java.util.*;
5   
6   /**
7    * Utility for getting a proxy that delegates to a list of delegates in order. The delegates are queried whether they
8    * implement a particular method and if so, it is called and the result returned. If they don't, the next handler is
9    * queried and so forth. If none of the objects implement the interface then the UnimplementedMethodhandler is invoked.
10   * None of the delegates need to actually implement the proxied interface, only have a method with the same signature.
11   * <p>
12   * So for instance, given:
13   *
14   * <pre>
15   * interface MyInterface
16   * {
17   *     String getString();
18   *
19   *     Long getLong();
20   *
21   *     Integer getInteger();
22   * }
23   * </pre>
24   *
25   * you can create a proxy for this interface without needing to implement the whole thing:
26   *
27   * <pre>
28   *      Object mocker = new Object()
29   *      {
30   *          public String myMethod()
31   *          {
32   *              return &quot;proxied&quot;;
33   *          }
34   *      }
35   *      MyInterface mock = (MyInterface) DuckTypeProxy.getProxy(MyInterface.class, mocker);
36   *      System.out.println(mock.getString());
37   * </pre>
38   *
39   * prints "proxied" to the console.
40   * <p>
41   * There are {@link UnimplementedMethodHandler facilities for handling unimplemented methods} either by
42   * {@link #RETURN_NULL returning null} or {@link #THROW throwing exceptions by default}.
43   */
44  public class DuckTypeProxy
45  {
46      // -------------------------------------------------------------------------------------------------- static members
47  
48      /**
49       * Return null if the method cannot be found.
50       */
51      public static UnimplementedMethodHandler RETURN_NULL = new UnimplementedMethodHandler()
52      {
53          public Object methodNotImplemented(Method method, Object[] args)
54          {
55              return null;
56          }
57      };
58  
59      /**
60       * Throw an exception if the method cannot be found.
61       */
62      public static UnimplementedMethodHandler THROW = new UnimplementedMethodHandler()
63      {
64          public Object methodNotImplemented(Method method, Object[] args)
65          {
66              throw new UnsupportedOperationException(method.toString());
67          }
68      };
69  
70      // ------------------------------------------------------------------------------------------------- factory methods
71  
72      /**
73       * Get a Proxy that checks each of the enclosed objects and calls them if they have a Method of the same signature.
74       * By default this will {@link #THROW throw an UnsupportedOperationException} if the method is not implemented
75       */
76      /* <T> */public static/* T */Object getProxy(/* Class<T> */Class implementingClass, List delegates)
77      {
78          return getProxy(new Class[] { implementingClass }, delegates);
79      }
80  
81      /**
82       * Get a Proxy that checks each of the enclosed objects and calls them if they have a Method of the same signature.
83       */
84      /* <T> */public static/* T */Object getProxy(/* Class<T> */Class implementingClass, List delegates,
85                                                   UnimplementedMethodHandler unimplementedMethodHandler)
86      {
87          return getProxy(new Class[] { implementingClass }, delegates, unimplementedMethodHandler);
88      }
89  
90      /**
91       * Get a Proxy that checks each of the enclosed objects and calls them if they have a Method of the same signature.
92       * Uses the {@link #THROW} {@link UnimplementedMethodHandler}
93       */
94      public static Object getProxy(Class[] implementingClasses, List delegates)
95      {
96          return getProxy(implementingClasses, delegates, THROW);
97      }
98  
99      public static Object getProxy(Class[] implementingClasses, List delegates, UnimplementedMethodHandler unimplementedMethodHandler)
100     {
101         return Proxy.newProxyInstance(DuckTypeProxy.class.getClassLoader(), implementingClasses, new DuckTypeInvocationHandler(delegates,
102             unimplementedMethodHandler));
103     }
104 
105     /**
106      * Get a Proxy that checks the enclosed object and calls it if it has a Method of the same signature.
107      * By default this will {@link #THROW throw an UnsupportedOperationException} if the method is not implemented
108      */
109     /* <T> */public static/* T */Object getProxy(/* Class<T> */Class implementingClass, Object delegate)
110     {
111         return getProxy(new Class[] { implementingClass }, Arrays.asList(new Object[] {delegate}));
112     }
113 
114     // --------------------------------------------------------------------------------------------------- inner classes
115 
116     public interface UnimplementedMethodHandler
117     {
118         Object methodNotImplemented(Method method, Object[] args);
119     }
120 
121     /**
122      * The invocation handler that keeps the references to the delegate objects.
123      */
124     private static class DuckTypeInvocationHandler implements InvocationHandler
125     {
126         private final List delegates;
127         private final UnimplementedMethodHandler unimplementedMethodHandler;
128 
129         DuckTypeInvocationHandler(List handlers, UnimplementedMethodHandler unimplementedMethodHandler)
130         {
131             this.delegates = Collections.unmodifiableList(new ArrayList(handlers));
132             this.unimplementedMethodHandler = unimplementedMethodHandler;
133         }
134 
135         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
136         {
137             for (Iterator it = delegates.iterator(); it.hasNext();)
138             {
139                 Object handler = it.next();
140                 Method duckTypeMethod;
141                 try
142                 {
143                     duckTypeMethod = handler.getClass().getMethod(method.getName(), method.getParameterTypes());
144                 }
145                 catch (NoSuchMethodException ignoreAndTryNext)
146                 {
147                     continue;
148                 }
149                 try
150                 {
151                     duckTypeMethod.setAccessible(true);
152                     return duckTypeMethod.invoke(handler, args);
153                 }
154                 catch (IllegalArgumentException ignoreAndContinue)
155                 {
156                     // ignored
157                 }
158                 catch (IllegalAccessException ignoreAndContinue)
159                 {
160                     // ignored
161                 }
162                 catch (InvocationTargetException e)
163                 {
164                     throw e.getCause();
165                 }
166             }
167             return unimplementedMethodHandler.methodNotImplemented(method, args);
168         }
169     }
170 }