From 00fcd0550fcdda621c2e4662f6ddd55ce673b931 Mon Sep 17 00:00:00 2001 From: Gegy Date: Thu, 24 Jan 2019 14:48:32 +0200 Subject: [WIP] Mapping rework (#91) * Move packages * Mapping & entry refactor: first pass * Fix deobf -> obf tree remapping * Resolve various issues * Give all entries the potential for parents and treat inner classes as children * Deobf UI tree elements * Tests pass * Sort mapping output * Fix delta tracking * Index separation and first pass for #97 * Keep track of remapped jar index * Fix child entries not being remapped * Drop non-root entries * Track dropped mappings * Fix enigma mapping ordering * EntryTreeNode interface * Small tweaks * Naive full index remap on rename * Entries can resolve to more than one root entry * Support alternative resolution strategies * Bridge method resolution * Tests pass * Fix mappings being used where there are none * Fix methods with different descriptors being considered unique. closes #89 --- .../translation/representation/AccessFlags.java | 112 +++++++++ .../representation/MethodDescriptor.java | 132 ++++++++++ .../representation/ProcyonEntryFactory.java | 45 ++++ .../representation/ReferencedEntryPool.java | 60 +++++ .../translation/representation/Signature.java | 93 +++++++ .../translation/representation/TypeDescriptor.java | 268 +++++++++++++++++++++ .../representation/entry/ClassDefEntry.java | 92 +++++++ .../representation/entry/ClassEntry.java | 180 ++++++++++++++ .../translation/representation/entry/DefEntry.java | 7 + .../translation/representation/entry/Entry.java | 99 ++++++++ .../representation/entry/FieldDefEntry.java | 61 +++++ .../representation/entry/FieldEntry.java | 86 +++++++ .../entry/LocalVariableDefEntry.java | 45 ++++ .../representation/entry/LocalVariableEntry.java | 92 +++++++ .../representation/entry/MethodDefEntry.java | 77 ++++++ .../representation/entry/MethodEntry.java | 95 ++++++++ .../representation/entry/ParentedEntry.java | 71 ++++++ 17 files changed, 1615 insertions(+) create mode 100644 src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/Signature.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java create mode 100644 src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java (limited to 'src/main/java/cuchaz/enigma/translation/representation') diff --git a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java new file mode 100644 index 0000000..0534edd --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java @@ -0,0 +1,112 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.analysis.Access; +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; + +public class AccessFlags { + public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE); + public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC); + + private int flags; + + public AccessFlags(int flags) { + this.flags = flags; + } + + public boolean isPrivate() { + return Modifier.isPrivate(this.flags); + } + + public boolean isProtected() { + return Modifier.isProtected(this.flags); + } + + public boolean isPublic() { + return Modifier.isPublic(this.flags); + } + + public boolean isSynthetic() { + return (this.flags & Opcodes.ACC_SYNTHETIC) != 0; + } + + public boolean isStatic() { + return Modifier.isStatic(this.flags); + } + + public boolean isEnum() { + return (flags & Opcodes.ACC_ENUM) != 0; + } + + public boolean isBridge() { + return (flags & Opcodes.ACC_BRIDGE) != 0; + } + + public boolean isFinal() { + return (flags & Opcodes.ACC_FINAL) != 0; + } + + public AccessFlags setPrivate() { + this.setVisibility(Opcodes.ACC_PRIVATE); + return this; + } + + public AccessFlags setProtected() { + this.setVisibility(Opcodes.ACC_PROTECTED); + return this; + } + + public AccessFlags setPublic() { + this.setVisibility(Opcodes.ACC_PUBLIC); + return this; + } + + public AccessFlags setBridge() { + flags |= Opcodes.ACC_BRIDGE; + return this; + } + + @Deprecated + public AccessFlags setBridged() { + return setBridge(); + } + + public void setVisibility(int visibility) { + this.resetVisibility(); + this.flags |= visibility; + } + + private void resetVisibility() { + this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); + } + + public int getFlags() { + return this.flags; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; + } + + @Override + public int hashCode() { + return flags; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase()); + if (isStatic()) { + builder.append(" static"); + } + if (isSynthetic()) { + builder.append(" synthetic"); + } + if (isBridge()) { + builder.append(" bridge"); + } + return builder.toString(); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java new file mode 100644 index 0000000..c59751f --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * 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.translation.representation; + +import com.google.common.collect.Lists; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.utils.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class MethodDescriptor implements Translatable { + + private List argumentDescs; + private TypeDescriptor returnDesc; + + public MethodDescriptor(String desc) { + try { + this.argumentDescs = Lists.newArrayList(); + int i = 0; + while (i < desc.length()) { + char c = desc.charAt(i); + if (c == '(') { + assert (this.argumentDescs.isEmpty()); + assert (this.returnDesc == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = TypeDescriptor.parseFirst(desc.substring(i)); + this.argumentDescs.add(new TypeDescriptor(type)); + i += type.length(); + } + } + this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); + } + } + + public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { + this.argumentDescs = argumentDescs; + this.returnDesc = returnDesc; + } + + public List getArgumentDescs() { + return this.argumentDescs; + } + + public TypeDescriptor getReturnDesc() { + return this.returnDesc; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (TypeDescriptor desc : this.argumentDescs) { + buf.append(desc); + } + buf.append(")"); + buf.append(this.returnDesc); + return buf.toString(); + } + + public Iterable types() { + List descs = Lists.newArrayList(); + descs.addAll(this.argumentDescs); + descs.add(this.returnDesc); + return descs; + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodDescriptor && equals((MethodDescriptor) other); + } + + public boolean equals(MethodDescriptor other) { + return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (TypeDescriptor desc : types()) { + if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) { + return true; + } + } + return false; + } + + public MethodDescriptor remap(Function remapper) { + List argumentDescs = new ArrayList<>(this.argumentDescs.size()); + for (TypeDescriptor desc : this.argumentDescs) { + argumentDescs.add(desc.remap(remapper)); + } + return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + List translatedArguments = new ArrayList<>(argumentDescs.size()); + for (TypeDescriptor argument : argumentDescs) { + translatedArguments.add(translator.translate(argument)); + } + return new MethodDescriptor(translatedArguments, translator.translate(returnDesc)); + } + + public boolean canConflictWith(MethodDescriptor descriptor) { + return descriptor.argumentDescs.equals(argumentDescs); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java new file mode 100644 index 0000000..9c9fa3d --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.translation.representation; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import cuchaz.enigma.translation.representation.entry.*; + +public class ProcyonEntryFactory { + private final ReferencedEntryPool entryPool; + + public ProcyonEntryFactory(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } + + public FieldEntry getFieldEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); + } + + public FieldDefEntry getFieldDefEntry(FieldDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } + + public MethodEntry getMethodEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature()); + } + + public MethodDefEntry getMethodDefEntry(MethodDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers())); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java new file mode 100644 index 0000000..631b375 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.translation.representation; + +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import java.util.HashMap; +import java.util.Map; + +public class ReferencedEntryPool { + private final Map classEntries = new HashMap<>(); + private final Map> methodEntries = new HashMap<>(); + private final Map> fieldEntries = new HashMap<>(); + + public ClassEntry getClass(String name) { + // TODO: FIXME - I'm a hack! + if ("[T".equals(name) || "[[T".equals(name) || "[[[T".equals(name)) { + name = name.replaceAll("T", "Ljava/lang/Object;"); + } + + final String computeName = name; + return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(computeName)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { + return getMethod(ownerEntry, name, new MethodDescriptor(desc)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { + String key = name + desc.toString(); + return getClassMethods(ownerEntry.getFullName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { + return getField(ownerEntry, name, new TypeDescriptor(desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + return getClassFields(ownerEntry.getFullName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); + } + + private Map getClassMethods(String name) { + return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); + } + + private Map getClassFields(String name) { + return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java new file mode 100644 index 0000000..dc241b7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/Signature.java @@ -0,0 +1,93 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.signature.SignatureWriter; + +import java.util.function.Function; +import java.util.regex.Pattern; + +public class Signature implements Translatable { + private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); + + private final String signature; + private final boolean isType; + + private Signature(String signature, boolean isType) { + if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { + signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); + } + + this.signature = signature; + this.isType = isType; + } + + public static Signature createTypedSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, true); + } + return new Signature(null, true); + } + + public static Signature createSignature(String signature) { + if (signature != null && !signature.isEmpty()) { + return new Signature(signature, false); + } + return new Signature(null, false); + } + + public String getSignature() { + return signature; + } + + public boolean isType() { + return isType; + } + + public Signature remap(Function remapper) { + if (signature == null) { + return this; + } + SignatureWriter writer = new SignatureWriter(); + SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer); + if (isType) { + new SignatureReader(signature).acceptType(visitor); + } else { + new SignatureReader(signature).accept(visitor); + } + return new Signature(writer.toString(), isType); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Signature) { + Signature other = (Signature) obj; + return (other.signature == null && signature == null || other.signature != null + && signature != null && other.signature.equals(signature)) + && other.isType == this.isType; + } + return false; + } + + @Override + public int hashCode() { + return signature.hashCode() | (isType ? 1 : 0) << 16; + } + + @Override + public String toString() { + return signature; + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java new file mode 100644 index 0000000..f7ba849 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * 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.translation.representation; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Map; +import java.util.function.Function; + +public class TypeDescriptor implements Translatable { + + protected final String desc; + + public TypeDescriptor(String desc) { + Preconditions.checkNotNull(desc, "Desc cannot be null"); + + // don't deal with generics + // this is just for raw jvm types + if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + desc); + } + + this.desc = desc; + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No desc to parse, input is empty!"); + } + + // read one desc from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for templates + if (c == 'T') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private static int countArrayDimension(String in) { + int i = 0; + while (i < in.length() && in.charAt(i) == '[') + i++; + return i; + } + + private static String readClass(String in) { + // read all the characters in the buffer until we hit a ';' + // include the parameters too + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } + + public static TypeDescriptor of(String name) { + return new TypeDescriptor("L" + name + ";"); + } + + @Override + public String toString() { + return this.desc; + } + + public boolean isVoid() { + return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.desc.charAt(0)); + } + + public boolean isType() { + return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; + } + + public ClassEntry getTypeEntry() { + if (isType()) { + String name = this.desc.substring(1, this.desc.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isType()) { + return getArrayType().getTypeEntry(); + } else { + throw new IllegalStateException("desc doesn't have a class"); + } + } + + public boolean isArray() { + return this.desc.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.desc); + } + + public TypeDescriptor getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new TypeDescriptor(this.desc.substring(getArrayDimension())); + } + + public boolean containsType() { + return isType() || (isArray() && getArrayType().containsType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeDescriptor && equals((TypeDescriptor) other); + } + + public boolean equals(TypeDescriptor other) { + return this.desc.equals(other.desc); + } + + @Override + public int hashCode() { + return this.desc.hashCode(); + } + + public TypeDescriptor remap(Function remapper) { + String desc = this.desc; + if (isType() || (isArray() && containsType())) { + String replacedName = remapper.apply(this.getTypeEntry().getFullName()); + if (replacedName != null) { + if (this.isType()) { + desc = "L" + replacedName + ";"; + } else { + desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + return new TypeDescriptor(desc); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + public int getSize() { + switch (desc.charAt(0)) { + case 'J': + case 'D': + if (desc.length() == 1) { + return 2; + } else { + return 1; + } + default: + return 1; + } + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + return remap(name -> translator.translate(new ClassEntry(name)).getFullName()); + } + + public enum Primitive { + BYTE('B'), + CHARACTER('C'), + SHORT('S'), + INTEGER('I'), + LONG('J'), + FLOAT('F'), + DOUBLE('D'), + BOOLEAN('Z'); + + private static final Map lookup; + + static { + lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + lookup.put(val.getCode(), val); + } + } + + private char code; + + Primitive(char code) { + this.code = code; + } + + public static Primitive get(char code) { + return lookup.get(code); + } + + public char getCode() { + return this.code; + } + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java new file mode 100644 index 0000000..b9391b0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import com.strobel.assembler.metadata.TypeDefinition; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Signature; + +import javax.annotation.Nullable; +import java.util.Arrays; + +public class ClassDefEntry extends ClassEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + private final ClassEntry superClass; + private final ClassEntry[] interfaces; + + public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { + this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces); + } + + public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) { + super(parent, className); + Preconditions.checkNotNull(signature, "Class signature cannot be null"); + Preconditions.checkNotNull(access, "Class access cannot be null"); + + this.signature = signature; + this.access = access; + this.superClass = superClass; + this.interfaces = interfaces != null ? interfaces : new ClassEntry[0]; + } + + public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) { + ClassEntry superClass = superName != null ? new ClassEntry(superName) : null; + ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses); + } + + public static ClassDefEntry parse(TypeDefinition def) { + String name = def.getInternalName(); + Signature signature = Signature.createSignature(def.getSignature()); + AccessFlags access = new AccessFlags(def.getModifiers()); + ClassEntry superClass = def.getBaseType() != null ? ClassEntry.parse(def.getBaseType()) : null; + ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(ClassEntry::parse).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, signature, access, superClass, interfaces); + } + + public Signature getSignature() { + return signature; + } + + @Override + public AccessFlags getAccess() { + return access; + } + + @Nullable + public ClassEntry getSuperClass() { + return superClass; + } + + public ClassEntry[] getInterfaces() { + return interfaces; + } + + @Override + public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + ClassEntry translatedSuper = translator.translate(superClass); + ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); + return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces); + } + + @Override + public ClassDefEntry withParent(ClassEntry parent) { + return new ClassDefEntry(parent, name, signature, access, superClass, interfaces); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java new file mode 100644 index 0000000..dcbb8d9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.strobel.assembler.metadata.TypeReference; +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.NameValidator; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +public class ClassEntry extends ParentedEntry implements Comparable { + private final String fullName; + + public ClassEntry(String className) { + this(getOuterClass(className), getInnerName(className)); + } + + public ClassEntry(@Nullable ClassEntry parent, String className) { + super(parent, className); + if (parent != null) { + fullName = parent.getFullName() + "$" + name; + } else { + fullName = name; + } + + if (parent == null && className.indexOf('.') >= 0) { + throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); + } + } + + public static ClassEntry parse(TypeReference typeReference) { + return new ClassEntry(typeReference.getInternalName()); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + @Override + public String getName() { + return this.name; + } + + public String getFullName() { + return fullName; + } + + @Override + public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new ClassEntry(parent, translatedName); + } + + @Override + public ClassEntry getContainingClass() { + return this; + } + + @Override + public int hashCode() { + return fullName.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ClassEntry && equals((ClassEntry) other); + } + + public boolean equals(ClassEntry other) { + return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name); + } + + @Override + public boolean canConflictWith(Entry entry) { + return true; + } + + @Override + public void validateName(String name) throws IllegalNameException { + NameValidator.validateClassName(name, !isInnerClass()); + } + + @Override + public ClassEntry withParent(ClassEntry parent) { + return new ClassEntry(parent, name); + } + + @Override + public String toString() { + return getFullName(); + } + + public String getPackageName() { + return getPackageName(this.name); + } + + public String getSimpleName() { + int packagePos = name.lastIndexOf('/'); + if (packagePos > 0) { + return name.substring(packagePos + 1); + } + return name; + } + + public boolean isInnerClass() { + return parent != null; + } + + @Nullable + public ClassEntry getOuterClass() { + return parent; + } + + public ClassEntry buildClassEntry(List classChain) { + assert (classChain.contains(this)); + StringBuilder buf = new StringBuilder(); + for (ClassEntry chainEntry : classChain) { + if (buf.length() == 0) { + buf.append(chainEntry.getFullName()); + } else { + buf.append("$"); + buf.append(chainEntry.getSimpleName()); + } + + if (chainEntry == this) { + break; + } + } + return new ClassEntry(buf.toString()); + } + + public boolean isJre() { + String packageName = getPackageName(); + return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax")); + } + + public static String getPackageName(String name) { + int pos = name.lastIndexOf('/'); + if (pos > 0) { + return name.substring(0, pos); + } + return null; + } + + @Nullable + public static ClassEntry getOuterClass(String name) { + int index = name.lastIndexOf('$'); + if (index >= 0) { + return new ClassEntry(name.substring(0, index)); + } + return null; + } + + public static String getInnerName(String name) { + int innerClassPos = name.lastIndexOf('$'); + if (innerClassPos > 0) { + return name.substring(innerClassPos + 1); + } + return name; + } + + @Override + public int compareTo(ClassEntry entry) { + return name.compareTo(entry.name); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java new file mode 100644 index 0000000..82536c7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.translation.representation.entry; + +import cuchaz.enigma.translation.representation.AccessFlags; + +public interface DefEntry

> extends Entry

{ + AccessFlags getAccess(); +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java new file mode 100644 index 0000000..1a2ca78 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import cuchaz.enigma.throwables.IllegalNameException; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.mapping.NameValidator; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public interface Entry

> extends Translatable { + String getName(); + + @Nullable + P getParent(); + + Class

getParentType(); + + Entry

withParent(P parent); + + boolean canConflictWith(Entry entry); + + @Nullable + default ClassEntry getContainingClass() { + P parent = getParent(); + if (parent == null) { + return null; + } + if (parent instanceof ClassEntry) { + return (ClassEntry) parent; + } + return parent.getContainingClass(); + } + + default List> getAncestry() { + P parent = getParent(); + List> entries = new ArrayList<>(); + if (parent != null) { + entries.addAll(parent.getAncestry()); + } + entries.add(this); + return entries; + } + + @Nullable + @SuppressWarnings("unchecked") + default > E findAncestor(Class type) { + List> ancestry = getAncestry(); + for (int i = ancestry.size() - 1; i >= 0; i--) { + Entry ancestor = ancestry.get(i); + if (type.isAssignableFrom(ancestor.getClass())) { + return (E) ancestor; + } + } + return null; + } + + @SuppressWarnings("unchecked") + default > Entry

replaceAncestor(E target, E replacement) { + if (replacement.equals(target)) { + return this; + } + + if (equals(target)) { + return (Entry

) replacement; + } + + P parent = getParent(); + if (parent == null) { + return this; + } + + return withParent((P) parent.replaceAncestor(target, replacement)); + } + + default void validateName(String name) throws IllegalNameException { + NameValidator.validateIdentifier(name); + } + + @SuppressWarnings("unchecked") + @Nullable + default > Entry castParent(Class parentType) { + if (parentType.equals(getParentType())) { + return (Entry) this; + } + return null; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java new file mode 100644 index 0000000..d487f71 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; + +public class FieldDefEntry extends FieldEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + + public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) { + super(owner, name, desc); + Preconditions.checkNotNull(access, "Field access cannot be null"); + Preconditions.checkNotNull(signature, "Field signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { + return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access)); + } + + @Override + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + TypeDescriptor translatedDesc = translator.translate(desc); + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess); + } + + @Override + public FieldDefEntry withParent(ClassEntry owner) { + return new FieldDefEntry(owner, this.name, this.desc, signature, access); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java new file mode 100644 index 0000000..2ec2471 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; + +public class FieldEntry extends ParentedEntry implements Comparable { + protected final TypeDescriptor desc; + + public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) { + super(parent, name); + + Preconditions.checkNotNull(parent, "Owner cannot be null"); + Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); + + this.desc = desc; + } + + public static FieldEntry parse(String owner, String name, String desc) { + return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + public TypeDescriptor getDesc() { + return this.desc; + } + + @Override + public FieldEntry withParent(ClassEntry parent) { + return new FieldEntry(parent, this.name, this.desc); + } + + @Override + protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new FieldEntry(parent, translatedName, translator.translate(desc)); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.parent, this.name, this.desc); + } + + @Override + public boolean equals(Object other) { + return other instanceof FieldEntry && equals((FieldEntry) other); + } + + public boolean equals(FieldEntry other) { + return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc); + } + + @Override + public boolean canConflictWith(Entry entry) { + return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent); + } + + @Override + public String toString() { + return this.parent.getFullName() + "." + this.name + ":" + this.desc; + } + + @Override + public int compareTo(FieldEntry entry) { + return (name + desc.toString()).compareTo(entry.name + entry.desc.toString()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java new file mode 100644 index 0000000..86bdf61 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java @@ -0,0 +1,45 @@ +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.TypeDescriptor; + +import javax.annotation.Nullable; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableDefEntry extends LocalVariableEntry { + protected final TypeDescriptor desc; + + public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc) { + super(ownerEntry, index, name, parameter); + Preconditions.checkNotNull(desc, "Variable desc cannot be null"); + + this.desc = desc; + } + + public TypeDescriptor getDesc() { + return desc; + } + + @Override + public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + TypeDescriptor translatedDesc = translator.translate(desc); + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc); + } + + @Override + public LocalVariableDefEntry withParent(MethodEntry entry) { + return new LocalVariableDefEntry(entry, index, name, parameter, desc); + } + + @Override + public String toString() { + return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java new file mode 100644 index 0000000..df96b59 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java @@ -0,0 +1,92 @@ +package cuchaz.enigma.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableEntry extends ParentedEntry implements Comparable { + + protected final int index; + protected final boolean parameter; + + @Deprecated + public LocalVariableEntry(MethodEntry parent, int index, String name) { + this(parent, index, name, true); + } + + public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter) { + super(parent, name); + + Preconditions.checkNotNull(parent, "Variable owner cannot be null"); + Preconditions.checkArgument(index >= 0, "Index must be positive"); + + this.index = index; + this.parameter = parameter; + } + + @Override + public Class getParentType() { + return MethodEntry.class; + } + + public boolean isParameter() { + return this.parameter; + } + + public int getIndex() { + return index; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new LocalVariableEntry(parent, index, translatedName, parameter); + } + + @Override + public LocalVariableEntry withParent(MethodEntry parent) { + return new LocalVariableEntry(parent, index, name, parameter); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.parent, this.index); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other); + } + + public boolean equals(LocalVariableEntry other) { + return this.parent.equals(other.parent) && this.index == other.index; + } + + @Override + public boolean canConflictWith(Entry entry) { + return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent); + } + + @Override + public String toString() { + return this.parent + "(" + this.index + ":" + this.name + ")"; + } + + @Override + public int compareTo(LocalVariableEntry entry) { + return Integer.compare(index, entry.index); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java new file mode 100644 index 0000000..3ecd470 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; + +import javax.annotation.Nullable; + +public class MethodDefEntry extends MethodEntry implements DefEntry { + private final AccessFlags access; + private final Signature signature; + + public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) { + super(owner, name, descriptor); + Preconditions.checkNotNull(access, "Method access cannot be null"); + Preconditions.checkNotNull(signature, "Method signature cannot be null"); + this.access = access; + this.signature = signature; + } + + public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) { + return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + } + + @Override + public AccessFlags getAccess() { + return access; + } + + public Signature getSignature() { + return signature; + } + + @Override + public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) { + MethodDescriptor translatedDesc = translator.translate(descriptor); + Signature translatedSignature = translator.translate(signature); + String translatedName = mapping != null ? mapping.getTargetName() : name; + AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; + return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess); + } + + @Override + public MethodDefEntry withParent(ClassEntry parent) { + return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access); + } + + public int getArgumentIndex(ClassDefEntry ownerEntry, int localVariableIndex) { + int argumentIndex = localVariableIndex; + + // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this" + if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) { + argumentIndex -= 2; + } + + // If we're not static, "this" is bound to index 0 + if (!getAccess().isStatic()) { + argumentIndex -= 1; + } + + return argumentIndex; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java new file mode 100644 index 0000000..3a1dbb3 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.utils.Utils; + +import javax.annotation.Nullable; + +public class MethodEntry extends ParentedEntry implements Comparable { + + protected final MethodDescriptor descriptor; + + public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) { + super(parent, name); + + Preconditions.checkNotNull(parent, "Parent cannot be null"); + Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); + + this.descriptor = descriptor; + } + + public static MethodEntry parse(String owner, String name, String desc) { + return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); + } + + @Override + public Class getParentType() { + return ClassEntry.class; + } + + public MethodDescriptor getDesc() { + return this.descriptor; + } + + public boolean isConstructor() { + return name.equals("") || name.equals(""); + } + + @Override + public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) { + String translatedName = mapping != null ? mapping.getTargetName() : name; + return new MethodEntry(parent, translatedName, translator.translate(descriptor)); + } + + @Override + public MethodEntry withParent(ClassEntry parent) { + return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor); + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodEntry && equals((MethodEntry) other); + } + + public boolean equals(MethodEntry other) { + return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); + } + + @Override + public boolean canConflictWith(Entry entry) { + if (entry instanceof MethodEntry) { + MethodEntry methodEntry = (MethodEntry) entry; + return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor); + } + return false; + } + + @Override + public String toString() { + return this.parent.getFullName() + "." + this.name + this.descriptor; + } + + @Override + public int compareTo(MethodEntry entry) { + return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString()); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java new file mode 100644 index 0000000..7ba7c19 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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.translation.representation.entry; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; + +import javax.annotation.Nullable; + +public abstract class ParentedEntry

> implements Entry

{ + protected final P parent; + protected final String name; + + protected ParentedEntry(P parent, String name) { + this.parent = parent; + this.name = name; + + Preconditions.checkNotNull(name, "Name cannot be null"); + } + + @Override + public abstract ParentedEntry

withParent(P parent); + + protected abstract ParentedEntry

translate(Translator translator, @Nullable EntryMapping mapping); + + @Override + public String getName() { + return name; + } + + @Override + @Nullable + public P getParent() { + return parent; + } + + @Override + public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + P parent = getParent(); + EntryMapping mapping = resolveMapping(resolver, mappings); + if (parent == null) { + return translate(translator, mapping); + } + P translatedParent = translator.translate(parent); + return withParent(translatedParent).translate(translator, mapping); + } + + private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings) { + for (ParentedEntry

entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + return mapping; + } + } + return null; + } +} -- cgit v1.2.3