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