summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/mapping
diff options
context:
space:
mode:
authorGravatar Michael Smith2015-05-21 23:30:00 +0100
committerGravatar Michael Smith2015-05-21 23:30:00 +0100
commite3f452250e51b7271f3989c7dfd12e4422934942 (patch)
tree5aa482f9a6e21eb318a3e23e7d8274d77c73faf6 /src/cuchaz/enigma/mapping
downloadenigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.tar.gz
enigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.tar.xz
enigma-fork-e3f452250e51b7271f3989c7dfd12e4422934942.zip
Support Gradle alongside SSJB
This makes builds faster, simpler and better automated but still keeps Cuchaz happy. :)
Diffstat (limited to 'src/cuchaz/enigma/mapping')
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/ArgumentMapping.java49
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorEntry.java15
-rw-r--r--src/cuchaz/enigma/mapping/ClassEntry.java172
-rw-r--r--src/cuchaz/enigma/mapping/ClassMapping.java460
-rw-r--r--src/cuchaz/enigma/mapping/ClassNameReplacer.java15
-rw-r--r--src/cuchaz/enigma/mapping/ConstructorEntry.java116
-rw-r--r--src/cuchaz/enigma/mapping/Entry.java18
-rw-r--r--src/cuchaz/enigma/mapping/EntryFactory.java166
-rw-r--r--src/cuchaz/enigma/mapping/EntryPair.java22
-rw-r--r--src/cuchaz/enigma/mapping/FieldEntry.java99
-rw-r--r--src/cuchaz/enigma/mapping/FieldMapping.java89
-rw-r--r--src/cuchaz/enigma/mapping/IllegalNameException.java44
-rw-r--r--src/cuchaz/enigma/mapping/MappingParseException.java29
-rw-r--r--src/cuchaz/enigma/mapping/Mappings.java216
-rw-r--r--src/cuchaz/enigma/mapping/MappingsChecker.java107
-rw-r--r--src/cuchaz/enigma/mapping/MappingsReader.java134
-rw-r--r--src/cuchaz/enigma/mapping/MappingsRenamer.java237
-rw-r--r--src/cuchaz/enigma/mapping/MappingsWriter.java88
-rw-r--r--src/cuchaz/enigma/mapping/MemberMapping.java17
-rw-r--r--src/cuchaz/enigma/mapping/MethodEntry.java104
-rw-r--r--src/cuchaz/enigma/mapping/MethodMapping.java191
-rw-r--r--src/cuchaz/enigma/mapping/NameValidator.java80
-rw-r--r--src/cuchaz/enigma/mapping/ProcyonEntryFactory.java55
-rw-r--r--src/cuchaz/enigma/mapping/Signature.java117
-rw-r--r--src/cuchaz/enigma/mapping/SignatureUpdater.java94
-rw-r--r--src/cuchaz/enigma/mapping/TranslationDirection.java29
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java289
-rw-r--r--src/cuchaz/enigma/mapping/Type.java247
29 files changed, 3415 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/mapping/ArgumentEntry.java b/src/cuchaz/enigma/mapping/ArgumentEntry.java
new file mode 100644
index 0000000..9d99016
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ArgumentEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 4472172468162696006L;
20
21 private BehaviorEntry m_behaviorEntry;
22 private int m_index;
23 private String m_name;
24
25 public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) {
26 if (behaviorEntry == null) {
27 throw new IllegalArgumentException("Behavior cannot be null!");
28 }
29 if (index < 0) {
30 throw new IllegalArgumentException("Index must be non-negative!");
31 }
32 if (name == null) {
33 throw new IllegalArgumentException("Argument name cannot be null!");
34 }
35
36 m_behaviorEntry = behaviorEntry;
37 m_index = index;
38 m_name = name;
39 }
40
41 public ArgumentEntry(ArgumentEntry other) {
42 m_behaviorEntry = (BehaviorEntry)m_behaviorEntry.cloneToNewClass(getClassEntry());
43 m_index = other.m_index;
44 m_name = other.m_name;
45 }
46
47 public ArgumentEntry(ArgumentEntry other, String newClassName) {
48 m_behaviorEntry = (BehaviorEntry)other.m_behaviorEntry.cloneToNewClass(new ClassEntry(newClassName));
49 m_index = other.m_index;
50 m_name = other.m_name;
51 }
52
53 public BehaviorEntry getBehaviorEntry() {
54 return m_behaviorEntry;
55 }
56
57 public int getIndex() {
58 return m_index;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public ClassEntry getClassEntry() {
68 return m_behaviorEntry.getClassEntry();
69 }
70
71 @Override
72 public String getClassName() {
73 return m_behaviorEntry.getClassName();
74 }
75
76 @Override
77 public ArgumentEntry cloneToNewClass(ClassEntry classEntry) {
78 return new ArgumentEntry(this, classEntry.getName());
79 }
80
81 public String getMethodName() {
82 return m_behaviorEntry.getName();
83 }
84
85 public Signature getMethodSignature() {
86 return m_behaviorEntry.getSignature();
87 }
88
89 @Override
90 public int hashCode() {
91 return Util.combineHashesOrdered(
92 m_behaviorEntry,
93 Integer.valueOf(m_index).hashCode(),
94 m_name.hashCode()
95 );
96 }
97
98 @Override
99 public boolean equals(Object other) {
100 if (other instanceof ArgumentEntry) {
101 return equals((ArgumentEntry)other);
102 }
103 return false;
104 }
105
106 public boolean equals(ArgumentEntry other) {
107 return m_behaviorEntry.equals(other.m_behaviorEntry)
108 && m_index == other.m_index
109 && m_name.equals(other.m_name);
110 }
111
112 @Override
113 public String toString() {
114 return m_behaviorEntry.toString() + "(" + m_index + ":" + m_name + ")";
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/ArgumentMapping.java b/src/cuchaz/enigma/mapping/ArgumentMapping.java
new file mode 100644
index 0000000..a0055a6
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ArgumentMapping.java
@@ -0,0 +1,49 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class ArgumentMapping implements Serializable, Comparable<ArgumentMapping> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private int m_index;
20 private String m_name;
21
22 // NOTE: this argument order is important for the MethodReader/MethodWriter
23 public ArgumentMapping(int index, String name) {
24 m_index = index;
25 m_name = NameValidator.validateArgumentName(name);
26 }
27
28 public ArgumentMapping(ArgumentMapping other) {
29 m_index = other.m_index;
30 m_name = other.m_name;
31 }
32
33 public int getIndex() {
34 return m_index;
35 }
36
37 public String getName() {
38 return m_name;
39 }
40
41 public void setName(String val) {
42 m_name = NameValidator.validateArgumentName(val);
43 }
44
45 @Override
46 public int compareTo(ArgumentMapping other) {
47 return Integer.compare(m_index, other.m_index);
48 }
49}
diff --git a/src/cuchaz/enigma/mapping/BehaviorEntry.java b/src/cuchaz/enigma/mapping/BehaviorEntry.java
new file mode 100644
index 0000000..031d267
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorEntry.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface BehaviorEntry extends Entry {
14 Signature getSignature();
15}
diff --git a/src/cuchaz/enigma/mapping/ClassEntry.java b/src/cuchaz/enigma/mapping/ClassEntry.java
new file mode 100644
index 0000000..373203f
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassEntry.java
@@ -0,0 +1,172 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18public class ClassEntry implements Entry, Serializable {
19
20 private static final long serialVersionUID = 4235460580973955811L;
21
22 private String m_name;
23
24 public ClassEntry(String className) {
25 if (className == null) {
26 throw new IllegalArgumentException("Class name cannot be null!");
27 }
28 if (className.indexOf('.') >= 0) {
29 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
30 }
31
32 m_name = className;
33
34 if (isInnerClass() && getInnermostClassName().indexOf('/') >= 0) {
35 throw new IllegalArgumentException("Inner class must not have a package: " + className);
36 }
37 }
38
39 public ClassEntry(ClassEntry other) {
40 m_name = other.m_name;
41 }
42
43 @Override
44 public String getName() {
45 return m_name;
46 }
47
48 @Override
49 public String getClassName() {
50 return m_name;
51 }
52
53 @Override
54 public ClassEntry getClassEntry() {
55 return this;
56 }
57
58 @Override
59 public ClassEntry cloneToNewClass(ClassEntry classEntry) {
60 return classEntry;
61 }
62
63 @Override
64 public int hashCode() {
65 return m_name.hashCode();
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 if (other instanceof ClassEntry) {
71 return equals((ClassEntry)other);
72 }
73 return false;
74 }
75
76 public boolean equals(ClassEntry other) {
77 return m_name.equals(other.m_name);
78 }
79
80 @Override
81 public String toString() {
82 return m_name;
83 }
84
85 public boolean isInnerClass() {
86 return m_name.lastIndexOf('$') >= 0;
87 }
88
89 public List<String> getClassChainNames() {
90 return Lists.newArrayList(m_name.split("\\$"));
91 }
92
93 public List<ClassEntry> getClassChain() {
94 List<ClassEntry> entries = Lists.newArrayList();
95 StringBuilder buf = new StringBuilder();
96 for (String name : getClassChainNames()) {
97 if (buf.length() > 0) {
98 buf.append("$");
99 }
100 buf.append(name);
101 entries.add(new ClassEntry(buf.toString()));
102 }
103 return entries;
104 }
105
106 public String getOutermostClassName() {
107 if (isInnerClass()) {
108 return m_name.substring(0, m_name.indexOf('$'));
109 }
110 return m_name;
111 }
112
113 public ClassEntry getOutermostClassEntry() {
114 return new ClassEntry(getOutermostClassName());
115 }
116
117 public String getOuterClassName() {
118 if (!isInnerClass()) {
119 throw new Error("This is not an inner class!");
120 }
121 return m_name.substring(0, m_name.lastIndexOf('$'));
122 }
123
124 public ClassEntry getOuterClassEntry() {
125 return new ClassEntry(getOuterClassName());
126 }
127
128 public String getInnermostClassName() {
129 if (!isInnerClass()) {
130 throw new Error("This is not an inner class!");
131 }
132 return m_name.substring(m_name.lastIndexOf('$') + 1);
133 }
134
135 public boolean isInDefaultPackage() {
136 return m_name.indexOf('/') < 0;
137 }
138
139 public String getPackageName() {
140 int pos = m_name.lastIndexOf('/');
141 if (pos > 0) {
142 return m_name.substring(0, pos);
143 }
144 return null;
145 }
146
147 public String getSimpleName() {
148 int pos = m_name.lastIndexOf('/');
149 if (pos > 0) {
150 return m_name.substring(pos + 1);
151 }
152 return m_name;
153 }
154
155 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
156 assert(classChain.contains(this));
157 StringBuilder buf = new StringBuilder();
158 for (ClassEntry chainEntry : classChain) {
159 if (buf.length() == 0) {
160 buf.append(chainEntry.getName());
161 } else {
162 buf.append("$");
163 buf.append(chainEntry.isInnerClass() ? chainEntry.getInnermostClassName() : chainEntry.getSimpleName());
164 }
165
166 if (chainEntry == this) {
167 break;
168 }
169 }
170 return new ClassEntry(buf.toString());
171 }
172}
diff --git a/src/cuchaz/enigma/mapping/ClassMapping.java b/src/cuchaz/enigma/mapping/ClassMapping.java
new file mode 100644
index 0000000..0b0105e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassMapping.java
@@ -0,0 +1,460 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Map;
16
17import com.google.common.collect.Maps;
18
19public class ClassMapping implements Serializable, Comparable<ClassMapping> {
20
21 private static final long serialVersionUID = -5148491146902340107L;
22
23 private String m_obfFullName;
24 private String m_obfSimpleName;
25 private String m_deobfName;
26 private Map<String,ClassMapping> m_innerClassesByObfSimple;
27 private Map<String,ClassMapping> m_innerClassesByDeobf;
28 private Map<String,FieldMapping> m_fieldsByObf;
29 private Map<String,FieldMapping> m_fieldsByDeobf;
30 private Map<String,MethodMapping> m_methodsByObf;
31 private Map<String,MethodMapping> m_methodsByDeobf;
32
33 public ClassMapping(String obfFullName) {
34 this(obfFullName, null);
35 }
36
37 public ClassMapping(String obfFullName, String deobfName) {
38 m_obfFullName = obfFullName;
39 ClassEntry classEntry = new ClassEntry(obfFullName);
40 m_obfSimpleName = classEntry.isInnerClass() ? classEntry.getInnermostClassName() : classEntry.getSimpleName();
41 m_deobfName = NameValidator.validateClassName(deobfName, false);
42 m_innerClassesByObfSimple = Maps.newHashMap();
43 m_innerClassesByDeobf = Maps.newHashMap();
44 m_fieldsByObf = Maps.newHashMap();
45 m_fieldsByDeobf = Maps.newHashMap();
46 m_methodsByObf = Maps.newHashMap();
47 m_methodsByDeobf = Maps.newHashMap();
48 }
49
50 public String getObfFullName() {
51 return m_obfFullName;
52 }
53
54 public String getObfSimpleName() {
55 return m_obfSimpleName;
56 }
57
58 public String getDeobfName() {
59 return m_deobfName;
60 }
61
62 public void setDeobfName(String val) {
63 m_deobfName = NameValidator.validateClassName(val, false);
64 }
65
66 //// INNER CLASSES ////////
67
68 public Iterable<ClassMapping> innerClasses() {
69 assert (m_innerClassesByObfSimple.size() >= m_innerClassesByDeobf.size());
70 return m_innerClassesByObfSimple.values();
71 }
72
73 public void addInnerClassMapping(ClassMapping classMapping) {
74 boolean obfWasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
75 assert (obfWasAdded);
76 if (classMapping.getDeobfName() != null) {
77 assert (isSimpleClassName(classMapping.getDeobfName()));
78 boolean deobfWasAdded = m_innerClassesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
79 assert (deobfWasAdded);
80 }
81 }
82
83 public void removeInnerClassMapping(ClassMapping classMapping) {
84 boolean obfWasRemoved = m_innerClassesByObfSimple.remove(classMapping.getObfSimpleName()) != null;
85 assert (obfWasRemoved);
86 if (classMapping.getDeobfName() != null) {
87 boolean deobfWasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
88 assert (deobfWasRemoved);
89 }
90 }
91
92 public ClassMapping getOrCreateInnerClass(ClassEntry obfInnerClass) {
93 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfInnerClass.getInnermostClassName());
94 if (classMapping == null) {
95 classMapping = new ClassMapping(obfInnerClass.getName());
96 boolean wasAdded = m_innerClassesByObfSimple.put(classMapping.getObfSimpleName(), classMapping) == null;
97 assert (wasAdded);
98 }
99 return classMapping;
100 }
101
102 public ClassMapping getInnerClassByObfSimple(String obfSimpleName) {
103 assert (isSimpleClassName(obfSimpleName));
104 return m_innerClassesByObfSimple.get(obfSimpleName);
105 }
106
107 public ClassMapping getInnerClassByDeobf(String deobfName) {
108 assert (isSimpleClassName(deobfName));
109 return m_innerClassesByDeobf.get(deobfName);
110 }
111
112 public ClassMapping getInnerClassByDeobfThenObfSimple(String name) {
113 ClassMapping classMapping = getInnerClassByDeobf(name);
114 if (classMapping == null) {
115 classMapping = getInnerClassByObfSimple(name);
116 }
117 return classMapping;
118 }
119
120 public String getDeobfInnerClassName(String obfSimpleName) {
121 assert (isSimpleClassName(obfSimpleName));
122 ClassMapping classMapping = m_innerClassesByObfSimple.get(obfSimpleName);
123 if (classMapping != null) {
124 return classMapping.getDeobfName();
125 }
126 return null;
127 }
128
129 public void setInnerClassName(ClassEntry obfInnerClass, String deobfName) {
130 ClassMapping classMapping = getOrCreateInnerClass(obfInnerClass);
131 if (classMapping.getDeobfName() != null) {
132 boolean wasRemoved = m_innerClassesByDeobf.remove(classMapping.getDeobfName()) != null;
133 assert (wasRemoved);
134 }
135 classMapping.setDeobfName(deobfName);
136 if (deobfName != null) {
137 assert (isSimpleClassName(deobfName));
138 boolean wasAdded = m_innerClassesByDeobf.put(deobfName, classMapping) == null;
139 assert (wasAdded);
140 }
141 }
142
143 public boolean hasInnerClassByObfSimple(String obfSimpleName) {
144 return m_innerClassesByObfSimple.containsKey(obfSimpleName);
145 }
146
147 public boolean hasInnerClassByDeobf(String deobfName) {
148 return m_innerClassesByDeobf.containsKey(deobfName);
149 }
150
151
152 //// FIELDS ////////
153
154 public Iterable<FieldMapping> fields() {
155 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
156 return m_fieldsByObf.values();
157 }
158
159 public boolean containsObfField(String obfName, Type obfType) {
160 return m_fieldsByObf.containsKey(getFieldKey(obfName, obfType));
161 }
162
163 public boolean containsDeobfField(String deobfName, Type deobfType) {
164 return m_fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType));
165 }
166
167 public void addFieldMapping(FieldMapping fieldMapping) {
168 String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
169 if (m_fieldsByObf.containsKey(obfKey)) {
170 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
171 }
172 String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType());
173 if (m_fieldsByDeobf.containsKey(deobfKey)) {
174 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
175 }
176 boolean obfWasAdded = m_fieldsByObf.put(obfKey, fieldMapping) == null;
177 assert (obfWasAdded);
178 boolean deobfWasAdded = m_fieldsByDeobf.put(deobfKey, fieldMapping) == null;
179 assert (deobfWasAdded);
180 assert (m_fieldsByObf.size() == m_fieldsByDeobf.size());
181 }
182
183 public void removeFieldMapping(FieldMapping fieldMapping) {
184 boolean obfWasRemoved = m_fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null;
185 assert (obfWasRemoved);
186 if (fieldMapping.getDeobfName() != null) {
187 boolean deobfWasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null;
188 assert (deobfWasRemoved);
189 }
190 }
191
192 public FieldMapping getFieldByObf(String obfName, Type obfType) {
193 return m_fieldsByObf.get(getFieldKey(obfName, obfType));
194 }
195
196 public FieldMapping getFieldByDeobf(String deobfName, Type obfType) {
197 return m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
198 }
199
200 public String getObfFieldName(String deobfName, Type obfType) {
201 FieldMapping fieldMapping = m_fieldsByDeobf.get(getFieldKey(deobfName, obfType));
202 if (fieldMapping != null) {
203 return fieldMapping.getObfName();
204 }
205 return null;
206 }
207
208 public String getDeobfFieldName(String obfName, Type obfType) {
209 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
210 if (fieldMapping != null) {
211 return fieldMapping.getDeobfName();
212 }
213 return null;
214 }
215
216 private String getFieldKey(String name, Type type) {
217 if (name == null) {
218 throw new IllegalArgumentException("name cannot be null!");
219 }
220 if (type == null) {
221 throw new IllegalArgumentException("type cannot be null!");
222 }
223 return name + ":" + type;
224 }
225
226
227 public void setFieldName(String obfName, Type obfType, String deobfName) {
228 assert(deobfName != null);
229 FieldMapping fieldMapping = m_fieldsByObf.get(getFieldKey(obfName, obfType));
230 if (fieldMapping == null) {
231 fieldMapping = new FieldMapping(obfName, obfType, deobfName);
232 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null;
233 assert (obfWasAdded);
234 } else {
235 boolean wasRemoved = m_fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null;
236 assert (wasRemoved);
237 }
238 fieldMapping.setDeobfName(deobfName);
239 if (deobfName != null) {
240 boolean wasAdded = m_fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null;
241 assert (wasAdded);
242 }
243 }
244
245 public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) {
246 assert(newObfName != null);
247 FieldMapping fieldMapping = m_fieldsByObf.remove(getFieldKey(oldObfName, obfType));
248 assert(fieldMapping != null);
249 fieldMapping.setObfName(newObfName);
250 fieldMapping.setObfType(newObfType);
251 boolean obfWasAdded = m_fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null;
252 assert(obfWasAdded);
253 }
254
255
256 //// METHODS ////////
257
258 public Iterable<MethodMapping> methods() {
259 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
260 return m_methodsByObf.values();
261 }
262
263 public boolean containsObfMethod(String obfName, Signature obfSignature) {
264 return m_methodsByObf.containsKey(getMethodKey(obfName, obfSignature));
265 }
266
267 public boolean containsDeobfMethod(String deobfName, Signature obfSignature) {
268 return m_methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature));
269 }
270
271 public void addMethodMapping(MethodMapping methodMapping) {
272 String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
273 if (m_methodsByObf.containsKey(obfKey)) {
274 throw new Error("Already have mapping for " + m_obfFullName + "." + obfKey);
275 }
276 boolean wasAdded = m_methodsByObf.put(obfKey, methodMapping) == null;
277 assert (wasAdded);
278 if (methodMapping.getDeobfName() != null) {
279 String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature());
280 if (m_methodsByDeobf.containsKey(deobfKey)) {
281 throw new Error("Already have mapping for " + m_deobfName + "." + deobfKey);
282 }
283 boolean deobfWasAdded = m_methodsByDeobf.put(deobfKey, methodMapping) == null;
284 assert (deobfWasAdded);
285 }
286 assert (m_methodsByObf.size() >= m_methodsByDeobf.size());
287 }
288
289 public void removeMethodMapping(MethodMapping methodMapping) {
290 boolean obfWasRemoved = m_methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null;
291 assert (obfWasRemoved);
292 if (methodMapping.getDeobfName() != null) {
293 boolean deobfWasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
294 assert (deobfWasRemoved);
295 }
296 }
297
298 public MethodMapping getMethodByObf(String obfName, Signature obfSignature) {
299 return m_methodsByObf.get(getMethodKey(obfName, obfSignature));
300 }
301
302 public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) {
303 return m_methodsByDeobf.get(getMethodKey(deobfName, obfSignature));
304 }
305
306 private String getMethodKey(String name, Signature signature) {
307 if (name == null) {
308 throw new IllegalArgumentException("name cannot be null!");
309 }
310 if (signature == null) {
311 throw new IllegalArgumentException("signature cannot be null!");
312 }
313 return name + signature;
314 }
315
316 public void setMethodName(String obfName, Signature obfSignature, String deobfName) {
317 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfName, obfSignature));
318 if (methodMapping == null) {
319 methodMapping = createMethodMapping(obfName, obfSignature);
320 } else if (methodMapping.getDeobfName() != null) {
321 boolean wasRemoved = m_methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null;
322 assert (wasRemoved);
323 }
324 methodMapping.setDeobfName(deobfName);
325 if (deobfName != null) {
326 boolean wasAdded = m_methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null;
327 assert (wasAdded);
328 }
329 }
330
331 public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) {
332 assert(newObfName != null);
333 MethodMapping methodMapping = m_methodsByObf.remove(getMethodKey(oldObfName, obfSignature));
334 assert(methodMapping != null);
335 methodMapping.setObfName(newObfName);
336 methodMapping.setObfSignature(newObfSignature);
337 boolean obfWasAdded = m_methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null;
338 assert(obfWasAdded);
339 }
340
341 //// ARGUMENTS ////////
342
343 public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) {
344 assert(argumentName != null);
345 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature));
346 if (methodMapping == null) {
347 methodMapping = createMethodMapping(obfMethodName, obfMethodSignature);
348 }
349 methodMapping.setArgumentName(argumentIndex, argumentName);
350 }
351
352 public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) {
353 m_methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex);
354 }
355
356 private MethodMapping createMethodMapping(String obfName, Signature obfSignature) {
357 MethodMapping methodMapping = new MethodMapping(obfName, obfSignature);
358 boolean wasAdded = m_methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null;
359 assert (wasAdded);
360 return methodMapping;
361 }
362
363 @Override
364 public String toString() {
365 StringBuilder buf = new StringBuilder();
366 buf.append(m_obfFullName);
367 buf.append(" <-> ");
368 buf.append(m_deobfName);
369 buf.append("\n");
370 buf.append("Fields:\n");
371 for (FieldMapping fieldMapping : fields()) {
372 buf.append("\t");
373 buf.append(fieldMapping.getObfName());
374 buf.append(" <-> ");
375 buf.append(fieldMapping.getDeobfName());
376 buf.append("\n");
377 }
378 buf.append("Methods:\n");
379 for (MethodMapping methodMapping : m_methodsByObf.values()) {
380 buf.append(methodMapping.toString());
381 buf.append("\n");
382 }
383 buf.append("Inner Classes:\n");
384 for (ClassMapping classMapping : m_innerClassesByObfSimple.values()) {
385 buf.append("\t");
386 buf.append(classMapping.getObfSimpleName());
387 buf.append(" <-> ");
388 buf.append(classMapping.getDeobfName());
389 buf.append("\n");
390 }
391 return buf.toString();
392 }
393
394 @Override
395 public int compareTo(ClassMapping other) {
396 // sort by a, b, c, ... aa, ab, etc
397 if (m_obfFullName.length() != other.m_obfFullName.length()) {
398 return m_obfFullName.length() - other.m_obfFullName.length();
399 }
400 return m_obfFullName.compareTo(other.m_obfFullName);
401 }
402
403 public boolean renameObfClass(String oldObfClassName, String newObfClassName) {
404
405 // rename inner classes
406 for (ClassMapping innerClassMapping : new ArrayList<ClassMapping>(m_innerClassesByObfSimple.values())) {
407 if (innerClassMapping.renameObfClass(oldObfClassName, newObfClassName)) {
408 boolean wasRemoved = m_innerClassesByObfSimple.remove(oldObfClassName) != null;
409 assert (wasRemoved);
410 boolean wasAdded = m_innerClassesByObfSimple.put(newObfClassName, innerClassMapping) == null;
411 assert (wasAdded);
412 }
413 }
414
415 // rename field types
416 for (FieldMapping fieldMapping : new ArrayList<FieldMapping>(m_fieldsByObf.values())) {
417 String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType());
418 if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) {
419 boolean wasRemoved = m_fieldsByObf.remove(oldFieldKey) != null;
420 assert (wasRemoved);
421 boolean wasAdded = m_fieldsByObf.put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null;
422 assert (wasAdded);
423 }
424 }
425
426 // rename method signatures
427 for (MethodMapping methodMapping : new ArrayList<MethodMapping>(m_methodsByObf.values())) {
428 String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature());
429 if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) {
430 boolean wasRemoved = m_methodsByObf.remove(oldMethodKey) != null;
431 assert (wasRemoved);
432 boolean wasAdded = m_methodsByObf.put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null;
433 assert (wasAdded);
434 }
435 }
436
437 if (m_obfFullName.equals(oldObfClassName)) {
438 // rename this class
439 m_obfFullName = newObfClassName;
440 return true;
441 }
442 return false;
443 }
444
445 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
446 MethodMapping methodMapping = m_methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature()));
447 if (methodMapping != null) {
448 return methodMapping.containsArgument(name);
449 }
450 return false;
451 }
452
453 public static boolean isSimpleClassName(String name) {
454 return name.indexOf('/') < 0 && name.indexOf('$') < 0;
455 }
456
457 public ClassEntry getObfEntry() {
458 return new ClassEntry(m_obfFullName);
459 }
460}
diff --git a/src/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
new file mode 100644
index 0000000..f00d811
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ClassNameReplacer.java
@@ -0,0 +1,15 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface ClassNameReplacer {
14 String replace(String className);
15}
diff --git a/src/cuchaz/enigma/mapping/ConstructorEntry.java b/src/cuchaz/enigma/mapping/ConstructorEntry.java
new file mode 100644
index 0000000..7cde8f6
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ConstructorEntry.java
@@ -0,0 +1,116 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class ConstructorEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = -868346075317366758L;
20
21 private ClassEntry m_classEntry;
22 private Signature m_signature;
23
24 public ConstructorEntry(ClassEntry classEntry) {
25 this(classEntry, null);
26 }
27
28 public ConstructorEntry(ClassEntry classEntry, Signature signature) {
29 if (classEntry == null) {
30 throw new IllegalArgumentException("Class cannot be null!");
31 }
32
33 m_classEntry = classEntry;
34 m_signature = signature;
35 }
36
37 public ConstructorEntry(ConstructorEntry other) {
38 m_classEntry = new ClassEntry(other.m_classEntry);
39 m_signature = other.m_signature;
40 }
41
42 public ConstructorEntry(ConstructorEntry other, String newClassName) {
43 m_classEntry = new ClassEntry(newClassName);
44 m_signature = other.m_signature;
45 }
46
47 @Override
48 public ClassEntry getClassEntry() {
49 return m_classEntry;
50 }
51
52 @Override
53 public String getName() {
54 if (isStatic()) {
55 return "<clinit>";
56 }
57 return "<init>";
58 }
59
60 public boolean isStatic() {
61 return m_signature == null;
62 }
63
64 @Override
65 public Signature getSignature() {
66 return m_signature;
67 }
68
69 @Override
70 public String getClassName() {
71 return m_classEntry.getName();
72 }
73
74 @Override
75 public ConstructorEntry cloneToNewClass(ClassEntry classEntry) {
76 return new ConstructorEntry(this, classEntry.getName());
77 }
78
79 @Override
80 public int hashCode() {
81 if (isStatic()) {
82 return Util.combineHashesOrdered(m_classEntry);
83 } else {
84 return Util.combineHashesOrdered(m_classEntry, m_signature);
85 }
86 }
87
88 @Override
89 public boolean equals(Object other) {
90 if (other instanceof ConstructorEntry) {
91 return equals((ConstructorEntry)other);
92 }
93 return false;
94 }
95
96 public boolean equals(ConstructorEntry other) {
97 if (isStatic() != other.isStatic()) {
98 return false;
99 }
100
101 if (isStatic()) {
102 return m_classEntry.equals(other.m_classEntry);
103 } else {
104 return m_classEntry.equals(other.m_classEntry) && m_signature.equals(other.m_signature);
105 }
106 }
107
108 @Override
109 public String toString() {
110 if (isStatic()) {
111 return m_classEntry.getName() + "." + getName();
112 } else {
113 return m_classEntry.getName() + "." + getName() + m_signature;
114 }
115 }
116}
diff --git a/src/cuchaz/enigma/mapping/Entry.java b/src/cuchaz/enigma/mapping/Entry.java
new file mode 100644
index 0000000..3c94a95
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Entry.java
@@ -0,0 +1,18 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public interface Entry {
14 String getName();
15 String getClassName();
16 ClassEntry getClassEntry();
17 Entry cloneToNewClass(ClassEntry classEntry);
18}
diff --git a/src/cuchaz/enigma/mapping/EntryFactory.java b/src/cuchaz/enigma/mapping/EntryFactory.java
new file mode 100644
index 0000000..03d97ba
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryFactory.java
@@ -0,0 +1,166 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import javassist.CtBehavior;
14import javassist.CtClass;
15import javassist.CtConstructor;
16import javassist.CtField;
17import javassist.CtMethod;
18import javassist.bytecode.Descriptor;
19import javassist.expr.ConstructorCall;
20import javassist.expr.FieldAccess;
21import javassist.expr.MethodCall;
22import javassist.expr.NewExpr;
23
24import cuchaz.enigma.analysis.JarIndex;
25
26public class EntryFactory {
27
28 public static ClassEntry getClassEntry(CtClass c) {
29 return new ClassEntry(Descriptor.toJvmName(c.getName()));
30 }
31
32 public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) {
33 ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName());
34 return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry));
35 }
36
37 private static ClassEntry getObfClassEntry(ClassMapping classMapping) {
38 return new ClassEntry(classMapping.getObfFullName());
39 }
40
41 public static ClassEntry getDeobfClassEntry(ClassMapping classMapping) {
42 return new ClassEntry(classMapping.getDeobfName());
43 }
44
45 public static ClassEntry getSuperclassEntry(CtClass c) {
46 return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
47 }
48
49 public static FieldEntry getFieldEntry(CtField field) {
50 return new FieldEntry(
51 getClassEntry(field.getDeclaringClass()),
52 field.getName(),
53 new Type(field.getFieldInfo().getDescriptor())
54 );
55 }
56
57 public static FieldEntry getFieldEntry(FieldAccess call) {
58 return new FieldEntry(
59 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
60 call.getFieldName(),
61 new Type(call.getSignature())
62 );
63 }
64
65 public static FieldEntry getFieldEntry(String className, String name, String type) {
66 return new FieldEntry(new ClassEntry(className), name, new Type(type));
67 }
68
69 public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) {
70 return new FieldEntry(
71 getObfClassEntry(classMapping),
72 fieldMapping.getObfName(),
73 fieldMapping.getObfType()
74 );
75 }
76
77 public static MethodEntry getMethodEntry(CtMethod method) {
78 return new MethodEntry(
79 getClassEntry(method.getDeclaringClass()),
80 method.getName(),
81 new Signature(method.getMethodInfo().getDescriptor())
82 );
83 }
84
85 public static MethodEntry getMethodEntry(MethodCall call) {
86 return new MethodEntry(
87 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
88 call.getMethodName(),
89 new Signature(call.getSignature())
90 );
91 }
92
93 public static ConstructorEntry getConstructorEntry(CtConstructor constructor) {
94 if (constructor.isClassInitializer()) {
95 return new ConstructorEntry(
96 getClassEntry(constructor.getDeclaringClass())
97 );
98 } else {
99 return new ConstructorEntry(
100 getClassEntry(constructor.getDeclaringClass()),
101 new Signature(constructor.getMethodInfo().getDescriptor())
102 );
103 }
104 }
105
106 public static ConstructorEntry getConstructorEntry(ConstructorCall call) {
107 return new ConstructorEntry(
108 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
109 new Signature(call.getSignature())
110 );
111 }
112
113 public static ConstructorEntry getConstructorEntry(NewExpr call) {
114 return new ConstructorEntry(
115 new ClassEntry(Descriptor.toJvmName(call.getClassName())),
116 new Signature(call.getSignature())
117 );
118 }
119
120 public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) {
121 if (behavior instanceof CtMethod) {
122 return getMethodEntry((CtMethod)behavior);
123 } else if (behavior instanceof CtConstructor) {
124 return getConstructorEntry((CtConstructor)behavior);
125 }
126 throw new Error("behavior is neither Method nor Constructor!");
127 }
128
129 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) {
130 return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature));
131 }
132
133 public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) {
134 return getBehaviorEntry(new ClassEntry(className), behaviorName);
135 }
136
137 public static BehaviorEntry getBehaviorEntry(String className) {
138 return new ConstructorEntry(new ClassEntry(className));
139 }
140
141 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) {
142 if (behaviorName.equals("<init>")) {
143 return new ConstructorEntry(classEntry, behaviorSignature);
144 } else if(behaviorName.equals("<clinit>")) {
145 return new ConstructorEntry(classEntry);
146 } else {
147 return new MethodEntry(classEntry, behaviorName, behaviorSignature);
148 }
149 }
150
151 public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) {
152 if(behaviorName.equals("<clinit>")) {
153 return new ConstructorEntry(classEntry);
154 } else {
155 throw new IllegalArgumentException("Only class initializers don't have signatures");
156 }
157 }
158
159 public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) {
160 return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature());
161 }
162
163 public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) {
164 return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping);
165 }
166}
diff --git a/src/cuchaz/enigma/mapping/EntryPair.java b/src/cuchaz/enigma/mapping/EntryPair.java
new file mode 100644
index 0000000..82b28cd
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/EntryPair.java
@@ -0,0 +1,22 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class EntryPair<T extends Entry> {
14
15 public T obf;
16 public T deobf;
17
18 public EntryPair(T obf, T deobf) {
19 this.obf = obf;
20 this.deobf = deobf;
21 }
22}
diff --git a/src/cuchaz/enigma/mapping/FieldEntry.java b/src/cuchaz/enigma/mapping/FieldEntry.java
new file mode 100644
index 0000000..e4a74f4
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldEntry.java
@@ -0,0 +1,99 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class FieldEntry implements Entry, Serializable {
18
19 private static final long serialVersionUID = 3004663582802885451L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Type m_type;
24
25 // NOTE: this argument order is important for the MethodReader/MethodWriter
26 public FieldEntry(ClassEntry classEntry, String name, Type type) {
27 if (classEntry == null) {
28 throw new IllegalArgumentException("Class cannot be null!");
29 }
30 if (name == null) {
31 throw new IllegalArgumentException("Field name cannot be null!");
32 }
33 if (type == null) {
34 throw new IllegalArgumentException("Field type cannot be null!");
35 }
36
37 m_classEntry = classEntry;
38 m_name = name;
39 m_type = type;
40 }
41
42 public FieldEntry(FieldEntry other) {
43 this(other, new ClassEntry(other.m_classEntry));
44 }
45
46 public FieldEntry(FieldEntry other, ClassEntry newClassEntry) {
47 m_classEntry = newClassEntry;
48 m_name = other.m_name;
49 m_type = other.m_type;
50 }
51
52 @Override
53 public ClassEntry getClassEntry() {
54 return m_classEntry;
55 }
56
57 @Override
58 public String getName() {
59 return m_name;
60 }
61
62 @Override
63 public String getClassName() {
64 return m_classEntry.getName();
65 }
66
67 public Type getType() {
68 return m_type;
69 }
70
71 @Override
72 public FieldEntry cloneToNewClass(ClassEntry classEntry) {
73 return new FieldEntry(this, classEntry);
74 }
75
76 @Override
77 public int hashCode() {
78 return Util.combineHashesOrdered(m_classEntry, m_name, m_type);
79 }
80
81 @Override
82 public boolean equals(Object other) {
83 if (other instanceof FieldEntry) {
84 return equals((FieldEntry)other);
85 }
86 return false;
87 }
88
89 public boolean equals(FieldEntry other) {
90 return m_classEntry.equals(other.m_classEntry)
91 && m_name.equals(other.m_name)
92 && m_type.equals(other.m_type);
93 }
94
95 @Override
96 public String toString() {
97 return m_classEntry.getName() + "." + m_name + ":" + m_type;
98 }
99}
diff --git a/src/cuchaz/enigma/mapping/FieldMapping.java b/src/cuchaz/enigma/mapping/FieldMapping.java
new file mode 100644
index 0000000..2855740
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/FieldMapping.java
@@ -0,0 +1,89 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15public class FieldMapping implements Serializable, Comparable<FieldMapping>, MemberMapping<FieldEntry> {
16
17 private static final long serialVersionUID = 8610742471440861315L;
18
19 private String m_obfName;
20 private String m_deobfName;
21 private Type m_obfType;
22
23 public FieldMapping(String obfName, Type obfType, String deobfName) {
24 m_obfName = obfName;
25 m_deobfName = NameValidator.validateFieldName(deobfName);
26 m_obfType = obfType;
27 }
28
29 public FieldMapping(FieldMapping other, ClassNameReplacer obfClassNameReplacer) {
30 m_obfName = other.m_obfName;
31 m_deobfName = other.m_deobfName;
32 m_obfType = new Type(other.m_obfType, obfClassNameReplacer);
33 }
34
35 @Override
36 public String getObfName() {
37 return m_obfName;
38 }
39
40 public void setObfName(String val) {
41 m_obfName = NameValidator.validateFieldName(val);
42 }
43
44 public String getDeobfName() {
45 return m_deobfName;
46 }
47
48 public void setDeobfName(String val) {
49 m_deobfName = NameValidator.validateFieldName(val);
50 }
51
52 public Type getObfType() {
53 return m_obfType;
54 }
55
56 public void setObfType(Type val) {
57 m_obfType = val;
58 }
59
60 @Override
61 public int compareTo(FieldMapping other) {
62 return (m_obfName + m_obfType).compareTo(other.m_obfName + other.m_obfType);
63 }
64
65 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
66
67 // rename obf classes in the type
68 Type newType = new Type(m_obfType, new ClassNameReplacer() {
69 @Override
70 public String replace(String className) {
71 if (className.equals(oldObfClassName)) {
72 return newObfClassName;
73 }
74 return null;
75 }
76 });
77
78 if (!newType.equals(m_obfType)) {
79 m_obfType = newType;
80 return true;
81 }
82 return false;
83 }
84
85 @Override
86 public FieldEntry getObfEntry(ClassEntry classEntry) {
87 return new FieldEntry(classEntry, m_obfName, new Type(m_obfType));
88 }
89}
diff --git a/src/cuchaz/enigma/mapping/IllegalNameException.java b/src/cuchaz/enigma/mapping/IllegalNameException.java
new file mode 100644
index 0000000..f62df7c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/IllegalNameException.java
@@ -0,0 +1,44 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class IllegalNameException extends RuntimeException {
14
15 private static final long serialVersionUID = -2279910052561114323L;
16
17 private String m_name;
18 private String m_reason;
19
20 public IllegalNameException(String name) {
21 this(name, null);
22 }
23
24 public IllegalNameException(String name, String reason) {
25 m_name = name;
26 m_reason = reason;
27 }
28
29 public String getReason() {
30 return m_reason;
31 }
32
33 @Override
34 public String getMessage() {
35 StringBuilder buf = new StringBuilder();
36 buf.append("Illegal name: ");
37 buf.append(m_name);
38 if (m_reason != null) {
39 buf.append(" because ");
40 buf.append(m_reason);
41 }
42 return buf.toString();
43 }
44}
diff --git a/src/cuchaz/enigma/mapping/MappingParseException.java b/src/cuchaz/enigma/mapping/MappingParseException.java
new file mode 100644
index 0000000..73fca94
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingParseException.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public class MappingParseException extends Exception {
14
15 private static final long serialVersionUID = -5487280332892507236L;
16
17 private int m_line;
18 private String m_message;
19
20 public MappingParseException(int line, String message) {
21 m_line = line;
22 m_message = message;
23 }
24
25 @Override
26 public String getMessage() {
27 return "Line " + m_line + ": " + m_message;
28 }
29}
diff --git a/src/cuchaz/enigma/mapping/Mappings.java b/src/cuchaz/enigma/mapping/Mappings.java
new file mode 100644
index 0000000..11ed5d0
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Mappings.java
@@ -0,0 +1,216 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19
20import com.google.common.collect.Lists;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Sets;
23
24import cuchaz.enigma.analysis.TranslationIndex;
25
26public class Mappings implements Serializable {
27
28 private static final long serialVersionUID = 4649790259460259026L;
29
30 protected Map<String,ClassMapping> m_classesByObf;
31 protected Map<String,ClassMapping> m_classesByDeobf;
32
33 public Mappings() {
34 m_classesByObf = Maps.newHashMap();
35 m_classesByDeobf = Maps.newHashMap();
36 }
37
38 public Mappings(Iterable<ClassMapping> classes) {
39 this();
40
41 for (ClassMapping classMapping : classes) {
42 m_classesByObf.put(classMapping.getObfFullName(), classMapping);
43 if (classMapping.getDeobfName() != null) {
44 m_classesByDeobf.put(classMapping.getDeobfName(), classMapping);
45 }
46 }
47 }
48
49 public Collection<ClassMapping> classes() {
50 assert (m_classesByObf.size() >= m_classesByDeobf.size());
51 return m_classesByObf.values();
52 }
53
54 public void addClassMapping(ClassMapping classMapping) {
55 if (m_classesByObf.containsKey(classMapping.getObfFullName())) {
56 throw new Error("Already have mapping for " + classMapping.getObfFullName());
57 }
58 boolean obfWasAdded = m_classesByObf.put(classMapping.getObfFullName(), classMapping) == null;
59 assert (obfWasAdded);
60 if (classMapping.getDeobfName() != null) {
61 if (m_classesByDeobf.containsKey(classMapping.getDeobfName())) {
62 throw new Error("Already have mapping for " + classMapping.getDeobfName());
63 }
64 boolean deobfWasAdded = m_classesByDeobf.put(classMapping.getDeobfName(), classMapping) == null;
65 assert (deobfWasAdded);
66 }
67 }
68
69 public void removeClassMapping(ClassMapping classMapping) {
70 boolean obfWasRemoved = m_classesByObf.remove(classMapping.getObfFullName()) != null;
71 assert (obfWasRemoved);
72 if (classMapping.getDeobfName() != null) {
73 boolean deobfWasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
74 assert (deobfWasRemoved);
75 }
76 }
77
78 public ClassMapping getClassByObf(ClassEntry entry) {
79 return getClassByObf(entry.getName());
80 }
81
82 public ClassMapping getClassByObf(String obfName) {
83 return m_classesByObf.get(obfName);
84 }
85
86 public ClassMapping getClassByDeobf(ClassEntry entry) {
87 return getClassByDeobf(entry.getName());
88 }
89
90 public ClassMapping getClassByDeobf(String deobfName) {
91 return m_classesByDeobf.get(deobfName);
92 }
93
94 public void setClassDeobfName(ClassMapping classMapping, String deobfName) {
95 if (classMapping.getDeobfName() != null) {
96 boolean wasRemoved = m_classesByDeobf.remove(classMapping.getDeobfName()) != null;
97 assert (wasRemoved);
98 }
99 classMapping.setDeobfName(deobfName);
100 if (deobfName != null) {
101 boolean wasAdded = m_classesByDeobf.put(deobfName, classMapping) == null;
102 assert (wasAdded);
103 }
104 }
105
106 public Translator getTranslator(TranslationDirection direction, TranslationIndex index) {
107 switch (direction) {
108 case Deobfuscating:
109
110 return new Translator(direction, m_classesByObf, index);
111
112 case Obfuscating:
113
114 // fill in the missing deobf class entries with obf entries
115 Map<String,ClassMapping> classes = Maps.newHashMap();
116 for (ClassMapping classMapping : classes()) {
117 if (classMapping.getDeobfName() != null) {
118 classes.put(classMapping.getDeobfName(), classMapping);
119 } else {
120 classes.put(classMapping.getObfFullName(), classMapping);
121 }
122 }
123
124 // translate the translation index
125 // NOTE: this isn't actually recursive
126 TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index));
127
128 return new Translator(direction, classes, deobfIndex);
129
130 default:
131 throw new Error("Invalid translation direction!");
132 }
133 }
134
135 @Override
136 public String toString() {
137 StringBuilder buf = new StringBuilder();
138 for (ClassMapping classMapping : m_classesByObf.values()) {
139 buf.append(classMapping.toString());
140 buf.append("\n");
141 }
142 return buf.toString();
143 }
144
145 public void renameObfClass(String oldObfName, String newObfName) {
146 for (ClassMapping classMapping : new ArrayList<ClassMapping>(classes())) {
147 if (classMapping.renameObfClass(oldObfName, newObfName)) {
148 boolean wasRemoved = m_classesByObf.remove(oldObfName) != null;
149 assert (wasRemoved);
150 boolean wasAdded = m_classesByObf.put(newObfName, classMapping) == null;
151 assert (wasAdded);
152 }
153 }
154 }
155
156 public Set<String> getAllObfClassNames() {
157 final Set<String> classNames = Sets.newHashSet();
158 for (ClassMapping classMapping : classes()) {
159
160 // add the class name
161 classNames.add(classMapping.getObfFullName());
162
163 // add classes from method signatures
164 for (MethodMapping methodMapping : classMapping.methods()) {
165 for (Type type : methodMapping.getObfSignature().types()) {
166 if (type.hasClass()) {
167 classNames.add(type.getClassEntry().getClassName());
168 }
169 }
170 }
171 }
172 return classNames;
173 }
174
175 public boolean containsDeobfClass(String deobfName) {
176 return m_classesByDeobf.containsKey(deobfName);
177 }
178
179 public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) {
180 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
181 if (classMapping != null) {
182 return classMapping.containsDeobfField(deobfName, obfType);
183 }
184 return false;
185 }
186
187 public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature deobfSignature) {
188 ClassMapping classMapping = m_classesByObf.get(obfClassEntry.getName());
189 if (classMapping != null) {
190 return classMapping.containsDeobfMethod(deobfName, deobfSignature);
191 }
192 return false;
193 }
194
195 public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) {
196 ClassMapping classMapping = m_classesByObf.get(obfBehaviorEntry.getClassName());
197 if (classMapping != null) {
198 return classMapping.containsArgument(obfBehaviorEntry, name);
199 }
200 return false;
201 }
202
203 public List<ClassMapping> getClassMappingChain(ClassEntry obfClass) {
204 List<ClassMapping> mappingChain = Lists.newArrayList();
205 ClassMapping classMapping = null;
206 for (ClassEntry obfClassEntry : obfClass.getClassChain()) {
207 if (mappingChain.isEmpty()) {
208 classMapping = m_classesByObf.get(obfClassEntry.getName());
209 } else if (classMapping != null) {
210 classMapping = classMapping.getInnerClassByObfSimple(obfClassEntry.getInnermostClassName());
211 }
212 mappingChain.add(classMapping);
213 }
214 return mappingChain;
215 }
216}
diff --git a/src/cuchaz/enigma/mapping/MappingsChecker.java b/src/cuchaz/enigma/mapping/MappingsChecker.java
new file mode 100644
index 0000000..b25ea3c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsChecker.java
@@ -0,0 +1,107 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Map;
14
15import com.google.common.collect.Lists;
16import com.google.common.collect.Maps;
17
18import cuchaz.enigma.analysis.JarIndex;
19import cuchaz.enigma.analysis.RelatedMethodChecker;
20
21
22public class MappingsChecker {
23
24 private JarIndex m_index;
25 private RelatedMethodChecker m_relatedMethodChecker;
26 private Map<ClassEntry,ClassMapping> m_droppedClassMappings;
27 private Map<ClassEntry,ClassMapping> m_droppedInnerClassMappings;
28 private Map<FieldEntry,FieldMapping> m_droppedFieldMappings;
29 private Map<BehaviorEntry,MethodMapping> m_droppedMethodMappings;
30
31 public MappingsChecker(JarIndex index) {
32 m_index = index;
33 m_relatedMethodChecker = new RelatedMethodChecker(m_index);
34 m_droppedClassMappings = Maps.newHashMap();
35 m_droppedInnerClassMappings = Maps.newHashMap();
36 m_droppedFieldMappings = Maps.newHashMap();
37 m_droppedMethodMappings = Maps.newHashMap();
38 }
39
40 public RelatedMethodChecker getRelatedMethodChecker() {
41 return m_relatedMethodChecker;
42 }
43
44 public Map<ClassEntry,ClassMapping> getDroppedClassMappings() {
45 return m_droppedClassMappings;
46 }
47
48 public Map<ClassEntry,ClassMapping> getDroppedInnerClassMappings() {
49 return m_droppedInnerClassMappings;
50 }
51
52 public Map<FieldEntry,FieldMapping> getDroppedFieldMappings() {
53 return m_droppedFieldMappings;
54 }
55
56 public Map<BehaviorEntry,MethodMapping> getDroppedMethodMappings() {
57 return m_droppedMethodMappings;
58 }
59
60 public void dropBrokenMappings(Mappings mappings) {
61 for (ClassMapping classMapping : Lists.newArrayList(mappings.classes())) {
62 if (!checkClassMapping(classMapping)) {
63 mappings.removeClassMapping(classMapping);
64 m_droppedClassMappings.put(EntryFactory.getObfClassEntry(m_index, classMapping), classMapping);
65 }
66 }
67 }
68
69 private boolean checkClassMapping(ClassMapping classMapping) {
70
71 // check the class
72 ClassEntry classEntry = EntryFactory.getObfClassEntry(m_index, classMapping);
73 if (!m_index.getObfClassEntries().contains(classEntry)) {
74 return false;
75 }
76
77 // check the fields
78 for (FieldMapping fieldMapping : Lists.newArrayList(classMapping.fields())) {
79 FieldEntry obfFieldEntry = EntryFactory.getObfFieldEntry(classMapping, fieldMapping);
80 if (!m_index.containsObfField(obfFieldEntry)) {
81 classMapping.removeFieldMapping(fieldMapping);
82 m_droppedFieldMappings.put(obfFieldEntry, fieldMapping);
83 }
84 }
85
86 // check methods
87 for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) {
88 BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping);
89 if (!m_index.containsObfBehavior(obfBehaviorEntry)) {
90 classMapping.removeMethodMapping(methodMapping);
91 m_droppedMethodMappings.put(obfBehaviorEntry, methodMapping);
92 }
93
94 m_relatedMethodChecker.checkMethod(classEntry, methodMapping);
95 }
96
97 // check inner classes
98 for (ClassMapping innerClassMapping : Lists.newArrayList(classMapping.innerClasses())) {
99 if (!checkClassMapping(innerClassMapping)) {
100 classMapping.removeInnerClassMapping(innerClassMapping);
101 m_droppedInnerClassMappings.put(EntryFactory.getObfClassEntry(m_index, innerClassMapping), innerClassMapping);
102 }
103 }
104
105 return true;
106 }
107}
diff --git a/src/cuchaz/enigma/mapping/MappingsReader.java b/src/cuchaz/enigma/mapping/MappingsReader.java
new file mode 100644
index 0000000..0a4b117
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsReader.java
@@ -0,0 +1,134 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.BufferedReader;
14import java.io.IOException;
15import java.io.Reader;
16import java.util.Deque;
17
18import com.google.common.collect.Queues;
19
20public class MappingsReader {
21
22 public Mappings read(Reader in)
23 throws IOException, MappingParseException {
24 return read(new BufferedReader(in));
25 }
26
27 public Mappings read(BufferedReader in)
28 throws IOException, MappingParseException {
29 Mappings mappings = new Mappings();
30 Deque<Object> mappingStack = Queues.newArrayDeque();
31
32 int lineNumber = 0;
33 String line = null;
34 while ( (line = in.readLine()) != null) {
35 lineNumber++;
36
37 // strip comments
38 int commentPos = line.indexOf('#');
39 if (commentPos >= 0) {
40 line = line.substring(0, commentPos);
41 }
42
43 // skip blank lines
44 if (line.trim().length() <= 0) {
45 continue;
46 }
47
48 // get the indent of this line
49 int indent = 0;
50 for (int i = 0; i < line.length(); i++) {
51 if (line.charAt(i) != '\t') {
52 break;
53 }
54 indent++;
55 }
56
57 // handle stack pops
58 while (indent < mappingStack.size()) {
59 mappingStack.pop();
60 }
61
62 String[] parts = line.trim().split("\\s");
63 try {
64 // read the first token
65 String token = parts[0];
66
67 if (token.equalsIgnoreCase("CLASS")) {
68 ClassMapping classMapping;
69 if (indent <= 0) {
70 // outer class
71 classMapping = readClass(parts, false);
72 mappings.addClassMapping(classMapping);
73 } else {
74
75 // inner class
76 if (!(mappingStack.peek() instanceof ClassMapping)) {
77 throw new MappingParseException(lineNumber, "Unexpected CLASS entry here!");
78 }
79
80 classMapping = readClass(parts, true);
81 ((ClassMapping)mappingStack.peek()).addInnerClassMapping(classMapping);
82 }
83 mappingStack.push(classMapping);
84 } else if (token.equalsIgnoreCase("FIELD")) {
85 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
86 throw new MappingParseException(lineNumber, "Unexpected FIELD entry here!");
87 }
88 ((ClassMapping)mappingStack.peek()).addFieldMapping(readField(parts));
89 } else if (token.equalsIgnoreCase("METHOD")) {
90 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof ClassMapping)) {
91 throw new MappingParseException(lineNumber, "Unexpected METHOD entry here!");
92 }
93 MethodMapping methodMapping = readMethod(parts);
94 ((ClassMapping)mappingStack.peek()).addMethodMapping(methodMapping);
95 mappingStack.push(methodMapping);
96 } else if (token.equalsIgnoreCase("ARG")) {
97 if (mappingStack.isEmpty() || ! (mappingStack.peek() instanceof MethodMapping)) {
98 throw new MappingParseException(lineNumber, "Unexpected ARG entry here!");
99 }
100 ((MethodMapping)mappingStack.peek()).addArgumentMapping(readArgument(parts));
101 }
102 } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException ex) {
103 throw new MappingParseException(lineNumber, "Malformed line:\n" + line);
104 }
105 }
106
107 return mappings;
108 }
109
110 private ArgumentMapping readArgument(String[] parts) {
111 return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]);
112 }
113
114 private ClassMapping readClass(String[] parts, boolean makeSimple) {
115 if (parts.length == 2) {
116 return new ClassMapping(parts[1]);
117 } else {
118 return new ClassMapping(parts[1], parts[2]);
119 }
120 }
121
122 /* TEMP */
123 protected FieldMapping readField(String[] parts) {
124 return new FieldMapping(parts[1], new Type(parts[3]), parts[2]);
125 }
126
127 private MethodMapping readMethod(String[] parts) {
128 if (parts.length == 3) {
129 return new MethodMapping(parts[1], new Signature(parts[2]));
130 } else {
131 return new MethodMapping(parts[1], new Signature(parts[3]), parts[2]);
132 }
133 }
134}
diff --git a/src/cuchaz/enigma/mapping/MappingsRenamer.java b/src/cuchaz/enigma/mapping/MappingsRenamer.java
new file mode 100644
index 0000000..47e5738
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsRenamer.java
@@ -0,0 +1,237 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.ObjectOutputStream;
15import java.io.OutputStream;
16import java.util.List;
17import java.util.Set;
18import java.util.zip.GZIPOutputStream;
19
20import cuchaz.enigma.analysis.JarIndex;
21
22public class MappingsRenamer {
23
24 private JarIndex m_index;
25 private Mappings m_mappings;
26
27 public MappingsRenamer(JarIndex index, Mappings mappings) {
28 m_index = index;
29 m_mappings = mappings;
30 }
31
32 public void setClassName(ClassEntry obf, String deobfName) {
33
34 deobfName = NameValidator.validateClassName(deobfName, !obf.isInnerClass());
35
36 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
37 if (mappingChain.size() == 1) {
38
39 if (deobfName != null) {
40 // make sure we don't rename to an existing obf or deobf class
41 if (m_mappings.containsDeobfClass(deobfName) || m_index.containsObfClass(new ClassEntry(deobfName))) {
42 throw new IllegalNameException(deobfName, "There is already a class with that name");
43 }
44 }
45
46 ClassMapping classMapping = mappingChain.get(0);
47 m_mappings.setClassDeobfName(classMapping, deobfName);
48
49 } else {
50
51 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
52
53 if (deobfName != null) {
54 // make sure we don't rename to an existing obf or deobf inner class
55 if (outerClassMapping.hasInnerClassByDeobf(deobfName) || outerClassMapping.hasInnerClassByObfSimple(deobfName)) {
56 throw new IllegalNameException(deobfName, "There is already a class with that name");
57 }
58 }
59
60 outerClassMapping.setInnerClassName(obf, deobfName);
61 }
62 }
63
64 public void removeClassMapping(ClassEntry obf) {
65 setClassName(obf, null);
66 }
67
68 public void markClassAsDeobfuscated(ClassEntry obf) {
69 String deobfName = obf.isInnerClass() ? obf.getInnermostClassName() : obf.getName();
70 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obf);
71 if (mappingChain.size() == 1) {
72 ClassMapping classMapping = mappingChain.get(0);
73 m_mappings.setClassDeobfName(classMapping, deobfName);
74 } else {
75 ClassMapping outerClassMapping = mappingChain.get(mappingChain.size() - 2);
76 outerClassMapping.setInnerClassName(obf, deobfName);
77 }
78 }
79
80 public void setFieldName(FieldEntry obf, String deobfName) {
81 deobfName = NameValidator.validateFieldName(deobfName);
82 FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType());
83 if (m_mappings.containsDeobfField(obf.getClassEntry(), deobfName, obf.getType()) || m_index.containsObfField(targetEntry)) {
84 throw new IllegalNameException(deobfName, "There is already a field with that name");
85 }
86
87 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
88 classMapping.setFieldName(obf.getName(), obf.getType(), deobfName);
89 }
90
91 public void removeFieldMapping(FieldEntry obf) {
92 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
93 classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType()));
94 }
95
96 public void markFieldAsDeobfuscated(FieldEntry obf) {
97 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
98 classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName());
99 }
100
101 public void setMethodTreeName(MethodEntry obf, String deobfName) {
102 Set<MethodEntry> implementations = m_index.getRelatedMethodImplementations(obf);
103
104 deobfName = NameValidator.validateMethodName(deobfName);
105 for (MethodEntry entry : implementations) {
106 Signature deobfSignature = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateSignature(obf.getSignature());
107 MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, deobfSignature);
108 if (m_mappings.containsDeobfMethod(entry.getClassEntry(), deobfName, entry.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
109 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(entry.getClassName());
110 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
111 }
112 }
113
114 for (MethodEntry entry : implementations) {
115 setMethodName(entry, deobfName);
116 }
117 }
118
119 public void setMethodName(MethodEntry obf, String deobfName) {
120 deobfName = NameValidator.validateMethodName(deobfName);
121 MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature());
122 if (m_mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) || m_index.containsObfBehavior(targetEntry)) {
123 String deobfClassName = m_mappings.getTranslator(TranslationDirection.Deobfuscating, m_index.getTranslationIndex()).translateClass(obf.getClassName());
124 throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName);
125 }
126
127 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
128 classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName);
129 }
130
131 public void removeMethodTreeMapping(MethodEntry obf) {
132 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
133 removeMethodMapping(implementation);
134 }
135 }
136
137 public void removeMethodMapping(MethodEntry obf) {
138 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
139 classMapping.setMethodName(obf.getName(), obf.getSignature(), null);
140 }
141
142 public void markMethodTreeAsDeobfuscated(MethodEntry obf) {
143 for (MethodEntry implementation : m_index.getRelatedMethodImplementations(obf)) {
144 markMethodAsDeobfuscated(implementation);
145 }
146 }
147
148 public void markMethodAsDeobfuscated(MethodEntry obf) {
149 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
150 classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName());
151 }
152
153 public void setArgumentName(ArgumentEntry obf, String deobfName) {
154 deobfName = NameValidator.validateArgumentName(deobfName);
155 // NOTE: don't need to check arguments for name collisions with names determined by Procyon
156 if (m_mappings.containsArgument(obf.getBehaviorEntry(), deobfName)) {
157 throw new IllegalNameException(deobfName, "There is already an argument with that name");
158 }
159
160 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
161 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName);
162 }
163
164 public void removeArgumentMapping(ArgumentEntry obf) {
165 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
166 classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex());
167 }
168
169 public void markArgumentAsDeobfuscated(ArgumentEntry obf) {
170 ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry());
171 classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName());
172 }
173
174 public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) {
175 classMapping.removeFieldMapping(fieldMapping);
176 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
177 if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) {
178 if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) {
179 targetClassMapping.addFieldMapping(fieldMapping);
180 return true;
181 } else {
182 System.err.println("WARNING: deobf field was already there: " + obfClass + "." + fieldMapping.getDeobfName());
183 }
184 }
185 return false;
186 }
187
188 public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) {
189 classMapping.removeMethodMapping(methodMapping);
190 ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass);
191 if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) {
192 if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) {
193 targetClassMapping.addMethodMapping(methodMapping);
194 return true;
195 } else {
196 System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature());
197 }
198 }
199 return false;
200 }
201
202 public void write(OutputStream out) throws IOException {
203 // TEMP: just use the object output for now. We can find a more efficient storage format later
204 GZIPOutputStream gzipout = new GZIPOutputStream(out);
205 ObjectOutputStream oout = new ObjectOutputStream(gzipout);
206 oout.writeObject(this);
207 gzipout.finish();
208 }
209
210 private ClassMapping getOrCreateClassMapping(ClassEntry obfClassEntry) {
211 List<ClassMapping> mappingChain = getOrCreateClassMappingChain(obfClassEntry);
212 return mappingChain.get(mappingChain.size() - 1);
213 }
214
215 private List<ClassMapping> getOrCreateClassMappingChain(ClassEntry obfClassEntry) {
216 List<ClassEntry> classChain = obfClassEntry.getClassChain();
217 List<ClassMapping> mappingChain = m_mappings.getClassMappingChain(obfClassEntry);
218 for (int i=0; i<classChain.size(); i++) {
219 ClassEntry classEntry = classChain.get(i);
220 ClassMapping classMapping = mappingChain.get(i);
221 if (classMapping == null) {
222
223 // create it
224 classMapping = new ClassMapping(classEntry.getName());
225 mappingChain.set(i, classMapping);
226
227 // add it to the right parent
228 if (i == 0) {
229 m_mappings.addClassMapping(classMapping);
230 } else {
231 mappingChain.get(i-1).addInnerClassMapping(classMapping);
232 }
233 }
234 }
235 return mappingChain;
236 }
237}
diff --git a/src/cuchaz/enigma/mapping/MappingsWriter.java b/src/cuchaz/enigma/mapping/MappingsWriter.java
new file mode 100644
index 0000000..1ebefef
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MappingsWriter.java
@@ -0,0 +1,88 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.io.Writer;
16import java.util.ArrayList;
17import java.util.Collections;
18import java.util.List;
19
20public class MappingsWriter {
21
22 public void write(Writer out, Mappings mappings) throws IOException {
23 write(new PrintWriter(out), mappings);
24 }
25
26 public void write(PrintWriter out, Mappings mappings) throws IOException {
27 for (ClassMapping classMapping : sorted(mappings.classes())) {
28 write(out, classMapping, 0);
29 }
30 }
31
32 private void write(PrintWriter out, ClassMapping classMapping, int depth) throws IOException {
33 if (classMapping.getDeobfName() == null) {
34 out.format("%sCLASS %s\n", getIndent(depth), classMapping.getObfFullName());
35 } else {
36 out.format("%sCLASS %s %s\n", getIndent(depth), classMapping.getObfFullName(), classMapping.getDeobfName());
37 }
38
39 for (ClassMapping innerClassMapping : sorted(classMapping.innerClasses())) {
40 write(out, innerClassMapping, depth + 1);
41 }
42
43 for (FieldMapping fieldMapping : sorted(classMapping.fields())) {
44 write(out, fieldMapping, depth + 1);
45 }
46
47 for (MethodMapping methodMapping : sorted(classMapping.methods())) {
48 write(out, methodMapping, depth + 1);
49 }
50 }
51
52 private void write(PrintWriter out, FieldMapping fieldMapping, int depth) throws IOException {
53 out.format("%sFIELD %s %s %s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString());
54 }
55
56 private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException {
57 if (methodMapping.getDeobfName() == null) {
58 out.format("%sMETHOD %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature());
59 } else {
60 out.format("%sMETHOD %s %s %s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature());
61 }
62
63 for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) {
64 write(out, argumentMapping, depth + 1);
65 }
66 }
67
68 private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) throws IOException {
69 out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName());
70 }
71
72 private <T extends Comparable<T>> List<T> sorted(Iterable<T> classes) {
73 List<T> out = new ArrayList<T>();
74 for (T t : classes) {
75 out.add(t);
76 }
77 Collections.sort(out);
78 return out;
79 }
80
81 private String getIndent(int depth) {
82 StringBuilder buf = new StringBuilder();
83 for (int i = 0; i < depth; i++) {
84 buf.append("\t");
85 }
86 return buf.toString();
87 }
88}
diff --git a/src/cuchaz/enigma/mapping/MemberMapping.java b/src/cuchaz/enigma/mapping/MemberMapping.java
new file mode 100644
index 0000000..8378297
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MemberMapping.java
@@ -0,0 +1,17 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13
14public interface MemberMapping<T extends Entry> {
15 T getObfEntry(ClassEntry classEntry);
16 String getObfName();
17}
diff --git a/src/cuchaz/enigma/mapping/MethodEntry.java b/src/cuchaz/enigma/mapping/MethodEntry.java
new file mode 100644
index 0000000..eb9e204
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodEntry.java
@@ -0,0 +1,104 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14
15import cuchaz.enigma.Util;
16
17public class MethodEntry implements BehaviorEntry, Serializable {
18
19 private static final long serialVersionUID = 4770915224467247458L;
20
21 private ClassEntry m_classEntry;
22 private String m_name;
23 private Signature m_signature;
24
25 public MethodEntry(ClassEntry classEntry, String name, Signature signature) {
26 if (classEntry == null) {
27 throw new IllegalArgumentException("Class cannot be null!");
28 }
29 if (name == null) {
30 throw new IllegalArgumentException("Method name cannot be null!");
31 }
32 if (signature == null) {
33 throw new IllegalArgumentException("Method signature cannot be null!");
34 }
35 if (name.startsWith("<")) {
36 throw new IllegalArgumentException("Don't use MethodEntry for a constructor!");
37 }
38
39 m_classEntry = classEntry;
40 m_name = name;
41 m_signature = signature;
42 }
43
44 public MethodEntry(MethodEntry other) {
45 m_classEntry = new ClassEntry(other.m_classEntry);
46 m_name = other.m_name;
47 m_signature = other.m_signature;
48 }
49
50 public MethodEntry(MethodEntry other, String newClassName) {
51 m_classEntry = new ClassEntry(newClassName);
52 m_name = other.m_name;
53 m_signature = other.m_signature;
54 }
55
56 @Override
57 public ClassEntry getClassEntry() {
58 return m_classEntry;
59 }
60
61 @Override
62 public String getName() {
63 return m_name;
64 }
65
66 @Override
67 public Signature getSignature() {
68 return m_signature;
69 }
70
71 @Override
72 public String getClassName() {
73 return m_classEntry.getName();
74 }
75
76 @Override
77 public MethodEntry cloneToNewClass(ClassEntry classEntry) {
78 return new MethodEntry(this, classEntry.getName());
79 }
80
81 @Override
82 public int hashCode() {
83 return Util.combineHashesOrdered(m_classEntry, m_name, m_signature);
84 }
85
86 @Override
87 public boolean equals(Object other) {
88 if (other instanceof MethodEntry) {
89 return equals((MethodEntry)other);
90 }
91 return false;
92 }
93
94 public boolean equals(MethodEntry other) {
95 return m_classEntry.equals(other.m_classEntry)
96 && m_name.equals(other.m_name)
97 && m_signature.equals(other.m_signature);
98 }
99
100 @Override
101 public String toString() {
102 return m_classEntry.getName() + "." + m_name + m_signature;
103 }
104}
diff --git a/src/cuchaz/enigma/mapping/MethodMapping.java b/src/cuchaz/enigma/mapping/MethodMapping.java
new file mode 100644
index 0000000..055e1fe
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/MethodMapping.java
@@ -0,0 +1,191 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15import java.util.Map.Entry;
16
17import com.google.common.collect.Maps;
18
19public class MethodMapping implements Serializable, Comparable<MethodMapping>, MemberMapping<BehaviorEntry> {
20
21 private static final long serialVersionUID = -4409570216084263978L;
22
23 private String m_obfName;
24 private String m_deobfName;
25 private Signature m_obfSignature;
26 private Map<Integer,ArgumentMapping> m_arguments;
27
28 public MethodMapping(String obfName, Signature obfSignature) {
29 this(obfName, obfSignature, null);
30 }
31
32 public MethodMapping(String obfName, Signature obfSignature, String deobfName) {
33 if (obfName == null) {
34 throw new IllegalArgumentException("obf name cannot be null!");
35 }
36 if (obfSignature == null) {
37 throw new IllegalArgumentException("obf signature cannot be null!");
38 }
39 m_obfName = obfName;
40 m_deobfName = NameValidator.validateMethodName(deobfName);
41 m_obfSignature = obfSignature;
42 m_arguments = Maps.newTreeMap();
43 }
44
45 public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) {
46 m_obfName = other.m_obfName;
47 m_deobfName = other.m_deobfName;
48 m_obfSignature = new Signature(other.m_obfSignature, obfClassNameReplacer);
49 m_arguments = Maps.newTreeMap();
50 for (Entry<Integer,ArgumentMapping> entry : other.m_arguments.entrySet()) {
51 m_arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue()));
52 }
53 }
54
55 @Override
56 public String getObfName() {
57 return m_obfName;
58 }
59
60 public void setObfName(String val) {
61 m_obfName = NameValidator.validateMethodName(val);
62 }
63
64 public String getDeobfName() {
65 return m_deobfName;
66 }
67
68 public void setDeobfName(String val) {
69 m_deobfName = NameValidator.validateMethodName(val);
70 }
71
72 public Signature getObfSignature() {
73 return m_obfSignature;
74 }
75
76 public void setObfSignature(Signature val) {
77 m_obfSignature = val;
78 }
79
80 public Iterable<ArgumentMapping> arguments() {
81 return m_arguments.values();
82 }
83
84 public boolean isConstructor() {
85 return m_obfName.startsWith("<");
86 }
87
88 public void addArgumentMapping(ArgumentMapping argumentMapping) {
89 boolean wasAdded = m_arguments.put(argumentMapping.getIndex(), argumentMapping) == null;
90 assert (wasAdded);
91 }
92
93 public String getObfArgumentName(int index) {
94 ArgumentMapping argumentMapping = m_arguments.get(index);
95 if (argumentMapping != null) {
96 return argumentMapping.getName();
97 }
98
99 return null;
100 }
101
102 public String getDeobfArgumentName(int index) {
103 ArgumentMapping argumentMapping = m_arguments.get(index);
104 if (argumentMapping != null) {
105 return argumentMapping.getName();
106 }
107
108 return null;
109 }
110
111 public void setArgumentName(int index, String name) {
112 ArgumentMapping argumentMapping = m_arguments.get(index);
113 if (argumentMapping == null) {
114 argumentMapping = new ArgumentMapping(index, name);
115 boolean wasAdded = m_arguments.put(index, argumentMapping) == null;
116 assert (wasAdded);
117 } else {
118 argumentMapping.setName(name);
119 }
120 }
121
122 public void removeArgumentName(int index) {
123 boolean wasRemoved = m_arguments.remove(index) != null;
124 assert (wasRemoved);
125 }
126
127 @Override
128 public String toString() {
129 StringBuilder buf = new StringBuilder();
130 buf.append("\t");
131 buf.append(m_obfName);
132 buf.append(" <-> ");
133 buf.append(m_deobfName);
134 buf.append("\n");
135 buf.append("\t");
136 buf.append(m_obfSignature);
137 buf.append("\n");
138 buf.append("\tArguments:\n");
139 for (ArgumentMapping argumentMapping : m_arguments.values()) {
140 buf.append("\t\t");
141 buf.append(argumentMapping.getIndex());
142 buf.append(" -> ");
143 buf.append(argumentMapping.getName());
144 buf.append("\n");
145 }
146 return buf.toString();
147 }
148
149 @Override
150 public int compareTo(MethodMapping other) {
151 return (m_obfName + m_obfSignature).compareTo(other.m_obfName + other.m_obfSignature);
152 }
153
154 public boolean renameObfClass(final String oldObfClassName, final String newObfClassName) {
155
156 // rename obf classes in the signature
157 Signature newSignature = new Signature(m_obfSignature, new ClassNameReplacer() {
158 @Override
159 public String replace(String className) {
160 if (className.equals(oldObfClassName)) {
161 return newObfClassName;
162 }
163 return null;
164 }
165 });
166
167 if (!newSignature.equals(m_obfSignature)) {
168 m_obfSignature = newSignature;
169 return true;
170 }
171 return false;
172 }
173
174 public boolean containsArgument(String name) {
175 for (ArgumentMapping argumentMapping : m_arguments.values()) {
176 if (argumentMapping.getName().equals(name)) {
177 return true;
178 }
179 }
180 return false;
181 }
182
183 @Override
184 public BehaviorEntry getObfEntry(ClassEntry classEntry) {
185 if (isConstructor()) {
186 return new ConstructorEntry(classEntry, m_obfSignature);
187 } else {
188 return new MethodEntry(classEntry, m_obfName, m_obfSignature);
189 }
190 }
191}
diff --git a/src/cuchaz/enigma/mapping/NameValidator.java b/src/cuchaz/enigma/mapping/NameValidator.java
new file mode 100644
index 0000000..12520e1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/NameValidator.java
@@ -0,0 +1,80 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.Arrays;
14import java.util.List;
15import java.util.regex.Pattern;
16
17import javassist.bytecode.Descriptor;
18
19public class NameValidator {
20
21 private static final Pattern IdentifierPattern;
22 private static final Pattern ClassPattern;
23 private static final List<String> ReservedWords = Arrays.asList(
24 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
25 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
26 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
27 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
28 "long", "strictfp", "volatile", "const", "float", "native", "super", "while"
29 );
30
31 static {
32
33 // java allows all kinds of weird characters...
34 StringBuilder startChars = new StringBuilder();
35 StringBuilder partChars = new StringBuilder();
36 for (int i = Character.MIN_CODE_POINT; i <= Character.MAX_CODE_POINT; i++) {
37 if (Character.isJavaIdentifierStart(i)) {
38 startChars.appendCodePoint(i);
39 }
40 if (Character.isJavaIdentifierPart(i)) {
41 partChars.appendCodePoint(i);
42 }
43 }
44
45 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
46 IdentifierPattern = Pattern.compile(identifierRegex);
47 ClassPattern = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
48 }
49
50 public static String validateClassName(String name, boolean packageRequired) {
51 if (name == null) {
52 return null;
53 }
54 if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) {
55 throw new IllegalNameException(name, "This doesn't look like a legal class name");
56 }
57 if (packageRequired && new ClassEntry(name).getPackageName() == null) {
58 throw new IllegalNameException(name, "Class must be in a package");
59 }
60 return Descriptor.toJvmName(name);
61 }
62
63 public static String validateFieldName(String name) {
64 if (name == null) {
65 return null;
66 }
67 if (!IdentifierPattern.matcher(name).matches() || ReservedWords.contains(name)) {
68 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
69 }
70 return name;
71 }
72
73 public static String validateMethodName(String name) {
74 return validateFieldName(name);
75 }
76
77 public static String validateArgumentName(String name) {
78 return validateFieldName(name);
79 }
80}
diff --git a/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
new file mode 100644
index 0000000..777a12e
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/ProcyonEntryFactory.java
@@ -0,0 +1,55 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import com.strobel.assembler.metadata.FieldDefinition;
14import com.strobel.assembler.metadata.MethodDefinition;
15
16
17public class ProcyonEntryFactory {
18
19 public static FieldEntry getFieldEntry(FieldDefinition def) {
20 return new FieldEntry(
21 new ClassEntry(def.getDeclaringType().getInternalName()),
22 def.getName(),
23 new Type(def.getErasedSignature())
24 );
25 }
26
27 public static MethodEntry getMethodEntry(MethodDefinition def) {
28 return new MethodEntry(
29 new ClassEntry(def.getDeclaringType().getInternalName()),
30 def.getName(),
31 new Signature(def.getErasedSignature())
32 );
33 }
34
35 public static ConstructorEntry getConstructorEntry(MethodDefinition def) {
36 if (def.isTypeInitializer()) {
37 return new ConstructorEntry(
38 new ClassEntry(def.getDeclaringType().getInternalName())
39 );
40 } else {
41 return new ConstructorEntry(
42 new ClassEntry(def.getDeclaringType().getInternalName()),
43 new Signature(def.getErasedSignature())
44 );
45 }
46 }
47
48 public static BehaviorEntry getBehaviorEntry(MethodDefinition def) {
49 if (def.isConstructor() || def.isTypeInitializer()) {
50 return getConstructorEntry(def);
51 } else {
52 return getMethodEntry(def);
53 }
54 }
55}
diff --git a/src/cuchaz/enigma/mapping/Signature.java b/src/cuchaz/enigma/mapping/Signature.java
new file mode 100644
index 0000000..8f2b6b2
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Signature.java
@@ -0,0 +1,117 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.List;
15
16import com.google.common.collect.Lists;
17
18import cuchaz.enigma.Util;
19
20public class Signature implements Serializable {
21
22 private static final long serialVersionUID = -5843719505729497539L;
23
24 private List<Type> m_argumentTypes;
25 private Type m_returnType;
26
27 public Signature(String signature) {
28 try {
29 m_argumentTypes = Lists.newArrayList();
30 int i=0;
31 while (i<signature.length()) {
32 char c = signature.charAt(i);
33 if (c == '(') {
34 assert(m_argumentTypes.isEmpty());
35 assert(m_returnType == null);
36 i++;
37 } else if (c == ')') {
38 i++;
39 break;
40 } else {
41 String type = Type.parseFirst(signature.substring(i));
42 m_argumentTypes.add(new Type(type));
43 i += type.length();
44 }
45 }
46 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
47 } catch (Exception ex) {
48 throw new IllegalArgumentException("Unable to parse signature: " + signature, ex);
49 }
50 }
51
52 public Signature(Signature other) {
53 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
54 m_returnType = new Type(other.m_returnType);
55 }
56
57 public Signature(Signature other, ClassNameReplacer replacer) {
58 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
59 for (int i=0; i<m_argumentTypes.size(); i++) {
60 m_argumentTypes.set(i, new Type(m_argumentTypes.get(i), replacer));
61 }
62 m_returnType = new Type(other.m_returnType, replacer);
63 }
64
65 public List<Type> getArgumentTypes() {
66 return m_argumentTypes;
67 }
68
69 public Type getReturnType() {
70 return m_returnType;
71 }
72
73 @Override
74 public String toString() {
75 StringBuilder buf = new StringBuilder();
76 buf.append("(");
77 for (Type type : m_argumentTypes) {
78 buf.append(type.toString());
79 }
80 buf.append(")");
81 buf.append(m_returnType.toString());
82 return buf.toString();
83 }
84
85 public Iterable<Type> types() {
86 List<Type> types = Lists.newArrayList();
87 types.addAll(m_argumentTypes);
88 types.add(m_returnType);
89 return types;
90 }
91
92 @Override
93 public boolean equals(Object other) {
94 if (other instanceof Signature) {
95 return equals((Signature)other);
96 }
97 return false;
98 }
99
100 public boolean equals(Signature other) {
101 return m_argumentTypes.equals(other.m_argumentTypes) && m_returnType.equals(other.m_returnType);
102 }
103
104 @Override
105 public int hashCode() {
106 return Util.combineHashesOrdered(m_argumentTypes.hashCode(), m_returnType.hashCode());
107 }
108
109 public boolean hasClass(ClassEntry classEntry) {
110 for (Type type : types()) {
111 if (type.hasClass() && type.getClassEntry().equals(classEntry)) {
112 return true;
113 }
114 }
115 return false;
116 }
117}
diff --git a/src/cuchaz/enigma/mapping/SignatureUpdater.java b/src/cuchaz/enigma/mapping/SignatureUpdater.java
new file mode 100644
index 0000000..eb53233
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/SignatureUpdater.java
@@ -0,0 +1,94 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.IOException;
14import java.io.StringReader;
15import java.util.List;
16
17import com.google.common.collect.Lists;
18
19public class SignatureUpdater {
20
21 public interface ClassNameUpdater {
22 String update(String className);
23 }
24
25 public static String update(String signature, ClassNameUpdater updater) {
26 try {
27 StringBuilder buf = new StringBuilder();
28
29 // read the signature character-by-character
30 StringReader reader = new StringReader(signature);
31 int i = -1;
32 while ( (i = reader.read()) != -1) {
33 char c = (char)i;
34
35 // does this character start a class name?
36 if (c == 'L') {
37 // update the class name and add it to the buffer
38 buf.append('L');
39 String className = readClass(reader);
40 if (className == null) {
41 throw new IllegalArgumentException("Malformed signature: " + signature);
42 }
43 buf.append(updater.update(className));
44 buf.append(';');
45 } else {
46 // copy the character into the buffer
47 buf.append(c);
48 }
49 }
50
51 return buf.toString();
52 } catch (IOException ex) {
53 // I'm pretty sure a StringReader will never throw one of these
54 throw new Error(ex);
55 }
56 }
57
58 private static String readClass(StringReader reader) throws IOException {
59 // read all the characters in the buffer until we hit a ';'
60 // remember to treat generics correctly
61 StringBuilder buf = new StringBuilder();
62 int depth = 0;
63 int i = -1;
64 while ( (i = reader.read()) != -1) {
65 char c = (char)i;
66
67 if (c == '<') {
68 depth++;
69 } else if (c == '>') {
70 depth--;
71 } else if (depth == 0) {
72 if (c == ';') {
73 return buf.toString();
74 } else {
75 buf.append(c);
76 }
77 }
78 }
79
80 return null;
81 }
82
83 public static List<String> getClasses(String signature) {
84 final List<String> classNames = Lists.newArrayList();
85 update(signature, new ClassNameUpdater() {
86 @Override
87 public String update(String className) {
88 classNames.add(className);
89 return className;
90 }
91 });
92 return classNames;
93 }
94}
diff --git a/src/cuchaz/enigma/mapping/TranslationDirection.java b/src/cuchaz/enigma/mapping/TranslationDirection.java
new file mode 100644
index 0000000..bc3aaa1
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/TranslationDirection.java
@@ -0,0 +1,29 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13public enum TranslationDirection {
14
15 Deobfuscating {
16 @Override
17 public <T> T choose(T deobfChoice, T obfChoice) {
18 return deobfChoice;
19 }
20 },
21 Obfuscating {
22 @Override
23 public <T> T choose(T deobfChoice, T obfChoice) {
24 return obfChoice;
25 }
26 };
27
28 public abstract <T> T choose(T deobfChoice, T obfChoice);
29}
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
new file mode 100644
index 0000000..41c7d7c
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -0,0 +1,289 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.util.List;
14import java.util.Map;
15
16import com.google.common.collect.Lists;
17import com.google.common.collect.Maps;
18
19import cuchaz.enigma.analysis.TranslationIndex;
20
21public class Translator {
22
23 private TranslationDirection m_direction;
24 private Map<String,ClassMapping> m_classes;
25 private TranslationIndex m_index;
26
27 private ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
28 @Override
29 public String replace(String className) {
30 return translateEntry(new ClassEntry(className)).getName();
31 }
32 };
33
34 public Translator() {
35 m_direction = null;
36 m_classes = Maps.newHashMap();
37 m_index = new TranslationIndex();
38 }
39
40 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {
41 m_direction = direction;
42 m_classes = classes;
43 m_index = index;
44 }
45
46 public TranslationDirection getDirection() {
47 return m_direction;
48 }
49
50 public TranslationIndex getTranslationIndex() {
51 return m_index;
52 }
53
54 @SuppressWarnings("unchecked")
55 public <T extends Entry> T translateEntry(T entry) {
56 if (entry instanceof ClassEntry) {
57 return (T)translateEntry((ClassEntry)entry);
58 } else if (entry instanceof FieldEntry) {
59 return (T)translateEntry((FieldEntry)entry);
60 } else if (entry instanceof MethodEntry) {
61 return (T)translateEntry((MethodEntry)entry);
62 } else if (entry instanceof ConstructorEntry) {
63 return (T)translateEntry((ConstructorEntry)entry);
64 } else if (entry instanceof ArgumentEntry) {
65 return (T)translateEntry((ArgumentEntry)entry);
66 } else {
67 throw new Error("Unknown entry type: " + entry.getClass().getName());
68 }
69 }
70
71 public <T extends Entry> String translate(T entry) {
72 if (entry instanceof ClassEntry) {
73 return translate((ClassEntry)entry);
74 } else if (entry instanceof FieldEntry) {
75 return translate((FieldEntry)entry);
76 } else if (entry instanceof MethodEntry) {
77 return translate((MethodEntry)entry);
78 } else if (entry instanceof ConstructorEntry) {
79 return translate((ConstructorEntry)entry);
80 } else if (entry instanceof ArgumentEntry) {
81 return translate((ArgumentEntry)entry);
82 } else {
83 throw new Error("Unknown entry type: " + entry.getClass().getName());
84 }
85 }
86
87 public String translate(ClassEntry in) {
88 ClassEntry translated = translateEntry(in);
89 if (translated.equals(in)) {
90 return null;
91 }
92 return translated.getName();
93 }
94
95 public String translateClass(String className) {
96 return translate(new ClassEntry(className));
97 }
98
99 public ClassEntry translateEntry(ClassEntry in) {
100
101 if (in.isInnerClass()) {
102
103 // translate as much of the class chain as we can
104 List<ClassMapping> mappingsChain = getClassMappingChain(in);
105 String[] obfClassNames = in.getName().split("\\$");
106 StringBuilder buf = new StringBuilder();
107 for (int i=0; i<obfClassNames.length; i++) {
108 boolean isFirstClass = buf.length() == 0;
109 String className = null;
110 ClassMapping classMapping = mappingsChain.get(i);
111 if (classMapping != null) {
112 className = m_direction.choose(
113 classMapping.getDeobfName(),
114 isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName()
115 );
116 }
117 if (className == null) {
118 className = obfClassNames[i];
119 }
120 if (!isFirstClass) {
121 buf.append("$");
122 }
123 buf.append(className);
124 }
125 return new ClassEntry(buf.toString());
126
127 } else {
128
129 // normal classes are easy
130 ClassMapping classMapping = m_classes.get(in.getName());
131 if (classMapping == null) {
132 return in;
133 }
134 return m_direction.choose(
135 classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in,
136 new ClassEntry(classMapping.getObfFullName())
137 );
138 }
139 }
140
141 public String translate(FieldEntry in) {
142
143 // resolve the class entry
144 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
145 if (resolvedClassEntry != null) {
146
147 // look for the class
148 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
149 if (classMapping != null) {
150
151 // look for the field
152 String translatedName = m_direction.choose(
153 classMapping.getDeobfFieldName(in.getName(), in.getType()),
154 classMapping.getObfFieldName(in.getName(), translateType(in.getType()))
155 );
156 if (translatedName != null) {
157 return translatedName;
158 }
159 }
160 }
161 return null;
162 }
163
164 public FieldEntry translateEntry(FieldEntry in) {
165 String name = translate(in);
166 if (name == null) {
167 name = in.getName();
168 }
169 return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType()));
170 }
171
172 public String translate(MethodEntry in) {
173
174 // resolve the class entry
175 ClassEntry resolvedClassEntry = m_index.resolveEntryClass(in);
176 if (resolvedClassEntry != null) {
177
178 // look for class
179 ClassMapping classMapping = findClassMapping(resolvedClassEntry);
180 if (classMapping != null) {
181
182 // look for the method
183 MethodMapping methodMapping = m_direction.choose(
184 classMapping.getMethodByObf(in.getName(), in.getSignature()),
185 classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature()))
186 );
187 if (methodMapping != null) {
188 return m_direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName());
189 }
190 }
191 }
192 return null;
193 }
194
195 public MethodEntry translateEntry(MethodEntry in) {
196 String name = translate(in);
197 if (name == null) {
198 name = in.getName();
199 }
200 return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature()));
201 }
202
203 public ConstructorEntry translateEntry(ConstructorEntry in) {
204 if (in.isStatic()) {
205 return new ConstructorEntry(translateEntry(in.getClassEntry()));
206 } else {
207 return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature()));
208 }
209 }
210
211 public BehaviorEntry translateEntry(BehaviorEntry in) {
212 if (in instanceof MethodEntry) {
213 return translateEntry((MethodEntry)in);
214 } else if (in instanceof ConstructorEntry) {
215 return translateEntry((ConstructorEntry)in);
216 }
217 throw new Error("Wrong entry type!");
218 }
219
220 public String translate(ArgumentEntry in) {
221
222 // look for the class
223 ClassMapping classMapping = findClassMapping(in.getClassEntry());
224 if (classMapping != null) {
225
226 // look for the method
227 MethodMapping methodMapping = m_direction.choose(
228 classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()),
229 classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature()))
230 );
231 if (methodMapping != null) {
232 return m_direction.choose(
233 methodMapping.getDeobfArgumentName(in.getIndex()),
234 methodMapping.getObfArgumentName(in.getIndex())
235 );
236 }
237 }
238 return null;
239 }
240
241 public ArgumentEntry translateEntry(ArgumentEntry in) {
242 String name = translate(in);
243 if (name == null) {
244 name = in.getName();
245 }
246 return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name);
247 }
248
249 public Type translateType(Type type) {
250 return new Type(type, m_classNameReplacer);
251 }
252
253 public Signature translateSignature(Signature signature) {
254 return new Signature(signature, m_classNameReplacer);
255 }
256
257 private ClassMapping findClassMapping(ClassEntry in) {
258 List<ClassMapping> mappingChain = getClassMappingChain(in);
259 return mappingChain.get(mappingChain.size() - 1);
260 }
261
262 private List<ClassMapping> getClassMappingChain(ClassEntry in) {
263
264 // get a list of all the classes in the hierarchy
265 String[] parts = in.getName().split("\\$");
266 List<ClassMapping> mappingsChain = Lists.newArrayList();
267
268 // get mappings for the outer class
269 ClassMapping outerClassMapping = m_classes.get(parts[0]);
270 mappingsChain.add(outerClassMapping);
271
272 for (int i=1; i<parts.length; i++) {
273
274 // get mappings for the inner class
275 ClassMapping innerClassMapping = null;
276 if (outerClassMapping != null) {
277 innerClassMapping = m_direction.choose(
278 outerClassMapping.getInnerClassByObfSimple(parts[i]),
279 outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i])
280 );
281 }
282 mappingsChain.add(innerClassMapping);
283 outerClassMapping = innerClassMapping;
284 }
285
286 assert(mappingsChain.size() == parts.length);
287 return mappingsChain;
288 }
289}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 0000000..f86a5cc
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,247 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11package cuchaz.enigma.mapping;
12
13import java.io.Serializable;
14import java.util.Map;
15
16import com.google.common.collect.Maps;
17
18public class Type implements Serializable {
19
20 private static final long serialVersionUID = 7862257669347104063L;
21
22 public enum Primitive {
23 Byte('B'),
24 Character('C'),
25 Short('S'),
26 Integer('I'),
27 Long('J'),
28 Float('F'),
29 Double('D'),
30 Boolean('Z');
31
32 private static final Map<Character,Primitive> m_lookup;
33
34 static {
35 m_lookup = Maps.newTreeMap();
36 for (Primitive val : values()) {
37 m_lookup.put(val.getCode(), val);
38 }
39 }
40
41 public static Primitive get(char code) {
42 return m_lookup.get(code);
43 }
44
45 private char m_code;
46
47 private Primitive(char code) {
48 m_code = code;
49 }
50
51 public char getCode() {
52 return m_code;
53 }
54 }
55
56 public static String parseFirst(String in) {
57
58 if (in == null || in.length() <= 0) {
59 throw new IllegalArgumentException("No type to parse, input is empty!");
60 }
61
62 // read one type from the input
63
64 char c = in.charAt(0);
65
66 // first check for void
67 if (c == 'V') {
68 return "V";
69 }
70
71 // then check for primitives
72 Primitive primitive = Primitive.get(c);
73 if (primitive != null) {
74 return in.substring(0, 1);
75 }
76
77 // then check for classes
78 if (c == 'L') {
79 return readClass(in);
80 }
81
82 // then check for templates
83 if (c == 'T') {
84 return readClass(in);
85 }
86
87 // then check for arrays
88 int dim = countArrayDimension(in);
89 if (dim > 0) {
90 String arrayType = Type.parseFirst(in.substring(dim));
91 return in.substring(0, dim + arrayType.length());
92 }
93
94 throw new IllegalArgumentException("don't know how to parse: " + in);
95 }
96
97 protected String m_name;
98
99 public Type(String name) {
100
101 // don't deal with generics
102 // this is just for raw jvm types
103 if (name.charAt(0) == 'T' || name.indexOf('<') >= 0 || name.indexOf('>') >= 0) {
104 throw new IllegalArgumentException("don't use with generic types or templates: " + name);
105 }
106
107 m_name = name;
108 }
109
110 public Type(Type other) {
111 m_name = other.m_name;
112 }
113
114 public Type(ClassEntry classEntry) {
115 m_name = "L" + classEntry.getClassName() + ";";
116 }
117
118 public Type(Type other, ClassNameReplacer replacer) {
119 m_name = other.m_name;
120 if (other.isClass()) {
121 String replacedName = replacer.replace(other.getClassEntry().getClassName());
122 if (replacedName != null) {
123 m_name = "L" + replacedName + ";";
124 }
125 } else if (other.isArray() && other.hasClass()) {
126 String replacedName = replacer.replace(other.getClassEntry().getClassName());
127 if (replacedName != null) {
128 m_name = Type.getArrayPrefix(other.getArrayDimension()) + "L" + replacedName + ";";
129 }
130 }
131 }
132
133 @Override
134 public String toString() {
135 return m_name;
136 }
137
138 public boolean isVoid() {
139 return m_name.length() == 1 && m_name.charAt(0) == 'V';
140 }
141
142 public boolean isPrimitive() {
143 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
144 }
145
146 public Primitive getPrimitive() {
147 if (!isPrimitive()) {
148 throw new IllegalStateException("not a primitive");
149 }
150 return Primitive.get(m_name.charAt(0));
151 }
152
153 public boolean isClass() {
154 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
155 }
156
157 public ClassEntry getClassEntry() {
158 if (isClass()) {
159 String name = m_name.substring(1, m_name.length() - 1);
160
161 int pos = name.indexOf('<');
162 if (pos >= 0) {
163 // remove the parameters from the class name
164 name = name.substring(0, pos);
165 }
166
167 return new ClassEntry(name);
168
169 } else if (isArray() && getArrayType().isClass()) {
170 return getArrayType().getClassEntry();
171 } else {
172 throw new IllegalStateException("type doesn't have a class");
173 }
174 }
175
176 public boolean isArray() {
177 return m_name.charAt(0) == '[';
178 }
179
180 public int getArrayDimension() {
181 if (!isArray()) {
182 throw new IllegalStateException("not an array");
183 }
184 return countArrayDimension(m_name);
185 }
186
187 public Type getArrayType() {
188 if (!isArray()) {
189 throw new IllegalStateException("not an array");
190 }
191 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
192 }
193
194 private static String getArrayPrefix(int dimension) {
195 StringBuilder buf = new StringBuilder();
196 for (int i=0; i<dimension; i++) {
197 buf.append("[");
198 }
199 return buf.toString();
200 }
201
202 public boolean hasClass() {
203 return isClass() || (isArray() && getArrayType().hasClass());
204 }
205
206 @Override
207 public boolean equals(Object other) {
208 if (other instanceof Type) {
209 return equals((Type)other);
210 }
211 return false;
212 }
213
214 public boolean equals(Type other) {
215 return m_name.equals(other.m_name);
216 }
217
218 public int hashCode() {
219 return m_name.hashCode();
220 }
221
222 private static int countArrayDimension(String in) {
223 int i=0;
224 for(; i < in.length() && in.charAt(i) == '['; i++);
225 return i;
226 }
227
228 private static String readClass(String in) {
229 // read all the characters in the buffer until we hit a ';'
230 // include the parameters too
231 StringBuilder buf = new StringBuilder();
232 int depth = 0;
233 for (int i=0; i<in.length(); i++) {
234 char c = in.charAt(i);
235 buf.append(c);
236
237 if (c == '<') {
238 depth++;
239 } else if (c == '>') {
240 depth--;
241 } else if (depth == 0 && c == ';') {
242 return buf.toString();
243 }
244 }
245 return null;
246 }
247}