|
| 1 | +package io.github.cichlidmc.cichlid_gradle.merge.element; |
| 2 | + |
| 3 | +import io.github.cichlidmc.cichlid_gradle.merge.JarMerger; |
| 4 | +import io.github.cichlidmc.cichlid_gradle.merge.MergeSource; |
| 5 | +import io.github.cichlidmc.distmarker.Dist; |
| 6 | +import io.github.cichlidmc.distmarker.Distribution; |
| 7 | +import org.jetbrains.annotations.Nullable; |
| 8 | +import org.objectweb.asm.Type; |
| 9 | +import org.objectweb.asm.TypeReference; |
| 10 | +import org.objectweb.asm.tree.ClassNode; |
| 11 | +import org.objectweb.asm.tree.TypeAnnotationNode; |
| 12 | + |
| 13 | +import java.util.ArrayList; |
| 14 | +import java.util.Collection; |
| 15 | +import java.util.List; |
| 16 | +import java.util.Map; |
| 17 | +import java.util.Objects; |
| 18 | +import java.util.stream.Collectors; |
| 19 | + |
| 20 | +public class InterfacesMerger { |
| 21 | + public static void merge(Map<MergeSource, ClassNode> classes, ClassNode output) { |
| 22 | + // no need to do anything when they already match |
| 23 | + if (JarMerger.allEqual(classes.values(), node -> node.interfaces, Objects::equals)) |
| 24 | + return; |
| 25 | + |
| 26 | + if (true) { |
| 27 | + // I can't find anything wrong with this code, but the added annotations |
| 28 | + // never show up. Might be an ASM issue? From a GitHub search, I couldn't |
| 29 | + // find a single use of class type annotations, so it might just be broken. |
| 30 | + // FIXME: make a minimal reproduction case and submit an issue |
| 31 | + throw new RuntimeException("Interface merging is currently broken."); |
| 32 | + } |
| 33 | + |
| 34 | + System.out.println("merging interfaces on " + output.name); |
| 35 | + output.interfaces.clear(); |
| 36 | + |
| 37 | + // handling this is annoying and I don't think they're ever present. |
| 38 | + // throw in case that assumption is wrong. |
| 39 | + assertEmpty(output.visibleTypeAnnotations, "visibleTypeAnnotations", output.name); |
| 40 | + assertEmpty(output.invisibleTypeAnnotations, "invisibleTypeAnnotations", output.name); |
| 41 | + |
| 42 | + List<InterfaceInfo> interfaces = getAllInterfaces(classes); |
| 43 | + System.out.println(interfaces); |
| 44 | + for (int i = 0; i < interfaces.size(); i++) { |
| 45 | + InterfaceInfo info = interfaces.get(i); |
| 46 | + output.interfaces.add(info.name); |
| 47 | + if (info.dist == null) |
| 48 | + continue; |
| 49 | + |
| 50 | + if (output.visibleTypeAnnotations == null) { |
| 51 | + output.visibleTypeAnnotations = new ArrayList<>(); |
| 52 | + } |
| 53 | + |
| 54 | + TypeReference reference = TypeReference.newSuperTypeReference(i); |
| 55 | + output.visibleTypeAnnotations.add(makeDistAnnotation(reference, info.dist)); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + private static List<InterfaceInfo> getAllInterfaces(Map<MergeSource, ClassNode> classes) { |
| 60 | + // start with all common interfaces |
| 61 | + List<String> common = getCommonInterfaces(classes); |
| 62 | + List<InterfaceInfo> infos = common.stream() |
| 63 | + .map(InterfaceInfo::new) |
| 64 | + .collect(Collectors.toCollection(ArrayList::new)); |
| 65 | + |
| 66 | + // then add all exclusives, one dist at a time |
| 67 | + classes.forEach((source, node) -> { |
| 68 | + for (String classInterface : node.interfaces) { |
| 69 | + if (!common.contains(classInterface)) { |
| 70 | + infos.add(new InterfaceInfo(classInterface, source.dist)); |
| 71 | + } |
| 72 | + } |
| 73 | + }); |
| 74 | + |
| 75 | + return infos; |
| 76 | + } |
| 77 | + |
| 78 | + private static List<String> getCommonInterfaces(Map<MergeSource, ClassNode> classes) { |
| 79 | + // pick an arbitrary base to initialize the common set |
| 80 | + MergeSource anySource = classes.keySet().iterator().next(); |
| 81 | + ClassNode base = classes.get(anySource); |
| 82 | + |
| 83 | + List<String> common = new ArrayList<>(base.interfaces); |
| 84 | + // go through each source and remove interfaces that aren't actually common |
| 85 | + for (ClassNode source : classes.values()) { |
| 86 | + // skip the one that was used as a base |
| 87 | + if (source != base) { |
| 88 | + common.removeIf(commonInterface -> !source.interfaces.contains(commonInterface)); |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + return common; |
| 93 | + } |
| 94 | + |
| 95 | + private static TypeAnnotationNode makeDistAnnotation(TypeReference reference, Dist dist) { |
| 96 | + TypeAnnotationNode node = new TypeAnnotationNode( |
| 97 | + reference.getSuperTypeIndex(), null, Type.getDescriptor(Distribution.class) |
| 98 | + ); |
| 99 | + |
| 100 | + node.visitEnum("value", Type.getDescriptor(Dist.class), dist.name()); |
| 101 | + return node; |
| 102 | + } |
| 103 | + |
| 104 | + private static void assertEmpty(@Nullable Collection<?> collection, String name, String className) { |
| 105 | + if (collection != null && !collection.isEmpty()) { |
| 106 | + throw new IllegalStateException(name + " is not empty in " + className); |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + private record InterfaceInfo(String name, @Nullable Dist dist) { |
| 111 | + private InterfaceInfo(String name) { |
| 112 | + this(name, null); |
| 113 | + } |
| 114 | + } |
| 115 | +} |
0 commit comments