/*******************************************************************************
* Copyright (c) 2015 Jeff Martin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public
* License v3.0 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl.html
*
* Contributors:
* Jeff Martin - initial API and implementation
******************************************************************************/
package cuchaz.enigma.analysis;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import cuchaz.enigma.bytecode.AccessFlags;
import cuchaz.enigma.mapping.*;
import cuchaz.enigma.mapping.entry.*;
import java.util.*;
public class TranslationIndex {
private final ReferencedEntryPool entryPool;
private Map superclasses;
private Map defEntries = new HashMap<>();
private Multimap fieldEntries;
private Multimap methodEntries;
private Multimap interfaces;
public TranslationIndex(ReferencedEntryPool entryPool) {
this.entryPool = entryPool;
this.superclasses = Maps.newHashMap();
this.fieldEntries = HashMultimap.create();
this.methodEntries = HashMultimap.create();
this.interfaces = HashMultimap.create();
for (FieldDefEntry entry : fieldEntries.values()) {
defEntries.put(entry, entry);
}
for (MethodDefEntry entry : methodEntries.values()) {
defEntries.put(entry, entry);
}
}
public TranslationIndex(TranslationIndex other, Translator translator) {
this.entryPool = other.entryPool;
// translate the superclasses
this.superclasses = Maps.newHashMap();
for (Map.Entry mapEntry : other.superclasses.entrySet()) {
this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue()));
}
// translate the interfaces
this.interfaces = HashMultimap.create();
for (Map.Entry mapEntry : other.interfaces.entries()) {
this.interfaces.put(
translator.getTranslatedClass(mapEntry.getKey()),
translator.getTranslatedClass(mapEntry.getValue())
);
}
// translate the fields
this.fieldEntries = HashMultimap.create();
for (Map.Entry mapEntry : other.fieldEntries.entries()) {
this.fieldEntries.put(
translator.getTranslatedClass(mapEntry.getKey()),
translator.getTranslatedFieldDef(mapEntry.getValue())
);
}
this.methodEntries = HashMultimap.create();
for (Map.Entry mapEntry : other.methodEntries.entries()) {
this.methodEntries.put(
translator.getTranslatedClass(mapEntry.getKey()),
translator.getTranslatedMethodDef(mapEntry.getValue())
);
}
for (FieldDefEntry entry : fieldEntries.values()) {
defEntries.put(entry, entry);
}
for (MethodDefEntry entry : methodEntries.values()) {
defEntries.put(entry, entry);
}
}
protected ClassDefEntry indexClass(int access, String name, String signature, String superName, String[] interfaces) {
ClassDefEntry classEntry = new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access));
if (isJre(classEntry)) {
return null;
}
// add the superclass
ClassEntry superclassEntry = entryPool.getClass(superName);
if (superclassEntry != null) {
this.superclasses.put(classEntry, superclassEntry);
}
// add the interfaces
for (String interfaceClassName : interfaces) {
ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName);
if (!isJre(interfaceClassEntry)) {
this.interfaces.put(classEntry, interfaceClassEntry);
}
}
return classEntry;
}
protected void indexField(FieldDefEntry fieldEntry) {
this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry);
this.defEntries.put(fieldEntry, fieldEntry);
}
protected void indexMethod(MethodDefEntry methodEntry) {
this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry);
this.defEntries.put(methodEntry, methodEntry);
}
public void renameClasses(Map renames) {
EntryRenamer.renameClassesInMap(renames, this.superclasses);
EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries);
EntryRenamer.renameClassesInMultimap(renames, this.methodEntries);
this.defEntries.clear();
for (FieldDefEntry entry : fieldEntries.values()) {
defEntries.put(entry, entry);
}
for (MethodDefEntry entry : methodEntries.values()) {
defEntries.put(entry, entry);
}
}
public ClassEntry getSuperclass(ClassEntry classEntry) {
return this.superclasses.get(classEntry);
}
public List getAncestry(ClassEntry classEntry) {
List ancestors = Lists.newArrayList();
while (classEntry != null) {
classEntry = getSuperclass(classEntry);
if (classEntry != null) {
ancestors.add(classEntry);
}
}
return ancestors;
}
public List getSubclass(ClassEntry classEntry) {
// linear search is fast enough for now
List subclasses = Lists.newArrayList();
for (Map.Entry entry : this.superclasses.entrySet()) {
ClassEntry subclass = entry.getKey();
ClassEntry superclass = entry.getValue();
if (classEntry.equals(superclass)) {
subclasses.add(subclass);
}
}
return subclasses;
}
public void getSubclassesRecursively(Set out, ClassEntry classEntry) {
for (ClassEntry subclassEntry : getSubclass(classEntry)) {
out.add(subclassEntry);
getSubclassesRecursively(out, subclassEntry);
}
}
public void getSubclassNamesRecursively(Set out, ClassEntry classEntry) {
for (ClassEntry subclassEntry : getSubclass(classEntry)) {
out.add(subclassEntry.getName());
getSubclassNamesRecursively(out, subclassEntry);
}
}
public Collection> getClassInterfaces() {
return this.interfaces.entries();
}
public Collection getInterfaces(ClassEntry classEntry) {
return this.interfaces.get(classEntry);
}
public boolean isInterface(ClassEntry classEntry) {
return this.interfaces.containsValue(classEntry);
}
public boolean entryExists(Entry entry) {
if (entry == null) {
return false;
}
if (entry instanceof FieldEntry) {
return fieldExists((FieldEntry) entry);
} else if (entry instanceof MethodEntry) {
return methodExists((MethodEntry) entry);
} else if (entry instanceof LocalVariableEntry) {
return methodExists(((LocalVariableEntry) entry).getOwnerEntry());
}
throw new IllegalArgumentException("Cannot check existence for " + entry.getClass());
}
public boolean fieldExists(FieldEntry fieldEntry) {
return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry);
}
public boolean methodExists(MethodEntry methodEntry) {
return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry);
}
public ClassEntry resolveEntryOwner(Entry entry) {
if (entry instanceof ClassEntry) {
return (ClassEntry) entry;
}
if (entryExists(entry)) {
return entry.getOwnerClassEntry();
}
DefEntry def = defEntries.get(entry);
if (def != null && (def.getAccess().isPrivate())) {
return null;
}
// if we're protected/public/non-static, chances are we're somewhere down
LinkedList classEntries = new LinkedList<>();
classEntries.add(entry.getOwnerClassEntry());
while (!classEntries.isEmpty()) {
ClassEntry c = classEntries.remove();
Entry cEntry = entry.updateOwnership(c);
if (entryExists(cEntry)) {
def = defEntries.get(cEntry);
if (def == null || (!def.getAccess().isPrivate())) {
return cEntry.getOwnerClassEntry();
}
}
ClassEntry superC = getSuperclass(c);
if (superC != null) {
classEntries.add(superC);
}
if (entry instanceof MethodEntry) {
classEntries.addAll(getInterfaces(c));
}
}
return null;
}
private boolean isJre(ClassEntry classEntry) {
String packageName = classEntry.getPackageName();
return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
}
}