package com.lexicalscope.fluentreflection.dynamicproxy;
import static com.lexicalscope.fluentreflection.FluentReflection.*;
import static com.lexicalscope.fluentreflection.ReflectionMatchers.*;
import static org.hamcrest.Matchers.anything;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.hamcrest.Matcher;
import com.google.inject.TypeLiteral;
import com.lexicalscope.fluentreflection.FluentClass;
import com.lexicalscope.fluentreflection.FluentMember;
import com.lexicalscope.fluentreflection.FluentMethod;
import com.lexicalscope.fluentreflection.FluentReflection;
import com.lexicalscope.fluentreflection.IllegalAccessRuntimeException;
import com.lexicalscope.fluentreflection.InvocationTargetRuntimeException;
import com.lexicalscope.fluentreflection.ReflectionMatcher;
import com.lexicalscope.fluentreflection.ReflectionMatchers;
import com.lexicalscope.fluentreflection.SecurityException;
public abstract class Implementing<T> implements ProxyImplementation<T> {
private final class MethodInvoker implements MethodBody {
private final Object subject;
private final Method method;
private MethodInvoker(final Object subject, final Method method) {
this.subject = subject;
this.method = method;
}
@Override public void body() throws Exception {
try {
method.setAccessible(true);
returnValue(method.invoke(subject, args()));
} catch (final SecurityException e) {
throw new SecurityException("unable to invoke method in " + subject.getClass(), e);
} catch (final IllegalAccessException e) {
throw new IllegalAccessRuntimeException("unable to invoke method in "
+ subject.getClass(), e);
} catch (final InvocationTargetException e) {
e.getCause();
}
}
}
private static class MethodInvokationContext {
private final FluentMethod method;
private final Object[] args;
public Object result;
private final Object proxy;
public MethodInvokationContext(final Object proxy, final Method method, final Object[] args) {
this.method = FluentReflection.boundMethod(proxy, method);
this.args = args == null ? new Object[] {} : args;
this.proxy = proxy;
}
}
private final ThreadLocal<Boolean> proxyingMethod =
new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() {
return Boolean.FALSE;
}
};
private final ThreadLocal<Boolean> callingDefaultHandler =
new ThreadLocal<Boolean>() {
@Override protected Boolean initialValue() {
return Boolean.FALSE;
}
};
private final ThreadLocal<MethodInvokationContext> methodInvokationContext =
new ThreadLocal<MethodInvokationContext>();
private final Map<Matcher<? super FluentMethod>, MethodBody> registeredMethodHandlers =
new LinkedHashMap<Matcher<? super FluentMethod>, MethodBody>();
private final TypeLiteral<?> typeLiteral;
public Implementing() {
typeLiteral = TypeLiteral.get(getSuperclassTypeParameter(getClass()));
registerDeclaredMethods();
}
public Implementing(final Class<?> klass) {
typeLiteral = TypeLiteral.get(klass);
registerDeclaredMethods();
}
public Implementing(final FluentClass<?> klass) {
typeLiteral = TypeLiteral.get(klass.type());
registerDeclaredMethods();
}
private static Type getSuperclassTypeParameter(final Class<?> subclass) {
final Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class<?>) {
throw new RuntimeException("Missing type parameter.");
}
return ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
private void registerDeclaredMethods() {
for (final FluentMethod reflectedMethod : object(this).methods(
isPublicMethod().and(isDeclaredByStrictSubtypeOf(Implementing.class)))) {
if (reflectedMethod.argCount() == 0) {
registeredMethodHandlers.put(
anything(),
new MethodBody() {
@Override public void body() throws Throwable {
try {
reflectedMethod.call();
} catch (final InvocationTargetRuntimeException e) {
throw e.getExceptionThrownByInvocationTarget();
}
}
});
} else {
registeredMethodHandlers.put(
matcherForMethodSignature(reflectedMethod),
new MethodInvoker(this, reflectedMethod.member()));
}
}
}
@Override public final Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
proxyingMethod.set(Boolean.TRUE);
try
{
methodInvokationContext.set(new MethodInvokationContext(proxy, method, args));
try {
MethodBody defaultHandler = null;
for (final Entry<Matcher<? super FluentMethod>, MethodBody> registeredMethodHandler : registeredMethodHandlers
.entrySet()) {
if (registeredMethodHandler.getKey().matches(methodInvokationContext.get().method)) {
final MethodBody registeredImplementation = registeredMethodHandler.getValue();
try {
registeredImplementation.body();
} catch (final CannotProxyThisException e) {
continue;
} catch (final CallIfUnmatchedException e) {
defaultHandler = registeredImplementation;
continue;
}
return methodInvokationContext.get().result;
}
}
if (defaultHandler != null)
{
callingDefaultHandler.set(Boolean.TRUE);
try {
defaultHandler.body();
return methodInvokationContext.get().result;
} finally {
callingDefaultHandler.remove();
}
}
throw new UnsupportedOperationException("no implemention found for method " + method);
} finally {
methodInvokationContext.remove();
}
} finally {
proxyingMethod.remove();
}
}
@Override public final Class<?> proxiedInterface() {
return typeLiteral.getRawType();
}
public final MethodBinding<T> whenProxying(final Matcher<? super FluentMethod> methodMatcher) {
if (proxyingMethod.get())
{
if (!methodMatcher.matches(methodInvokationContext.get().method)) {
throw new CannotProxyThisException();
}
return null;
}
else
{
return new MethodBinding<T>() {
@Override public void execute(final MethodBody methodBody) {
registeredMethodHandlers.put(methodMatcher, methodBody);
}
@Override public void execute(final QueryMethod queryMethod) {
execute(new MethodInvoker(queryMethod, queryMethod.getClass().getDeclaredMethods()[0]));
}
};
}
}
public final void whenProxyingUnmatched()
{
if (!callingDefaultHandler.get())
{
throw new CallIfUnmatchedException();
}
}
public final void matchingSignature(final QueryMethod queryMethod) {
final FluentMethod userDefinedMethod = type(queryMethod.getClass()).methods().get(0);
whenProxying(matcherForMethodSignature(userDefinedMethod)).execute(queryMethod);
}
private ReflectionMatcher<FluentMember> matcherForMethodSignature(final FluentMethod userDefinedMethod) {
final List<FluentClass<?>> argumentTypes =
new ArrayList<FluentClass<?>>(userDefinedMethod.args());
final ReflectionMatcher<FluentMember> matchArguments =
hasReflectedArgumentList(argumentTypes);
final ReflectionMatcher<FluentMember> matchReturnType =
hasType(userDefinedMethod.type());
final ReflectionMatcher<FluentMember> matcher = matchArguments.and(matchReturnType);
return matcher;
}
public final String methodName() {
return methodInvokationContext.get().method.name();
}
public final FluentMethod method() {
return methodInvokationContext.get().method;
}
public final void returnValue(final Object value) {
methodInvokationContext.get().result = value;
}
public final Object[] args() {
return methodInvokationContext.get().args;
}
public final <T> T arg(final Class<T> type) {
return type.cast(args()[methodInvokationContext.get().method.indexOfArg(ReflectionMatchers.assignableTo(type))]);
}
public final Object proxy() {
return methodInvokationContext.get().proxy;
}
}