package me.august.lumen.compile.resolve.data;
import me.august.lumen.common.ModifierSet;
import me.august.lumen.compile.resolve.lookup.ClassLookup;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* Skeletal representation of a Java class.
*
* Stores class version, methods, fields,
* super class, and implemented interfaces.
*/
public class ClassData extends BaseData {
private static Map<Class<?>, ClassData> FROM_CLASS_CACHE = new HashMap<>();
int version;
List<MethodData> methods = new ArrayList<>();
List<FieldData> fields = new ArrayList<>();
String superClass;
String[] interfaces;
public ClassData(String name, ModifierSet modifiers) {
super(name, modifiers);
}
public static ClassData fromClassFile(InputStream input) throws IOException {
ClassReader reader = new ClassReader(input);
ClassAnalyzer analyzer = new ClassAnalyzer();
reader.accept(analyzer, 0);
return analyzer.classData;
}
public static ClassData fromClass(Class<?> cls) {
if (FROM_CLASS_CACHE.containsKey(cls)) {
return FROM_CLASS_CACHE.get(cls);
}
ClassData data = new ClassData(cls.getName(), new ModifierSet(cls.getModifiers()));
if (cls.getSuperclass() != null)
data.superClass = cls.getSuperclass().getName();
String[] interfaces = new String[cls.getInterfaces().length];
for (int i = 0; i < interfaces.length; i++) {
interfaces[i] = cls.getInterfaces()[i].getName();
}
data.interfaces = interfaces;
for (Method method : cls.getDeclaredMethods()) {
Type returnType = Type.getType(method.getReturnType());
Type[] paramsTypes = new Type[method.getParameterCount()];
for (int i = 0; i < paramsTypes.length; i++) {
paramsTypes[i] = Type.getType(method.getParameterTypes()[i]);
}
ModifierSet mods = new ModifierSet(method.getModifiers());
MethodData methodData = new MethodData(method.getName(), returnType, paramsTypes, mods);
data.getMethods().add(methodData);
}
for (Field field : cls.getDeclaredFields()) {
ModifierSet mods = new ModifierSet(field.getModifiers());
Type type = Type.getType(field.getType());
FieldData fieldData = new FieldData(field.getName(), type, mods);
data.getFields().add(fieldData);
}
FROM_CLASS_CACHE.put(cls, data);
return data;
}
public boolean isAssignableTo(String type, ClassLookup lookup) {
return assignableDistance(type, lookup, 0) > -1;
}
public int assignableDistance(Type type, ClassLookup lookup) {
if (type.getSort() == Type.OBJECT) {
return assignableDistance(type.getClassName(), lookup);
} else {
return -1;
}
}
public int assignableDistance(String type, ClassLookup lookup) {
return assignableDistance(type, lookup, 0);
}
private int assignableDistance(String type, ClassLookup lookup, int dist) {
if (type.equals(getName()))
return dist;
String sup;
if (isInterface() && superClass == null) {
sup = Object.class.getName();
} else {
sup = superClass;
}
if (sup != null) {
ClassData supClass = lookup.lookup(sup);
if (supClass != null) {
int supDist = supClass.assignableDistance(type, lookup, dist + 1);
if (supDist > -1)
return supDist;
}
}
for (String itf : interfaces) {
ClassData itfClass = lookup.lookup(itf);
if (itf != null) {
int itfDist = itfClass.assignableDistance(type, lookup, dist + 1);
if (itfClass.isAssignableTo(type, lookup))
return itfDist;
}
}
return -1;
}
@Override
public String toString() {
return "ClassData{" +
"version=" + version +
", methods=" + methods +
", fields=" + fields +
", superClass='" + superClass + '\'' +
", interfaces=" + Arrays.toString(interfaces) +
'}';
}
public List<MethodData> getMethods() {
return methods;
}
public List<FieldData> getFields() {
return fields;
}
public String getSuperclass() {
return superClass;
}
public String[] getInterfaces() {
return interfaces;
}
public FieldData getField(String name) {
for (FieldData field : fields) {
if (field.getName().equals(name)) return field;
}
return null;
}
public List<MethodData> getMethods(String name) {
List<MethodData> found = new ArrayList<>();
for (MethodData method : methods) {
if (method.getName().equals(name)) found.add(method);
}
return found;
}
public boolean isInterface() {
return modifiers.isInterface();
}
private static class ClassAnalyzer extends ClassVisitor {
ClassData classData;
public ClassAnalyzer() {
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access, String name, String signature, String sup, String[] impls) {
ModifierSet modifiers = new ModifierSet(access);
classData = new ClassData(name, modifiers);
classData.version = version;
classData.superClass = sup;
classData.interfaces = impls;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exs) {
Type methodType = Type.getMethodType(desc);
Type returnType = methodType.getReturnType();
Type[] types = methodType.getArgumentTypes();
MethodData method = new MethodData(name, returnType, types, new ModifierSet(access));
classData.methods.add(method);
return super.visitMethod(access, name, desc, sig, exs);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String sig, Object val) {
Type type = Type.getType(desc);
FieldData field = new FieldData(name, type, new ModifierSet(access));
classData.fields.add(field);
return super.visitField(access, name, desc, sig, val);
}
}
}