package de.take_weiland.mods.commons.asm.info; import de.take_weiland.mods.commons.asm.ASMUtils; import de.take_weiland.mods.commons.asm.MissingClassException; import de.take_weiland.mods.commons.internal.exclude.ClassInfoUtil; import net.minecraft.launchwrapper.Launch; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import static java.lang.invoke.MethodHandles.publicLookup; import static org.objectweb.asm.Opcodes.*; /** * <p>Some information about a class, obtain via {@link ClassInfo#of(String)}, {@link ClassInfo#of(Class)} or {@link ClassInfo#of(org.objectweb.asm.tree.ClassNode)}.</p> * * @author diesieben07 */ public abstract class ClassInfo extends HasModifiers { private ClassInfo zuper; // limit subclasses to this package ClassInfo() { } /** * <p>Create a {@code ClassInfo} representing the given class.</p> * * @param clazz the Class * @return a ClassInfo */ public static ClassInfo of(Class<?> clazz) { return new ClassInfoReflect(clazz); } /** * <p>Create a {@code ClassInfo} representing the given ClassNode.</p> * * @param clazz the ClassNode * @return a ClassInfo */ public static ClassInfo of(ClassNode clazz) { return new ClassInfoASM(clazz); } /** * <p>Create a {@code ClassInfo} representing the given class.</p> * <p>This method will try to avoid loading actual classes into the JVM, but will instead use the ASM library * to analyze the raw class bytes if possible.</p> * * @param className the internal or binary name representing the class * @return a ClassInfo * @throws de.take_weiland.mods.commons.asm.MissingClassException if the class could not be found */ public static ClassInfo of(String className) { return of(Type.getObjectType(ASMUtils.internalName(className))); } /** * <p>Create a {@code ClassInfo} representing the given Type.</p> * <p>This method will try to avoid loading actual classes into the JVM, but will instead use the ASM library * to analyze the raw class bytes if possible.</p> * * @param type a Type representing the class to load, must not be a method type * @return a ClassInfo * @throws de.take_weiland.mods.commons.asm.MissingClassException if the class could not be found */ public static ClassInfo of(Type type) { switch (type.getSort()) { case Type.ARRAY: Type rootType = type.getElementType(); int dimensions = type.getDimensions(); return new ClassInfoArray(dimensions, of(rootType)); case Type.OBJECT: return ofObject(type.getClassName()); case Type.VOID: return of(void.class); case Type.BOOLEAN: return of(boolean.class); case Type.BYTE: return of(byte.class); case Type.SHORT: return of(short.class); case Type.CHAR: return of(char.class); case Type.INT: return of(int.class); case Type.LONG: return of(long.class); case Type.FLOAT: return of(float.class); case Type.DOUBLE: return of(double.class); case Type.METHOD: throw new IllegalArgumentException("Cannot create ClassInfo of a Method Type!"); default: throw new AssertionError(); } } // don't use the accessor interface mechanic // this happens very early in startup private static final MethodHandle findLoadedClass; static { try { Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); method.setAccessible(true); findLoadedClass = publicLookup().unreflect(method); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } } private static Class<?> findLoadedClass(ClassLoader cl, String name) { try { return (Class<?>) findLoadedClass.invokeExact(cl, name); } catch (Throwable e) { throw new RuntimeException(e); } } private static ClassInfo ofObject(String className) { Class<?> clazz; // first, try to get the class if it's already loaded if ((clazz = findLoadedClass(Launch.classLoader, className)) != null) { return new ClassInfoReflect(clazz); } else { try { // the class is not loaded, try get it's bytes byte[] bytes = Launch.classLoader.getClassBytes(ASMUtils.untransformName(className)); // somehow we can't access the class bytes (happens for JDK classes for example) // we try and load the class now if (bytes == null) { return forceLoad(className); } else { // we found the bytes, lets use them return new ClassInfoASM(ASMUtils.getThinClassNode(bytes)); } } catch (IOException e) { // something went wrong getting the class bytes. try and load it return forceLoad(className); } } } private static ClassInfo forceLoad(String className) { try { return of(Class.forName(className)); } catch (Exception e) { throw new MissingClassException(className, e); } } /** * <p>Get all interfaces directly implemented by this class (equivalent to {@link Class#getInterfaces()}.</p> * * @return the interfaces implemented by this class */ public abstract List<String> interfaces(); /** * <p>Get the internal name of the superclass of this class.</p> * * @return the superclass, or null if this ClassInfo is an interface or represents {@code java/lang/Object}. */ public abstract String superName(); public boolean hasSuper() { return superName() != null; } /** * <p>Get the internal name of this class (e.g. {@code java/lang/Object}.</p> * * @return the internal name */ public abstract String internalName(); /** * <p>Get a {@code ClassInfo} representing the superclass of this class.</p> * * @return the superclass, or null if this class has no superclass (see {@link #superName()}) */ public ClassInfo superclass() { if (zuper != null) { return zuper; } if (superName() == null) { return null; } return (zuper = of(superName())); } /** * <p>Determine if the given class can be safely casted to this class (equivalent to {@link java.lang.Class#isAssignableFrom(Class)}.</p> * <p>Like {@link #of(String)} this method will try to avoid loading actual classes.</p> * * @param child the class to check for * @return true if the given class can be casted to this class */ public final boolean isAssignableFrom(ClassInfo child) { // double dispatch return child.callRightAssignableFrom(this); } boolean callRightAssignableFrom(ClassInfo parent) { return parent.isAssignableFromNormal(this); } boolean isAssignableFromNormal(ClassInfo child) { // some cheap tests first String childName = child.internalName(); String myName = internalName(); if (childName.equals("java/lang/Object")) { // Object is only assignable to itself return myName.equals("java/lang/Object"); } if (myName.equals("java/lang/Object") // everything is assignable to Object || childName.equals(myName) // we are the same || myName.equals(child.superName()) // we are the superclass of child || child.interfaces().contains(myName)) { // we are an interface that child implements return true; } // if we are a class no interface can be cast to us if (!isInterface() && child.isInterface()) { return false; } // final classes cannot be subclassed if (isFinal()) { return false; } // need to compute supers now return child.getSupers().contains(myName); } boolean isAssignableFromReflect(ClassInfoReflect child) { return isAssignableFromNormal(child); } /** * <p>Get all superclasses in the hierarchy chain of this class as well as all interfaces this class * implements directly or indirectly.</p> * <p>In other words return all classes that this class can be safely casted to.</p> * * @return an immutable Set containing all superclasses and interfaces */ public Set<String> getSupers() { return ClassInfoUtil.getSupers(this); } /** * <p>Get the number of dimensions of this array class, or 0 if this ClassInfo does not represent an array class.</p> * * @return the number of dimensions */ public abstract int getDimensions(); /** * <p>Determine if this class is an array class (equivalent to {@link Class#isArray()}</p> * * @return true if this class is an array class */ public boolean isArray() { return getDimensions() > 0; } /** * <p>Determine if this class is an interface.</p> * * @return true if this class is an interface */ public boolean isInterface() { return hasModifier(ACC_INTERFACE); } /** * <p>Determine if this class is abstract</p> * * @return true if this class is abstract */ public boolean isAbstract() { return hasModifier(ACC_ABSTRACT); } /** * <p>Determine if this class is an annotation.</p> * * @return true if this class is an annotation */ public boolean isAnnotation() { return hasModifier(ACC_ANNOTATION); } /** * <p>Determine if this ClassInfo represents an enum class (equivalent to {@link Class#isEnum()}.</p> * <p>Note: Like the JDK method this method will return false for the classes generated for specialized enum constants. * Use {@code hasModifier(ACC_ENUM)} to include those explicitly.</p> * * @return true if this ClassInfo represents an enum class */ public boolean isEnum() { return hasModifier(ACC_ENUM) && superName().equals("java/lang/Enum"); } /** * <p>Get the name of the source file this class was declared in.</p> * @return the source file or null if unknown */ public abstract String getSourceFile(); @Override public boolean equals(Object o) { return this == o || o instanceof ClassInfo && internalName().equals(((ClassInfo) o).internalName()); } @Override public int hashCode() { return internalName().hashCode(); } @Override public String toString() { return (isInterface() ? "interface" : isAbstract() ? "abstract class" : "class") + " " + internalName(); } }