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 }