package com.googlecode.totallylazy.reflection;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.functions.Lazy;
import com.googlecode.totallylazy.predicates.LogicalPredicate;
import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import static com.googlecode.totallylazy.Exceptions.optional;
import static com.googlecode.totallylazy.Unchecked.cast;
import static com.googlecode.totallylazy.functions.Lazy.lazy;
import static com.googlecode.totallylazy.predicates.Predicates.and;
import static com.googlecode.totallylazy.reflection.Fields.name;
import static com.googlecode.totallylazy.predicates.Predicates.is;
import static com.googlecode.totallylazy.predicates.Predicates.where;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Strings.startsWith;
import static com.googlecode.totallylazy.reflection.Methods.allMethods;
import static com.googlecode.totallylazy.reflection.Methods.invokeOn;
import static com.googlecode.totallylazy.reflection.Methods.modifier;
import static com.googlecode.totallylazy.reflection.Methods.returnType;
import static com.googlecode.totallylazy.reflection.StackFrames.stackFrames;
import static com.googlecode.totallylazy.reflection.Types.matches;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.STATIC;
public class Reflection {
private final static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
private final static Lazy<Constructor<?>> constructor = lazy(() -> Object.class.getConstructor((Class[]) null));
public static LogicalPredicate<Integer> synthetic = new LogicalPredicate<Integer>() {
@Override
public boolean matches(Integer mod) {
return (mod & 0x00001000) != 0;
}
};
public static Object enclosingInstance(Object innerClass) {
try {
Field field = Fields.syntheticFields(innerClass.getClass()).
find(where(name, startsWith("this$"))).
get();
field.setAccessible(true);
return field.get(innerClass);
} catch (Exception e) {
throw new UnsupportedOperationException(e);
}
}
public static StackFrame enclosingFrame() {
return stackFrames().tail().head();
}
public static Method enclosingMethod() {
return stackFrames().tail().head().method();
}
public static Constructor<?> enclosingConstructor() {
return stackFrames().tail().head().constructor();
}
public static StackFrame callingFrame() {
return stackFrames().drop(2).head();
}
public static Method callingMethod() {
return stackFrames().drop(2).head().method();
}
public static Constructor<?> callingConstructor() {
return stackFrames().drop(2).head().constructor();
}
public static Class<?> callingClass() {
return stackFrames().drop(2).head().aClass();
}
private static Map<Class<?>, Class<?>> primitiveToBoxed = new HashMap<>();
private static Map<Class<?>, Class<?>> boxedToPrimative = new HashMap<>();
private static void map(Class<?> primitive, Class<?> boxed) {
if (!primitive.isPrimitive())
throw new IllegalArgumentException("Class must be primitive but was " + primitive);
primitiveToBoxed.put(primitive, boxed);
boxedToPrimative.put(boxed, primitive);
}
static {
map(void.class, Void.class);
map(char.class, Character.class);
map(boolean.class, Boolean.class);
map(byte.class, Byte.class);
map(short.class, Short.class);
map(int.class, Integer.class);
map(long.class, Long.class);
map(float.class, Float.class);
map(double.class, Double.class);
}
public static Class<?> box(Class<?> primitive) {
if (!primitive.isPrimitive())
throw new IllegalArgumentException("Class must be primitive but was " + primitive);
return primitiveToBoxed.get(primitive);
}
public static Class<?> unbox(Class<?> boxed) {
Class<?> aClass = boxedToPrimative.get(boxed);
if (aClass == null) throw new IllegalArgumentException("Class must be boxed type but was " + boxed);
return aClass;
}
/**
* Creates object by calling any constructor (private or otherwise)
**/
public static <T> T newInstance(Class<T> recordType, Object... args) throws Exception {
Constructor<T> constructor = recordType.getDeclaredConstructor(sequence(args).map(Object::getClass).toArray(Class.class));
constructor.setAccessible(true);
return constructor.newInstance(args);
}
/**
* Evil creates object without calling constructors or field initialises
**/
public static <T> T create(Class aClass) throws ReflectiveOperationException {
Constructor<?> constructor = reflectionFactory.newConstructorForSerialization(aClass, Reflection.constructor.value());
return cast(constructor.newInstance());
}
public static Class<?> defineClass(ClassLoader classLoader, String name, byte[] bytes) {
return Methods.allMethods(classLoader.getClass()).
filter(m -> m.getName().equals("defineClass")).
find(m -> sequence(m.getParameterTypes()).equals(sequence(String.class, byte[].class, int.class, int.class))).
map(m -> {
m.setAccessible(true);
return (Class<?>) m.invoke(classLoader, name, bytes, 0, bytes.length);
}).get();
}
/**
* Finds a static method on actualType that takes a parameter of the same type as value and return
* an instance of actualType.
* <p>
* Calls that static method with value and returns the result.
**/
public static <T> T valueOf(Class<T> actualType, Object value) {
Sequence<Method> candidates = allMethods(actualType)
.filter(and(modifier(STATIC),
where(m -> m.getParameterTypes().length, is(1)))).
filter(and(
where(returnType(), matches(actualType)),
where(m -> m.getParameterTypes()[0], (t) -> t.isAssignableFrom(value.getClass()))));
if (candidates.isEmpty()) {
throw new NoSuchElementException(format(
"Cannot create %s from '%s' (%s). You need to add a static constructor method to %s that accepts a %s",
actualType.getCanonicalName(), value, value.getClass().getCanonicalName(),
actualType.getSimpleName(), value.getClass().getSimpleName()));
}
return actualType.cast(candidates.pick(optional(invokeOn(null, value))));
}
}