Skip to content

Commit

Permalink
Add --annotation argument to extract inheritance task to extract mark…
Browse files Browse the repository at this point in the history
…er annotations.

Also use tree maps for inheritance maps to stabilize order in the json.
  • Loading branch information
LexManos committed May 16, 2022
1 parent d31d223 commit 268fdc8
Showing 1 changed file with 124 additions and 29 deletions.
153 changes: 124 additions & 29 deletions src/main/java/net/minecraftforge/installertools/ExtractInheritance.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -74,15 +80,18 @@ public void process(String[] args) throws IOException {
OptionSpec<File> inputO = parser.accepts("input").withRequiredArg().ofType(File.class).required();
OptionSpec<File> outputO = parser.accepts("output").withRequiredArg().ofType(File.class).required();
OptionSpec<File> libraryO = parser.accepts("lib").withRequiredArg().ofType(File.class);
OptionSpec<Void> 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);
Expand All @@ -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<String, ClassInfo> entry : inClasses.entrySet())
resolveClass(entry.getValue());
resolveClass(entry.getValue(), annotations);

Files.write(output.toPath(), GSON.toJson(inClasses).getBytes(StandardCharsets.UTF_8));

Expand All @@ -111,32 +120,32 @@ public void process(String[] args) throws IOException {
}
}

private void readJar(File input, Map<String, ClassInfo> classes) throws IOException {
private void readJar(File input, Map<String, ClassInfo> classes, boolean annotations) throws IOException {
try (ZipFile inJar = new ZipFile(input)) {
Utils.forZip(inJar, entry -> {
if (!entry.getName().endsWith(".class") || entry.getName().startsWith("."))
return;
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) {
throw new FileNotFoundException("Could not open input file: " + e.getMessage());
}
}

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()) {
Expand All @@ -150,16 +159,16 @@ private void resolveClass(ClassInfo cls) {
Set<String> 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());

Expand All @@ -178,24 +187,24 @@ private void resolveClass(ClassInfo cls) {
cls.resolved = true;
}

private void addQueue(String cls, Set<String> visited, Queue<ClassInfo> que) {
private void addQueue(String cls, Set<String> visited, Queue<ClassInfo> 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;
ret = libClasses.get(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);
Expand All @@ -214,35 +223,42 @@ private static class ClassInfo {
public final Map<String, MethodInfo> methods;
@SuppressWarnings("unused")
public final Map<String, FieldInfo> fields;
@SuppressWarnings("unused")
public final List<AnnotationInfo> annotations;

private boolean resolved = false;

private Map<String, MethodInfo> makeMap(List<MethodInfo> lst) {
if (lst.isEmpty())
return null;
Map<String, MethodInfo> ret = new HashMap<>();
Map<String, MethodInfo> 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;
this.interfaces = node.interfaces.isEmpty() ? null : node.interfaces;

List<MethodInfo> 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('.', '/');
Expand All @@ -254,18 +270,23 @@ private Map<String, MethodInfo> makeMap(List<MethodInfo> lst) {
List<MethodInfo> 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) {
Expand All @@ -279,17 +300,29 @@ private static class FieldInfo {
public final String desc;
@SuppressWarnings("unused")
public final int access;
@SuppressWarnings("unused")
public final List<AnnotationInfo> 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;
}
}

Expand All @@ -304,13 +337,21 @@ private static class MethodInfo {
public final Bouncer bouncer;
@SuppressWarnings("unused")
public String override = null;
@SuppressWarnings("unused")
public final List<AnnotationInfo> 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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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 = "<init>";
this.desc = Type.getConstructorDescriptor(node);
this.access = node.getModifiers();
Expand All @@ -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() {
Expand Down Expand Up @@ -403,4 +454,48 @@ public Bouncer(String name, String desc) {
this.desc = desc;
}
}

@SafeVarargs
private static List<AnnotationInfo> getAnnotations(List<AnnotationNode>... lists) {
List<AnnotationInfo> ret = new ArrayList<>();
for (List<AnnotationNode> list : lists) {
if (list != null) {
list.stream().map(AnnotationInfo::new).forEach(ret::add);
}
}
Collections.sort(ret);
return ret.isEmpty() ? null : ret;
}

private static List<AnnotationInfo> getAnnotations(Annotation[] anns) {
List<AnnotationInfo> 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<AnnotationInfo> {
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 <T, K, U> Collector<T, ?, Map<K,U>> toTreeMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) {
return Collectors.toMap(keyMapper, valueMapper, (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, TreeMap::new);
}
}

0 comments on commit 268fdc8

Please sign in to comment.