package de.take_weiland.mods.commons.reflect;
import com.google.common.base.Throwables;
import com.google.common.io.Files;
import com.google.common.reflect.Reflection;
import de.take_weiland.mods.commons.OverrideSetter;
import de.take_weiland.mods.commons.asm.ASMUtils;
import de.take_weiland.mods.commons.asm.info.ClassInfo;
import de.take_weiland.mods.commons.internal.SevenCommonsLoader;
import org.objectweb.asm.tree.ClassNode;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.invoke.MethodHandles.publicLookup;
/**
* <p>Reflection utilities.</p>
* <p>This class provides an alternative to using traditional reflection to access inaccessible fields and methods.</p>
*/
@ParametersAreNonnullByDefault
public final class SCReflection {
private static final Logger logger = SevenCommonsLoader.scLogger("Reflection");
private static final ReflectionStrategy strategy = selectStrategy();
private static final AtomicInteger nextDynClassId = new AtomicInteger(0);
private static final boolean DEBUG = Boolean.getBoolean("sevencommons.reflect.debug");
/**
* <p>Create an object that implements the given Accessor Interface. The result of this method should be permanently cached,
* because this operation is likely to be relatively expensive.</p>
* <p>The given class must be an interface and must not extend any other interfaces.
* All methods must be marked with one of {@link de.take_weiland.mods.commons.reflect.Getter},
* {@link de.take_weiland.mods.commons.reflect.Setter}, {@link de.take_weiland.mods.commons.reflect.Invoke} or {@link de.take_weiland.mods.commons.reflect.Construct}
* and comply with the respective contract.</p>
*
* @param iface the Accessor Interface
* @return a newly created object, implementing the given interface
*/
public static <T> T createAccessor(Class<T> iface) {
try {
return strategy.createAccessor(iface);
} catch (IllegalAccessorException e) {
throw e;
} catch (Exception e) {
throw new IllegalAccessorException("Failed to parse accessor interface " + iface.getName(), e);
}
}
public static Method findSetter(Method getter) {
checkArgument(getter.getParameterTypes().length == 0);
Class<?> type = getter.getReturnType();
checkArgument(type != void.class);
String setterName;
OverrideSetter ann = getter.getAnnotation(OverrideSetter.class);
if (ann != null) {
setterName = ann.value();
} else {
String getterName = getter.getName();
if (getterName.startsWith("get") && getterName.length() >= 4 && Character.isUpperCase(getterName.charAt(3))) {
setterName = "set" + getterName.substring(3);
} else if (getterName.startsWith("is") && getterName.length() >= 3 && Character.isUpperCase(getterName.charAt(2))) {
setterName = "set" + getterName.substring(2);
} else if (isScala(getter.getDeclaringClass())) {
setterName = getterName + "_$eq";
} else {
setterName = getterName;
}
}
try {
return getter.getDeclaringClass().getDeclaredMethod(setterName, type);
} catch (NoSuchMethodException e) {
// no setter
return null;
}
}
private static Class<? extends Annotation> scalaSigClass;
private static Class<? extends Annotation> scalaLongSigClass;
/**
* <p>Tries to determine if the given class is a scala class.</p>
* <p>If this method returns true, the class is most definitely a scala class. If it however returns false,
* that is not an indication that the class is <i>not</i> a scala class.</p>
* @param clazz the class to check
* @return true if the class is a scala class
*/
public static boolean isScala(Class<?> clazz) {
try {
if (scalaSigClass == null) {
scalaSigClass = Class.forName("scala.reflect.ScalaSignature")
.asSubclass(Annotation.class);
scalaLongSigClass = Class.forName("scala.reflect.ScalaLongSignature")
.asSubclass(Annotation.class);
}
} catch (ClassNotFoundException e) {
// ignored
}
// if these classes are not found use a dummy annotation that will never be present
// to avoid checking Class.forName over and over again
if (scalaSigClass == null) {
scalaSigClass = Override.class;
}
if (scalaLongSigClass == null) {
scalaLongSigClass = Override.class;
}
return clazz.isAnnotationPresent(scalaSigClass)
|| clazz.isAnnotationPresent(scalaLongSigClass)
|| ClassInfo.of(clazz).getSourceFile().endsWith(".scala");
}
private static final MethodHandle CLASS_LOADER_DEFINE;
static {
try {
Method define = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
define.setAccessible(true);
CLASS_LOADER_DEFINE = publicLookup().unreflect(define);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Could not find defineClass in ClassLoader!");
} catch (IllegalAccessException e) {
throw new AssertionError("impossible");
}
}
/**
* <p>Define a new Class specified by the given bytes.</p>
*
* @param bytes the bytes describing the class
* @return the defined class
*/
public static Class<?> defineDynamicClass(byte[] bytes) {
return defineDynamicClass(bytes, SCReflection.class);
}
/**
* <p>Define a new Class specified by the given bytes.</p>
*
* @param bytes the bytes describing the class
* @param context the class which to use as the context
* @return the defined class
*/
public static Class<?> defineDynamicClass(byte[] bytes, Class<?> context) {
if (DEBUG) {
try {
ClassNode node = ASMUtils.getThinClassNode(bytes);
File file = new File("sevencommonsdyn/" + node.name + ".class");
Files.createParentDirs(file);
OutputStream out = new FileOutputStream(file);
out.write(bytes);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
ClassLoader cl = context.getClassLoader();
Class<?> clazz;
try {
clazz = (Class<?>) CLASS_LOADER_DEFINE.invokeExact(cl, (String) null, bytes, 0, bytes.length);
} catch (Throwable t) {
throw Throwables.propagate(t);
}
Reflection.initialize(clazz);
return clazz;
}
/**
* <p>Get a new unique class name as an internal name.</p>
*
* @return a unique name
*/
public static String nextDynamicClassName() {
return nextDynamicClassName("de/take_weiland/mods/commons/reflect/dyn");
}
/**
* <p>Get a new unique class name in the given package as an internal name.</p>
*
* @param pkg the package
* @return a unique name
*/
public static String nextDynamicClassName(Package pkg) {
return nextDynamicClassName(pkg.getName());
}
/**
* <p>Get a new unique class name in the given package as an internal name.</p>
*
* @param pkg the package
* @return a unique name
*/
public static String nextDynamicClassName(String pkg) {
return ASMUtils.internalName(pkg) + "/_sc_dyn_" + nextDynClassId.getAndIncrement();
}
private static ReflectionStrategy selectStrategy() {
return new MethodHandleStrategy();
}
private SCReflection() { }
}