+ * Contributors:
+ * Jeff Martin - initial API and implementation
+ ******************************************************************************/
+package cuchaz.enigma.mapping;
+
+import java.io.Serializable;
+
+import cuchaz.enigma.Util;
+
+public class ArgumentEntry implements Entry, Serializable {
+
+ private static final long serialVersionUID = 4472172468162696006L;
+
+ private BehaviorEntry m_behaviorEntry;
+ private int m_index;
+ private String m_name;
+
+ public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
+ if (behaviorEntry == null) {
+ throw new IllegalArgumentException("Behavior cannot be null!");
+ }
+ if (index < 0) {
+ throw new IllegalArgumentException("Index must be non-negative!");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("Argument name cannot be null!");
+ }
+
+ m_behaviorEntry = behaviorEntry;
+ m_index = index;
+ m_name = name;
+ }
+
+ public ArgumentEntry(ArgumentEntry other) {
+ m_behaviorEntry = (BehaviorEntry) m_behaviorEntry.cloneToNewClass(getClassEntry());
+ m_index = other.m_index;
+ m_name = other.m_name;
+ }
+
+ public ArgumentEntry(ArgumentEntry other, String newClassName) {
+ m_behaviorEntry = (BehaviorEntry) other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
+ m_index = other.m_index;
+ m_name = other.m_name;
+ }
+
+ public BehaviorEntry getBehaviorEntry() {
+ return m_behaviorEntry;
+ }
+
+ public int getIndex() {
+ return m_index;
+ }
+
+ @Override
+ public String getName() {
+ return m_name;
+ }
+
+ @Override
+ public ClassEntry getClassEntry() {
+ return m_behaviorEntry.getClassEntry();
+ }
+
+ @Override
+ public String getClassName() {
+ return m_behaviorEntry.getClassName();
+ }
+
+ @Override
+ public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
+ return new ArgumentEntry(this, classEntry.getName());
+ }
+
+ public String getMethodName() {
+ return m_behaviorEntry.getName();
+ }
+
+ public Signature getMethodSignature() {
+ return m_behaviorEntry.getSignature();
+ }
+
+ @Override
+ public int hashCode() {
+ return Util.combineHashesOrdered(
+ m_behaviorEntry,
+ Integer.valueOf(m_index).hashCode(),
+ m_name.hashCode()
+ );
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ArgumentEntry) {
+ return equals((ArgumentEntry) other);
+ }
+ return false;
+ }
+
+ public boolean equals(ArgumentEntry other) {
+ return m_behaviorEntry.equals(other.m_behaviorEntry)
+ && m_index == other.m_index
+ && m_name.equals(other.m_name);
+ }
+
+ @Override
+ public String toString() {
+ return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
new file mode 100644
index 0000000..2b77d6e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import java.io.Serializable;
+
+public class ArgumentMapping implements Serializable, Comparable {
+
+ private static final long serialVersionUID = 8610742471440861315L;
+
+ private int m_index;
+ private String m_name;
+
+ // NOTE: this argument order is important for the MethodReader/MethodWriter
+ public ArgumentMapping(int index, String name) {
+ m_index = index;
+ m_name = NameValidator.validateArgumentName(name);
+ }
+
+ public ArgumentMapping(ArgumentMapping other) {
+ m_index = other.m_index;
+ m_name = other.m_name;
+ }
+
+ public int getIndex() {
+ return m_index;
+ }
+
+ public String getName() {
+ return m_name;
+ }
+
+ public void setName(String val) {
+ m_name = NameValidator.validateArgumentName(val);
+ }
+
+ @Override
+ public int compareTo(ArgumentMapping other) {
+ return Integer.compare(m_index, other.m_index);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java
new file mode 100644
index 0000000..f5c6c05
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -0,0 +1,15 @@
+/*******************************************************************************
+ * 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.mapping;
+
+public interface BehaviorEntry extends Entry {
+ Signature getSignature();
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 0000000..2e7711b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class ClassEntry implements Entry, Serializable {
+
+ private static final long serialVersionUID = 4235460580973955811L;
+
+ private String m_name;
+
+ public ClassEntry(String className) {
+ if (className == null) {
+ throw new IllegalArgumentException("Class name cannot be null!");
+ }
+ if (className.indexOf('.') >= 0) {
+ throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
+ }
+
+ m_name = className;
+
+ if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
+ throw new IllegalArgumentException("Inner class must not have a package: " + className);
+ }
+ }
+
+ public ClassEntry(ClassEntry other) {
+ m_name = other.m_name;
+ }
+
+ @Override
+ public String getName() {
+ return m_name;
+ }
+
+ @Override
+ public String getClassName() {
+ return m_name;
+ }
+
+ @Override
+ public ClassEntry getClassEntry() {
+ return this;
+ }
+
+ @Override
+ public ClassEntry cloneToNewClass(ClassEntry classEntry) {
+ return classEntry;
+ }
+
+ @Override
+ public int hashCode() {
+ return m_name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ClassEntry) {
+ return equals((ClassEntry) other);
+ }
+ return false;
+ }
+
+ public boolean equals(ClassEntry other) {
+ return m_name.equals(other.m_name);
+ }
+
+ @Override
+ public String toString() {
+ return m_name;
+ }
+
+ public boolean isInnerClass() {
+ return m_name.lastIndexOf('$') >= 0;
+ }
+
+ public List getClassChainNames() {
+ return Lists.newArrayList(m_name.split("\\$"));
+ }
+
+ public List getClassChain() {
+ List entries = Lists.newArrayList();
+ StringBuilder buf = new StringBuilder();
+ for (String name : getClassChainNames()) {
+ if (buf.length() > 0) {
+ buf.append("$");
+ }
+ buf.append(name);
+ entries.add(new ClassEntry(buf.toString()));
+ }
+ return entries;
+ }
+
+ public String getOutermostClassName() {
+ if (isInnerClass()) {
+ return m_name.substring(0, m_name.indexOf('$'));
+ }
+ return m_name;
+ }
+
+ public ClassEntry getOutermostClassEntry() {
+ return new ClassEntry(getOutermostClassName());
+ }
+
+ public String getOuterClassName() {
+ if (!isInnerClass()) {
+ throw new Error("This is not an inner class!");
+ }
+ return m_name.substring(0, m_name.lastIndexOf('$'));
+ }
+
+ public ClassEntry getOuterClassEntry() {
+ return new ClassEntry(getOuterClassName());
+ }
+
+ public String getInnermostClassName() {
+ if (!isInnerClass()) {
+ throw new Error("This is not an inner class!");
+ }
+ return m_name.substring(m_name.lastIndexOf('$') + 1);
+ }
+
+ public boolean isInDefaultPackage() {
+ return m_name.indexOf('/') < 0;
+ }
+
+ public String getPackageName() {
+ int pos = m_name.lastIndexOf('/');
+ if (pos > 0) {
+ return m_name.substring(0, pos);
+ }
+ return null;
+ }
+
+ public String getSimpleName() {
+ int pos = m_name.lastIndexOf('/');
+ if (pos > 0) {
+ return m_name.substring(pos + 1);
+ }
+ return m_name;
+ }
+
+ public ClassEntry buildClassEntry(List classChain) {
+ assert (classChain.contains(this));
+ StringBuilder buf = new StringBuilder();
+ for (ClassEntry chainEntry : classChain) {
+ if (buf.length() == 0) {
+ buf.append(chainEntry.getName());
+ } else {
+ buf.append("$");
+ buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
+ }
+
+ if (chainEntry == this) {
+ break;
+ }
+ }
+ return new ClassEntry(buf.toString());
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 0000000..9258ec7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,460 @@
+/*******************************************************************************
+ * 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.mapping;
+
+public interface ClassNameReplacer {
+ String replace(String className);
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 0000000..907bd4c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import java.io.Serializable;
+
+import cuchaz.enigma.Util;
+
+public class ConstructorEntry implements BehaviorEntry, Serializable {
+
+ private static final long serialVersionUID = -868346075317366758L;
+
+ private ClassEntry m_classEntry;
+ private Signature m_signature;
+
+ public ConstructorEntry(ClassEntry classEntry) {
+ this(classEntry, null);
+ }
+
+ public ConstructorEntry(ClassEntry classEntry, Signature signature) {
+ if (classEntry == null) {
+ throw new IllegalArgumentException("Class cannot be null!");
+ }
+
+ m_classEntry = classEntry;
+ m_signature = signature;
+ }
+
+ public ConstructorEntry(ConstructorEntry other) {
+ m_classEntry = new ClassEntry(other.m_classEntry);
+ m_signature = other.m_signature;
+ }
+
+ public ConstructorEntry(ConstructorEntry other, String newClassName) {
+ m_classEntry = new ClassEntry(newClassName);
+ m_signature = other.m_signature;
+ }
+
+ @Override
+ public ClassEntry getClassEntry() {
+ return m_classEntry;
+ }
+
+ @Override
+ public String getName() {
+ if (isStatic()) {
+ return "";
+ }
+ return "";
+ }
+
+ public boolean isStatic() {
+ return m_signature == null;
+ }
+
+ @Override
+ public Signature getSignature() {
+ return m_signature;
+ }
+
+ @Override
+ public String getClassName() {
+ return m_classEntry.getName();
+ }
+
+ @Override
+ public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
+ return new ConstructorEntry(this, classEntry.getName());
+ }
+
+ @Override
+ public int hashCode() {
+ if (isStatic()) {
+ return Util.combineHashesOrdered(m_classEntry);
+ } else {
+ return Util.combineHashesOrdered(m_classEntry, m_signature);
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ConstructorEntry) {
+ return equals((ConstructorEntry) other);
+ }
+ return false;
+ }
+
+ public boolean equals(ConstructorEntry other) {
+ if (isStatic() != other.isStatic()) {
+ return false;
+ }
+
+ if (isStatic()) {
+ return m_classEntry.equals(other.m_classEntry);
+ } else {
+ return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
+ }
+ }
+
+ @Override
+ public String toString() {
+ if (isStatic()) {
+ return m_classEntry.getName() + "." + getName();
+ } else {
+ return m_classEntry.getName() + "." + getName() + m_signature;
+ }
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/Entry.java
new file mode 100644
index 0000000..95747d5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * 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.mapping;
+
+public interface Entry {
+ String getName();
+
+ String getClassName();
+
+ ClassEntry getClassEntry();
+
+ Entry cloneToNewClass(ClassEntry classEntry);
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
new file mode 100644
index 0000000..bd6ce4e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import cuchaz.enigma.analysis.JarIndex;
+import javassist.*;
+import javassist.bytecode.Descriptor;
+import javassist.expr.ConstructorCall;
+import javassist.expr.FieldAccess;
+import javassist.expr.MethodCall;
+import javassist.expr.NewExpr;
+
+public class EntryFactory {
+
+ public static ClassEntry getClassEntry(CtClass c) {
+ return new ClassEntry(Descriptor.toJvmName(c.getName()));
+ }
+
+ public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
+ ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
+ return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
+ }
+
+ private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
+ return new ClassEntry(classMapping.getObfFullName());
+ }
+
+ public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
+ return new ClassEntry(classMapping.getDeobfName());
+ }
+
+ public static ClassEntry getSuperclassEntry(CtClass c) {
+ return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
+ }
+
+ public static FieldEntry getFieldEntry(CtField field) {
+ return new FieldEntry(
+ getClassEntry(field.getDeclaringClass()),
+ field.getName(),
+ new Type(field.getFieldInfo().getDescriptor())
+ );
+ }
+
+ public static FieldEntry getFieldEntry(FieldAccess call) {
+ return new FieldEntry(
+ new ClassEntry(Descriptor.toJvmName(call.getClassName())),
+ call.getFieldName(),
+ new Type(call.getSignature())
+ );
+ }
+
+ public static FieldEntry getFieldEntry(String className, String name, String type) {
+ return new FieldEntry(new ClassEntry(className), name, new Type(type));
+ }
+
+ public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
+ return new FieldEntry(
+ getObfClassEntry(classMapping),
+ fieldMapping.getObfName(),
+ fieldMapping.getObfType()
+ );
+ }
+
+ public static MethodEntry getMethodEntry(CtMethod method) {
+ return new MethodEntry(
+ getClassEntry(method.getDeclaringClass()),
+ method.getName(),
+ new Signature(method.getMethodInfo().getDescriptor())
+ );
+ }
+
+ public static MethodEntry getMethodEntry(MethodCall call) {
+ return new MethodEntry(
+ new ClassEntry(Descriptor.toJvmName(call.getClassName())),
+ call.getMethodName(),
+ new Signature(call.getSignature())
+ );
+ }
+
+ public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
+ if (constructor.isClassInitializer()) {
+ return new ConstructorEntry(
+ getClassEntry(constructor.getDeclaringClass())
+ );
+ } else {
+ return new ConstructorEntry(
+ getClassEntry(constructor.getDeclaringClass()),
+ new Signature(constructor.getMethodInfo().getDescriptor())
+ );
+ }
+ }
+
+ public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
+ return new ConstructorEntry(
+ new ClassEntry(Descriptor.toJvmName(call.getClassName())),
+ new Signature(call.getSignature())
+ );
+ }
+
+ public static ConstructorEntry getConstructorEntry(NewExpr call) {
+ return new ConstructorEntry(
+ new ClassEntry(Descriptor.toJvmName(call.getClassName())),
+ new Signature(call.getSignature())
+ );
+ }
+
+ public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
+ if (behavior instanceof CtMethod) {
+ return getMethodEntry((CtMethod) behavior);
+ } else if (behavior instanceof CtConstructor) {
+ return getConstructorEntry((CtConstructor) behavior);
+ }
+ throw new Error("behavior is neither Method nor Constructor!");
+ }
+
+ public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
+ return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
+ }
+
+ public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
+ return getBehaviorEntry(new ClassEntry(className), behaviorName);
+ }
+
+ public static BehaviorEntry getBehaviorEntry(String className) {
+ return new ConstructorEntry(new ClassEntry(className));
+ }
+
+ public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
+ switch (behaviorName) {
+ case "":
+ return new ConstructorEntry(classEntry, behaviorSignature);
+ case "":
+ return new ConstructorEntry(classEntry);
+ default:
+ return new MethodEntry(classEntry, behaviorName, behaviorSignature);
+ }
+ }
+
+ public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
+ if (behaviorName.equals("")) {
+ return new ConstructorEntry(classEntry);
+ } else {
+ throw new IllegalArgumentException("Only class initializers don't have signatures");
+ }
+ }
+
+ public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
+ return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
+ }
+
+ public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
+ return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/EntryPair.java b/src/main/java/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 0000000..1c93d53
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * 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.mapping;
+
+public class EntryPair {
+
+ public T obf;
+ public T deobf;
+
+ public EntryPair(T obf, T deobf) {
+ this.obf = obf;
+ this.deobf = deobf;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java
new file mode 100644
index 0000000..3de7223
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/FieldEntry.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.mapping;
+
+import java.io.Serializable;
+
+import cuchaz.enigma.Util;
+
+public class FieldEntry implements Entry, Serializable {
+
+ private static final long serialVersionUID = 3004663582802885451L;
+
+ private ClassEntry m_classEntry;
+ private String m_name;
+ private Type m_type;
+
+ // NOTE: this argument order is important for the MethodReader/MethodWriter
+ public FieldEntry(ClassEntry classEntry, String name, Type type) {
+ if (classEntry == null) {
+ throw new IllegalArgumentException("Class cannot be null!");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("Field name cannot be null!");
+ }
+ if (type == null) {
+ throw new IllegalArgumentException("Field type cannot be null!");
+ }
+
+ m_classEntry = classEntry;
+ m_name = name;
+ m_type = type;
+ }
+
+ public FieldEntry(FieldEntry other) {
+ this(other, new ClassEntry(other.m_classEntry));
+ }
+
+ public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
+ m_classEntry = newClassEntry;
+ m_name = other.m_name;
+ m_type = other.m_type;
+ }
+
+ @Override
+ public ClassEntry getClassEntry() {
+ return m_classEntry;
+ }
+
+ @Override
+ public String getName() {
+ return m_name;
+ }
+
+ @Override
+ public String getClassName() {
+ return m_classEntry.getName();
+ }
+
+ public Type getType() {
+ return m_type;
+ }
+
+ @Override
+ public FieldEntry cloneToNewClass(ClassEntry classEntry) {
+ return new FieldEntry(this, classEntry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof FieldEntry) {
+ return equals((FieldEntry) other);
+ }
+ return false;
+ }
+
+ public boolean equals(FieldEntry other) {
+ return m_classEntry.equals(other.m_classEntry)
+ && m_name.equals(other.m_name)
+ && m_type.equals(other.m_type);
+ }
+
+ @Override
+ public String toString() {
+ return m_classEntry.getName() + "." + m_name + ":" + m_type;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 0000000..3f5a382
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import java.io.Serializable;
+
+public class FieldMapping implements Serializable, Comparable, MemberMapping {
+
+ private static final long serialVersionUID = 8610742471440861315L;
+
+ private String m_obfName;
+ private String m_deobfName;
+ private Type m_obfType;
+
+ public FieldMapping(String obfName, Type obfType, String deobfName) {
+ m_obfName = obfName;
+ m_deobfName = NameValidator.validateFieldName(deobfName);
+ m_obfType = obfType;
+ }
+
+ public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
+ m_obfName = other.m_obfName;
+ m_deobfName = other.m_deobfName;
+ m_obfType = new Type(other.m_obfType, obfClassNameReplacer);
+ }
+
+ @Override
+ public String getObfName() {
+ return m_obfName;
+ }
+
+ public void setObfName(String val) {
+ m_obfName = NameValidator.validateFieldName(val);
+ }
+
+ public String getDeobfName() {
+ return m_deobfName;
+ }
+
+ public void setDeobfName(String val) {
+ m_deobfName = NameValidator.validateFieldName(val);
+ }
+
+ public Type getObfType() {
+ return m_obfType;
+ }
+
+ public void setObfType(Type val) {
+ m_obfType = val;
+ }
+
+ @Override
+ public int compareTo(FieldMapping other) {
+ return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType);
+ }
+
+ public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
+
+ // rename obf classes in the type
+ Type newType = new Type(m_obfType, new ClassNameReplacer() {
+ @Override
+ public String replace(String className) {
+ if (className.equals(oldObfClassName)) {
+ return newObfClassName;
+ }
+ return null;
+ }
+ });
+
+ if (!newType.equals(m_obfType)) {
+ m_obfType = newType;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public FieldEntry getObfEntry(ClassEntry classEntry) {
+ return new FieldEntry(classEntry, m_obfName, new Type(m_obfType));
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/IllegalNameException.java b/src/main/java/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 0000000..f2119d8
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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.mapping;
+
+public class IllegalNameException extends RuntimeException {
+
+ private static final long serialVersionUID = -2279910052561114323L;
+
+ private String m_name;
+ private String m_reason;
+
+ public IllegalNameException(String name) {
+ this(name, null);
+ }
+
+ public IllegalNameException(String name, String reason) {
+ m_name = name;
+ m_reason = reason;
+ }
+
+ public String getReason() {
+ return m_reason;
+ }
+
+ @Override
+ public String getMessage() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("Illegal name: ");
+ buf.append(m_name);
+ if (m_reason != null) {
+ buf.append(" because ");
+ buf.append(m_reason);
+ }
+ return buf.toString();
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingParseException.java b/src/main/java/cuchaz/enigma/mapping/MappingParseException.java
new file mode 100644
index 0000000..3c25ea5
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingParseException.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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.mapping;
+
+public class MappingParseException extends Exception {
+
+ private static final long serialVersionUID = -5487280332892507236L;
+
+ private int m_line;
+ private String m_message;
+
+ public MappingParseException(int line, String message) {
+ m_line = line;
+ m_message = message;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Line " + m_line + ": " + m_message;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java
new file mode 100644
index 0000000..a48ec3f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,201 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import java.io.Serializable;
+import java.util.*;
+
+import cuchaz.enigma.analysis.TranslationIndex;
+
+public class Mappings implements Serializable {
+
+ private static final long serialVersionUID = 4649790259460259026L;
+
+ protected Map m_classesByObf;
+ protected Map m_classesByDeobf;
+
+ public Mappings() {
+ m_classesByObf = Maps.newHashMap();
+ m_classesByDeobf = Maps.newHashMap();
+ }
+
+ public Mappings(Iterable classes) {
+ this();
+
+ for (ClassMapping classMapping : classes) {
+ m_classesByObf.put(classMapping.getObfFullName(), classMapping);
+ if (classMapping.getDeobfName() != null) {
+ m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
+ }
+ }
+ }
+
+ public Collection classes() {
+ assert (m_classesByObf.size() >= m_classesByDeobf.size());
+ return m_classesByObf.values();
+ }
+
+ public void addClassMapping(ClassMapping classMapping) {
+ if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
+ throw new Error("Already have mapping for " + classMapping.getObfFullName());
+ }
+ boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
+ assert (obfWasAdded);
+ if (classMapping.getDeobfName() != null) {
+ if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
+ throw new Error("Already have mapping for " + classMapping.getDeobfName());
+ }
+ boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
+ assert (deobfWasAdded);
+ }
+ }
+
+ public void removeClassMapping(ClassMapping classMapping) {
+ boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
+ assert (obfWasRemoved);
+ if (classMapping.getDeobfName() != null) {
+ boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
+ assert (deobfWasRemoved);
+ }
+ }
+
+ public ClassMapping getClassByObf(ClassEntry entry) {
+ return getClassByObf(entry.getName());
+ }
+
+ public ClassMapping getClassByObf(String obfName) {
+ return m_classesByObf.get(obfName);
+ }
+
+ public ClassMapping getClassByDeobf(ClassEntry entry) {
+ return getClassByDeobf(entry.getName());
+ }
+
+ public ClassMapping getClassByDeobf(String deobfName) {
+ return m_classesByDeobf.get(deobfName);
+ }
+
+ public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
+ if (classMapping.getDeobfName() != null) {
+ boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
+ assert (wasRemoved);
+ }
+ classMapping.setDeobfName(deobfName);
+ if (deobfName != null) {
+ boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null;
+ assert (wasAdded);
+ }
+ }
+
+ public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
+ switch (direction) {
+ case Deobfuscating:
+
+ return new Translator(direction, m_classesByObf, index);
+
+ case Obfuscating:
+
+ // fill in the missing deobf class entries with obf entries
+ Map classes = Maps.newHashMap();
+ for (ClassMapping classMapping : classes()) {
+ if (classMapping.getDeobfName() != null) {
+ classes.put(classMapping.getDeobfName(), classMapping);
+ } else {
+ classes.put(classMapping.getObfFullName(), classMapping);
+ }
+ }
+
+ // translate the translation index
+ // NOTE: this isn't actually recursive
+ TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
+
+ return new Translator(direction, classes, deobfIndex);
+
+ default:
+ throw new Error("Invalid translation direction!");
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder buf = new StringBuilder();
+ for (ClassMapping classMapping : m_classesByObf.values()) {
+ buf.append(classMapping.toString());
+ buf.append("\n");
+ }
+ return buf.toString();
+ }
+
+ public void renameObfClass(String oldObfName, String newObfName) {
+ new ArrayList<>(classes()).stream().filter(classMapping -> classMapping.renameObfClass(oldObfName, newObfName)).forEach(classMapping -> {
+ boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
+ assert (wasRemoved);
+ boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
+ assert (wasAdded);
+ });
+ }
+
+ public Set getAllObfClassNames() {
+ final Set classNames = Sets.newHashSet();
+ for (ClassMapping classMapping : classes()) {
+
+ // add the class name
+ classNames.add(classMapping.getObfFullName());
+
+ // add classes from method signatures
+ for (MethodMapping methodMapping : classMapping.methods()) {
+ for (Type type : methodMapping.getObfSignature().types()) {
+ if (type.hasClass()) {
+ classNames.add(type.getClassEntry().getClassName());
+ }
+ }
+ }
+ }
+ return classNames;
+ }
+
+ public boolean containsDeobfClass(String deobfName) {
+ return m_classesByDeobf.containsKey(deobfName);
+ }
+
+ public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) {
+ ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
+ return classMapping != null && classMapping.containsDeobfField(deobfName, obfType);
+ }
+
+ public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
+ ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
+ return classMapping != null && classMapping.containsDeobfMethod(deobfName, deobfSignature);
+ }
+
+ public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
+ ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
+ return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name);
+ }
+
+ public List getClassMappingChain(ClassEntry obfClass) {
+ List mappingChain = Lists.newArrayList();
+ ClassMapping classMapping = null;
+ for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
+ if (mappingChain.isEmpty()) {
+ classMapping = m_classesByObf.get(obfClassEntry.getName());
+ } else if (classMapping != null) {
+ classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
+ }
+ mappingChain.add(classMapping);
+ }
+ return mappingChain;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java
new file mode 100644
index 0000000..ab68682
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * 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.mapping;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.Map;
+
+import cuchaz.enigma.analysis.JarIndex;
+import cuchaz.enigma.analysis.RelatedMethodChecker;
+
+
+public class MappingsChecker {
+
+ private JarIndex m_index;
+ private RelatedMethodChecker m_relatedMethodChecker;
+ private Map m_droppedClassMappings;
+ private Map m_droppedInnerClassMappings;
+ private Map m_droppedFieldMappings;
+ private Map m_droppedMethodMappings;
+
+ public MappingsChecker(JarIndex index) {
+ m_index = index;
+ m_relatedMethodChecker = new RelatedMethodChecker(m_index);
+ m_droppedClassMappings = Maps.newHashMap();
+ m_droppedInnerClassMappings = Maps.newHashMap();
+ m_droppedFieldMappings = Maps.newHashMap();
+ m_droppedMethodMappings = Maps.newHashMap();
+ }
+
+ public RelatedMethodChecker getRelatedMethodChecker() {
+ return m_relatedMethodChecker;
+ }
+
+ public Map getDroppedClassMappings() {
+ return m_droppedClassMappings;
+ }
+
+ public Map getDroppedInnerClassMappings() {
+ return m_droppedInnerClassMappings;
+ }
+
+ public Map getDroppedFieldMappings() {
+ return m_droppedFieldMappings;
+ }
+
+ public Map getDroppedMethodMappings() {
+ return m_droppedMethodMappings;
+ }
+
+ public void dropBrokenMappings(Mappings mappings) {
+ for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
+ if (!checkClassMapping(classMapping)) {
+ mappings.removeClassMapping(classMapping);
+ m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping);
+ }
+ }
+ }
+
+ private boolean checkClassMapping(ClassMapping classMapping) {
+
+ // check the class
+ ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping);
+ if (!m_index.getObfClassEntries().contains(classEntry)) {
+ return false;
+ }
+
+ // check the fields
+ for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
+ FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
+ if (!m_index.containsObfField(obfFieldEntry)) {
+ classMapping.removeFieldMapping(fieldMapping);
+ m_droppedFieldMappings.put(obfFieldEntry, fieldMapping);
+ }
+ }
+
+ // check methods
+ for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
+ BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
+ if (!m_index.containsObfBehavior(obfBehaviorEntry)) {
+ classMapping.removeMethodMapping(methodMapping);
+ m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping);
+ }
+
+ m_relatedMethodChecker.checkMethod(classEntry, methodMapping);
+ }
+
+ // check inner classes
+ for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
+ if (!checkClassMapping(innerClassMapping)) {
+ classMapping.removeInnerClassMapping(innerClassMapping);
+ m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping);
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 0000000..c790eed
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/mapping/MappingsReader.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
+ *