diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java index 308629b75..f44cf4f5c 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java @@ -1,19 +1,13 @@ package org.quiltmc.enigma.api; import com.google.common.base.Preconditions; -import com.google.common.base.Predicates; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.Streams; import com.google.common.io.MoreFiles; import org.jspecify.annotations.Nullable; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; import org.quiltmc.enigma.api.analysis.index.jar.CombinedJarIndex; -import org.quiltmc.enigma.api.analysis.index.jar.EntryIndex; -import org.quiltmc.enigma.api.analysis.index.jar.InheritanceIndex; import org.quiltmc.enigma.api.analysis.index.jar.LibrariesJarIndex; import org.quiltmc.enigma.api.analysis.index.jar.MainJarIndex; -import org.quiltmc.enigma.api.analysis.index.jar.ReferenceIndex; import org.quiltmc.enigma.api.analysis.index.mapping.MappingsIndex; import org.quiltmc.enigma.api.class_provider.CachingClassProvider; import org.quiltmc.enigma.api.class_provider.ClassProvider; @@ -23,9 +17,6 @@ import org.quiltmc.enigma.api.class_provider.ObfuscationFixClassProvider; import org.quiltmc.enigma.api.class_provider.ProjectClassProvider; import org.quiltmc.enigma.api.translation.mapping.serde.MappingParseException; -import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; -import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; -import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import org.quiltmc.enigma.impl.analysis.ClassLoaderClassProvider; import org.quiltmc.enigma.api.service.EnigmaService; import org.quiltmc.enigma.api.service.EnigmaServiceContext; @@ -40,7 +31,6 @@ import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.quiltmc.enigma.impl.analysis.index.AbstractJarIndex; -import org.quiltmc.enigma.impl.analysis.index.IndexClassVisitor; import org.quiltmc.enigma.util.Either; import org.quiltmc.enigma.util.I18n; import org.quiltmc.enigma.util.Utils; @@ -56,20 +46,15 @@ import java.nio.file.attribute.BasicFileAttributes; import java.sql.DriverManager; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Properties; import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; public class Enigma { public static final String NAME = "Enigma"; @@ -119,9 +104,9 @@ public static Builder builder() { public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException { JarClassProvider jarClassProvider = new JarClassProvider(path); - AbstractJarIndex jarIndex = MainJarIndex.empty(); - AbstractJarIndex libIndex = LibrariesJarIndex.empty(); - AbstractJarIndex comboIndex = CombinedJarIndex.empty(); + MainJarIndex jarIndex = MainJarIndex.empty(); + LibrariesJarIndex libIndex = LibrariesJarIndex.empty(); + CombinedJarIndex comboIndex = CombinedJarIndex.empty(jarIndex, libIndex); ClassLoaderClassProvider jreProvider = new ClassLoaderClassProvider(DriverManager.class.getClassLoader()); ClasspathClassProvider javaClassProvider = new ClasspathClassProvider(); @@ -130,16 +115,13 @@ public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, Prog ProjectClassProvider projectClassProvider = new ProjectClassProvider(mainProjectProvider, librariesProvider); // main index - this.index(jarIndex, projectClassProvider, progress, "jar", false, null); - - // TODO make filtering toggleable with arg once JavaClassProvider is used - final Predicate mainReferencedPredicate = this.createMainReferencedPredicate(jarIndex, projectClassProvider); + this.index(jarIndex, projectClassProvider, progress, "jar", false); // lib index - this.index(libIndex, projectClassProvider, progress, "jar", true, mainReferencedPredicate); + this.index(libIndex, projectClassProvider, progress, "libs", true); // combined main and lib index - this.index(comboIndex, projectClassProvider, progress, "combined", true, mainReferencedPredicate); + this.index(comboIndex, projectClassProvider, progress, "combined", true); // name proposal var nameProposalServices = this.getNameProposalServices(); @@ -168,75 +150,11 @@ public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, Prog return new EnigmaProject(this, path, mainProjectProvider, jarIndex, libIndex, comboIndex, mappingsIndex, proposedNames, Utils.zipSha1(path)); } - private Predicate createMainReferencedPredicate(AbstractJarIndex mainIndex, ProjectClassProvider classProvider) { - final EntryIndex mainEntryIndex = mainIndex.getIndex(EntryIndex.class); - - final EntryIndex entryIndex = new EntryIndex(); - final ReferenceIndex referenceIndex = new ReferenceIndex(); - final InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); - - final Collection allClassNames = classProvider.getClassNames(); - for (final String className : allClassNames) { - final ClassNode classNode = Objects.requireNonNull(classProvider.get(className)); - classNode.accept(new IndexClassVisitor(entryIndex, Enigma.ASM_VERSION)); - classNode.accept(new IndexClassVisitor(referenceIndex, Enigma.ASM_VERSION)); - classNode.accept(new IndexClassVisitor(inheritanceIndex, Enigma.ASM_VERSION)); - } - - return className -> { - final ClassEntry classEntry = new ClassEntry(className); - if (mainEntryIndex.hasClass(classEntry)) { - return true; - } - - if (inheritanceIndex.getChildren(classEntry).stream().anyMatch(mainEntryIndex::hasClass)) { - return true; - } - - final boolean typeReferenced = Streams - .concat( - referenceIndex.getReferencesToClass(classEntry).stream(), - referenceIndex.getMethodTypeReferencesToClass(classEntry).stream(), - referenceIndex.getFieldTypeReferencesToClass(classEntry).stream() - ) - .anyMatch(reference -> - mainEntryIndex.hasClass(reference.entry) || mainEntryIndex.hasEntry(reference.context) - ); - - if (typeReferenced) { - return true; - } - - final List mainMethods = mainIndex.getChildrenByClass().values().stream() - .flatMap(entry -> entry instanceof MethodEntry method ? Stream.of(method) : Stream.empty()) - .toList(); - - final boolean methodReferenced = mainMethods.stream() - .flatMap(method -> referenceIndex.getMethodsReferencedBy(method).stream()) - .map(MethodEntry::getParent) - .anyMatch(classEntry::equals); - if (methodReferenced) { - return true; - } - - // field referenced - return mainMethods.stream() - .flatMap(method -> referenceIndex.getFieldsReferencedBy(method).stream()) - .map(FieldEntry::getParent) - .anyMatch(classEntry::equals); - }; - } - private void index( AbstractJarIndex index, ProjectClassProvider classProvider, ProgressListener progress, String progressKey, - boolean includesLibraries, @Nullable Predicate classNameFilter + boolean includesLibraries ) { - if (classNameFilter == null) { - index.indexJar(classProvider, progress); - classNameFilter = Predicates.alwaysTrue(); - } else { - index.indexJar(classProvider, progress, classNameFilter); - } + index.indexJar(classProvider, progress); List indexers = this.services.get(JarIndexerService.TYPE); progress.init(indexers.size(), I18n.translate("progress." + progressKey + ".custom_indexing")); @@ -245,9 +163,7 @@ private void index( for (var service : indexers) { if (!(includesLibraries && !service.shouldIndexLibraries())) { progress.step(i++, I18n.translateFormatted("progress." + progressKey + ".custom_indexing.indexer", service.getId())); - Set scope = index.getIndexableClassNames(classProvider).stream() - .filter(classNameFilter) - .collect(Collectors.toCollection(HashSet::new)); + Set scope = new HashSet<>(index.getIndexableClassNames(classProvider)); service.acceptJar(scope, classProvider, index); } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/BridgeMethodIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/BridgeMethodIndex.java index 146c66522..4422fbe86 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/BridgeMethodIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/BridgeMethodIndex.java @@ -1,180 +1,40 @@ package org.quiltmc.enigma.api.analysis.index.jar; -import com.google.common.collect.Maps; import org.jspecify.annotations.Nullable; -import org.quiltmc.enigma.api.translation.representation.AccessFlags; -import org.quiltmc.enigma.api.translation.representation.ArgumentDescriptor; -import org.quiltmc.enigma.api.translation.representation.MethodDescriptor; -import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; -import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; -import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Map; -public class BridgeMethodIndex implements JarIndexer { - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - private final ReferenceIndex referenceIndex; +public sealed interface BridgeMethodIndex extends JarIndexer + permits CombinedBridgeMethodIndex, IndependentBridgeMethodIndex { + void findBridgeMethods(); - private final Map bridgeToSpecialized = Maps.newHashMap(); - private final Map specializedToBridge = Maps.newHashMap(); + boolean isBridgeMethod(MethodEntry entry); - public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; - this.referenceIndex = referenceIndex; - } - - public void findBridgeMethods() { - // look for access and bridged methods - for (MethodEntry methodEntry : this.entryIndex.getMethods()) { - MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry; - - AccessFlags access = methodDefEntry.getAccess(); - if (access == null || !access.isSynthetic()) { - continue; - } - - this.indexSyntheticMethod(methodDefEntry, access); - } - } - - @Override - public void processIndex(JarIndex index) { - Map copiedAccessToBridge = new HashMap<>(this.specializedToBridge); - - for (Map.Entry entry : copiedAccessToBridge.entrySet()) { - MethodEntry specializedEntry = entry.getKey(); - MethodEntry bridgeEntry = entry.getValue(); - if (bridgeEntry.getName().equals(specializedEntry.getName())) { - continue; - } - - MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); - this.specializedToBridge.put(renamedSpecializedEntry, this.specializedToBridge.get(specializedEntry)); - } - } - - private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { - MethodEntry specializedMethod = this.findSpecializedMethod(syntheticMethod); - if (specializedMethod == null) { - return; - } - - if (access.isBridge() || this.isPotentialBridge(syntheticMethod, specializedMethod)) { - this.bridgeToSpecialized.put(syntheticMethod, specializedMethod); - if (this.specializedToBridge.containsKey(specializedMethod)) { - // we already have a bridge for this method, so we keep the one higher in the hierarchy - // can happen with a class inheriting from a superclass with one or more bridge method(s) - MethodEntry bridgeMethod = this.specializedToBridge.get(specializedMethod); - this.specializedToBridge.put(specializedMethod, this.getHigherMethod(syntheticMethod, bridgeMethod)); - } else { - this.specializedToBridge.put(specializedMethod, syntheticMethod); - } - } - } - - private MethodEntry findSpecializedMethod(MethodEntry method) { - // we want to find all compiler-added methods that directly call another with no processing - - // get all the methods that we call - final Collection referencedMethods = this.referenceIndex.getMethodsReferencedBy(method); - - // is there just one? - if (referencedMethods.size() != 1) { - return null; - } - - return referencedMethods.stream().findFirst().orElse(null); - } - - private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) { - // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited - AccessFlags bridgeAccess = bridgeMethod.getAccess(); - if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) { - return false; - } - - MethodDescriptor bridgeDesc = bridgeMethod.getDesc(); - MethodDescriptor specializedDesc = specializedMethod.getDesc(); - List bridgeArguments = bridgeDesc.getArgumentDescs(); - List specializedArguments = specializedDesc.getArgumentDescs(); - - // A bridge method will always have the same number of arguments - if (bridgeArguments.size() != specializedArguments.size()) { - return false; - } - - // Check that all argument types are bridge-compatible - for (int i = 0; i < bridgeArguments.size(); i++) { - if (!this.areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) { - return false; - } - } - - // Check that the return type is bridge-compatible - return this.areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc()); - } - - private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) { - if (bridgeDesc.equals(specializedDesc)) { - return true; - } - - // Either the descs will be equal, or they are both types and different through a generic - if (bridgeDesc.isType() && specializedDesc.isType()) { - ClassEntry bridgeType = bridgeDesc.getTypeEntry(); - ClassEntry accessedType = specializedDesc.getTypeEntry(); - - // If the given types are completely unrelated to each other, this can't be bridge compatible - InheritanceIndex.Relation relation = this.inheritanceIndex.computeClassRelation(accessedType, bridgeType); - return relation != InheritanceIndex.Relation.UNRELATED; - } - - return false; - } - - // Get the method higher in the hierarchy - private MethodEntry getHigherMethod(MethodEntry bridgeMethod1, MethodEntry bridgeMethod2) { - ClassEntry parent1 = bridgeMethod1.getParent(); - ClassEntry parent2 = bridgeMethod2.getParent(); - return this.inheritanceIndex.getDescendants(parent1).contains(parent2) ? bridgeMethod1 : bridgeMethod2; - } - - public boolean isBridgeMethod(MethodEntry entry) { - return this.bridgeToSpecialized.containsKey(entry); - } - - public boolean isSpecializedMethod(MethodEntry entry) { - return this.specializedToBridge.containsKey(entry); - } + boolean isSpecializedMethod(MethodEntry entry); @Nullable - public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) { - return this.specializedToBridge.get(specialized); - } + MethodEntry getBridgeFromSpecialized(MethodEntry specialized); - public MethodEntry getSpecializedFromBridge(MethodEntry bridge) { - return this.bridgeToSpecialized.get(bridge); - } + MethodEntry getSpecializedFromBridge(MethodEntry bridge); - /** Includes "renamed specialized -> bridge" entries. */ - public Map getSpecializedToBridge() { - return Collections.unmodifiableMap(this.specializedToBridge); - } + /** + * Includes "renamed specialized -> bridge" entries. + */ + Map getSpecializedToBridge(); - /** Only "bridge -> original name" entries. **/ - public Map getBridgeToSpecialized() { - return Collections.unmodifiableMap(this.bridgeToSpecialized); + /** + * Only "bridge -> original name" entries. + */ + Map getBridgeToSpecialized(); + + @Override + default Class getType() { + return BridgeMethodIndex.class; } @Override - public String getTranslationKey() { + default String getTranslationKey() { return "progress.jar.indexing.process.bridge_methods"; } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedBridgeMethodIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedBridgeMethodIndex.java new file mode 100644 index 000000000..7ffac9680 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedBridgeMethodIndex.java @@ -0,0 +1,66 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import org.jspecify.annotations.Nullable; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; +import org.quiltmc.enigma.util.UnmodifiableCombinedMap; + +import java.util.Map; + +final class CombinedBridgeMethodIndex implements BridgeMethodIndex { + private final BridgeMethodIndex mainIndex; + private final BridgeMethodIndex libIndex; + private final Map specializedToBridge; + private final Map bridgeToSpecialized; + + CombinedBridgeMethodIndex(BridgeMethodIndex mainIndex, BridgeMethodIndex libIndex) { + this.mainIndex = mainIndex; + this.libIndex = libIndex; + + this.specializedToBridge = new UnmodifiableCombinedMap<>( + this.mainIndex.getSpecializedToBridge(), + this.libIndex.getSpecializedToBridge() + ); + + this.bridgeToSpecialized = new UnmodifiableCombinedMap<>( + this.mainIndex.getBridgeToSpecialized(), + this.libIndex.getBridgeToSpecialized() + ); + } + + @Override + public void findBridgeMethods() { + // this is done by the main and lib delegates + } + + @Override + public boolean isBridgeMethod(MethodEntry entry) { + return this.mainIndex.isBridgeMethod(entry) || this.libIndex.isBridgeMethod(entry); + } + + @Override + public boolean isSpecializedMethod(MethodEntry entry) { + return this.mainIndex.isSpecializedMethod(entry) || this.libIndex.isSpecializedMethod(entry); + } + + @Override + public @Nullable MethodEntry getBridgeFromSpecialized(MethodEntry specialized) { + final MethodEntry mainBridge = this.mainIndex.getBridgeFromSpecialized(specialized); + return mainBridge == null ? this.libIndex.getBridgeFromSpecialized(specialized) : mainBridge; + } + + @Override + public MethodEntry getSpecializedFromBridge(MethodEntry bridge) { + final MethodEntry mainSpecialized = this.mainIndex.getSpecializedFromBridge(bridge); + return mainSpecialized == null ? this.libIndex.getSpecializedFromBridge(bridge) : mainSpecialized; + } + + @Override + public Map getSpecializedToBridge() { + return this.specializedToBridge; + } + + @Override + public Map getBridgeToSpecialized() { + return this.bridgeToSpecialized; + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedEntryIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedEntryIndex.java new file mode 100644 index 000000000..3a557bab6 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedEntryIndex.java @@ -0,0 +1,152 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import org.jspecify.annotations.Nullable; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; +import org.quiltmc.enigma.api.translation.mapping.tree.MergedEntryMappingTree; +import org.quiltmc.enigma.api.translation.representation.AccessFlags; +import org.quiltmc.enigma.api.translation.representation.entry.ClassDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.Entry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; +import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; +import org.quiltmc.enigma.util.CombinedCollection; + +import java.util.Collection; + +final class CombinedEntryIndex implements EntryIndex { + private final EntryIndex mainIndex; + private final EntryIndex libIndex; + + private final Collection classes; + private final Collection methods; + private final Collection parameters; + private final Collection fields; + + private final EntryTree tree; + + CombinedEntryIndex(EntryIndex mainIndex, EntryIndex libIndex) { + this.mainIndex = mainIndex; + this.libIndex = libIndex; + + this.classes = new CombinedCollection<>(this.mainIndex.getClasses(), this.libIndex.getClasses()); + this.methods = new CombinedCollection<>(this.mainIndex.getMethods(), this.libIndex.getMethods()); + this.parameters = new CombinedCollection<>(this.mainIndex.getParameters(), this.libIndex.getParameters()); + this.fields = new CombinedCollection<>(this.mainIndex.getFields(), this.libIndex.getFields()); + + this.tree = new MergedEntryMappingTree(this.mainIndex.getTree(), this.libIndex.getTree()); + } + + @Override + public boolean hasClass(ClassEntry entry) { + return this.mainIndex.hasClass(entry) || this.libIndex.hasClass(entry); + } + + @Override + public boolean hasMethod(MethodEntry entry) { + return this.mainIndex.hasMethod(entry) || this.libIndex.hasMethod(entry); + } + + @Override + public boolean hasParameter(LocalVariableEntry entry) { + return this.mainIndex.hasParameter(entry) || this.libIndex.hasParameter(entry); + } + + @Override + public boolean hasField(FieldEntry entry) { + return this.mainIndex.hasField(entry) || this.libIndex.hasField(entry); + } + + @Override + public boolean hasEntry(Entry entry) { + return this.mainIndex.hasEntry(entry) || this.libIndex.hasEntry(entry); + } + + @Override + public boolean validateParameterIndex(LocalVariableEntry parameter) { + return this.mainIndex.validateParameterIndex(parameter) || this.libIndex.validateParameterIndex(parameter); + } + + @Override + public @Nullable AccessFlags getMethodAccess(MethodEntry entry) { + final AccessFlags mainAccess = this.mainIndex.getMethodAccess(entry); + return mainAccess == null ? this.libIndex.getMethodAccess(entry) : mainAccess; + } + + @Override + public @Nullable AccessFlags getParameterAccess(LocalVariableEntry entry) { + final AccessFlags mainAccess = this.mainIndex.getParameterAccess(entry); + return mainAccess == null ? this.libIndex.getParameterAccess(entry) : mainAccess; + } + + @Override + public @Nullable AccessFlags getFieldAccess(FieldEntry entry) { + final AccessFlags mainAccess = this.mainIndex.getFieldAccess(entry); + return mainAccess == null ? this.libIndex.getFieldAccess(entry) : mainAccess; + } + + @Override + public @Nullable AccessFlags getClassAccess(ClassEntry entry) { + final AccessFlags mainAccess = this.mainIndex.getClassAccess(entry); + return mainAccess == null ? this.libIndex.getClassAccess(entry) : mainAccess; + } + + @Override + public @Nullable AccessFlags getEntryAccess(Entry entry) { + final AccessFlags mainAccess = this.mainIndex.getEntryAccess(entry); + return mainAccess == null ? this.libIndex.getEntryAccess(entry) : mainAccess; + } + + @Override + public @Nullable ClassDefEntry getDefinition(ClassEntry entry) { + final ClassDefEntry mainDef = this.mainIndex.getDefinition(entry); + return mainDef == null ? this.libIndex.getDefinition(entry) : mainDef; + } + + @Override + public @Nullable MethodDefEntry getDefinition(MethodEntry entry) { + final MethodDefEntry mainDef = this.mainIndex.getDefinition(entry); + return mainDef == null ? this.libIndex.getDefinition(entry) : mainDef; + } + + @Override + public @Nullable LocalVariableDefEntry getDefinition(LocalVariableEntry entry) { + final LocalVariableDefEntry mainDef = this.mainIndex.getDefinition(entry); + return mainDef == null ? this.libIndex.getDefinition(entry) : mainDef; + } + + @Override + public @Nullable FieldDefEntry getDefinition(FieldEntry entry) { + final FieldDefEntry mainDef = this.mainIndex.getDefinition(entry); + return mainDef == null ? this.libIndex.getDefinition(entry) : mainDef; + } + + @Override + public Collection getClasses() { + return this.classes; + } + + @Override + public Collection getMethods() { + return this.methods; + } + + @Override + public Collection getParameters() { + return this.parameters; + } + + @Override + public Collection getFields() { + return this.fields; + } + + @Override + public EntryTree getTree() { + return this.tree; + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedJarIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedJarIndex.java index e9dcd2dd0..9de97c2d1 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedJarIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedJarIndex.java @@ -8,9 +8,12 @@ /** * An index of the main jar and library jars of an {@link EnigmaProject}. + * + *

Note: currently the contained {@link ReferenceIndex} does not index main jar references to + * library types and members. */ -public class CombinedJarIndex extends AbstractJarIndex { - public CombinedJarIndex( +public final class CombinedJarIndex extends AbstractJarIndex { + private CombinedJarIndex( EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, JarIndexer... otherIndexers ) { @@ -21,14 +24,15 @@ public CombinedJarIndex( * Creates an empty index, configured to use all built-in indexers. * @return the newly created index */ - public static CombinedJarIndex empty() { - EntryIndex entryIndex = new EntryIndex(); - ReferenceIndex referenceIndex = new ReferenceIndex(); + public static CombinedJarIndex empty(MainJarIndex mainIndex, LibrariesJarIndex libIndex) { + EntryIndex entryIndex = new CombinedEntryIndex(mainIndex.getEntryIndex(), libIndex.getEntryIndex()); + ReferenceIndex referenceIndex = + new CombinedReferenceIndex(mainIndex.getReferenceIndex(), libIndex.getReferenceIndex()); InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); LambdaIndex lambdaIndex = new LambdaIndex(); return new CombinedJarIndex( entryIndex, inheritanceIndex, referenceIndex, - new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex), + new CombinedBridgeMethodIndex(mainIndex.getBridgeMethodIndex(), libIndex.getBridgeMethodIndex()), // required by MappingValidator lambdaIndex ); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedReferenceIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedReferenceIndex.java new file mode 100644 index 000000000..db780c980 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/CombinedReferenceIndex.java @@ -0,0 +1,80 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import org.quiltmc.enigma.api.analysis.EntryReference; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; +import org.quiltmc.enigma.util.CombinedCollection; + +import java.util.Collection; + +/** + * Note: does not currently index main jar references to library types and members. + */ +final class CombinedReferenceIndex implements ReferenceIndex { + private final ReferenceIndex mainIndex; + private final ReferenceIndex libIndex; + + CombinedReferenceIndex(ReferenceIndex mainIndex, ReferenceIndex libIndex) { + this.mainIndex = mainIndex; + this.libIndex = libIndex; + } + + @Override + public Collection getMethodsReferencedBy(MethodEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getMethodsReferencedBy(entry), + this.libIndex.getMethodsReferencedBy(entry) + ); + } + + @Override + public Collection getFieldsReferencedBy(MethodEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getFieldsReferencedBy(entry), + this.libIndex.getFieldsReferencedBy(entry) + ); + } + + @Override + public Collection> getReferencesToField(FieldEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getReferencesToField(entry), + this.libIndex.getReferencesToField(entry) + ); + } + + @Override + public Collection> getReferencesToClass(ClassEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getReferencesToClass(entry), + this.libIndex.getReferencesToClass(entry) + ); + } + + @Override + public Collection> getReferencesToMethod(MethodEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getReferencesToMethod(entry), + this.libIndex.getReferencesToMethod(entry) + ); + } + + @Override + public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getFieldTypeReferencesToClass(entry), + this.libIndex.getFieldTypeReferencesToClass(entry) + ); + } + + @Override + public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { + return new CombinedCollection<>( + this.mainIndex.getMethodTypeReferencesToClass(entry), + this.libIndex.getMethodTypeReferencesToClass(entry) + ); + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/EntryIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/EntryIndex.java index ddb7e4b84..545b41211 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/EntryIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/EntryIndex.java @@ -4,7 +4,6 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.translation.mapping.EntryMapping; import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; -import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; import org.quiltmc.enigma.api.translation.representation.AccessFlags; import org.quiltmc.enigma.api.translation.representation.entry.ClassDefEntry; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; @@ -17,69 +16,15 @@ import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -public class EntryIndex implements JarIndexer { - private final EntryTree tree = new HashEntryTree<>(); +public sealed interface EntryIndex extends JarIndexer permits CombinedEntryIndex, IndependentEntryIndex { + boolean hasClass(ClassEntry entry); - private final Map fieldDefinitions = new HashMap<>(); - private final Map methodDefinitions = new HashMap<>(); - private final Map classDefinitions = new HashMap<>(); - private final Map parameterDefinitions = new HashMap<>(); + boolean hasMethod(MethodEntry entry); - @Override - public void indexClass(ClassDefEntry classEntry) { - this.classDefinitions.put(classEntry, classEntry); - } - - @Override - public void indexMethod(MethodDefEntry methodEntry) { - this.methodDefinitions.put(methodEntry, methodEntry); - methodEntry.streamParameters(this).forEach(paramEntry -> - this.parameterDefinitions.put(paramEntry, paramEntry) - ); - } + boolean hasParameter(LocalVariableEntry entry); - @Override - public void indexField(FieldDefEntry fieldEntry) { - this.fieldDefinitions.put(fieldEntry, fieldEntry); - } - - @Override - public void processIndex(JarIndex index) { - for (ClassEntry entry : this.getClasses()) { - this.tree.insert(entry, null); - } - - for (FieldEntry entry : this.getFields()) { - this.tree.insert(entry, null); - } - - for (MethodEntry entry : this.getMethods()) { - this.tree.insert(entry, null); - } - - for (LocalVariableEntry entry : this.getParameters()) { - this.tree.insert(entry, null); - } - } - - public boolean hasClass(ClassEntry entry) { - return this.classDefinitions.containsKey(entry); - } - - public boolean hasMethod(MethodEntry entry) { - return this.methodDefinitions.containsKey(entry); - } - - public boolean hasParameter(LocalVariableEntry entry) { - return this.parameterDefinitions.containsKey(entry); - } - - public boolean hasField(FieldEntry entry) { - return this.fieldDefinitions.containsKey(entry); - } + boolean hasField(FieldEntry entry); /** * Checks whether the entry has been indexed and therefore exists in the JAR file. @@ -90,21 +35,7 @@ public boolean hasField(FieldEntry entry) { * @param entry the entry to check * @return whether the entry exists */ - public boolean hasEntry(Entry entry) { - if (entry instanceof ClassEntry classEntry) { - return this.hasClass(classEntry); - } else if (entry instanceof MethodEntry methodEntry) { - return this.hasMethod(methodEntry); - } else if (entry instanceof FieldEntry fieldEntry) { - return this.hasField(fieldEntry); - } else if (entry instanceof LocalVariableEntry localVariableEntry) { - if (this.hasParameter(localVariableEntry)) { - return this.validateParameterIndex(localVariableEntry); - } - } - - return false; - } + boolean hasEntry(Entry entry); /** * Validates that the parameter index is not below the minimum index for its parent method and therefore could be valid. @@ -114,100 +45,58 @@ public boolean hasEntry(Entry entry) { * @return whether the index could be valid * @see EnigmaProject#validateParameterIndex(LocalVariableEntry) */ - public boolean validateParameterIndex(LocalVariableEntry parameter) { - MethodEntry parent = parameter.getParent(); - AccessFlags parentAccess = this.getMethodAccess(parent); - - int startIndex = parentAccess != null && parentAccess.isStatic() ? 0 : 1; - return parameter.getIndex() >= startIndex; - } + boolean validateParameterIndex(LocalVariableEntry parameter); @Nullable - public AccessFlags getMethodAccess(MethodEntry entry) { - var def = this.methodDefinitions.get(entry); - return def == null ? null : def.getAccess(); - } + AccessFlags getMethodAccess(MethodEntry entry); @Nullable - public AccessFlags getParameterAccess(LocalVariableEntry entry) { - var def = this.parameterDefinitions.get(entry); - return def == null ? null : this.getMethodAccess(def.getParent()); - } + AccessFlags getParameterAccess(LocalVariableEntry entry); @Nullable - public AccessFlags getFieldAccess(FieldEntry entry) { - var def = this.fieldDefinitions.get(entry); - return def == null ? null : def.getAccess(); - } + AccessFlags getFieldAccess(FieldEntry entry); @Nullable - public AccessFlags getClassAccess(ClassEntry entry) { - var def = this.classDefinitions.get(entry); - return def == null ? null : def.getAccess(); - } + AccessFlags getClassAccess(ClassEntry entry); @Nullable - public AccessFlags getEntryAccess(Entry entry) { - if (entry instanceof MethodEntry methodEntry) { - return this.getMethodAccess(methodEntry); - } else if (entry instanceof FieldEntry fieldEntry) { - return this.getFieldAccess(fieldEntry); - } else if (entry instanceof LocalVariableEntry localVariableEntry) { - return this.getParameterAccess(localVariableEntry); - } else if (entry instanceof ClassEntry classEntry) { - return this.getClassAccess(classEntry); - } - - return null; - } + AccessFlags getEntryAccess(Entry entry); @Nullable - public ClassDefEntry getDefinition(ClassEntry entry) { - return this.classDefinitions.get(entry); - } + ClassDefEntry getDefinition(ClassEntry entry); @Nullable - public MethodDefEntry getDefinition(MethodEntry entry) { - return this.methodDefinitions.get(entry); - } + MethodDefEntry getDefinition(MethodEntry entry); @Nullable - public LocalVariableDefEntry getDefinition(LocalVariableEntry entry) { - return this.parameterDefinitions.get(entry); - } + LocalVariableDefEntry getDefinition(LocalVariableEntry entry); @Nullable - public FieldDefEntry getDefinition(FieldEntry entry) { - return this.fieldDefinitions.get(entry); - } + FieldDefEntry getDefinition(FieldEntry entry); - public Collection getClasses() { - return this.classDefinitions.keySet(); - } + Collection getClasses(); - public Collection getMethods() { - return this.methodDefinitions.keySet(); - } + Collection getMethods(); - public Collection getParameters() { - return this.parameterDefinitions.keySet(); - } + Collection getParameters(); - public Collection getFields() { - return this.fieldDefinitions.keySet(); - } + Collection getFields(); /** * Returns all indexed entries, organised into an {@link EntryTree}. * Note that all entries will have their mapping set to {@code null}. + * * @return the entry tree */ - public EntryTree getTree() { - return this.tree; + EntryTree getTree(); + + @Override + default Class getType() { + return EntryIndex.class; } @Override - public String getTranslationKey() { + default String getTranslationKey() { return "progress.jar.indexing.process.entries"; } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentBridgeMethodIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentBridgeMethodIndex.java new file mode 100644 index 000000000..61b26ed61 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentBridgeMethodIndex.java @@ -0,0 +1,179 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import com.google.common.collect.Maps; +import org.jspecify.annotations.Nullable; +import org.quiltmc.enigma.api.translation.representation.AccessFlags; +import org.quiltmc.enigma.api.translation.representation.ArgumentDescriptor; +import org.quiltmc.enigma.api.translation.representation.MethodDescriptor; +import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final class IndependentBridgeMethodIndex implements BridgeMethodIndex { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final ReferenceIndex referenceIndex; + + private final Map bridgeToSpecialized = Maps.newHashMap(); + private final Map specializedToBridge = Maps.newHashMap(); + + IndependentBridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + this.referenceIndex = referenceIndex; + } + + @Override + public void findBridgeMethods() { + // look for access and bridged methods + for (MethodEntry methodEntry : this.entryIndex.getMethods()) { + MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry; + + AccessFlags access = methodDefEntry.getAccess(); + if (access == null || !access.isSynthetic()) { + continue; + } + + this.indexSyntheticMethod(methodDefEntry, access); + } + } + + @Override + public void processIndex(JarIndex index) { + Map copiedAccessToBridge = new HashMap<>(this.specializedToBridge); + + for (Map.Entry entry : copiedAccessToBridge.entrySet()) { + MethodEntry specializedEntry = entry.getKey(); + MethodEntry bridgeEntry = entry.getValue(); + if (bridgeEntry.getName().equals(specializedEntry.getName())) { + continue; + } + + MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); + this.specializedToBridge.put(renamedSpecializedEntry, this.specializedToBridge.get(specializedEntry)); + } + } + + private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { + MethodEntry specializedMethod = this.findSpecializedMethod(syntheticMethod); + if (specializedMethod == null) { + return; + } + + if (access.isBridge() || this.isPotentialBridge(syntheticMethod, specializedMethod)) { + this.bridgeToSpecialized.put(syntheticMethod, specializedMethod); + if (this.specializedToBridge.containsKey(specializedMethod)) { + // we already have a bridge for this method, so we keep the one higher in the hierarchy + // can happen with a class inheriting from a superclass with one or more bridge method(s) + MethodEntry bridgeMethod = this.specializedToBridge.get(specializedMethod); + this.specializedToBridge.put(specializedMethod, this.getHigherMethod(syntheticMethod, bridgeMethod)); + } else { + this.specializedToBridge.put(specializedMethod, syntheticMethod); + } + } + } + + private MethodEntry findSpecializedMethod(MethodEntry method) { + // we want to find all compiler-added methods that directly call another with no processing + + // get all the methods that we call + final Collection referencedMethods = this.referenceIndex.getMethodsReferencedBy(method); + + // is there just one? + if (referencedMethods.size() != 1) { + return null; + } + + return referencedMethods.stream().findFirst().orElse(null); + } + + private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) { + // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited + AccessFlags bridgeAccess = bridgeMethod.getAccess(); + if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) { + return false; + } + + MethodDescriptor bridgeDesc = bridgeMethod.getDesc(); + MethodDescriptor specializedDesc = specializedMethod.getDesc(); + List bridgeArguments = bridgeDesc.getArgumentDescs(); + List specializedArguments = specializedDesc.getArgumentDescs(); + + // A bridge method will always have the same number of arguments + if (bridgeArguments.size() != specializedArguments.size()) { + return false; + } + + // Check that all argument types are bridge-compatible + for (int i = 0; i < bridgeArguments.size(); i++) { + if (!this.areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) { + return false; + } + } + + // Check that the return type is bridge-compatible + return this.areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc()); + } + + private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) { + if (bridgeDesc.equals(specializedDesc)) { + return true; + } + + // Either the descs will be equal, or they are both types and different through a generic + if (bridgeDesc.isType() && specializedDesc.isType()) { + ClassEntry bridgeType = bridgeDesc.getTypeEntry(); + ClassEntry accessedType = specializedDesc.getTypeEntry(); + + // If the given types are completely unrelated to each other, this can't be bridge compatible + InheritanceIndex.Relation relation = this.inheritanceIndex.computeClassRelation(accessedType, bridgeType); + return relation != InheritanceIndex.Relation.UNRELATED; + } + + return false; + } + + // Get the method higher in the hierarchy + private MethodEntry getHigherMethod(MethodEntry bridgeMethod1, MethodEntry bridgeMethod2) { + ClassEntry parent1 = bridgeMethod1.getParent(); + ClassEntry parent2 = bridgeMethod2.getParent(); + return this.inheritanceIndex.getDescendants(parent1).contains(parent2) ? bridgeMethod1 : bridgeMethod2; + } + + @Override + public boolean isBridgeMethod(MethodEntry entry) { + return this.bridgeToSpecialized.containsKey(entry); + } + + @Override + public boolean isSpecializedMethod(MethodEntry entry) { + return this.specializedToBridge.containsKey(entry); + } + + @Override + public @Nullable MethodEntry getBridgeFromSpecialized(MethodEntry specialized) { + return this.specializedToBridge.get(specialized); + } + + @Override + public MethodEntry getSpecializedFromBridge(MethodEntry bridge) { + return this.bridgeToSpecialized.get(bridge); + } + + @Override + public Map getSpecializedToBridge() { + return Collections.unmodifiableMap(this.specializedToBridge); + } + + @Override + public Map getBridgeToSpecialized() { + return Collections.unmodifiableMap(this.bridgeToSpecialized); + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentEntryIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentEntryIndex.java new file mode 100644 index 000000000..2f0079434 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentEntryIndex.java @@ -0,0 +1,196 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import org.jspecify.annotations.Nullable; +import org.quiltmc.enigma.api.translation.mapping.EntryMapping; +import org.quiltmc.enigma.api.translation.mapping.tree.EntryTree; +import org.quiltmc.enigma.api.translation.mapping.tree.HashEntryTree; +import org.quiltmc.enigma.api.translation.representation.AccessFlags; +import org.quiltmc.enigma.api.translation.representation.entry.ClassDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.Entry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; +import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.LocalVariableEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +final class IndependentEntryIndex implements EntryIndex { + private final EntryTree tree = new HashEntryTree<>(); + + private final Map fieldDefinitions = new HashMap<>(); + private final Map methodDefinitions = new HashMap<>(); + private final Map classDefinitions = new HashMap<>(); + private final Map parameterDefinitions = new HashMap<>(); + + @Override + public void indexClass(ClassDefEntry classEntry) { + this.classDefinitions.put(classEntry, classEntry); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + this.methodDefinitions.put(methodEntry, methodEntry); + methodEntry.streamParameters(this).forEach(paramEntry -> + this.parameterDefinitions.put(paramEntry, paramEntry) + ); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + this.fieldDefinitions.put(fieldEntry, fieldEntry); + } + + @Override + public void processIndex(JarIndex index) { + for (ClassEntry entry : this.getClasses()) { + this.tree.insert(entry, null); + } + + for (FieldEntry entry : this.getFields()) { + this.tree.insert(entry, null); + } + + for (MethodEntry entry : this.getMethods()) { + this.tree.insert(entry, null); + } + + for (LocalVariableEntry entry : this.getParameters()) { + this.tree.insert(entry, null); + } + } + + @Override + public boolean hasClass(ClassEntry entry) { + return this.classDefinitions.containsKey(entry); + } + + @Override + public boolean hasMethod(MethodEntry entry) { + return this.methodDefinitions.containsKey(entry); + } + + @Override + public boolean hasParameter(LocalVariableEntry entry) { + return this.parameterDefinitions.containsKey(entry); + } + + @Override + public boolean hasField(FieldEntry entry) { + return this.fieldDefinitions.containsKey(entry); + } + + @Override + public boolean hasEntry(Entry entry) { + if (entry instanceof ClassEntry classEntry) { + return this.hasClass(classEntry); + } else if (entry instanceof MethodEntry methodEntry) { + return this.hasMethod(methodEntry); + } else if (entry instanceof FieldEntry fieldEntry) { + return this.hasField(fieldEntry); + } else if (entry instanceof LocalVariableEntry localVariableEntry) { + if (this.hasParameter(localVariableEntry)) { + return this.validateParameterIndex(localVariableEntry); + } + } + + return false; + } + + @Override + public boolean validateParameterIndex(LocalVariableEntry parameter) { + MethodEntry parent = parameter.getParent(); + AccessFlags parentAccess = this.getMethodAccess(parent); + + int startIndex = parentAccess != null && parentAccess.isStatic() ? 0 : 1; + return parameter.getIndex() >= startIndex; + } + + @Override + public @Nullable AccessFlags getMethodAccess(MethodEntry entry) { + var def = this.methodDefinitions.get(entry); + return def == null ? null : def.getAccess(); + } + + @Override + public @Nullable AccessFlags getParameterAccess(LocalVariableEntry entry) { + var def = this.parameterDefinitions.get(entry); + return def == null ? null : this.getMethodAccess(def.getParent()); + } + + @Override + public @Nullable AccessFlags getFieldAccess(FieldEntry entry) { + var def = this.fieldDefinitions.get(entry); + return def == null ? null : def.getAccess(); + } + + @Override + public @Nullable AccessFlags getClassAccess(ClassEntry entry) { + var def = this.classDefinitions.get(entry); + return def == null ? null : def.getAccess(); + } + + @Override + public @Nullable AccessFlags getEntryAccess(Entry entry) { + if (entry instanceof MethodEntry methodEntry) { + return this.getMethodAccess(methodEntry); + } else if (entry instanceof FieldEntry fieldEntry) { + return this.getFieldAccess(fieldEntry); + } else if (entry instanceof LocalVariableEntry localVariableEntry) { + return this.getParameterAccess(localVariableEntry); + } else if (entry instanceof ClassEntry classEntry) { + return this.getClassAccess(classEntry); + } + + return null; + } + + @Override + public @Nullable ClassDefEntry getDefinition(ClassEntry entry) { + return this.classDefinitions.get(entry); + } + + @Override + public @Nullable MethodDefEntry getDefinition(MethodEntry entry) { + return this.methodDefinitions.get(entry); + } + + @Override + public @Nullable LocalVariableDefEntry getDefinition(LocalVariableEntry entry) { + return this.parameterDefinitions.get(entry); + } + + @Override + public @Nullable FieldDefEntry getDefinition(FieldEntry entry) { + return this.fieldDefinitions.get(entry); + } + + @Override + public Collection getClasses() { + return this.classDefinitions.keySet(); + } + + @Override + public Collection getMethods() { + return this.methodDefinitions.keySet(); + } + + @Override + public Collection getParameters() { + return this.parameterDefinitions.keySet(); + } + + @Override + public Collection getFields() { + return this.fieldDefinitions.keySet(); + } + + @Override + public EntryTree getTree() { + return this.tree; + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentJarIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentJarIndex.java new file mode 100644 index 000000000..e3661d552 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentJarIndex.java @@ -0,0 +1,32 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import org.quiltmc.enigma.impl.analysis.index.AbstractJarIndex; + +abstract class IndependentJarIndex extends AbstractJarIndex { + private final EntryIndex entryIndex; + private final ReferenceIndex referenceIndex; + private final BridgeMethodIndex bridgeMethodIndex; + + IndependentJarIndex( + EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, + BridgeMethodIndex bridgeMethodIndex, JarIndexer... otherIndexers + ) { + super(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, otherIndexers); + + this.entryIndex = entryIndex; + this.referenceIndex = referenceIndex; + this.bridgeMethodIndex = bridgeMethodIndex; + } + + EntryIndex getEntryIndex() { + return this.entryIndex; + } + + ReferenceIndex getReferenceIndex() { + return this.referenceIndex; + } + + BridgeMethodIndex getBridgeMethodIndex() { + return this.bridgeMethodIndex; + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentReferenceIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentReferenceIndex.java new file mode 100644 index 000000000..de27f630d --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/IndependentReferenceIndex.java @@ -0,0 +1,175 @@ +package org.quiltmc.enigma.api.analysis.index.jar; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import org.quiltmc.enigma.api.analysis.EntryReference; +import org.quiltmc.enigma.api.analysis.ReferenceTargetType; +import org.quiltmc.enigma.api.translation.mapping.ResolutionStrategy; +import org.quiltmc.enigma.api.translation.representation.Lambda; +import org.quiltmc.enigma.api.translation.representation.MethodDescriptor; +import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.Entry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; +import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; + +import java.util.Collection; +import java.util.Map; + +final class IndependentReferenceIndex implements ReferenceIndex { + private Multimap methodReferences = HashMultimap.create(); + private Multimap fieldReferences = HashMultimap.create(); + + private Multimap> referencesToMethods = HashMultimap.create(); + private Multimap> referencesToClasses = HashMultimap.create(); + private Multimap> referencesToFields = HashMultimap.create(); + private Multimap> fieldTypeReferences = HashMultimap.create(); + private Multimap> methodTypeReferences = HashMultimap.create(); + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + this.indexMethodDescriptor(methodEntry, methodEntry.getDesc()); + } + + private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { + for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { + this.indexMethodTypeDescriptor(entry, typeDescriptor); + } + + this.indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); + } + + private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isType()) { + ClassEntry referencedClass = typeDescriptor.getTypeEntry(); + this.methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); + } else if (typeDescriptor.isArray()) { + this.indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); + } + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + this.indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); + } + + private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isType()) { + ClassEntry referencedClass = typeDescriptor.getTypeEntry(); + this.fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); + } else if (typeDescriptor.isArray()) { + this.indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); + } + } + + @Override + public void indexClassReference(MethodDefEntry callerEntry, ClassEntry referencedEntry, ReferenceTargetType targetType) { + this.referencesToClasses.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + } + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { + this.referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + this.methodReferences.put(callerEntry, referencedEntry); + + if (referencedEntry.isConstructor()) { + ClassEntry referencedClass = referencedEntry.getParent(); + this.referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); + } + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { + this.referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + this.fieldReferences.put(callerEntry, referencedEntry); + } + + @Override + public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { + if (lambda.implMethod() instanceof MethodEntry method) { + this.indexMethodReference(callerEntry, method, targetType); + } else { + this.indexFieldReference(callerEntry, (FieldEntry) lambda.implMethod(), targetType); + } + + this.indexMethodDescriptor(callerEntry, lambda.invokedType()); + this.indexMethodDescriptor(callerEntry, lambda.samMethodType()); + this.indexMethodDescriptor(callerEntry, lambda.instantiatedMethodType()); + } + + @Override + public void processIndex(JarIndex index) { + this.methodReferences = this.remapReferences(index, this.methodReferences); + this.fieldReferences = this.remapReferences(index, this.fieldReferences); + this.referencesToMethods = this.remapReferencesTo(index, this.referencesToMethods); + this.referencesToClasses = this.remapReferencesTo(index, this.referencesToClasses); + this.referencesToFields = this.remapReferencesTo(index, this.referencesToFields); + this.fieldTypeReferences = this.remapReferencesTo(index, this.fieldTypeReferences); + this.methodTypeReferences = this.remapReferencesTo(index, this.methodTypeReferences); + } + + private , V extends Entry> Multimap remapReferences(JarIndex index, Multimap multimap) { + final int keySetSize = multimap.keySet().size(); + Multimap resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize); + for (Map.Entry entry : multimap.entries()) { + resolved.put(this.remap(index, entry.getKey()), this.remap(index, entry.getValue())); + } + + return resolved; + } + + private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { + final int keySetSize = multimap.keySet().size(); + Multimap> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); + for (Map.Entry> entry : multimap.entries()) { + resolved.put(this.remap(index, entry.getKey()), this.remap(index, entry.getValue())); + } + + return resolved; + } + + private > E remap(JarIndex index, E entry) { + return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); + } + + private , C extends Entry> EntryReference remap(JarIndex index, EntryReference reference) { + return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + } + + @Override + public Collection getMethodsReferencedBy(MethodEntry entry) { + return this.methodReferences.get(entry); + } + + @Override + public Collection getFieldsReferencedBy(MethodEntry entry) { + return this.fieldReferences.get(entry); + } + + @Override + public Collection> getReferencesToField(FieldEntry entry) { + return this.referencesToFields.get(entry); + } + + @Override + public Collection> getReferencesToClass(ClassEntry entry) { + return this.referencesToClasses.get(entry); + } + + @Override + public Collection> getReferencesToMethod(MethodEntry entry) { + return this.referencesToMethods.get(entry); + } + + @Override + public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { + return this.fieldTypeReferences.get(entry); + } + + @Override + public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { + return this.methodTypeReferences.get(entry); + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/JarIndexer.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/JarIndexer.java index 37581fbb6..ce8e73710 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/JarIndexer.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/JarIndexer.java @@ -39,6 +39,18 @@ default void processIndex(JarIndex index) { String getTranslationKey(); + // TODO we should probably replace this with a type object in a breaking update + /** + * @return the class used to {@linkplain JarIndex#getIndex(Class) look up} this indexer in a {@link JarIndex} + * + * @implSpec must return a type to which this indexer is assignable + * + * @implNote a {@link JarIndex} can only contain one index of a given type + */ + default Class getType() { + return this.getClass(); + } + record EnclosingMethodData(String owner, String name, String descriptor) { public MethodEntry getMethod() { return MethodEntry.parse(this.owner, this.name, this.descriptor); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/LibrariesJarIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/LibrariesJarIndex.java index 7c998853e..f3a2a4fda 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/LibrariesJarIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/LibrariesJarIndex.java @@ -2,14 +2,13 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.class_provider.ProjectClassProvider; -import org.quiltmc.enigma.impl.analysis.index.AbstractJarIndex; import java.util.Collection; /** * An index of the library jars of an {@link EnigmaProject}. */ -public class LibrariesJarIndex extends AbstractJarIndex { +public class LibrariesJarIndex extends IndependentJarIndex { public LibrariesJarIndex( EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, JarIndexer... otherIndexers @@ -22,12 +21,12 @@ public LibrariesJarIndex( * @return the newly created index */ public static LibrariesJarIndex empty() { - EntryIndex entryIndex = new EntryIndex(); - ReferenceIndex referenceIndex = new ReferenceIndex(); + EntryIndex entryIndex = new IndependentEntryIndex(); + ReferenceIndex referenceIndex = new IndependentReferenceIndex(); InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); return new LibrariesJarIndex( entryIndex, inheritanceIndex, referenceIndex, - new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex) + new IndependentBridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex) ); } diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/MainJarIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/MainJarIndex.java index 6b0cd5ba9..95fb08eef 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/MainJarIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/MainJarIndex.java @@ -2,14 +2,13 @@ import org.quiltmc.enigma.api.EnigmaProject; import org.quiltmc.enigma.api.class_provider.ProjectClassProvider; -import org.quiltmc.enigma.impl.analysis.index.AbstractJarIndex; import java.util.Collection; /** * An index of the main jar of an {@link EnigmaProject}. */ -public class MainJarIndex extends AbstractJarIndex { +public class MainJarIndex extends IndependentJarIndex { public MainJarIndex( EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, JarIndexer... otherIndexers @@ -22,10 +21,10 @@ public MainJarIndex( * @return the newly created index */ public static MainJarIndex empty() { - EntryIndex entryIndex = new EntryIndex(); + EntryIndex entryIndex = new IndependentEntryIndex(); InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); - ReferenceIndex referenceIndex = new ReferenceIndex(); - BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); + ReferenceIndex referenceIndex = new IndependentReferenceIndex(); + BridgeMethodIndex bridgeMethodIndex = new IndependentBridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex(); EnclosingMethodIndex enclosingMethodIndex = new EnclosingMethodIndex(); LambdaIndex lambdaIndex = new LambdaIndex(); diff --git a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/ReferenceIndex.java b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/ReferenceIndex.java index c81e488fb..fdbea1c8f 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/ReferenceIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/api/analysis/index/jar/ReferenceIndex.java @@ -1,173 +1,36 @@ package org.quiltmc.enigma.api.analysis.index.jar; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; import org.quiltmc.enigma.api.analysis.EntryReference; -import org.quiltmc.enigma.api.analysis.ReferenceTargetType; -import org.quiltmc.enigma.api.translation.mapping.ResolutionStrategy; -import org.quiltmc.enigma.api.translation.representation.Lambda; -import org.quiltmc.enigma.api.translation.representation.MethodDescriptor; -import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; -import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.quiltmc.enigma.api.translation.representation.entry.FieldDefEntry; import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; import org.quiltmc.enigma.api.translation.representation.entry.MethodDefEntry; import org.quiltmc.enigma.api.translation.representation.entry.MethodEntry; import java.util.Collection; -import java.util.Map; -public class ReferenceIndex implements JarIndexer { - private Multimap methodReferences = HashMultimap.create(); - private Multimap fieldReferences = HashMultimap.create(); +public sealed interface ReferenceIndex extends JarIndexer permits CombinedReferenceIndex, IndependentReferenceIndex { + Collection getMethodsReferencedBy(MethodEntry entry); - private Multimap> referencesToMethods = HashMultimap.create(); - private Multimap> referencesToClasses = HashMultimap.create(); - private Multimap> referencesToFields = HashMultimap.create(); - private Multimap> fieldTypeReferences = HashMultimap.create(); - private Multimap> methodTypeReferences = HashMultimap.create(); + Collection getFieldsReferencedBy(MethodEntry entry); - @Override - public void indexMethod(MethodDefEntry methodEntry) { - this.indexMethodDescriptor(methodEntry, methodEntry.getDesc()); - } - - private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { - for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { - this.indexMethodTypeDescriptor(entry, typeDescriptor); - } + Collection> getReferencesToField(FieldEntry entry); - this.indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); - } - - private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { - if (typeDescriptor.isType()) { - ClassEntry referencedClass = typeDescriptor.getTypeEntry(); - this.methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); - } else if (typeDescriptor.isArray()) { - this.indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); - } - } + Collection> getReferencesToClass(ClassEntry entry); - @Override - public void indexField(FieldDefEntry fieldEntry) { - this.indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); - } + Collection> getReferencesToMethod(MethodEntry entry); - private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { - if (typeDescriptor.isType()) { - ClassEntry referencedClass = typeDescriptor.getTypeEntry(); - this.fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); - } else if (typeDescriptor.isArray()) { - this.indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); - } - } + Collection> getFieldTypeReferencesToClass(ClassEntry entry); - @Override - public void indexClassReference(MethodDefEntry callerEntry, ClassEntry referencedEntry, ReferenceTargetType targetType) { - this.referencesToClasses.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); - } + Collection> getMethodTypeReferencesToClass(ClassEntry entry); @Override - public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { - this.referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); - this.methodReferences.put(callerEntry, referencedEntry); - - if (referencedEntry.isConstructor()) { - ClassEntry referencedClass = referencedEntry.getParent(); - this.referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); - } - } - - @Override - public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { - this.referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); - this.fieldReferences.put(callerEntry, referencedEntry); - } - - @Override - public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) { - if (lambda.implMethod() instanceof MethodEntry method) { - this.indexMethodReference(callerEntry, method, targetType); - } else { - this.indexFieldReference(callerEntry, (FieldEntry) lambda.implMethod(), targetType); - } - - this.indexMethodDescriptor(callerEntry, lambda.invokedType()); - this.indexMethodDescriptor(callerEntry, lambda.samMethodType()); - this.indexMethodDescriptor(callerEntry, lambda.instantiatedMethodType()); - } - - @Override - public void processIndex(JarIndex index) { - this.methodReferences = this.remapReferences(index, this.methodReferences); - this.fieldReferences = this.remapReferences(index, this.fieldReferences); - this.referencesToMethods = this.remapReferencesTo(index, this.referencesToMethods); - this.referencesToClasses = this.remapReferencesTo(index, this.referencesToClasses); - this.referencesToFields = this.remapReferencesTo(index, this.referencesToFields); - this.fieldTypeReferences = this.remapReferencesTo(index, this.fieldTypeReferences); - this.methodTypeReferences = this.remapReferencesTo(index, this.methodTypeReferences); - } - - private , V extends Entry> Multimap remapReferences(JarIndex index, Multimap multimap) { - final int keySetSize = multimap.keySet().size(); - Multimap resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize); - for (Map.Entry entry : multimap.entries()) { - resolved.put(this.remap(index, entry.getKey()), this.remap(index, entry.getValue())); - } - - return resolved; - } - - private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { - final int keySetSize = multimap.keySet().size(); - Multimap> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); - for (Map.Entry> entry : multimap.entries()) { - resolved.put(this.remap(index, entry.getKey()), this.remap(index, entry.getValue())); - } - - return resolved; - } - - private > E remap(JarIndex index, E entry) { - return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); - } - - private , C extends Entry> EntryReference remap(JarIndex index, EntryReference reference) { - return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); - } - - public Collection getMethodsReferencedBy(MethodEntry entry) { - return this.methodReferences.get(entry); - } - - public Collection getFieldsReferencedBy(MethodEntry entry) { - return this.fieldReferences.get(entry); - } - - public Collection> getReferencesToField(FieldEntry entry) { - return this.referencesToFields.get(entry); - } - - public Collection> getReferencesToClass(ClassEntry entry) { - return this.referencesToClasses.get(entry); - } - - public Collection> getReferencesToMethod(MethodEntry entry) { - return this.referencesToMethods.get(entry); - } - - public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { - return this.fieldTypeReferences.get(entry); - } - - public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { - return this.methodTypeReferences.get(entry); + default Class getType() { + return ReferenceIndex.class; } @Override - public String getTranslationKey() { + default String getTranslationKey() { return "progress.jar.indexing.process.references"; } } diff --git a/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/index/AbstractJarIndex.java b/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/index/AbstractJarIndex.java index 893ec9efd..2db12a060 100644 --- a/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/index/AbstractJarIndex.java +++ b/enigma/src/main/java/org/quiltmc/enigma/impl/analysis/index/AbstractJarIndex.java @@ -67,7 +67,10 @@ public AbstractJarIndex( Stream.of(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex), Arrays.stream(otherIndexers) ) - .collect(ImmutableMap.toImmutableMap(JarIndexer::getClass, Function.identity())); + .collect(ImmutableMap.toImmutableMap( + JarIndexer::getType, + Function.identity() + )); this.entryResolver = new IndexEntryResolver(this); this.childrenByClass = ArrayListMultimap.create(); diff --git a/enigma/src/main/java/org/quiltmc/enigma/util/CombinedCollection.java b/enigma/src/main/java/org/quiltmc/enigma/util/CombinedCollection.java new file mode 100644 index 000000000..7912a213d --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/util/CombinedCollection.java @@ -0,0 +1,151 @@ +package org.quiltmc.enigma.util; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.Iterator; + +/** + * A collection backed by two other collections. + * + *

Does not support the {@link #add(Object)} or {@link #addAll(Collection)} methods! + * + *

Removal is supported. + * + * @param the type of elements + */ +public class CombinedCollection implements Collection { + private static UnsupportedOperationException createUnsupportedAddException() { + return new UnsupportedOperationException("cannot add to combined collection!"); + } + + private final Collection first; + private final Collection second; + + public CombinedCollection(Collection first, Collection second) { + this.first = first; + this.second = second; + } + + @Override + public int size() { + return this.first.size() + this.second.size(); + } + + @Override + public boolean isEmpty() { + return this.first.isEmpty() && this.second.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return this.first.contains(o) || this.second.contains(o); + } + + @NonNull + @Override + public Iterator iterator() { + return new CombinedIterator(); + } + + @Override + public Object @NonNull[] toArray() { + final int size = this.size(); + final Object[] array = new Object[size]; + final Iterator itr = this.iterator(); + int i = 0; + while (itr.hasNext()) { + array[i++] = itr.next(); + } + + return array; + } + + // Based on HashMap::prepareArray and HashMap::keysToArray + @SuppressWarnings("unchecked") + @Override + public T1 @NonNull[] toArray(T1[] a) { + final int size = this.size(); + if (a.length < size) { + a = (T1[]) java.lang.reflect.Array + .newInstance(a.getClass().getComponentType(), size); + } else if (a.length > size) { + a[size] = null; + } + + final Iterator itr = this.iterator(); + final Object[] objects = a; + for (int i = 0; i < size; i++) { + objects[i] = itr.next(); + } + + return a; + } + + @Override + public boolean add(T t) { + throw createUnsupportedAddException(); + } + + @Override + public boolean remove(Object o) { + return this.first.remove(o) | this.second.remove(o); + } + + @SuppressWarnings("SuspiciousMethodCalls") + @Override + public boolean containsAll(@NonNull Collection collection) { + return collection.stream().allMatch(o -> this.first.contains(o) || this.second.contains(o)); + } + + @Override + public boolean addAll(@NonNull Collection collection) { + throw createUnsupportedAddException(); + } + + @Override + public boolean retainAll(@NonNull Collection collection) { + return this.first.retainAll(collection) | this.second.retainAll(collection); + } + + @Override + public boolean removeAll(@NonNull Collection collection) { + return this.first.removeAll(collection) | this.second.removeAll(collection); + } + + @Override + public void clear() { + this.first.clear(); + this.second.clear(); + } + + private class CombinedIterator implements Iterator { + final Iterator first = CombinedCollection.this.first.iterator(); + final Iterator second = CombinedCollection.this.second.iterator(); + + // null iff next has not been called + @Nullable + Iterator nextSource; + + @Override + public boolean hasNext() { + return this.first.hasNext() || this.second.hasNext(); + } + + @Override + public T next() { + this.nextSource = this.first.hasNext() ? this.first : this.second; + return this.nextSource.next(); + } + + @Override + public void remove() { + if (this.nextSource == null) { + throw new IllegalStateException("remove called before any calls to next!"); + } else { + this.nextSource.remove(); + } + } + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/util/CombinedSet.java b/enigma/src/main/java/org/quiltmc/enigma/util/CombinedSet.java new file mode 100644 index 000000000..7e2cd6a4b --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/util/CombinedSet.java @@ -0,0 +1,9 @@ +package org.quiltmc.enigma.util; + +import java.util.Set; + +public class CombinedSet extends CombinedCollection implements Set { + public CombinedSet(Set first, Set second) { + super(first, second); + } +} diff --git a/enigma/src/main/java/org/quiltmc/enigma/util/UnmodifiableCombinedMap.java b/enigma/src/main/java/org/quiltmc/enigma/util/UnmodifiableCombinedMap.java new file mode 100644 index 000000000..597ea1ce6 --- /dev/null +++ b/enigma/src/main/java/org/quiltmc/enigma/util/UnmodifiableCombinedMap.java @@ -0,0 +1,103 @@ +package org.quiltmc.enigma.util; + +import org.jspecify.annotations.NonNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * An unmodifiable view of two backing maps. + * + *

Only intended for maps with no {@link #keySet()} overlap. Behavior is undefined in the case of overlap. + * + * @param the type of keys + * @param the type of values + */ +public class UnmodifiableCombinedMap implements Map { + private static UnsupportedOperationException createUnsupportedModificationException() { + return new UnsupportedOperationException("cannot modify combined map!"); + } + + private final Map first; + private final Map second; + + private final Set keySet; + private final Collection values; + private final Set> entrySet; + + public UnmodifiableCombinedMap(Map first, Map second) { + this.first = first; + this.second = second; + + this.keySet = Collections.unmodifiableSet(new CombinedSet<>(this.first.keySet(), this.second.keySet())); + this.values = Collections + .unmodifiableCollection(new CombinedCollection<>(this.first.values(), this.second.values())); + this.entrySet = Collections.unmodifiableSet(new CombinedSet<>(this.first.entrySet(), this.second.entrySet())); + } + + @Override + public int size() { + return this.first.size() + this.second.size(); + } + + @Override + public boolean isEmpty() { + return this.first.isEmpty() && this.second.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.first.containsKey(key) || this.second.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.first.containsValue(value) || this.second.containsValue(value); + } + + @SuppressWarnings("SuspiciousMethodCalls") + @Override + public V get(Object key) { + return this.first.containsKey(key) ? this.first.get(key) : this.second.get(key); + } + + @Override + public V put(K key, V value) { + throw createUnsupportedModificationException(); + } + + @Override + public V remove(Object key) { + throw createUnsupportedModificationException(); + } + + @Override + public void putAll(@NonNull Map m) { + throw createUnsupportedModificationException(); + } + + @Override + public void clear() { + throw createUnsupportedModificationException(); + } + + @NonNull + @Override + public Set keySet() { + return this.keySet; + } + + @NonNull + @Override + public Collection values() { + return this.values; + } + + @NonNull + @Override + public Set> entrySet() { + return this.entrySet; + } +}