package com.googlecode.totallylazy.structural;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.functions.Function1;
import com.googlecode.totallylazy.predicates.Predicate;
import com.googlecode.totallylazy.proxy.Proxy;
import com.googlecode.totallylazy.reflection.Methods;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import static com.googlecode.totallylazy.Maps.map;
import static com.googlecode.totallylazy.Monad.methods.sequenceO;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.reflection.Methods.allMethods;
public class Structural {
public static boolean instanceOf(final Class<?> structuralType, final Object instance) {
return castOption(structuralType, instance).isDefined();
}
public static <T> T cast(final Class<T> structuralType, final Object instance) {
return castOption(structuralType, instance).getOrThrow(new ClassCastException("Does not comply with structural contract"));
}
public static <T> Option<T> castOption(final Class<T> structuralType, final Object instance) {
return extractMethods(instance, structuralType).
map(methods -> Proxy.proxy(structuralType,
(o, method, objects) -> Methods.invoke(methods.get(method), instance, objects)));
}
private static <T> Option<Map<Method, Method>> extractMethods(final Object instance, Class<T> structuralType) {
final Sequence<Method> requiredMethods = sequence(structuralType.getMethods());
final Sequence<Method> instanceMethods = allMethods(instance.getClass());
return sequenceO(requiredMethods.map(findMethod(instanceMethods))).map(foundMethods -> map(requiredMethods.zip(foundMethods)));
}
private static Function1<Method, Option<Method>> findMethod(final Sequence<Method> instanceMethods) {
return requiredMethod -> findMethod(requiredMethod, instanceMethods);
}
private static Option<Method> findMethod(final Method requiredMethod, final Sequence<Method> instanceMethods) {
return instanceMethods.find(structuralMatch(requiredMethod));
}
private static Predicate<Method> structuralMatch(final Method required) {
final Sequence<Type> requiredParameters = sequence(required.getGenericParameterTypes());
final Sequence<Type> requiredReturnType = sequence(required.getGenericReturnType());
return objects -> objects.getName().equals(required.getName()) &&
sequence(objects.getGenericParameterTypes()).equals(requiredParameters) &&
sequence(objects.getGenericReturnType()).equals(requiredReturnType);
}
}