From 268fdc80e1da355152d8f307f5987c703796c4a7 Mon Sep 17 00:00:00 2001 From: LexManos Date: Mon, 16 May 2022 16:19:12 -0700 Subject: [PATCH] Add --annotation argument to extract inheritance task to extract marker annotations. Also use tree maps for inheritance maps to stabilize order in the json. --- .../installertools/ExtractInheritance.java | 153 ++++++++++++++---- 1 file changed, 124 insertions(+), 29 deletions(-) diff --git a/src/main/java/net/minecraftforge/installertools/ExtractInheritance.java b/src/main/java/net/minecraftforge/installertools/ExtractInheritance.java index e2734b9..e6e623c 100644 --- a/src/main/java/net/minecraftforge/installertools/ExtractInheritance.java +++ b/src/main/java/net/minecraftforge/installertools/ExtractInheritance.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -30,18 +31,23 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.Map.Entry; +import java.util.function.Function; import java.util.zip.ZipFile; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.LabelNode; @@ -74,15 +80,18 @@ public void process(String[] args) throws IOException { OptionSpec inputO = parser.accepts("input").withRequiredArg().ofType(File.class).required(); OptionSpec outputO = parser.accepts("output").withRequiredArg().ofType(File.class).required(); OptionSpec libraryO = parser.accepts("lib").withRequiredArg().ofType(File.class); + OptionSpec annotationsO = parser.accepts("annotations"); try { OptionSet options = parser.parse(args); File input = options.valueOf(inputO).getAbsoluteFile(); File output = options.valueOf(outputO).getAbsoluteFile(); + boolean annotations = options.has(annotationsO); log("Input: " + input); log("Output: " + output); + log("Ann: " + annotations); if (!input.exists()) error("Missing required input jar: " + input); @@ -92,15 +101,15 @@ public void process(String[] args) throws IOException { output.createNewFile(); log("Reading Input: " + input); - readJar(input, inClasses); + readJar(input, inClasses, annotations); for (File lib : options.valuesOf(libraryO)) { log("Reading Library: " + lib); - readJar(lib, libClasses); + readJar(lib, libClasses, annotations); } for (Entry entry : inClasses.entrySet()) - resolveClass(entry.getValue()); + resolveClass(entry.getValue(), annotations); Files.write(output.toPath(), GSON.toJson(inClasses).getBytes(StandardCharsets.UTF_8)); @@ -111,7 +120,7 @@ public void process(String[] args) throws IOException { } } - private void readJar(File input, Map classes) throws IOException { + private void readJar(File input, Map classes, boolean annotations) throws IOException { try (ZipFile inJar = new ZipFile(input)) { Utils.forZip(inJar, entry -> { if (!entry.getName().endsWith(".class") || entry.getName().startsWith(".")) @@ -119,7 +128,7 @@ private void readJar(File input, Map classes) throws IOExcept ClassReader reader = new ClassReader(Utils.toByteArray(inJar.getInputStream(entry))); ClassNode classNode = new ClassNode(); reader.accept(classNode, 0); - ClassInfo info = new ClassInfo(classNode); + ClassInfo info = new ClassInfo(classNode, annotations); classes.put(info.name, info); }); } catch (FileNotFoundException e) { @@ -127,16 +136,16 @@ private void readJar(File input, Map classes) throws IOExcept } } - private void resolveClass(ClassInfo cls) { + private void resolveClass(ClassInfo cls, boolean annotations) { if (cls == null || cls.resolved) return; if (!cls.name.equals("java/lang/Object") && cls.superName != null) - resolveClass(getClassInfo(cls.superName)); + resolveClass(getClassInfo(cls.superName, annotations), annotations); if (cls.interfaces != null) for (String intf : cls.interfaces) - resolveClass(getClassInfo(intf)); + resolveClass(getClassInfo(intf, annotations), annotations); if (cls.methods != null) { for (MethodInfo mtd : cls.methods.values()) { @@ -150,16 +159,16 @@ private void resolveClass(ClassInfo cls) { Set processed = new HashSet<>(); if (cls.superName != null) - addQueue(cls.superName, processed, que); + addQueue(cls.superName, processed, que, annotations); if (cls.interfaces != null) - cls.interfaces.forEach(intf -> addQueue(intf, processed, que)); + cls.interfaces.forEach(intf -> addQueue(intf, processed, que, annotations)); while (!que.isEmpty()) { ClassInfo c = que.poll(); if (c.superName != null) - addQueue(c.superName, processed, que); + addQueue(c.superName, processed, que, annotations); if (c.interfaces != null) - c.interfaces.forEach(intf -> addQueue(intf, processed, que)); + c.interfaces.forEach(intf -> addQueue(intf, processed, que, annotations)); MethodInfo m = c.getMethod(mtd.getName(), mtd.getDesc()); @@ -178,16 +187,16 @@ private void resolveClass(ClassInfo cls) { cls.resolved = true; } - private void addQueue(String cls, Set visited, Queue que) { + private void addQueue(String cls, Set visited, Queue que, boolean annotations) { if (!visited.contains(cls)) { - ClassInfo ci = getClassInfo(cls); + ClassInfo ci = getClassInfo(cls, annotations); if (ci != null) que.add(ci); visited.add(cls); } } - private ClassInfo getClassInfo(String name) { + private ClassInfo getClassInfo(String name, boolean annotations) { ClassInfo ret = inClasses.get(name); if (ret != null) return ret; @@ -195,7 +204,7 @@ private ClassInfo getClassInfo(String name) { if (ret == null && !failedClasses.contains(name)) { try { Class cls = Class.forName(name.replaceAll("/", "."), false, this.getClass().getClassLoader()); - ret = new ClassInfo(cls); + ret = new ClassInfo(cls, annotations); libClasses.put(name, ret); } catch (ClassNotFoundException ex) { log("Cant Find Class: " + name); @@ -214,18 +223,20 @@ private static class ClassInfo { public final Map methods; @SuppressWarnings("unused") public final Map fields; + @SuppressWarnings("unused") + public final List annotations; private boolean resolved = false; private Map makeMap(List lst) { if (lst.isEmpty()) return null; - Map ret = new HashMap<>(); + Map ret = new TreeMap<>(); lst.forEach(info -> ret.put(info.getName() + " " + info.getDesc(), info)); return ret; } - ClassInfo(ClassNode node) { + ClassInfo(ClassNode node, boolean annotations) { this.name = node.name; this.access = node.access; this.superName = node.superName; @@ -233,16 +244,21 @@ private Map makeMap(List lst) { List lst = new ArrayList<>(); if (!node.methods.isEmpty()) - node.methods.forEach(mn -> lst.add(new MethodInfo(this, mn))); + node.methods.forEach(mn -> lst.add(new MethodInfo(this, mn, annotations))); this.methods = makeMap(lst); if (!node.fields.isEmpty()) - this.fields = node.fields.stream().map(FieldInfo::new).collect(Collectors.toMap(e -> e.name, e -> e)); + this.fields = node.fields.stream().map(fn -> new FieldInfo(fn, annotations)).collect(toTreeMap(e -> e.name, e -> e)); else this.fields = null; + + if (annotations) + this.annotations = getAnnotations(node.visibleAnnotations, node.invisibleAnnotations); + else + this.annotations = null; } - ClassInfo(Class node) { + ClassInfo(Class node, boolean annotations) { this.name = node.getName().replace('.', '/'); this.access = node.getModifiers(); this.superName = node.getSuperclass() == null ? null : node.getSuperclass().getName().replace('.', '/'); @@ -254,18 +270,23 @@ private Map makeMap(List lst) { List mtds = new ArrayList<>(); for (Constructor ctr : node.getConstructors()) - mtds.add(new MethodInfo(this, ctr)); + mtds.add(new MethodInfo(this, ctr, annotations)); for (Method mtd : node.getDeclaredMethods()) - mtds.add(new MethodInfo(this, mtd)); + mtds.add(new MethodInfo(this, mtd, annotations)); this.methods = makeMap(mtds); Field[] flds = node.getDeclaredFields(); if (flds != null && flds.length > 0) - this.fields = Arrays.asList(flds).stream().map(FieldInfo::new).collect(Collectors.toMap(e -> e.name, e -> e)); + this.fields = Arrays.asList(flds).stream().map(fn -> new FieldInfo(fn, annotations)).collect(toTreeMap(e -> e.name, e -> e)); else this.fields = null; + + if (annotations) + this.annotations = getAnnotations(node.getDeclaredAnnotations()); + else + this.annotations = null; } public MethodInfo getMethod(String name, String desc) { @@ -279,17 +300,29 @@ private static class FieldInfo { public final String desc; @SuppressWarnings("unused") public final int access; + @SuppressWarnings("unused") + public final List annotations; - public FieldInfo(FieldNode node) { + public FieldInfo(FieldNode node, boolean annotations) { this.name = node.name; this.desc = node.desc; this.access = node.access; + + if (annotations) + this.annotations = getAnnotations(node.visibleAnnotations, node.invisibleAnnotations); + else + this.annotations = null; } - public FieldInfo(Field node) { + public FieldInfo(Field node, boolean annotations) { this.name = node.getName(); this.desc = Type.getType(node.getType()).getDescriptor(); this.access = node.getModifiers(); + + if (annotations) + this.annotations = getAnnotations(node.getDeclaredAnnotations()); + else + this.annotations = null; } } @@ -304,13 +337,21 @@ private static class MethodInfo { public final Bouncer bouncer; @SuppressWarnings("unused") public String override = null; + @SuppressWarnings("unused") + public final List annotations; - MethodInfo(ClassInfo parent, MethodNode node) { + MethodInfo(ClassInfo parent, MethodNode node, boolean annotations) { this.name = node.name; this.desc = node.desc; this.access = node.access; this.exceptions = node.exceptions.isEmpty() ? null : new ArrayList<>(node.exceptions); this.parent = parent; + + if (annotations) + this.annotations = getAnnotations(node.visibleAnnotations, node.invisibleAnnotations); + else + this.annotations = null; + Bouncer bounce = null; if ((node.access & (ACC_SYNTHETIC | ACC_BRIDGE)) != 0 && (node.access & ACC_STATIC) == 0) { @@ -354,7 +395,7 @@ private static class MethodInfo { this.bouncer = bounce; } - MethodInfo(ClassInfo parent, Method node) { + MethodInfo(ClassInfo parent, Method node, boolean annotations) { this.name = node.getName(); this.desc = Type.getMethodDescriptor(node); this.access = node.getModifiers(); @@ -364,9 +405,14 @@ private static class MethodInfo { this.exceptions = execs.isEmpty() ? null : execs; this.parent = parent; this.bouncer = null; + + if (annotations) + this.annotations = getAnnotations(node.getDeclaredAnnotations()); + else + this.annotations = null; } - MethodInfo(ClassInfo parent, Constructor node) { + MethodInfo(ClassInfo parent, Constructor node, boolean annotations) { this.name = ""; this.desc = Type.getConstructorDescriptor(node); this.access = node.getModifiers(); @@ -376,6 +422,11 @@ private static class MethodInfo { this.exceptions = execs.isEmpty() ? null : execs; this.parent = parent; this.bouncer = null; + + if (annotations) + this.annotations = getAnnotations(node.getDeclaredAnnotations()); + else + this.annotations = null; } public ClassInfo getParent() { @@ -403,4 +454,48 @@ public Bouncer(String name, String desc) { this.desc = desc; } } + + @SafeVarargs + private static List getAnnotations(List... lists) { + List ret = new ArrayList<>(); + for (List list : lists) { + if (list != null) { + list.stream().map(AnnotationInfo::new).forEach(ret::add); + } + } + Collections.sort(ret); + return ret.isEmpty() ? null : ret; + } + + private static List getAnnotations(Annotation[] anns) { + List ret = new ArrayList<>(); + if (anns != null && anns.length > 0) { + Arrays.stream(anns).map(AnnotationInfo::new).forEach(ret::add); + } + Collections.sort(ret); + return ret.isEmpty() ? null : ret; + } + + // I don't feel like extracting values right now because of their weird infinitely nestable types. + // And it's not necessary for my current need, Finding OnlyIn Markers. + // If anyone else wants to do it feel free, i've left this as an object so you can add a 'values' field. + // -Lex 05/16/22 + public static class AnnotationInfo implements Comparable { + public final String desc; + private AnnotationInfo(AnnotationNode node) { + this.desc = node.desc; + } + private AnnotationInfo(Annotation node) { + this.desc = Type.getDescriptor(node.annotationType()); + } + + @Override + public int compareTo(AnnotationInfo o) { + return this.desc.compareTo(o.desc); + } + } + + public static Collector> toTreeMap(Function keyMapper, Function valueMapper) { + return Collectors.toMap(keyMapper, valueMapper, (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, TreeMap::new); + } }