View Javadoc

1   package com.lexicalscope.fluentreflection.dynamicproxy;
2   
3   import static com.lexicalscope.fluentreflection.FluentReflection.*;
4   import static com.lexicalscope.fluentreflection.ReflectionMatchers.*;
5   import static org.hamcrest.Matchers.anything;
6   
7   import java.lang.reflect.InvocationTargetException;
8   import java.lang.reflect.Method;
9   import java.lang.reflect.ParameterizedType;
10  import java.lang.reflect.Type;
11  import java.util.ArrayList;
12  import java.util.LinkedHashMap;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Map.Entry;
16  
17  import org.hamcrest.Matcher;
18  
19  import com.google.inject.TypeLiteral;
20  import com.lexicalscope.fluentreflection.FluentReflection;
21  import com.lexicalscope.fluentreflection.IllegalAccessRuntimeException;
22  import com.lexicalscope.fluentreflection.InvocationTargetRuntimeException;
23  import com.lexicalscope.fluentreflection.FluentMember;
24  import com.lexicalscope.fluentreflection.FluentClass;
25  import com.lexicalscope.fluentreflection.FluentMethod;
26  import com.lexicalscope.fluentreflection.ReflectionMatcher;
27  import com.lexicalscope.fluentreflection.SecurityException;
28  
29  public abstract class Implementing<T> implements ProxyImplementation<T> {
30      private final class MethodInvoker implements MethodBody {
31          private final Object subject;
32          private final Method method;
33  
34          private MethodInvoker(final Object subject, final Method method) {
35              this.subject = subject;
36              this.method = method;
37          }
38  
39          @Override public void body() throws Exception {
40              try {
41                  method.setAccessible(true);
42                  returnValue(method.invoke(subject, args()));
43              } catch (final SecurityException e) {
44                  throw new SecurityException("unable to invoke method in " + subject.getClass(), e);
45              } catch (final IllegalAccessException e) {
46                  throw new IllegalAccessRuntimeException("unable to invoke method in "
47                          + subject.getClass(), e);
48              } catch (final InvocationTargetException e) {
49                  e.getCause();
50              }
51          }
52      }
53  
54      private static class MethodInvokationContext {
55          private final FluentMethod method;
56          private final Object[] args;
57          public Object result;
58          private final Object proxy;
59  
60          public MethodInvokationContext(final Object proxy, final Method method, final Object[] args) {
61              this.method = FluentReflection.method(method, proxy);
62              this.args = args == null ? new Object[] {} : args;
63              this.proxy = proxy;
64          }
65      }
66  
67      private final ThreadLocal<Boolean> proxyingMethod =
68              new ThreadLocal<Boolean>() {
69                  @Override protected Boolean initialValue() {
70                      return Boolean.FALSE;
71                  }
72              };
73      private final ThreadLocal<Boolean> callingDefaultHandler =
74              new ThreadLocal<Boolean>() {
75                  @Override protected Boolean initialValue() {
76                      return Boolean.FALSE;
77                  }
78              };
79      private final ThreadLocal<MethodInvokationContext> methodInvokationContext =
80              new ThreadLocal<MethodInvokationContext>();
81  
82      private final Map<Matcher<? super FluentMethod>, MethodBody> registeredMethodHandlers =
83              new LinkedHashMap<Matcher<? super FluentMethod>, MethodBody>();
84  
85      private final TypeLiteral<?> typeLiteral;
86  
87      public Implementing() {
88          typeLiteral = TypeLiteral.get(getSuperclassTypeParameter(getClass()));
89          registerDeclaredMethods();
90      }
91  
92      public Implementing(final Class<?> klass) {
93          typeLiteral = TypeLiteral.get(klass);
94          registerDeclaredMethods();
95      }
96  
97      public Implementing(final FluentClass<?> klass) {
98          typeLiteral = TypeLiteral.get(klass.type());
99          registerDeclaredMethods();
100     }
101 
102     private static Type getSuperclassTypeParameter(final Class<?> subclass) {
103         final Type superclass = subclass.getGenericSuperclass();
104         if (superclass instanceof Class<?>) {
105             throw new RuntimeException("Missing type parameter.");
106         }
107         return ((ParameterizedType) superclass).getActualTypeArguments()[0];
108     }
109 
110     private void registerDeclaredMethods() {
111         for (final FluentMethod reflectedMethod : object(this).methods(
112                 isPublicMethod().and(isDeclaredByStrictSubtypeOf(Implementing.class)))) {
113             if (reflectedMethod.argCount() == 0) {
114                 registeredMethodHandlers.put(
115                         anything(),
116                         new MethodBody() {
117                             @Override public void body() throws Throwable {
118                                 try {
119                                     reflectedMethod.callRaw();
120                                 } catch (final InvocationTargetRuntimeException e) {
121                                     throw e.getExceptionThrownByInvocationTarget();
122                                 }
123                             }
124                         });
125             } else {
126                 registeredMethodHandlers.put(
127                         matcherForMethodSignature(reflectedMethod),
128                         new MethodInvoker(this, reflectedMethod.member()));
129             }
130         }
131     }
132 
133     @Override public final Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
134         proxyingMethod.set(Boolean.TRUE);
135         try
136         {
137             methodInvokationContext.set(new MethodInvokationContext(proxy, method, args));
138             try {
139                 MethodBody defaultHandler = null;
140                 for (final Entry<Matcher<? super FluentMethod>, MethodBody> registeredMethodHandler : registeredMethodHandlers
141                         .entrySet()) {
142                     if (registeredMethodHandler.getKey().matches(methodInvokationContext.get().method)) {
143                         final MethodBody registeredImplementation = registeredMethodHandler.getValue();
144                         try {
145                             registeredImplementation.body();
146                         } catch (final CannotProxyThisException e) {
147                             continue;
148                         } catch (final CallIfUnmatchedException e) {
149                             defaultHandler = registeredImplementation;
150                             continue;
151                         }
152                         return methodInvokationContext.get().result;
153                     }
154                 }
155                 if (defaultHandler != null)
156                 {
157                     callingDefaultHandler.set(Boolean.TRUE);
158                     try {
159                         defaultHandler.body();
160                         return methodInvokationContext.get().result;
161                     } finally {
162                         callingDefaultHandler.set(Boolean.FALSE);
163                     }
164                 }
165                 throw new UnsupportedOperationException("no implemention found for method " + method);
166             } finally {
167                 methodInvokationContext.set(null);
168             }
169         } finally {
170             proxyingMethod.set(Boolean.FALSE);
171         }
172     }
173 
174     @Override public final Class<?> proxiedInterface() {
175         return typeLiteral.getRawType();
176     }
177 
178     public final MethodBinding<T> whenProxying(final Matcher<? super FluentMethod> methodMatcher) {
179         if (proxyingMethod.get())
180         {
181             if (!methodMatcher.matches(methodInvokationContext.get().method)) {
182                 throw new CannotProxyThisException();
183             }
184             return null;
185         }
186         else
187         {
188             return new MethodBinding<T>() {
189                 @Override public void execute(final MethodBody methodBody) {
190                     registeredMethodHandlers.put(methodMatcher, methodBody);
191                 }
192 
193                 @Override public void execute(final QueryMethod queryMethod) {
194                     execute(new MethodInvoker(queryMethod, queryMethod.getClass().getDeclaredMethods()[0]));
195                 }
196             };
197         }
198     }
199 
200     public final void whenProxyingUnmatched()
201     {
202         if (!callingDefaultHandler.get())
203         {
204             throw new CallIfUnmatchedException();
205         }
206     }
207 
208     public final void matchingSignature(final QueryMethod queryMethod) {
209         final FluentMethod userDefinedMethod = type(queryMethod.getClass()).methods().get(0);
210 
211         whenProxying(matcherForMethodSignature(userDefinedMethod)).execute(queryMethod);
212     }
213 
214     private ReflectionMatcher<FluentMember> matcherForMethodSignature(final FluentMethod userDefinedMethod) {
215         final List<FluentClass<?>> argumentTypes =
216                 new ArrayList<FluentClass<?>>(userDefinedMethod.args());
217 
218         final ReflectionMatcher<FluentMember> matchArguments =
219                 hasReflectedArgumentList(argumentTypes);
220 
221         final ReflectionMatcher<FluentMember> matchReturnType =
222                 hasType(userDefinedMethod.type());
223 
224         final ReflectionMatcher<FluentMember> matcher = matchArguments.and(matchReturnType);
225         return matcher;
226     }
227 
228     public final String methodName() {
229         return methodInvokationContext.get().method.name();
230     }
231 
232     public final FluentMethod method() {
233         return methodInvokationContext.get().method;
234     }
235 
236     public final void returnValue(final Object value) {
237         methodInvokationContext.get().result = value;
238     }
239 
240     public final Object[] args() {
241         return methodInvokationContext.get().args;
242     }
243 
244     public final Object proxy() {
245         return methodInvokationContext.get().proxy;
246     }
247 }