summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/convert
diff options
context:
space:
mode:
authorGravatar Thog2017-03-08 08:17:04 +0100
committerGravatar Thog2017-03-08 08:17:04 +0100
commit6e464ea251cab63c776ece0b2a356f1498ffa294 (patch)
tree5ed30c03f5ac4cd2d6877874f5ede576049954f7 /src/main/java/cuchaz/enigma/convert
parentDrop unix case style and implement hashCode when equals is overrided (diff)
downloadenigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.tar.gz
enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.tar.xz
enigma-fork-6e464ea251cab63c776ece0b2a356f1498ffa294.zip
Follow Fabric guidelines
Diffstat (limited to 'src/main/java/cuchaz/enigma/convert')
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassForest.java79
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentifier.java61
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassIdentity.java821
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatch.java106
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatches.java273
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassMatching.java256
-rw-r--r--src/main/java/cuchaz/enigma/convert/ClassNamer.java71
-rw-r--r--src/main/java/cuchaz/enigma/convert/FieldMatches.java261
-rw-r--r--src/main/java/cuchaz/enigma/convert/MappingsConverter.java1363
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesReader.java157
-rw-r--r--src/main/java/cuchaz/enigma/convert/MatchesWriter.java185
-rw-r--r--src/main/java/cuchaz/enigma/convert/MemberMatches.java315
12 files changed, 1968 insertions, 1980 deletions
diff --git a/src/main/java/cuchaz/enigma/convert/ClassForest.java b/src/main/java/cuchaz/enigma/convert/ClassForest.java
index b08d48f..4542fb3 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassForest.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassForest.java
@@ -8,53 +8,52 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.HashMultimap; 14import com.google.common.collect.HashMultimap;
14import com.google.common.collect.Multimap; 15import com.google.common.collect.Multimap;
15
16import java.util.Collection;
17
18import cuchaz.enigma.mapping.ClassEntry; 16import cuchaz.enigma.mapping.ClassEntry;
19 17
18import java.util.Collection;
20 19
21public class ClassForest { 20public class ClassForest {
22 21
23 private ClassIdentifier identifier; 22 private ClassIdentifier identifier;
24 private Multimap<ClassIdentity, ClassEntry> forest; 23 private Multimap<ClassIdentity, ClassEntry> forest;
25 24
26 public ClassForest(ClassIdentifier identifier) { 25 public ClassForest(ClassIdentifier identifier) {
27 this.identifier = identifier; 26 this.identifier = identifier;
28 this.forest = HashMultimap.create(); 27 this.forest = HashMultimap.create();
29 } 28 }
30 29
31 public void addAll(Iterable<ClassEntry> entries) { 30 public void addAll(Iterable<ClassEntry> entries) {
32 for (ClassEntry entry : entries) { 31 for (ClassEntry entry : entries) {
33 add(entry); 32 add(entry);
34 } 33 }
35 } 34 }
36 35
37 public void add(ClassEntry entry) { 36 public void add(ClassEntry entry) {
38 try { 37 try {
39 this.forest.put(this.identifier.identify(entry), entry); 38 this.forest.put(this.identifier.identify(entry), entry);
40 } catch (ClassNotFoundException ex) { 39 } catch (ClassNotFoundException ex) {
41 throw new Error("Unable to find class " + entry.getName()); 40 throw new Error("Unable to find class " + entry.getName());
42 } 41 }
43 } 42 }
44 43
45 public Collection<ClassIdentity> identities() { 44 public Collection<ClassIdentity> identities() {
46 return this.forest.keySet(); 45 return this.forest.keySet();
47 } 46 }
48 47
49 public Collection<ClassEntry> classes() { 48 public Collection<ClassEntry> classes() {
50 return this.forest.values(); 49 return this.forest.values();
51 } 50 }
52 51
53 public Collection<ClassEntry> getClasses(ClassIdentity identity) { 52 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
54 return this.forest.get(identity); 53 return this.forest.get(identity);
55 } 54 }
56 55
57 public boolean containsIdentity(ClassIdentity identity) { 56 public boolean containsIdentity(ClassIdentity identity) {
58 return this.forest.containsKey(identity); 57 return this.forest.containsKey(identity);
59 } 58 }
60} 59}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
index 557e608..0a72073 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentifier.java
@@ -8,13 +8,10 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.Maps; 14import com.google.common.collect.Maps;
14
15import java.util.Map;
16import java.util.jar.JarFile;
17
18import cuchaz.enigma.TranslatingTypeLoader; 15import cuchaz.enigma.TranslatingTypeLoader;
19import cuchaz.enigma.analysis.JarIndex; 16import cuchaz.enigma.analysis.JarIndex;
20import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; 17import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
@@ -22,34 +19,36 @@ import cuchaz.enigma.mapping.ClassEntry;
22import cuchaz.enigma.mapping.Translator; 19import cuchaz.enigma.mapping.Translator;
23import javassist.CtClass; 20import javassist.CtClass;
24 21
22import java.util.Map;
23import java.util.jar.JarFile;
25 24
26public class ClassIdentifier { 25public class ClassIdentifier {
27 26
28 private JarIndex index; 27 private JarIndex index;
29 private SidedClassNamer namer; 28 private SidedClassNamer namer;
30 private boolean useReferences; 29 private boolean useReferences;
31 private TranslatingTypeLoader loader; 30 private TranslatingTypeLoader loader;
32 private Map<ClassEntry, ClassIdentity> cache; 31 private Map<ClassEntry, ClassIdentity> cache;
33 32
34 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) { 33 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
35 this.index = index; 34 this.index = index;
36 this.namer = namer; 35 this.namer = namer;
37 this.useReferences = useReferences; 36 this.useReferences = useReferences;
38 this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator()); 37 this.loader = new TranslatingTypeLoader(jar, index, new Translator(), new Translator());
39 this.cache = Maps.newHashMap(); 38 this.cache = Maps.newHashMap();
40 } 39 }
41 40
42 public ClassIdentity identify(ClassEntry classEntry) 41 public ClassIdentity identify(ClassEntry classEntry)
43 throws ClassNotFoundException { 42 throws ClassNotFoundException {
44 ClassIdentity identity = this.cache.get(classEntry); 43 ClassIdentity identity = this.cache.get(classEntry);
45 if (identity == null) { 44 if (identity == null) {
46 CtClass c = this.loader.loadClass(classEntry.getName()); 45 CtClass c = this.loader.loadClass(classEntry.getName());
47 if (c == null) { 46 if (c == null) {
48 throw new ClassNotFoundException(classEntry.getName()); 47 throw new ClassNotFoundException(classEntry.getName());
49 } 48 }
50 identity = new ClassIdentity(c, this.namer, this.index, this.useReferences); 49 identity = new ClassIdentity(c, this.namer, this.index, this.useReferences);
51 this.cache.put(classEntry, identity); 50 this.cache.put(classEntry, identity);
52 } 51 }
53 return identity; 52 return identity;
54 } 53 }
55} 54}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
index f72bf70..a395b75 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassIdentity.java
@@ -8,18 +8,10 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.*; 14import com.google.common.collect.*;
14
15import java.io.UnsupportedEncodingException;
16import java.security.MessageDigest;
17import java.security.NoSuchAlgorithmException;
18import java.util.Enumeration;
19import java.util.List;
20import java.util.Map;
21import java.util.Set;
22
23import cuchaz.enigma.analysis.ClassImplementationsTreeNode; 15import cuchaz.enigma.analysis.ClassImplementationsTreeNode;
24import cuchaz.enigma.analysis.EntryReference; 16import cuchaz.enigma.analysis.EntryReference;
25import cuchaz.enigma.analysis.JarIndex; 17import cuchaz.enigma.analysis.JarIndex;
@@ -33,408 +25,415 @@ import javassist.*;
33import javassist.bytecode.*; 25import javassist.bytecode.*;
34import javassist.expr.*; 26import javassist.expr.*;
35 27
28import java.io.UnsupportedEncodingException;
29import java.security.MessageDigest;
30import java.security.NoSuchAlgorithmException;
31import java.util.Enumeration;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35
36public class ClassIdentity { 36public class ClassIdentity {
37 37
38 private ClassEntry classEntry; 38 private ClassEntry classEntry;
39 private SidedClassNamer namer; 39 private SidedClassNamer namer;
40 private Multiset<String> fields; 40 private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() {
41 private Multiset<String> methods; 41
42 private Multiset<String> constructors; 42 private Map<String, String> classNames = Maps.newHashMap();
43 private String staticInitializer; 43
44 private String extendz; 44 @Override
45 private Multiset<String> implementz; 45 public String replace(String className) {
46 private Set<String> stringLiterals; 46
47 private Multiset<String> implementations; 47 // classes not in the none package can be passed through
48 private Multiset<String> references; 48 ClassEntry classEntry = new ClassEntry(className);
49 private String outer; 49 if (classEntry.getPackageName() != null) {
50 50 return className;
51 private final ClassNameReplacer classNameReplacer = new ClassNameReplacer() { 51 }
52 52
53 private Map<String, String> classNames = Maps.newHashMap(); 53 // is this class ourself?
54 54 if (className.equals(classEntry.getName())) {
55 @Override 55 return "CSelf";
56 public String replace(String className) { 56 }
57 57
58 // classes not in the none package can be passed through 58 // try the namer
59 ClassEntry classEntry = new ClassEntry(className); 59 if (namer != null) {
60 if (classEntry.getPackageName() != null) { 60 String newName = namer.getName(className);
61 return className; 61 if (newName != null) {
62 } 62 return newName;
63 63 }
64 // is this class ourself? 64 }
65 if (className.equals(classEntry.getName())) { 65
66 return "CSelf"; 66 // otherwise, use local naming
67 } 67 if (!classNames.containsKey(className)) {
68 68 classNames.put(className, getNewClassName());
69 // try the namer 69 }
70 if (namer != null) { 70 return classNames.get(className);
71 String newName = namer.getName(className); 71 }
72 if (newName != null) { 72
73 return newName; 73 private String getNewClassName() {
74 } 74 return String.format("C%03d", classNames.size());
75 } 75 }
76 76 };
77 // otherwise, use local naming 77 private Multiset<String> fields;
78 if (!classNames.containsKey(className)) { 78 private Multiset<String> methods;
79 classNames.put(className, getNewClassName()); 79 private Multiset<String> constructors;
80 } 80 private String staticInitializer;
81 return classNames.get(className); 81 private String extendz;
82 } 82 private Multiset<String> implementz;
83 83 private Set<String> stringLiterals;
84 private String getNewClassName() { 84 private Multiset<String> implementations;
85 return String.format("C%03d", classNames.size()); 85 private Multiset<String> references;
86 } 86 private String outer;
87 }; 87
88 88 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
89 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { 89 this.namer = namer;
90 this.namer = namer; 90
91 91 // stuff from the bytecode
92 // stuff from the bytecode 92
93 93 this.classEntry = EntryFactory.getClassEntry(c);
94 this.classEntry = EntryFactory.getClassEntry(c); 94 this.fields = HashMultiset.create();
95 this.fields = HashMultiset.create(); 95 for (CtField field : c.getDeclaredFields()) {
96 for (CtField field : c.getDeclaredFields()) { 96 this.fields.add(scrubType(field.getSignature()));
97 this.fields.add(scrubType(field.getSignature())); 97 }
98 } 98 this.methods = HashMultiset.create();
99 this.methods = HashMultiset.create(); 99 for (CtMethod method : c.getDeclaredMethods()) {
100 for (CtMethod method : c.getDeclaredMethods()) { 100 this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method));
101 this.methods.add(scrubSignature(method.getSignature()) + "0x" + getBehaviorSignature(method)); 101 }
102 } 102 this.constructors = HashMultiset.create();
103 this.constructors = HashMultiset.create(); 103 for (CtConstructor constructor : c.getDeclaredConstructors()) {
104 for (CtConstructor constructor : c.getDeclaredConstructors()) { 104 this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor));
105 this.constructors.add(scrubSignature(constructor.getSignature()) + "0x" + getBehaviorSignature(constructor)); 105 }
106 } 106 this.staticInitializer = "";
107 this.staticInitializer = ""; 107 if (c.getClassInitializer() != null) {
108 if (c.getClassInitializer() != null) { 108 this.staticInitializer = getBehaviorSignature(c.getClassInitializer());
109 this.staticInitializer = getBehaviorSignature(c.getClassInitializer()); 109 }
110 } 110 this.extendz = "";
111 this.extendz = ""; 111 if (c.getClassFile().getSuperclass() != null) {
112 if (c.getClassFile().getSuperclass() != null) { 112 this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
113 this.extendz = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass())); 113 }
114 } 114 this.implementz = HashMultiset.create();
115 this.implementz = HashMultiset.create(); 115 for (String interfaceName : c.getClassFile().getInterfaces()) {
116 for (String interfaceName : c.getClassFile().getInterfaces()) { 116 this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
117 this.implementz.add(scrubClassName(Descriptor.toJvmName(interfaceName))); 117 }
118 } 118
119 119 this.stringLiterals = Sets.newHashSet();
120 this.stringLiterals = Sets.newHashSet(); 120 ConstPool constants = c.getClassFile().getConstPool();
121 ConstPool constants = c.getClassFile().getConstPool(); 121 for (int i = 1; i < constants.getSize(); i++) {
122 for (int i = 1; i < constants.getSize(); i++) { 122 if (constants.getTag(i) == ConstPool.CONST_String) {
123 if (constants.getTag(i) == ConstPool.CONST_String) { 123 this.stringLiterals.add(constants.getStringInfo(i));
124 this.stringLiterals.add(constants.getStringInfo(i)); 124 }
125 } 125 }
126 } 126
127 127 // stuff from the jar index
128 // stuff from the jar index 128
129 129 this.implementations = HashMultiset.create();
130 this.implementations = HashMultiset.create(); 130 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry);
131 ClassImplementationsTreeNode implementationsNode = index.getClassImplementations(null, this.classEntry); 131 if (implementationsNode != null) {
132 if (implementationsNode != null) { 132 @SuppressWarnings("unchecked")
133 @SuppressWarnings("unchecked") 133 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children();
134 Enumeration<ClassImplementationsTreeNode> implementations = implementationsNode.children(); 134 while (implementations.hasMoreElements()) {
135 while (implementations.hasMoreElements()) { 135 ClassImplementationsTreeNode node = implementations.nextElement();
136 ClassImplementationsTreeNode node = implementations.nextElement(); 136 this.implementations.add(scrubClassName(node.getClassEntry().getName()));
137 this.implementations.add(scrubClassName(node.getClassEntry().getName())); 137 }
138 } 138 }
139 } 139
140 140 this.references = HashMultiset.create();
141 this.references = HashMultiset.create(); 141 if (useReferences) {
142 if (useReferences) { 142 for (CtField field : c.getDeclaredFields()) {
143 for (CtField field : c.getDeclaredFields()) { 143 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field);
144 FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); 144 index.getFieldReferences(fieldEntry).forEach(this::addReference);
145 index.getFieldReferences(fieldEntry).forEach(this::addReference); 145 }
146 } 146 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
147 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 147 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior);
148 BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); 148 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference);
149 index.getBehaviorReferences(behaviorEntry).forEach(this::addReference); 149 }
150 } 150 }
151 } 151
152 152 this.outer = null;
153 this.outer = null; 153 if (this.classEntry.isInnerClass()) {
154 if (this.classEntry.isInnerClass()) { 154 this.outer = this.classEntry.getOuterClassName();
155 this.outer = this.classEntry.getOuterClassName(); 155 }
156 } 156 }
157 } 157
158 158 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) {
159 private void addReference(EntryReference<? extends Entry, BehaviorEntry> reference) { 159 if (reference.context.getSignature() != null) {
160 if (reference.context.getSignature() != null) { 160 this.references.add(String.format("%s_%s",
161 this.references.add(String.format("%s_%s", 161 scrubClassName(reference.context.getClassName()),
162 scrubClassName(reference.context.getClassName()), 162 scrubSignature(reference.context.getSignature())
163 scrubSignature(reference.context.getSignature()) 163 ));
164 )); 164 } else {
165 } else { 165 this.references.add(String.format("%s_<clinit>",
166 this.references.add(String.format("%s_<clinit>", 166 scrubClassName(reference.context.getClassName())
167 scrubClassName(reference.context.getClassName()) 167 ));
168 )); 168 }
169 } 169 }
170 } 170
171 171 public ClassEntry getClassEntry() {
172 public ClassEntry getClassEntry() { 172 return this.classEntry;
173 return this.classEntry; 173 }
174 } 174
175 175 @Override
176 @Override 176 public String toString() {
177 public String toString() { 177 StringBuilder buf = new StringBuilder();
178 StringBuilder buf = new StringBuilder(); 178 buf.append("class: ");
179 buf.append("class: "); 179 buf.append(this.classEntry.getName());
180 buf.append(this.classEntry.getName()); 180 buf.append(" ");
181 buf.append(" "); 181 buf.append(hashCode());
182 buf.append(hashCode()); 182 buf.append("\n");
183 buf.append("\n"); 183 for (String field : this.fields) {
184 for (String field : this.fields) { 184 buf.append("\tfield ");
185 buf.append("\tfield "); 185 buf.append(field);
186 buf.append(field); 186 buf.append("\n");
187 buf.append("\n"); 187 }
188 } 188 for (String method : this.methods) {
189 for (String method : this.methods) { 189 buf.append("\tmethod ");
190 buf.append("\tmethod "); 190 buf.append(method);
191 buf.append(method); 191 buf.append("\n");
192 buf.append("\n"); 192 }
193 } 193 for (String constructor : this.constructors) {
194 for (String constructor : this.constructors) { 194 buf.append("\tconstructor ");
195 buf.append("\tconstructor "); 195 buf.append(constructor);
196 buf.append(constructor); 196 buf.append("\n");
197 buf.append("\n"); 197 }
198 } 198 if (!this.staticInitializer.isEmpty()) {
199 if (this.staticInitializer.length() > 0) { 199 buf.append("\tinitializer ");
200 buf.append("\tinitializer "); 200 buf.append(this.staticInitializer);
201 buf.append(this.staticInitializer); 201 buf.append("\n");
202 buf.append("\n"); 202 }
203 } 203 if (!this.extendz.isEmpty()) {
204 if (this.extendz.length() > 0) { 204 buf.append("\textends ");
205 buf.append("\textends "); 205 buf.append(this.extendz);
206 buf.append(this.extendz); 206 buf.append("\n");
207 buf.append("\n"); 207 }
208 } 208 for (String interfaceName : this.implementz) {
209 for (String interfaceName : this.implementz) { 209 buf.append("\timplements ");
210 buf.append("\timplements "); 210 buf.append(interfaceName);
211 buf.append(interfaceName); 211 buf.append("\n");
212 buf.append("\n"); 212 }
213 } 213 for (String implementation : this.implementations) {
214 for (String implementation : this.implementations) { 214 buf.append("\timplemented by ");
215 buf.append("\timplemented by "); 215 buf.append(implementation);
216 buf.append(implementation); 216 buf.append("\n");
217 buf.append("\n"); 217 }
218 } 218 for (String reference : this.references) {
219 for (String reference : this.references) { 219 buf.append("\treference ");
220 buf.append("\treference "); 220 buf.append(reference);
221 buf.append(reference); 221 buf.append("\n");
222 buf.append("\n"); 222 }
223 } 223 buf.append("\touter ");
224 buf.append("\touter "); 224 buf.append(this.outer);
225 buf.append(this.outer); 225 buf.append("\n");
226 buf.append("\n"); 226 return buf.toString();
227 return buf.toString(); 227 }
228 } 228
229 229 private String scrubClassName(String className) {
230 private String scrubClassName(String className) { 230 return classNameReplacer.replace(className);
231 return classNameReplacer.replace(className); 231 }
232 } 232
233 233 private String scrubType(String typeName) {
234 private String scrubType(String typeName) { 234 return scrubType(new Type(typeName)).toString();
235 return scrubType(new Type(typeName)).toString(); 235 }
236 } 236
237 237 private Type scrubType(Type type) {
238 private Type scrubType(Type type) { 238 if (type.hasClass()) {
239 if (type.hasClass()) { 239 return new Type(type, classNameReplacer);
240 return new Type(type, classNameReplacer); 240 } else {
241 } else { 241 return type;
242 return type; 242 }
243 } 243 }
244 } 244
245 245 private String scrubSignature(String signature) {
246 private String scrubSignature(String signature) { 246 return scrubSignature(new Signature(signature)).toString();
247 return scrubSignature(new Signature(signature)).toString(); 247 }
248 } 248
249 249 private Signature scrubSignature(Signature signature) {
250 private Signature scrubSignature(Signature signature) { 250 return new Signature(signature, classNameReplacer);
251 return new Signature(signature, classNameReplacer); 251 }
252 } 252
253 253 private boolean isClassMatchedUniquely(String className) {
254 private boolean isClassMatchedUniquely(String className) { 254 return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null;
255 return this.namer != null && this.namer.getName(Descriptor.toJvmName(className)) != null; 255 }
256 } 256
257 257 private String getBehaviorSignature(CtBehavior behavior) {
258 private String getBehaviorSignature(CtBehavior behavior) { 258 try {
259 try { 259 // does this method have an implementation?
260 // does this method have an implementation? 260 if (behavior.getMethodInfo().getCodeAttribute() == null) {
261 if (behavior.getMethodInfo().getCodeAttribute() == null) { 261 return "(none)";
262 return "(none)"; 262 }
263 } 263
264 264 // compute the hash from the opcodes
265 // compute the hash from the opcodes 265 ConstPool constants = behavior.getMethodInfo().getConstPool();
266 ConstPool constants = behavior.getMethodInfo().getConstPool(); 266 final MessageDigest digest = MessageDigest.getInstance("MD5");
267 final MessageDigest digest = MessageDigest.getInstance("MD5"); 267 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator();
268 CodeIterator iter = behavior.getMethodInfo().getCodeAttribute().iterator(); 268 while (iter.hasNext()) {
269 while (iter.hasNext()) { 269 int pos = iter.next();
270 int pos = iter.next(); 270
271 271 // update the hash with the opcode
272 // update the hash with the opcode 272 int opcode = iter.byteAt(pos);
273 int opcode = iter.byteAt(pos); 273 digest.update((byte) opcode);
274 digest.update((byte) opcode); 274 int constIndex;
275 int constIndex; 275 switch (opcode) {
276 switch (opcode) { 276 case Opcode.LDC:
277 case Opcode.LDC: 277 constIndex = iter.byteAt(pos + 1);
278 constIndex = iter.byteAt(pos + 1); 278 updateHashWithConstant(digest, constants, constIndex);
279 updateHashWithConstant(digest, constants, constIndex); 279 break;
280 break; 280
281 281 case Opcode.LDC_W:
282 case Opcode.LDC_W: 282 case Opcode.LDC2_W:
283 case Opcode.LDC2_W: 283 constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2);
284 constIndex = (iter.byteAt(pos + 1) << 8) | iter.byteAt(pos + 2); 284 updateHashWithConstant(digest, constants, constIndex);
285 updateHashWithConstant(digest, constants, constIndex); 285 break;
286 break; 286 default:
287 default: 287 break;
288 break; 288 }
289 } 289 }
290 } 290
291 291 // update hash with method and field accesses
292 // update hash with method and field accesses 292 behavior.instrument(new ExprEditor() {
293 behavior.instrument(new ExprEditor() { 293 @Override
294 @Override 294 public void edit(MethodCall call) {
295 public void edit(MethodCall call) { 295 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
296 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); 296 updateHashWithString(digest, scrubSignature(call.getSignature()));
297 updateHashWithString(digest, scrubSignature(call.getSignature())); 297 if (isClassMatchedUniquely(call.getClassName())) {
298 if (isClassMatchedUniquely(call.getClassName())) { 298 updateHashWithString(digest, call.getMethodName());
299 updateHashWithString(digest, call.getMethodName()); 299 }
300 } 300 }
301 } 301
302 302 @Override
303 @Override 303 public void edit(FieldAccess access) {
304 public void edit(FieldAccess access) { 304 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
305 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName()))); 305 updateHashWithString(digest, scrubType(access.getSignature()));
306 updateHashWithString(digest, scrubType(access.getSignature())); 306 if (isClassMatchedUniquely(access.getClassName())) {
307 if (isClassMatchedUniquely(access.getClassName())) { 307 updateHashWithString(digest, access.getFieldName());
308 updateHashWithString(digest, access.getFieldName()); 308 }
309 } 309 }
310 } 310
311 311 @Override
312 @Override 312 public void edit(ConstructorCall call) {
313 public void edit(ConstructorCall call) { 313 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
314 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName()))); 314 updateHashWithString(digest, scrubSignature(call.getSignature()));
315 updateHashWithString(digest, scrubSignature(call.getSignature())); 315 }
316 } 316
317 317 @Override
318 @Override 318 public void edit(NewExpr expr) {
319 public void edit(NewExpr expr) { 319 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
320 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName()))); 320 }
321 } 321 });
322 }); 322
323 323 // convert the hash to a hex string
324 // convert the hash to a hex string 324 return toHex(digest.digest());
325 return toHex(digest.digest()); 325 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) {
326 } catch (BadBytecode | NoSuchAlgorithmException | CannotCompileException ex) { 326 throw new Error(ex);
327 throw new Error(ex); 327 }
328 } 328 }
329 } 329
330 330 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) {
331 private void updateHashWithConstant(MessageDigest digest, ConstPool constants, int index) { 331 ConstPoolEditor editor = new ConstPoolEditor(constants);
332 ConstPoolEditor editor = new ConstPoolEditor(constants); 332 ConstInfoAccessor item = editor.getItem(index);
333 ConstInfoAccessor item = editor.getItem(index); 333 if (item.getType() == InfoType.StringInfo) {
334 if (item.getType() == InfoType.StringInfo) { 334 updateHashWithString(digest, constants.getStringInfo(index));
335 updateHashWithString(digest, constants.getStringInfo(index)); 335 }
336 } 336 // TODO: other constants
337 // TODO: other constants 337 }
338 } 338
339 339 private void updateHashWithString(MessageDigest digest, String val) {
340 private void updateHashWithString(MessageDigest digest, String val) { 340 try {
341 try { 341 digest.update(val.getBytes("UTF8"));
342 digest.update(val.getBytes("UTF8")); 342 } catch (UnsupportedEncodingException ex) {
343 } catch (UnsupportedEncodingException ex) { 343 throw new Error(ex);
344 throw new Error(ex); 344 }
345 } 345 }
346 } 346
347 347 private String toHex(byte[] bytes) {
348 private String toHex(byte[] bytes) { 348 // function taken from:
349 // function taken from: 349 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java
350 // http://stackoverflow.com/questions/9655181/convert-from-byte-array-to-hex-string-in-java 350 final char[] hexArray = "0123456789ABCDEF".toCharArray();
351 final char[] hexArray = "0123456789ABCDEF".toCharArray(); 351 char[] hexChars = new char[bytes.length * 2];
352 char[] hexChars = new char[bytes.length * 2]; 352 for (int j = 0; j < bytes.length; j++) {
353 for (int j = 0; j < bytes.length; j++) { 353 int v = bytes[j] & 0xFF;
354 int v = bytes[j] & 0xFF; 354 hexChars[j * 2] = hexArray[v >>> 4];
355 hexChars[j * 2] = hexArray[v >>> 4]; 355 hexChars[j * 2 + 1] = hexArray[v & 0x0F];
356 hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 356 }
357 } 357 return new String(hexChars);
358 return new String(hexChars); 358 }
359 } 359
360 360 @Override
361 @Override 361 public boolean equals(Object other) {
362 public boolean equals(Object other) { 362 return other instanceof ClassIdentity && equals((ClassIdentity) other);
363 return other instanceof ClassIdentity && equals((ClassIdentity) other); 363 }
364 } 364
365 365 public boolean equals(ClassIdentity other) {
366 public boolean equals(ClassIdentity other) { 366 return this.fields.equals(other.fields)
367 return this.fields.equals(other.fields) 367 && this.methods.equals(other.methods)
368 && this.methods.equals(other.methods) 368 && this.constructors.equals(other.constructors)
369 && this.constructors.equals(other.constructors) 369 && this.staticInitializer.equals(other.staticInitializer)
370 && this.staticInitializer.equals(other.staticInitializer) 370 && this.extendz.equals(other.extendz)
371 && this.extendz.equals(other.extendz) 371 && this.implementz.equals(other.implementz)
372 && this.implementz.equals(other.implementz) 372 && this.implementations.equals(other.implementations)
373 && this.implementations.equals(other.implementations) 373 && this.references.equals(other.references);
374 && this.references.equals(other.references); 374 }
375 } 375
376 376 @Override
377 @Override 377 public int hashCode() {
378 public int hashCode() { 378 List<Object> objs = Lists.newArrayList();
379 List<Object> objs = Lists.newArrayList(); 379 objs.addAll(this.fields);
380 objs.addAll(this.fields); 380 objs.addAll(this.methods);
381 objs.addAll(this.methods); 381 objs.addAll(this.constructors);
382 objs.addAll(this.constructors); 382 objs.add(this.staticInitializer);
383 objs.add(this.staticInitializer); 383 objs.add(this.extendz);
384 objs.add(this.extendz); 384 objs.addAll(this.implementz);
385 objs.addAll(this.implementz); 385 objs.addAll(this.implementations);
386 objs.addAll(this.implementations); 386 objs.addAll(this.references);
387 objs.addAll(this.references); 387 return Utils.combineHashesOrdered(objs);
388 return Utils.combineHashesOrdered(objs); 388 }
389 } 389
390 390 public int getMatchScore(ClassIdentity other) {
391 public int getMatchScore(ClassIdentity other) { 391 return 2 * getNumMatches(this.extendz, other.extendz)
392 return 2 * getNumMatches(this.extendz, other.extendz) 392 + 2 * getNumMatches(this.outer, other.outer)
393 + 2 * getNumMatches(this.outer, other.outer) 393 + 2 * getNumMatches(this.implementz, other.implementz)
394 + 2 * getNumMatches(this.implementz, other.implementz) 394 + getNumMatches(this.stringLiterals, other.stringLiterals)
395 + getNumMatches(this.stringLiterals, other.stringLiterals) 395 + getNumMatches(this.fields, other.fields)
396 + getNumMatches(this.fields, other.fields) 396 + getNumMatches(this.methods, other.methods)
397 + getNumMatches(this.methods, other.methods) 397 + getNumMatches(this.constructors, other.constructors);
398 + getNumMatches(this.constructors, other.constructors); 398 }
399 } 399
400 400 public int getMaxMatchScore() {
401 public int getMaxMatchScore() { 401 return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size();
402 return 2 + 2 + 2 * this.implementz.size() + this.stringLiterals.size() + this.fields.size() + this.methods.size() + this.constructors.size(); 402 }
403 } 403
404 404 public boolean matches(CtClass c) {
405 public boolean matches(CtClass c) { 405 // just compare declaration counts
406 // just compare declaration counts 406 return this.fields.size() == c.getDeclaredFields().length
407 return this.fields.size() == c.getDeclaredFields().length 407 && this.methods.size() == c.getDeclaredMethods().length
408 && this.methods.size() == c.getDeclaredMethods().length 408 && this.constructors.size() == c.getDeclaredConstructors().length;
409 && this.constructors.size() == c.getDeclaredConstructors().length; 409 }
410 } 410
411 411 private int getNumMatches(Set<String> a, Set<String> b) {
412 private int getNumMatches(Set<String> a, Set<String> b) { 412 int numMatches = 0;
413 int numMatches = 0; 413 for (String val : a) {
414 for (String val : a) { 414 if (b.contains(val)) {
415 if (b.contains(val)) { 415 numMatches++;
416 numMatches++; 416 }
417 } 417 }
418 } 418 return numMatches;
419 return numMatches; 419 }
420 } 420
421 421 private int getNumMatches(Multiset<String> a, Multiset<String> b) {
422 private int getNumMatches(Multiset<String> a, Multiset<String> b) { 422 int numMatches = 0;
423 int numMatches = 0; 423 for (String val : a) {
424 for (String val : a) { 424 if (b.contains(val)) {
425 if (b.contains(val)) { 425 numMatches++;
426 numMatches++; 426 }
427 } 427 }
428 } 428 return numMatches;
429 return numMatches; 429 }
430 } 430
431 431 private int getNumMatches(String a, String b) {
432 private int getNumMatches(String a, String b) { 432 if (a == null && b == null) {
433 if (a == null && b == null) { 433 return 1;
434 return 1; 434 } else if (a != null && b != null && a.equals(b)) {
435 } else if (a != null && b != null && a.equals(b)) { 435 return 1;
436 return 1; 436 }
437 } 437 return 0;
438 return 0; 438 }
439 }
440} 439}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatch.java b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
index 9fa35f0..bb3e4f4 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassMatch.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatch.java
@@ -8,76 +8,76 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.Sets; 14import com.google.common.collect.Sets;
15import cuchaz.enigma.mapping.ClassEntry;
16import cuchaz.enigma.utils.Utils;
14 17
15import java.util.Collection; 18import java.util.Collection;
16import java.util.Set; 19import java.util.Set;
17 20
18import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.utils.Utils;
20
21public class ClassMatch { 21public class ClassMatch {
22 22
23 public Set<ClassEntry> sourceClasses; 23 public Set<ClassEntry> sourceClasses;
24 public Set<ClassEntry> destClasses; 24 public Set<ClassEntry> destClasses;
25 25
26 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) { 26 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
27 this.sourceClasses = Sets.newHashSet(sourceClasses); 27 this.sourceClasses = Sets.newHashSet(sourceClasses);
28 this.destClasses = Sets.newHashSet(destClasses); 28 this.destClasses = Sets.newHashSet(destClasses);
29 } 29 }
30 30
31 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) { 31 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
32 sourceClasses = Sets.newHashSet(); 32 sourceClasses = Sets.newHashSet();
33 if (sourceClass != null) { 33 if (sourceClass != null) {
34 sourceClasses.add(sourceClass); 34 sourceClasses.add(sourceClass);
35 } 35 }
36 destClasses = Sets.newHashSet(); 36 destClasses = Sets.newHashSet();
37 if (destClass != null) { 37 if (destClass != null) {
38 destClasses.add(destClass); 38 destClasses.add(destClass);
39 } 39 }
40 } 40 }
41 41
42 public boolean isMatched() { 42 public boolean isMatched() {
43 return sourceClasses.size() > 0 && destClasses.size() > 0; 43 return !sourceClasses.isEmpty() && !destClasses.isEmpty();
44 } 44 }
45 45
46 public boolean isAmbiguous() { 46 public boolean isAmbiguous() {
47 return sourceClasses.size() > 1 || destClasses.size() > 1; 47 return sourceClasses.size() > 1 || destClasses.size() > 1;
48 } 48 }
49 49
50 public ClassEntry getUniqueSource() { 50 public ClassEntry getUniqueSource() {
51 if (sourceClasses.size() != 1) { 51 if (sourceClasses.size() != 1) {
52 throw new IllegalStateException("Match has ambiguous source!"); 52 throw new IllegalStateException("Match has ambiguous source!");
53 } 53 }
54 return sourceClasses.iterator().next(); 54 return sourceClasses.iterator().next();
55 } 55 }
56 56
57 public ClassEntry getUniqueDest() { 57 public ClassEntry getUniqueDest() {
58 if (destClasses.size() != 1) { 58 if (destClasses.size() != 1) {
59 throw new IllegalStateException("Match has ambiguous source!"); 59 throw new IllegalStateException("Match has ambiguous source!");
60 } 60 }
61 return destClasses.iterator().next(); 61 return destClasses.iterator().next();
62 } 62 }
63 63
64 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) { 64 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
65 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses); 65 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
66 intersection.retainAll(classes); 66 intersection.retainAll(classes);
67 return intersection; 67 return intersection;
68 } 68 }
69 69
70 @Override 70 @Override
71 public int hashCode() { 71 public int hashCode() {
72 return Utils.combineHashesOrdered(sourceClasses, destClasses); 72 return Utils.combineHashesOrdered(sourceClasses, destClasses);
73 } 73 }
74 74
75 @Override 75 @Override
76 public boolean equals(Object other) { 76 public boolean equals(Object other) {
77 return other instanceof ClassMatch && equals((ClassMatch) other); 77 return other instanceof ClassMatch && equals((ClassMatch) other);
78 } 78 }
79 79
80 public boolean equals(ClassMatch other) { 80 public boolean equals(ClassMatch other) {
81 return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses); 81 return this.sourceClasses.equals(other.sourceClasses) && this.destClasses.equals(other.destClasses);
82 } 82 }
83} 83}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatches.java b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
index 431c4f2..db2c550 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassMatches.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatches.java
@@ -8,152 +8,151 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.BiMap; 14import com.google.common.collect.BiMap;
14import com.google.common.collect.HashBiMap; 15import com.google.common.collect.HashBiMap;
15import com.google.common.collect.Maps; 16import com.google.common.collect.Maps;
16import com.google.common.collect.Sets; 17import com.google.common.collect.Sets;
17
18import java.util.*;
19
20import cuchaz.enigma.mapping.ClassEntry; 18import cuchaz.enigma.mapping.ClassEntry;
21 19
20import java.util.*;
22 21
23public class ClassMatches implements Iterable<ClassMatch> { 22public class ClassMatches implements Iterable<ClassMatch> {
24 23
25 private Collection<ClassMatch> matches; 24 private Collection<ClassMatch> matches;
26 private Map<ClassEntry, ClassMatch> matchesBySource; 25 private Map<ClassEntry, ClassMatch> matchesBySource;
27 private Map<ClassEntry, ClassMatch> matchesByDest; 26 private Map<ClassEntry, ClassMatch> matchesByDest;
28 private BiMap<ClassEntry, ClassEntry> uniqueMatches; 27 private BiMap<ClassEntry, ClassEntry> uniqueMatches;
29 private Map<ClassEntry, ClassMatch> ambiguousMatchesBySource; 28 private Map<ClassEntry, ClassMatch> ambiguousMatchesBySource;
30 private Map<ClassEntry, ClassMatch> ambiguousMatchesByDest; 29 private Map<ClassEntry, ClassMatch> ambiguousMatchesByDest;
31 private Set<ClassEntry> unmatchedSourceClasses; 30 private Set<ClassEntry> unmatchedSourceClasses;
32 private Set<ClassEntry> unmatchedDestClasses; 31 private Set<ClassEntry> unmatchedDestClasses;
33 32
34 public ClassMatches() { 33 public ClassMatches() {
35 this(new ArrayList<>()); 34 this(new ArrayList<>());
36 } 35 }
37 36
38 public ClassMatches(Collection<ClassMatch> matches) { 37 public ClassMatches(Collection<ClassMatch> matches) {
39 this.matches = matches; 38 this.matches = matches;
40 matchesBySource = Maps.newHashMap(); 39 matchesBySource = Maps.newHashMap();
41 matchesByDest = Maps.newHashMap(); 40 matchesByDest = Maps.newHashMap();
42 uniqueMatches = HashBiMap.create(); 41 uniqueMatches = HashBiMap.create();
43 ambiguousMatchesBySource = Maps.newHashMap(); 42 ambiguousMatchesBySource = Maps.newHashMap();
44 ambiguousMatchesByDest = Maps.newHashMap(); 43 ambiguousMatchesByDest = Maps.newHashMap();
45 unmatchedSourceClasses = Sets.newHashSet(); 44 unmatchedSourceClasses = Sets.newHashSet();
46 unmatchedDestClasses = Sets.newHashSet(); 45 unmatchedDestClasses = Sets.newHashSet();
47 46
48 for (ClassMatch match : matches) { 47 for (ClassMatch match : matches) {
49 indexMatch(match); 48 indexMatch(match);
50 } 49 }
51 } 50 }
52 51
53 public void add(ClassMatch match) { 52 public void add(ClassMatch match) {
54 matches.add(match); 53 matches.add(match);
55 indexMatch(match); 54 indexMatch(match);
56 } 55 }
57 56
58 public void remove(ClassMatch match) { 57 public void remove(ClassMatch match) {
59 for (ClassEntry sourceClass : match.sourceClasses) { 58 for (ClassEntry sourceClass : match.sourceClasses) {
60 matchesBySource.remove(sourceClass); 59 matchesBySource.remove(sourceClass);
61 uniqueMatches.remove(sourceClass); 60 uniqueMatches.remove(sourceClass);
62 ambiguousMatchesBySource.remove(sourceClass); 61 ambiguousMatchesBySource.remove(sourceClass);
63 unmatchedSourceClasses.remove(sourceClass); 62 unmatchedSourceClasses.remove(sourceClass);
64 } 63 }
65 for (ClassEntry destClass : match.destClasses) { 64 for (ClassEntry destClass : match.destClasses) {
66 matchesByDest.remove(destClass); 65 matchesByDest.remove(destClass);
67 uniqueMatches.inverse().remove(destClass); 66 uniqueMatches.inverse().remove(destClass);
68 ambiguousMatchesByDest.remove(destClass); 67 ambiguousMatchesByDest.remove(destClass);
69 unmatchedDestClasses.remove(destClass); 68 unmatchedDestClasses.remove(destClass);
70 } 69 }
71 matches.remove(match); 70 matches.remove(match);
72 } 71 }
73 72
74 public int size() { 73 public int size() {
75 return matches.size(); 74 return matches.size();
76 } 75 }
77 76
78 @Override 77 @Override
79 public Iterator<ClassMatch> iterator() { 78 public Iterator<ClassMatch> iterator() {
80 return matches.iterator(); 79 return matches.iterator();
81 } 80 }
82 81
83 private void indexMatch(ClassMatch match) { 82 private void indexMatch(ClassMatch match) {
84 if (!match.isMatched()) { 83 if (!match.isMatched()) {
85 // unmatched 84 // unmatched
86 unmatchedSourceClasses.addAll(match.sourceClasses); 85 unmatchedSourceClasses.addAll(match.sourceClasses);
87 unmatchedDestClasses.addAll(match.destClasses); 86 unmatchedDestClasses.addAll(match.destClasses);
88 } else { 87 } else {
89 if (match.isAmbiguous()) { 88 if (match.isAmbiguous()) {
90 // ambiguously matched 89 // ambiguously matched
91 for (ClassEntry entry : match.sourceClasses) { 90 for (ClassEntry entry : match.sourceClasses) {
92 ambiguousMatchesBySource.put(entry, match); 91 ambiguousMatchesBySource.put(entry, match);
93 } 92 }
94 for (ClassEntry entry : match.destClasses) { 93 for (ClassEntry entry : match.destClasses) {
95 ambiguousMatchesByDest.put(entry, match); 94 ambiguousMatchesByDest.put(entry, match);
96 } 95 }
97 } else { 96 } else {
98 // uniquely matched 97 // uniquely matched
99 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); 98 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
100 } 99 }
101 } 100 }
102 for (ClassEntry entry : match.sourceClasses) { 101 for (ClassEntry entry : match.sourceClasses) {
103 matchesBySource.put(entry, match); 102 matchesBySource.put(entry, match);
104 } 103 }
105 for (ClassEntry entry : match.destClasses) { 104 for (ClassEntry entry : match.destClasses) {
106 matchesByDest.put(entry, match); 105 matchesByDest.put(entry, match);
107 } 106 }
108 } 107 }
109 108
110 public BiMap<ClassEntry, ClassEntry> getUniqueMatches() { 109 public BiMap<ClassEntry, ClassEntry> getUniqueMatches() {
111 return uniqueMatches; 110 return uniqueMatches;
112 } 111 }
113 112
114 public Set<ClassEntry> getUnmatchedSourceClasses() { 113 public Set<ClassEntry> getUnmatchedSourceClasses() {
115 return unmatchedSourceClasses; 114 return unmatchedSourceClasses;
116 } 115 }
117 116
118 public Set<ClassEntry> getUnmatchedDestClasses() { 117 public Set<ClassEntry> getUnmatchedDestClasses() {
119 return unmatchedDestClasses; 118 return unmatchedDestClasses;
120 } 119 }
121 120
122 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() { 121 public Set<ClassEntry> getAmbiguouslyMatchedSourceClasses() {
123 return ambiguousMatchesBySource.keySet(); 122 return ambiguousMatchesBySource.keySet();
124 } 123 }
125 124
126 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) { 125 public ClassMatch getAmbiguousMatchBySource(ClassEntry sourceClass) {
127 return ambiguousMatchesBySource.get(sourceClass); 126 return ambiguousMatchesBySource.get(sourceClass);
128 } 127 }
129 128
130 public ClassMatch getMatchBySource(ClassEntry sourceClass) { 129 public ClassMatch getMatchBySource(ClassEntry sourceClass) {
131 return matchesBySource.get(sourceClass); 130 return matchesBySource.get(sourceClass);
132 } 131 }
133 132
134 public ClassMatch getMatchByDest(ClassEntry destClass) { 133 public ClassMatch getMatchByDest(ClassEntry destClass) {
135 return matchesByDest.get(destClass); 134 return matchesByDest.get(destClass);
136 } 135 }
137 136
138 public void removeSource(ClassEntry sourceClass) { 137 public void removeSource(ClassEntry sourceClass) {
139 ClassMatch match = matchesBySource.get(sourceClass); 138 ClassMatch match = matchesBySource.get(sourceClass);
140 if (match != null) { 139 if (match != null) {
141 remove(match); 140 remove(match);
142 match.sourceClasses.remove(sourceClass); 141 match.sourceClasses.remove(sourceClass);
143 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { 142 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
144 add(match); 143 add(match);
145 } 144 }
146 } 145 }
147 } 146 }
148 147
149 public void removeDest(ClassEntry destClass) { 148 public void removeDest(ClassEntry destClass) {
150 ClassMatch match = matchesByDest.get(destClass); 149 ClassMatch match = matchesByDest.get(destClass);
151 if (match != null) { 150 if (match != null) {
152 remove(match); 151 remove(match);
153 match.destClasses.remove(destClass); 152 match.destClasses.remove(destClass);
154 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) { 153 if (!match.sourceClasses.isEmpty() || !match.destClasses.isEmpty()) {
155 add(match); 154 add(match);
156 } 155 }
157 } 156 }
158 } 157 }
159} 158}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassMatching.java b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
index b05df87..f302f13 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassMatching.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassMatching.java
@@ -8,12 +8,14 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.BiMap; 14import com.google.common.collect.BiMap;
14import com.google.common.collect.HashBiMap; 15import com.google.common.collect.HashBiMap;
15import com.google.common.collect.Lists; 16import com.google.common.collect.Lists;
16import com.google.common.collect.Sets; 17import com.google.common.collect.Sets;
18import cuchaz.enigma.mapping.ClassEntry;
17 19
18import java.util.ArrayList; 20import java.util.ArrayList;
19import java.util.Collection; 21import java.util.Collection;
@@ -21,134 +23,132 @@ import java.util.List;
21import java.util.Map.Entry; 23import java.util.Map.Entry;
22import java.util.Set; 24import java.util.Set;
23 25
24import cuchaz.enigma.mapping.ClassEntry;
25
26public class ClassMatching { 26public class ClassMatching {
27 27
28 private ClassForest sourceClasses; 28 private ClassForest sourceClasses;
29 private ClassForest destClasses; 29 private ClassForest destClasses;
30 private BiMap<ClassEntry, ClassEntry> knownMatches; 30 private BiMap<ClassEntry, ClassEntry> knownMatches;
31 31
32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) { 32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
33 sourceClasses = new ClassForest(sourceIdentifier); 33 sourceClasses = new ClassForest(sourceIdentifier);
34 destClasses = new ClassForest(destIdentifier); 34 destClasses = new ClassForest(destIdentifier);
35 knownMatches = HashBiMap.create(); 35 knownMatches = HashBiMap.create();
36 } 36 }
37 37
38 public void addKnownMatches(BiMap<ClassEntry, ClassEntry> knownMatches) { 38 public void addKnownMatches(BiMap<ClassEntry, ClassEntry> knownMatches) {
39 this.knownMatches.putAll(knownMatches); 39 this.knownMatches.putAll(knownMatches);
40 } 40 }
41 41
42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) { 42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
43 for (ClassEntry sourceClass : sourceClasses) { 43 for (ClassEntry sourceClass : sourceClasses) {
44 if (!knownMatches.containsKey(sourceClass)) { 44 if (!knownMatches.containsKey(sourceClass)) {
45 this.sourceClasses.add(sourceClass); 45 this.sourceClasses.add(sourceClass);
46 } 46 }
47 } 47 }
48 for (ClassEntry destClass : destClasses) { 48 for (ClassEntry destClass : destClasses) {
49 if (!knownMatches.containsValue(destClass)) { 49 if (!knownMatches.containsValue(destClass)) {
50 this.destClasses.add(destClass); 50 this.destClasses.add(destClass);
51 } 51 }
52 } 52 }
53 } 53 }
54 54
55 public Collection<ClassMatch> matches() { 55 public Collection<ClassMatch> matches() {
56 List<ClassMatch> matches = Lists.newArrayList(); 56 List<ClassMatch> matches = Lists.newArrayList();
57 for (Entry<ClassEntry, ClassEntry> entry : knownMatches.entrySet()) { 57 for (Entry<ClassEntry, ClassEntry> entry : knownMatches.entrySet()) {
58 matches.add(new ClassMatch( 58 matches.add(new ClassMatch(
59 entry.getKey(), 59 entry.getKey(),
60 entry.getValue() 60 entry.getValue()
61 )); 61 ));
62 } 62 }
63 for (ClassIdentity identity : sourceClasses.identities()) { 63 for (ClassIdentity identity : sourceClasses.identities()) {
64 matches.add(new ClassMatch( 64 matches.add(new ClassMatch(
65 sourceClasses.getClasses(identity), 65 sourceClasses.getClasses(identity),
66 destClasses.getClasses(identity) 66 destClasses.getClasses(identity)
67 )); 67 ));
68 } 68 }
69 for (ClassIdentity identity : destClasses.identities()) { 69 for (ClassIdentity identity : destClasses.identities()) {
70 if (!sourceClasses.containsIdentity(identity)) { 70 if (!sourceClasses.containsIdentity(identity)) {
71 matches.add(new ClassMatch( 71 matches.add(new ClassMatch(
72 new ArrayList<>(), 72 new ArrayList<>(),
73 destClasses.getClasses(identity) 73 destClasses.getClasses(identity)
74 )); 74 ));
75 } 75 }
76 } 76 }
77 return matches; 77 return matches;
78 } 78 }
79 79
80 public Collection<ClassEntry> sourceClasses() { 80 public Collection<ClassEntry> sourceClasses() {
81 Set<ClassEntry> classes = Sets.newHashSet(); 81 Set<ClassEntry> classes = Sets.newHashSet();
82 for (ClassMatch match : matches()) { 82 for (ClassMatch match : matches()) {
83 classes.addAll(match.sourceClasses); 83 classes.addAll(match.sourceClasses);
84 } 84 }
85 return classes; 85 return classes;
86 } 86 }
87 87
88 public Collection<ClassEntry> destClasses() { 88 public Collection<ClassEntry> destClasses() {
89 Set<ClassEntry> classes = Sets.newHashSet(); 89 Set<ClassEntry> classes = Sets.newHashSet();
90 for (ClassMatch match : matches()) { 90 for (ClassMatch match : matches()) {
91 classes.addAll(match.destClasses); 91 classes.addAll(match.destClasses);
92 } 92 }
93 return classes; 93 return classes;
94 } 94 }
95 95
96 public BiMap<ClassEntry, ClassEntry> uniqueMatches() { 96 public BiMap<ClassEntry, ClassEntry> uniqueMatches() {
97 BiMap<ClassEntry, ClassEntry> uniqueMatches = HashBiMap.create(); 97 BiMap<ClassEntry, ClassEntry> uniqueMatches = HashBiMap.create();
98 for (ClassMatch match : matches()) { 98 for (ClassMatch match : matches()) {
99 if (match.isMatched() && !match.isAmbiguous()) { 99 if (match.isMatched() && !match.isAmbiguous()) {
100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest()); 100 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
101 } 101 }
102 } 102 }
103 return uniqueMatches; 103 return uniqueMatches;
104 } 104 }
105 105
106 public Collection<ClassMatch> ambiguousMatches() { 106 public Collection<ClassMatch> ambiguousMatches() {
107 List<ClassMatch> ambiguousMatches = Lists.newArrayList(); 107 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
108 for (ClassMatch match : matches()) { 108 for (ClassMatch match : matches()) {
109 if (match.isMatched() && match.isAmbiguous()) { 109 if (match.isMatched() && match.isAmbiguous()) {
110 ambiguousMatches.add(match); 110 ambiguousMatches.add(match);
111 } 111 }
112 } 112 }
113 return ambiguousMatches; 113 return ambiguousMatches;
114 } 114 }
115 115
116 public Collection<ClassEntry> unmatchedSourceClasses() { 116 public Collection<ClassEntry> unmatchedSourceClasses() {
117 List<ClassEntry> classes = Lists.newArrayList(); 117 List<ClassEntry> classes = Lists.newArrayList();
118 for (ClassMatch match : matches()) { 118 for (ClassMatch match : matches()) {
119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) { 119 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
120 classes.addAll(match.sourceClasses); 120 classes.addAll(match.sourceClasses);
121 } 121 }
122 } 122 }
123 return classes; 123 return classes;
124 } 124 }
125 125
126 public Collection<ClassEntry> unmatchedDestClasses() { 126 public Collection<ClassEntry> unmatchedDestClasses() {
127 List<ClassEntry> classes = Lists.newArrayList(); 127 List<ClassEntry> classes = Lists.newArrayList();
128 for (ClassMatch match : matches()) { 128 for (ClassMatch match : matches()) {
129 if (!match.isMatched() && !match.destClasses.isEmpty()) { 129 if (!match.isMatched() && !match.destClasses.isEmpty()) {
130 classes.addAll(match.destClasses); 130 classes.addAll(match.destClasses);
131 } 131 }
132 } 132 }
133 return classes; 133 return classes;
134 } 134 }
135 135
136 @Override 136 @Override
137 public String toString() { 137 public String toString() {
138 138
139 // count the ambiguous classes 139 // count the ambiguous classes
140 int numAmbiguousSource = 0; 140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0; 141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) { 142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size(); 143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size(); 144 numAmbiguousDest += match.destClasses.size();
145 } 145 }
146 146
147 String buf = String.format("%20s%8s%8s\n", "", "Source", "Dest") + String 147 String buf = String.format("%20s%8s%8s\n", "", "Source", "Dest") + String
148 .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String 148 .format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()) + String
149 .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String 149 .format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()) + String
150 .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String 150 .format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest) + String
151 .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()); 151 .format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size());
152 return buf; 152 return buf;
153 } 153 }
154} 154}
diff --git a/src/main/java/cuchaz/enigma/convert/ClassNamer.java b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
index e471c7d..3969910 100644
--- a/src/main/java/cuchaz/enigma/convert/ClassNamer.java
+++ b/src/main/java/cuchaz/enigma/convert/ClassNamer.java
@@ -8,49 +8,48 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.BiMap; 14import com.google.common.collect.BiMap;
14import com.google.common.collect.Maps; 15import com.google.common.collect.Maps;
16import cuchaz.enigma.mapping.ClassEntry;
15 17
16import java.util.Map; 18import java.util.Map;
17 19
18import cuchaz.enigma.mapping.ClassEntry;
19
20public class ClassNamer { 20public class ClassNamer {
21 21
22 public interface SidedClassNamer { 22 private Map<String, String> sourceNames;
23 String getName(String name); 23 private Map<String, String> destNames;
24 } 24 public ClassNamer(BiMap<ClassEntry, ClassEntry> mappings) {
25 25 // convert the identity mappings to name maps
26 private Map<String, String> sourceNames; 26 this.sourceNames = Maps.newHashMap();
27 private Map<String, String> destNames; 27 this.destNames = Maps.newHashMap();
28 28 int i = 0;
29 public ClassNamer(BiMap<ClassEntry, ClassEntry> mappings) { 29 for (Map.Entry<ClassEntry, ClassEntry> entry : mappings.entrySet()) {
30 // convert the identity mappings to name maps 30 String name = String.format("M%04d", i++);
31 this.sourceNames = Maps.newHashMap(); 31 this.sourceNames.put(entry.getKey().getName(), name);
32 this.destNames = Maps.newHashMap(); 32 this.destNames.put(entry.getValue().getName(), name);
33 int i = 0; 33 }
34 for (Map.Entry<ClassEntry, ClassEntry> entry : mappings.entrySet()) { 34 }
35 String name = String.format("M%04d", i++); 35
36 this.sourceNames.put(entry.getKey().getName(), name); 36 public String getSourceName(String name) {
37 this.destNames.put(entry.getValue().getName(), name); 37 return this.sourceNames.get(name);
38 } 38 }
39 } 39
40 40 public String getDestName(String name) {
41 public String getSourceName(String name) { 41 return this.destNames.get(name);
42 return this.sourceNames.get(name); 42 }
43 } 43
44 44 public SidedClassNamer getSourceNamer() {
45 public String getDestName(String name) { 45 return this::getSourceName;
46 return this.destNames.get(name); 46 }
47 } 47
48 48 public SidedClassNamer getDestNamer() {
49 public SidedClassNamer getSourceNamer() { 49 return this::getDestName;
50 return this::getSourceName; 50 }
51 } 51
52 52 public interface SidedClassNamer {
53 public SidedClassNamer getDestNamer() { 53 String getName(String name);
54 return this::getDestName; 54 }
55 }
56} 55}
diff --git a/src/main/java/cuchaz/enigma/convert/FieldMatches.java b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
index 236cd4d..a528b27 100644
--- a/src/main/java/cuchaz/enigma/convert/FieldMatches.java
+++ b/src/main/java/cuchaz/enigma/convert/FieldMatches.java
@@ -8,144 +8,143 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.*; 14import com.google.common.collect.*;
14
15import java.util.Collection;
16import java.util.Set;
17
18import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.FieldEntry; 16import cuchaz.enigma.mapping.FieldEntry;
20 17
18import java.util.Collection;
19import java.util.Set;
21 20
22public class FieldMatches { 21public class FieldMatches {
23 22
24 private BiMap<FieldEntry, FieldEntry> matches; 23 private BiMap<FieldEntry, FieldEntry> matches;
25 private Multimap<ClassEntry, FieldEntry> matchedSourceFields; 24 private Multimap<ClassEntry, FieldEntry> matchedSourceFields;
26 private Multimap<ClassEntry, FieldEntry> unmatchedSourceFields; 25 private Multimap<ClassEntry, FieldEntry> unmatchedSourceFields;
27 private Multimap<ClassEntry, FieldEntry> unmatchedDestFields; 26 private Multimap<ClassEntry, FieldEntry> unmatchedDestFields;
28 private Multimap<ClassEntry, FieldEntry> unmatchableSourceFields; 27 private Multimap<ClassEntry, FieldEntry> unmatchableSourceFields;
29 28
30 public FieldMatches() { 29 public FieldMatches() {
31 matches = HashBiMap.create(); 30 matches = HashBiMap.create();
32 matchedSourceFields = HashMultimap.create(); 31 matchedSourceFields = HashMultimap.create();
33 unmatchedSourceFields = HashMultimap.create(); 32 unmatchedSourceFields = HashMultimap.create();
34 unmatchedDestFields = HashMultimap.create(); 33 unmatchedDestFields = HashMultimap.create();
35 unmatchableSourceFields = HashMultimap.create(); 34 unmatchableSourceFields = HashMultimap.create();
36 } 35 }
37 36
38 public void addMatch(FieldEntry srcField, FieldEntry destField) { 37 public void addMatch(FieldEntry srcField, FieldEntry destField) {
39 boolean wasAdded = matches.put(srcField, destField) == null; 38 boolean wasAdded = matches.put(srcField, destField) == null;
40 assert (wasAdded); 39 assert (wasAdded);
41 wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField); 40 wasAdded = matchedSourceFields.put(srcField.getClassEntry(), srcField);
42 assert (wasAdded); 41 assert (wasAdded);
43 } 42 }
44 43
45 public void addUnmatchedSourceField(FieldEntry fieldEntry) { 44 public void addUnmatchedSourceField(FieldEntry fieldEntry) {
46 boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry); 45 boolean wasAdded = unmatchedSourceFields.put(fieldEntry.getClassEntry(), fieldEntry);
47 assert (wasAdded); 46 assert (wasAdded);
48 } 47 }
49 48
50 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) { 49 public void addUnmatchedSourceFields(Iterable<FieldEntry> fieldEntries) {
51 for (FieldEntry fieldEntry : fieldEntries) { 50 for (FieldEntry fieldEntry : fieldEntries) {
52 addUnmatchedSourceField(fieldEntry); 51 addUnmatchedSourceField(fieldEntry);
53 } 52 }
54 } 53 }
55 54
56 public void addUnmatchedDestField(FieldEntry fieldEntry) { 55 public void addUnmatchedDestField(FieldEntry fieldEntry) {
57 boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry); 56 boolean wasAdded = unmatchedDestFields.put(fieldEntry.getClassEntry(), fieldEntry);
58 assert (wasAdded); 57 assert (wasAdded);
59 } 58 }
60 59
61 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) { 60 public void addUnmatchedDestFields(Iterable<FieldEntry> fieldEntries) {
62 for (FieldEntry fieldEntry : fieldEntries) { 61 for (FieldEntry fieldEntry : fieldEntries) {
63 addUnmatchedDestField(fieldEntry); 62 addUnmatchedDestField(fieldEntry);
64 } 63 }
65 } 64 }
66 65
67 public void addUnmatchableSourceField(FieldEntry sourceField) { 66 public void addUnmatchableSourceField(FieldEntry sourceField) {
68 boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField); 67 boolean wasAdded = unmatchableSourceFields.put(sourceField.getClassEntry(), sourceField);
69 assert (wasAdded); 68 assert (wasAdded);
70 } 69 }
71 70
72 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() { 71 public Set<ClassEntry> getSourceClassesWithUnmatchedFields() {
73 return unmatchedSourceFields.keySet(); 72 return unmatchedSourceFields.keySet();
74 } 73 }
75 74
76 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() { 75 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedFields() {
77 Set<ClassEntry> out = Sets.newHashSet(); 76 Set<ClassEntry> out = Sets.newHashSet();
78 out.addAll(matchedSourceFields.keySet()); 77 out.addAll(matchedSourceFields.keySet());
79 out.removeAll(unmatchedSourceFields.keySet()); 78 out.removeAll(unmatchedSourceFields.keySet());
80 return out; 79 return out;
81 } 80 }
82 81
83 public Collection<FieldEntry> getUnmatchedSourceFields() { 82 public Collection<FieldEntry> getUnmatchedSourceFields() {
84 return unmatchedSourceFields.values(); 83 return unmatchedSourceFields.values();
85 } 84 }
86 85
87 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) { 86 public Collection<FieldEntry> getUnmatchedSourceFields(ClassEntry sourceClass) {
88 return unmatchedSourceFields.get(sourceClass); 87 return unmatchedSourceFields.get(sourceClass);
89 } 88 }
90 89
91 public Collection<FieldEntry> getUnmatchedDestFields() { 90 public Collection<FieldEntry> getUnmatchedDestFields() {
92 return unmatchedDestFields.values(); 91 return unmatchedDestFields.values();
93 } 92 }
94 93
95 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) { 94 public Collection<FieldEntry> getUnmatchedDestFields(ClassEntry destClass) {
96 return unmatchedDestFields.get(destClass); 95 return unmatchedDestFields.get(destClass);
97 } 96 }
98 97
99 public Collection<FieldEntry> getUnmatchableSourceFields() { 98 public Collection<FieldEntry> getUnmatchableSourceFields() {
100 return unmatchableSourceFields.values(); 99 return unmatchableSourceFields.values();
101 } 100 }
102 101
103 public boolean hasSource(FieldEntry fieldEntry) { 102 public boolean hasSource(FieldEntry fieldEntry) {
104 return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry); 103 return matches.containsKey(fieldEntry) || unmatchedSourceFields.containsValue(fieldEntry);
105 } 104 }
106 105
107 public boolean hasDest(FieldEntry fieldEntry) { 106 public boolean hasDest(FieldEntry fieldEntry) {
108 return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry); 107 return matches.containsValue(fieldEntry) || unmatchedDestFields.containsValue(fieldEntry);
109 } 108 }
110 109
111 public BiMap<FieldEntry, FieldEntry> matches() { 110 public BiMap<FieldEntry, FieldEntry> matches() {
112 return matches; 111 return matches;
113 } 112 }
114 113
115 public boolean isMatchedSourceField(FieldEntry sourceField) { 114 public boolean isMatchedSourceField(FieldEntry sourceField) {
116 return matches.containsKey(sourceField); 115 return matches.containsKey(sourceField);
117 } 116 }
118 117
119 public boolean isMatchedDestField(FieldEntry destField) { 118 public boolean isMatchedDestField(FieldEntry destField) {
120 return matches.containsValue(destField); 119 return matches.containsValue(destField);
121 } 120 }
122 121
123 public void makeMatch(FieldEntry sourceField, FieldEntry destField) { 122 public void makeMatch(FieldEntry sourceField, FieldEntry destField) {
124 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); 123 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
125 assert (wasRemoved); 124 assert (wasRemoved);
126 wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField); 125 wasRemoved = unmatchedDestFields.remove(destField.getClassEntry(), destField);
127 assert (wasRemoved); 126 assert (wasRemoved);
128 addMatch(sourceField, destField); 127 addMatch(sourceField, destField);
129 } 128 }
130 129
131 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) { 130 public boolean isMatched(FieldEntry sourceField, FieldEntry destField) {
132 FieldEntry match = matches.get(sourceField); 131 FieldEntry match = matches.get(sourceField);
133 return match != null && match.equals(destField); 132 return match != null && match.equals(destField);
134 } 133 }
135 134
136 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) { 135 public void unmakeMatch(FieldEntry sourceField, FieldEntry destField) {
137 boolean wasRemoved = matches.remove(sourceField) != null; 136 boolean wasRemoved = matches.remove(sourceField) != null;
138 assert (wasRemoved); 137 assert (wasRemoved);
139 wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField); 138 wasRemoved = matchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
140 assert (wasRemoved); 139 assert (wasRemoved);
141 addUnmatchedSourceField(sourceField); 140 addUnmatchedSourceField(sourceField);
142 addUnmatchedDestField(destField); 141 addUnmatchedDestField(destField);
143 } 142 }
144 143
145 public void makeSourceUnmatchable(FieldEntry sourceField) { 144 public void makeSourceUnmatchable(FieldEntry sourceField) {
146 assert (!isMatchedSourceField(sourceField)); 145 assert (!isMatchedSourceField(sourceField));
147 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField); 146 boolean wasRemoved = unmatchedSourceFields.remove(sourceField.getClassEntry(), sourceField);
148 assert (wasRemoved); 147 assert (wasRemoved);
149 addUnmatchableSourceField(sourceField); 148 addUnmatchableSourceField(sourceField);
150 } 149 }
151} 150}
diff --git a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
index a5ded67..fa3e936 100644
--- a/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
+++ b/src/main/java/cuchaz/enigma/convert/MappingsConverter.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.*; 14import com.google.common.collect.*;
@@ -29,688 +30,682 @@ import java.util.jar.JarFile;
29 30
30public class MappingsConverter { 31public class MappingsConverter {
31 32
32 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) { 33 public static ClassMatches computeClassMatches(JarFile sourceJar, JarFile destJar, Mappings mappings) {
33 34
34 // index jars 35 // index jars
35 System.out.println("Indexing source jar..."); 36 System.out.println("Indexing source jar...");
36 JarIndex sourceIndex = new JarIndex(); 37 JarIndex sourceIndex = new JarIndex();
37 sourceIndex.indexJar(sourceJar, false); 38 sourceIndex.indexJar(sourceJar, false);
38 System.out.println("Indexing dest jar..."); 39 System.out.println("Indexing dest jar...");
39 JarIndex destIndex = new JarIndex(); 40 JarIndex destIndex = new JarIndex();
40 destIndex.indexJar(destJar, false); 41 destIndex.indexJar(destJar, false);
41 42
42 // compute the matching 43 // compute the matching
43 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null); 44 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
44 return new ClassMatches(matching.matches()); 45 return new ClassMatches(matching.matches());
45 } 46 }
46 47
47 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) { 48 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry, ClassEntry> knownMatches) {
48 49
49 System.out.println("Iteratively matching classes"); 50 System.out.println("Iteratively matching classes");
50 51
51 ClassMatching lastMatching = null; 52 ClassMatching lastMatching = null;
52 int round = 0; 53 int round = 0;
53 SidedClassNamer sourceNamer = null; 54 SidedClassNamer sourceNamer = null;
54 SidedClassNamer destNamer = null; 55 SidedClassNamer destNamer = null;
55 for (boolean useReferences : Arrays.asList(false, true)) { 56 for (boolean useReferences : Arrays.asList(false, true)) {
56 57
57 int numUniqueMatchesLastTime = 0; 58 int numUniqueMatchesLastTime = 0;
58 if (lastMatching != null) { 59 if (lastMatching != null) {
59 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size(); 60 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
60 } 61 }
61 62
62 while (true) { 63 while (true) {
63 64
64 System.out.println("Round " + (++round) + "..."); 65 System.out.println("Round " + (++round) + "...");
65 66
66 // init the matching with identity settings 67 // init the matching with identity settings
67 ClassMatching matching = new ClassMatching( 68 ClassMatching matching = new ClassMatching(
68 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences), 69 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
69 new ClassIdentifier(destJar, destIndex, destNamer, useReferences) 70 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
70 ); 71 );
71 72
72 if (knownMatches != null) { 73 if (knownMatches != null) {
73 matching.addKnownMatches(knownMatches); 74 matching.addKnownMatches(knownMatches);
74 } 75 }
75 76
76 if (lastMatching == null) { 77 if (lastMatching == null) {
77 // search all classes 78 // search all classes
78 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); 79 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
79 } else { 80 } else {
80 // we already know about these matches from last time 81 // we already know about these matches from last time
81 matching.addKnownMatches(lastMatching.uniqueMatches()); 82 matching.addKnownMatches(lastMatching.uniqueMatches());
82 83
83 // search unmatched and ambiguously-matched classes 84 // search unmatched and ambiguously-matched classes
84 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses()); 85 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
85 for (ClassMatch match : lastMatching.ambiguousMatches()) { 86 for (ClassMatch match : lastMatching.ambiguousMatches()) {
86 matching.match(match.sourceClasses, match.destClasses); 87 matching.match(match.sourceClasses, match.destClasses);
87 } 88 }
88 } 89 }
89 System.out.println(matching); 90 System.out.println(matching);
90 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches(); 91 BiMap<ClassEntry, ClassEntry> uniqueMatches = matching.uniqueMatches();
91 92
92 // did we match anything new this time? 93 // did we match anything new this time?
93 if (uniqueMatches.size() > numUniqueMatchesLastTime) { 94 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
94 numUniqueMatchesLastTime = uniqueMatches.size(); 95 numUniqueMatchesLastTime = uniqueMatches.size();
95 lastMatching = matching; 96 lastMatching = matching;
96 } else { 97 } else {
97 break; 98 break;
98 } 99 }
99 100
100 // update the namers 101 // update the namers
101 ClassNamer namer = new ClassNamer(uniqueMatches); 102 ClassNamer namer = new ClassNamer(uniqueMatches);
102 sourceNamer = namer.getSourceNamer(); 103 sourceNamer = namer.getSourceNamer();
103 destNamer = namer.getDestNamer(); 104 destNamer = namer.getDestNamer();
104 } 105 }
105 } 106 }
106 107
107 return lastMatching; 108 return lastMatching;
108 } 109 }
109 110
110 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) 111 public static Mappings newMappings(ClassMatches matches, Mappings oldMappings, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator)
111 throws MappingConflict { 112 throws MappingConflict {
112 // sort the unique matches by size of inner class chain 113 // sort the unique matches by size of inner class chain
113 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create(); 114 Multimap<Integer, java.util.Map.Entry<ClassEntry, ClassEntry>> matchesByDestChainSize = HashMultimap.create();
114 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) { 115 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matches.getUniqueMatches().entrySet()) {
115 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size(); 116 int chainSize = destDeobfuscator.getJarIndex().getObfClassChain(match.getValue()).size();
116 matchesByDestChainSize.put(chainSize, match); 117 matchesByDestChainSize.put(chainSize, match);
117 } 118 }
118 119
119 // build the mappings (in order of small-to-large inner chains) 120 // build the mappings (in order of small-to-large inner chains)
120 Mappings newMappings = new Mappings(); 121 Mappings newMappings = new Mappings();
121 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet()); 122 List<Integer> chainSizes = Lists.newArrayList(matchesByDestChainSize.keySet());
122 Collections.sort(chainSizes); 123 Collections.sort(chainSizes);
123 for (int chainSize : chainSizes) { 124 for (int chainSize : chainSizes) {
124 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) { 125 for (java.util.Map.Entry<ClassEntry, ClassEntry> match : matchesByDestChainSize.get(chainSize)) {
125 // get class info 126 // get class info
126 ClassEntry obfSourceClassEntry = match.getKey(); 127 ClassEntry obfSourceClassEntry = match.getKey();
127 ClassEntry obfDestClassEntry = match.getValue(); 128 ClassEntry obfDestClassEntry = match.getValue();
128 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry); 129 List<ClassEntry> destClassChain = destDeobfuscator.getJarIndex().getObfClassChain(obfDestClassEntry);
129 130
130 ClassMapping sourceMapping; 131 ClassMapping sourceMapping;
131 if (obfSourceClassEntry.isInnerClass()) { 132 if (obfSourceClassEntry.isInnerClass()) {
132 List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry); 133 List<ClassMapping> srcClassChain = sourceDeobfuscator.getMappings().getClassMappingChain(obfSourceClassEntry);
133 sourceMapping = srcClassChain.get(srcClassChain.size() - 1); 134 sourceMapping = srcClassChain.get(srcClassChain.size() - 1);
134 } else { 135 } else {
135 sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry); 136 sourceMapping = sourceDeobfuscator.getMappings().getClassByObf(obfSourceClassEntry);
136 } 137 }
137 138
138 if (sourceMapping == null) { 139 if (sourceMapping == null) {
139 // if this class was never deobfuscated, don't try to match it 140 // if this class was never deobfuscated, don't try to match it
140 continue; 141 continue;
141 } 142 }
142 143
143 // find out where to make the dest class mapping 144 // find out where to make the dest class mapping
144 if (destClassChain.size() == 1) { 145 if (destClassChain.size() == 1) {
145 // not an inner class, add directly to mappings 146 // not an inner class, add directly to mappings
146 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false)); 147 newMappings.addClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, false));
147 } else { 148 } else {
148 // inner class, find the outer class mapping 149 // inner class, find the outer class mapping
149 ClassMapping destMapping = null; 150 ClassMapping destMapping = null;
150 for (int i = 0; i < destClassChain.size() - 1; i++) { 151 for (int i = 0; i < destClassChain.size() - 1; i++) {
151 ClassEntry destChainClassEntry = destClassChain.get(i); 152 ClassEntry destChainClassEntry = destClassChain.get(i);
152 if (destMapping == null) { 153 if (destMapping == null) {
153 destMapping = newMappings.getClassByObf(destChainClassEntry); 154 destMapping = newMappings.getClassByObf(destChainClassEntry);
154 if (destMapping == null) { 155 if (destMapping == null) {
155 destMapping = new ClassMapping(destChainClassEntry.getName()); 156 destMapping = new ClassMapping(destChainClassEntry.getName());
156 newMappings.addClassMapping(destMapping); 157 newMappings.addClassMapping(destMapping);
157 } 158 }
158 } else { 159 } else {
159 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName()); 160 destMapping = destMapping.getInnerClassByObfSimple(destChainClassEntry.getInnermostClassName());
160 if (destMapping == null) { 161 if (destMapping == null) {
161 destMapping = new ClassMapping(destChainClassEntry.getName()); 162 destMapping = new ClassMapping(destChainClassEntry.getName());
162 destMapping.addInnerClassMapping(destMapping); 163 destMapping.addInnerClassMapping(destMapping);
163 } 164 }
164 } 165 }
165 } 166 }
166 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true)); 167 destMapping.addInnerClassMapping(migrateClassMapping(obfDestClassEntry, sourceMapping, matches, true));
167 } 168 }
168 } 169 }
169 } 170 }
170 return newMappings; 171 return newMappings;
171 } 172 }
172 173
173 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) { 174 private static ClassMapping migrateClassMapping(ClassEntry newObfClass, ClassMapping oldClassMapping, final ClassMatches matches, boolean useSimpleName) {
174 175
175 ClassNameReplacer replacer = className -> 176 ClassNameReplacer replacer = className ->
176 { 177 {
177 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className)); 178 ClassEntry newClassEntry = matches.getUniqueMatches().get(new ClassEntry(className));
178 if (newClassEntry != null) { 179 if (newClassEntry != null) {
179 return newClassEntry.getName(); 180 return newClassEntry.getName();
180 } 181 }
181 return null; 182 return null;
182 }; 183 };
183 184
184 ClassMapping newClassMapping; 185 ClassMapping newClassMapping;
185 String deobfName = oldClassMapping.getDeobfName(); 186 String deobfName = oldClassMapping.getDeobfName();
186 if (deobfName != null) { 187 if (deobfName != null) {
187 if (useSimpleName) { 188 if (useSimpleName) {
188 deobfName = new ClassEntry(deobfName).getSimpleName(); 189 deobfName = new ClassEntry(deobfName).getSimpleName();
189 } 190 }
190 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName); 191 newClassMapping = new ClassMapping(newObfClass.getName(), deobfName);
191 } else { 192 } else {
192 newClassMapping = new ClassMapping(newObfClass.getName()); 193 newClassMapping = new ClassMapping(newObfClass.getName());
193 } 194 }
194 195
195 // migrate fields 196 // migrate fields
196 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) { 197 for (FieldMapping oldFieldMapping : oldClassMapping.fields()) {
197 if (canMigrate(oldFieldMapping.getObfType(), matches)) { 198 if (canMigrate(oldFieldMapping.getObfType(), matches)) {
198 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer)); 199 newClassMapping.addFieldMapping(new FieldMapping(oldFieldMapping, replacer));
199 } else { 200 } else {
200 System.out.println(String.format("Can't map field, dropping: %s.%s %s", 201 System.out.println(String.format("Can't map field, dropping: %s.%s %s",
201 oldClassMapping.getDeobfName(), 202 oldClassMapping.getDeobfName(),
202 oldFieldMapping.getDeobfName(), 203 oldFieldMapping.getDeobfName(),
203 oldFieldMapping.getObfType() 204 oldFieldMapping.getObfType()
204 )); 205 ));
205 } 206 }
206 } 207 }
207 208
208 // migrate methods 209 // migrate methods
209 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) { 210 for (MethodMapping oldMethodMapping : oldClassMapping.methods()) {
210 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) { 211 if (canMigrate(oldMethodMapping.getObfSignature(), matches)) {
211 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer)); 212 newClassMapping.addMethodMapping(new MethodMapping(oldMethodMapping, replacer));
212 } else { 213 } else {
213 System.out.println(String.format("Can't map method, dropping: %s.%s %s", 214 System.out.println(String.format("Can't map method, dropping: %s.%s %s",
214 oldClassMapping.getDeobfName(), 215 oldClassMapping.getDeobfName(),
215 oldMethodMapping.getDeobfName(), 216 oldMethodMapping.getDeobfName(),
216 oldMethodMapping.getObfSignature() 217 oldMethodMapping.getObfSignature()
217 )); 218 ));
218 } 219 }
219 } 220 }
220 221
221 return newClassMapping; 222 return newClassMapping;
222 } 223 }
223 224
224 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) { 225 private static boolean canMigrate(Signature oldObfSignature, ClassMatches classMatches) {
225 for (Type oldObfType : oldObfSignature.types()) { 226 for (Type oldObfType : oldObfSignature.types()) {
226 if (!canMigrate(oldObfType, classMatches)) { 227 if (!canMigrate(oldObfType, classMatches)) {
227 return false; 228 return false;
228 } 229 }
229 } 230 }
230 return true; 231 return true;
231 } 232 }
232 233
233 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) { 234 private static boolean canMigrate(Type oldObfType, ClassMatches classMatches) {
234 235
235 // non classes can be migrated 236 // non classes can be migrated
236 if (!oldObfType.hasClass()) { 237 if (!oldObfType.hasClass()) {
237 return true; 238 return true;
238 } 239 }
239 240
240 // non obfuscated classes can be migrated 241 // non obfuscated classes can be migrated
241 ClassEntry classEntry = oldObfType.getClassEntry(); 242 ClassEntry classEntry = oldObfType.getClassEntry();
242 if (classEntry.getPackageName() != null) { 243 if (classEntry.getPackageName() != null) {
243 return true; 244 return true;
244 } 245 }
245 246
246 // obfuscated classes with mappings can be migrated 247 // obfuscated classes with mappings can be migrated
247 return classMatches.getUniqueMatches().containsKey(classEntry); 248 return classMatches.getUniqueMatches().containsKey(classEntry);
248 } 249 }
249 250
250 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) { 251 public static void convertMappings(Mappings mappings, BiMap<ClassEntry, ClassEntry> changes) {
251 252
252 // sort the changes so classes are renamed in the correct order 253 // sort the changes so classes are renamed in the correct order
253 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b 254 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
254 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap(); 255 LinkedHashMap<ClassEntry, ClassEntry> sortedChanges = Maps.newLinkedHashMap();
255 int numChangesLeft = changes.size(); 256 int numChangesLeft = changes.size();
256 while (!changes.isEmpty()) { 257 while (!changes.isEmpty()) {
257 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator(); 258 Iterator<Map.Entry<ClassEntry, ClassEntry>> iter = changes.entrySet().iterator();
258 while (iter.hasNext()) { 259 while (iter.hasNext()) {
259 Map.Entry<ClassEntry, ClassEntry> change = iter.next(); 260 Map.Entry<ClassEntry, ClassEntry> change = iter.next();
260 if (changes.containsKey(change.getValue())) { 261 if (changes.containsKey(change.getValue())) {
261 sortedChanges.put(change.getKey(), change.getValue()); 262 sortedChanges.put(change.getKey(), change.getValue());
262 iter.remove(); 263 iter.remove();
263 } 264 }
264 } 265 }
265 266
266 // did we remove any changes? 267 // did we remove any changes?
267 if (numChangesLeft - changes.size() > 0) { 268 if (numChangesLeft - changes.size() > 0) {
268 // keep going 269 // keep going
269 numChangesLeft = changes.size(); 270 numChangesLeft = changes.size();
270 } else { 271 } else {
271 // can't sort anymore. There must be a loop 272 // can't sort anymore. There must be a loop
272 break; 273 break;
273 } 274 }
274 } 275 }
275 if (!changes.isEmpty()) { 276 if (!changes.isEmpty()) {
276 throw new Error("Unable to sort class changes! There must be a cycle."); 277 throw new Error("Unable to sort class changes! There must be a cycle.");
277 } 278 }
278 279
279 // convert the mappings in the correct class order 280 // convert the mappings in the correct class order
280 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) { 281 for (Map.Entry<ClassEntry, ClassEntry> entry : sortedChanges.entrySet()) {
281 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName()); 282 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
282 } 283 }
283 } 284 }
284 285
285 public interface Doer<T extends Entry> { 286 public static Doer<FieldEntry> getFieldDoer() {
286 Collection<T> getDroppedEntries(MappingsChecker checker); 287 return new Doer<FieldEntry>() {
287 288
288 Collection<T> getObfEntries(JarIndex jarIndex); 289 @Override
289 290 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) {
290 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping); 291 return checker.getDroppedFieldMappings().keySet();
291 292 }
292 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches); 293
293 294 @Override
294 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry); 295 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) {
295 296 return jarIndex.getObfFieldEntries();
296 boolean hasObfMember(ClassMapping classMapping, T obfEntry); 297 }
297 298
298 void removeMemberByObf(ClassMapping classMapping, T obfEntry); 299 @Override
299 } 300 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) {
300 301 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields();
301 public static Doer<FieldEntry> getFieldDoer() { 302 }
302 return new Doer<FieldEntry>() { 303
303 304 @Override
304 @Override 305 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) {
305 public Collection<FieldEntry> getDroppedEntries(MappingsChecker checker) { 306 Set<FieldEntry> out = Sets.newHashSet();
306 return checker.getDroppedFieldMappings().keySet(); 307 for (FieldEntry obfDestField : obfDestFields) {
307 } 308 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse());
308 309 if (translatedDestType.equals(obfSourceField.getType())) {
309 @Override 310 out.add(obfDestField);
310 public Collection<FieldEntry> getObfEntries(JarIndex jarIndex) { 311 }
311 return jarIndex.getObfFieldEntries(); 312 }
312 } 313 return out;
313 314 }
314 @Override 315
315 public Collection<? extends MemberMapping<FieldEntry>> getMappings(ClassMapping destClassMapping) { 316 @Override
316 return (Collection<? extends MemberMapping<FieldEntry>>) destClassMapping.fields(); 317 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) {
317 } 318 FieldMapping fieldMapping = (FieldMapping) memberMapping;
318 319 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType());
319 @Override 320 }
320 public Set<FieldEntry> filterEntries(Collection<FieldEntry> obfDestFields, FieldEntry obfSourceField, ClassMatches classMatches) { 321
321 Set<FieldEntry> out = Sets.newHashSet(); 322 @Override
322 for (FieldEntry obfDestField : obfDestFields) { 323 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) {
323 Type translatedDestType = translate(obfDestField.getType(), classMatches.getUniqueMatches().inverse()); 324 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null;
324 if (translatedDestType.equals(obfSourceField.getType())) { 325 }
325 out.add(obfDestField); 326
326 } 327 @Override
327 } 328 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) {
328 return out; 329 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType()));
329 } 330 }
330 331 };
331 @Override 332 }
332 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<FieldEntry> memberMapping, FieldEntry newField) { 333
333 FieldMapping fieldMapping = (FieldMapping) memberMapping; 334 public static Doer<BehaviorEntry> getMethodDoer() {
334 classMapping.setFieldObfNameAndType(fieldMapping.getObfName(), fieldMapping.getObfType(), newField.getName(), newField.getType()); 335 return new Doer<BehaviorEntry>() {
335 } 336
336 337 @Override
337 @Override 338 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) {
338 public boolean hasObfMember(ClassMapping classMapping, FieldEntry obfField) { 339 return checker.getDroppedMethodMappings().keySet();
339 return classMapping.getFieldByObf(obfField.getName(), obfField.getType()) != null; 340 }
340 } 341
341 342 @Override
342 @Override 343 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) {
343 public void removeMemberByObf(ClassMapping classMapping, FieldEntry obfField) { 344 return jarIndex.getObfBehaviorEntries();
344 classMapping.removeFieldMapping(classMapping.getFieldByObf(obfField.getName(), obfField.getType())); 345 }
345 } 346
346 }; 347 @Override
347 } 348 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) {
348 349 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods();
349 public static Doer<BehaviorEntry> getMethodDoer() { 350 }
350 return new Doer<BehaviorEntry>() { 351
351 352 @Override
352 @Override 353 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) {
353 public Collection<BehaviorEntry> getDroppedEntries(MappingsChecker checker) { 354 Set<BehaviorEntry> out = Sets.newHashSet();
354 return checker.getDroppedMethodMappings().keySet(); 355 for (BehaviorEntry obfDestField : obfDestFields) {
355 } 356 // Try to translate the signature
356 357 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse());
357 @Override 358 if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature()))
358 public Collection<BehaviorEntry> getObfEntries(JarIndex jarIndex) { 359 out.add(obfDestField);
359 return jarIndex.getObfBehaviorEntries(); 360 }
360 } 361 return out;
361 362 }
362 @Override 363
363 public Collection<? extends MemberMapping<BehaviorEntry>> getMappings(ClassMapping destClassMapping) { 364 @Override
364 return (Collection<? extends MemberMapping<BehaviorEntry>>) destClassMapping.methods(); 365 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) {
365 } 366 MethodMapping methodMapping = (MethodMapping) memberMapping;
366 367 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature());
367 @Override 368 }
368 public Set<BehaviorEntry> filterEntries(Collection<BehaviorEntry> obfDestFields, BehaviorEntry obfSourceField, ClassMatches classMatches) { 369
369 Set<BehaviorEntry> out = Sets.newHashSet(); 370 @Override
370 for (BehaviorEntry obfDestField : obfDestFields) { 371 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) {
371 // Try to translate the signature 372 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null;
372 Signature translatedDestSignature = translate(obfDestField.getSignature(), classMatches.getUniqueMatches().inverse()); 373 }
373 if (translatedDestSignature != null && obfSourceField.getSignature() != null && translatedDestSignature.equals(obfSourceField.getSignature())) 374
374 out.add(obfDestField); 375 @Override
375 } 376 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) {
376 return out; 377 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()));
377 } 378 }
378 379 };
379 @Override 380 }
380 public void setUpdateObfMember(ClassMapping classMapping, MemberMapping<BehaviorEntry> memberMapping, BehaviorEntry newBehavior) { 381
381 MethodMapping methodMapping = (MethodMapping) memberMapping; 382 public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) {
382 classMapping.setMethodObfNameAndSignature(methodMapping.getObfName(), methodMapping.getObfSignature(), newBehavior.getName(), newBehavior.getSignature()); 383 int sourcePos = 0;
383 } 384 int destPos = 0;
384 385 while (sourceIt.hasNext() && destIt.hasNext()) {
385 @Override 386 try {
386 public boolean hasObfMember(ClassMapping classMapping, BehaviorEntry obfBehavior) { 387 sourcePos = sourceIt.next();
387 return classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature()) != null; 388 destPos = destIt.next();
388 } 389 if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos))
389 390 return sourcePos;
390 @Override 391 } catch (BadBytecode badBytecode) {
391 public void removeMemberByObf(ClassMapping classMapping, BehaviorEntry obfBehavior) { 392 // Ignore bad bytecode (it might be a little bit dangerous...)
392 classMapping.removeMethodMapping(classMapping.getMethodByObf(obfBehavior.getName(), obfBehavior.getSignature())); 393 }
393 } 394 }
394 }; 395 if (sourcePos < destPos)
395 } 396 return sourcePos;
396 397 else if (destPos < sourcePos)
397 public static int compareMethodByteCode(CodeIterator sourceIt, CodeIterator destIt) 398 return destPos;
398 { 399 return sourcePos;
399 int sourcePos = 0; 400 }
400 int destPos = 0; 401
401 while (sourceIt.hasNext() && destIt.hasNext()) 402 public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry,
402 { 403 Set<BehaviorEntry> obfDestEntries) {
403 try 404 try {
404 { 405 // Get the source method with Javassist
405 sourcePos = sourceIt.next(); 406 CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString());
406 destPos = destIt.next(); 407 CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute();
407 if (sourceIt.byteAt(sourcePos) != destIt.byteAt(destPos)) 408
408 return sourcePos; 409 // Empty method body, ignore!
409 } catch (BadBytecode badBytecode) 410 if (sourceAttribute == null)
410 { 411 return null;
411 // Ignore bad bytecode (it might be a little bit dangerous...) 412 for (BehaviorEntry desEntry : obfDestEntries) {
412 } 413 try {
413 } 414 CtMethod destCtClassMethod = destCtClass
414 if (sourcePos < destPos) 415 .getMethod(desEntry.getName(), desEntry.getSignature().toString());
415 return sourcePos; 416 CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute();
416 else if (destPos < sourcePos) 417
417 return destPos; 418 // Ignore empty body methods
418 return sourcePos; 419 if (destAttribute == null)
419 } 420 continue;
420 421 CodeIterator destIterator = destAttribute.iterator();
421 public static BehaviorEntry compareMethods(CtClass destCtClass, CtClass sourceCtClass, BehaviorEntry obfSourceEntry, 422 int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator);
422 Set<BehaviorEntry> obfDestEntries) 423
423 { 424 // The bytecode is identical to the original method, assuming that the method is correct!
424 try 425 if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1)
425 { 426 return desEntry;
426 // Get the source method with Javassist 427 } catch (NotFoundException e) {
427 CtMethod sourceCtClassMethod = sourceCtClass.getMethod(obfSourceEntry.getName(), obfSourceEntry.getSignature().toString()); 428 e.printStackTrace();
428 CodeAttribute sourceAttribute = sourceCtClassMethod.getMethodInfo().getCodeAttribute(); 429 }
429 430 }
430 // Empty method body, ignore! 431 } catch (NotFoundException e) {
431 if (sourceAttribute == null) 432 e.printStackTrace();
432 return null; 433 return null;
433 for (BehaviorEntry desEntry : obfDestEntries) 434 }
434 { 435 return null;
435 try 436 }
436 { 437
437 CtMethod destCtClassMethod = destCtClass 438 public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator,
438 .getMethod(desEntry.getName(), desEntry.getSignature().toString()); 439 Mappings destMappings,
439 CodeAttribute destAttribute = destCtClassMethod.getMethodInfo().getCodeAttribute(); 440 Deobfuscator sourceDeobfuscator,
440 441 Mappings sourceMappings,
441 // Ignore empty body methods 442 ClassMatches classMatches,
442 if (destAttribute == null) 443 Doer<BehaviorEntry> doer) {
443 continue; 444
444 CodeIterator destIterator = destAttribute.iterator(); 445 MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>();
445 int maxPos = compareMethodByteCode(sourceAttribute.iterator(), destIterator); 446
446 447 // unmatched source fields are easy
447 // The bytecode is identical to the original method, assuming that the method is correct! 448 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
448 if (sourceAttribute.getCodeLength() == (maxPos + 1) && maxPos > 1) 449 checker.dropBrokenMappings(destMappings);
449 return desEntry; 450 for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) {
450 } catch (NotFoundException e) 451 BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
451 { 452 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
452 e.printStackTrace(); 453 }
453 } 454
454 } 455 // get matched fields (anything that's left after the checks/drops is matched(
455 } catch (NotFoundException e) 456 for (ClassMapping classMapping : destMappings.classes())
456 { 457 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
457 e.printStackTrace(); 458
458 return null; 459 // get unmatched dest fields
459 } 460 doer.getObfEntries(destDeobfuscator.getJarIndex()).stream()
460 return null; 461 .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry))
461 } 462 .forEach(memberMatches::addUnmatchedDestEntry);
462 463
463 public static MemberMatches<BehaviorEntry> computeMethodsMatches(Deobfuscator destDeobfuscator, Mappings destMappings, Deobfuscator sourceDeobfuscator, Mappings sourceMappings, ClassMatches classMatches, Doer<BehaviorEntry> doer) { 464 // Apply mappings to deobfuscator
464 465
465 MemberMatches<BehaviorEntry> memberMatches = new MemberMatches<>(); 466 // Create type loader
466 467 TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader();
467 // unmatched source fields are easy 468 TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader();
468 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); 469
469 checker.dropBrokenMappings(destMappings); 470 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
470 for (BehaviorEntry destObfEntry : doer.getDroppedEntries(checker)) { 471
471 BehaviorEntry srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); 472 // go through the unmatched source fields and try to pick out the easy matches
472 memberMatches.addUnmatchedSourceEntry(srcObfEntry); 473 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
473 } 474 for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
474 475
475 // get matched fields (anything that's left after the checks/drops is matched( 476 // get the possible dest matches
476 for (ClassMapping classMapping : destMappings.classes()) 477 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
477 collectMatchedFields(memberMatches, classMapping, classMatches, doer); 478
478 479 // filter by type/signature
479 // get unmatched dest fields 480 Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
480 doer.getObfEntries(destDeobfuscator.getJarIndex()).stream() 481
481 .filter(destEntry -> !memberMatches.isMatchedDestEntry(destEntry)) 482 if (obfDestEntries.size() == 1) {
482 .forEach(memberMatches::addUnmatchedDestEntry); 483 // make the easy match
483 484 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
484 // Apply mappings to deobfuscator 485 } else if (obfDestEntries.isEmpty()) {
485 486 // no match is possible =(
486 // Create type loader 487 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
487 TranslatingTypeLoader destTypeLoader = destDeobfuscator.createTypeLoader(); 488 } else {
488 TranslatingTypeLoader sourceTypeLoader = sourceDeobfuscator.createTypeLoader(); 489 // Multiple matches! Scan methods instructions
489 490 CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName());
490 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); 491 CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName());
491 492 BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries);
492 // go through the unmatched source fields and try to pick out the easy matches 493 // the method match correctly, match it on the member mapping!
493 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { 494 if (match != null)
494 for (BehaviorEntry obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { 495 memberMatches.makeMatch(obfSourceEntry, match);
495 496 }
496 // get the possible dest matches 497 }
497 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); 498 }
498 499
499 // filter by type/signature 500 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
500 Set<BehaviorEntry> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); 501 memberMatches.getUnmatchedSourceEntries().size(),
501 502 memberMatches.getUnmatchableSourceEntries().size()
502 if (obfDestEntries.size() == 1) { 503 ));
503 // make the easy match 504
504 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); 505 return memberMatches;
505 } else if (obfDestEntries.isEmpty()) { 506 }
506 // no match is possible =( 507
507 memberMatches.makeSourceUnmatchable(obfSourceEntry, null); 508 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) {
508 } else 509
509 { 510 MemberMatches<T> memberMatches = new MemberMatches<>();
510 // Multiple matches! Scan methods instructions 511
511 CtClass destCtClass = destTypeLoader.loadClass(obfDestClass.getClassName()); 512 // unmatched source fields are easy
512 CtClass sourceCtClass = sourceTypeLoader.loadClass(obfSourceClass.getClassName()); 513 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex());
513 BehaviorEntry match = compareMethods(destCtClass, sourceCtClass, obfSourceEntry, obfDestEntries); 514 checker.dropBrokenMappings(destMappings);
514 // the method match correctly, match it on the member mapping! 515 for (T destObfEntry : doer.getDroppedEntries(checker)) {
515 if (match != null) 516 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse());
516 memberMatches.makeMatch(obfSourceEntry, match); 517 memberMatches.addUnmatchedSourceEntry(srcObfEntry);
517 } 518 }
518 } 519
519 } 520 // get matched fields (anything that's left after the checks/drops is matched(
520 521 for (ClassMapping classMapping : destMappings.classes()) {
521 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", 522 collectMatchedFields(memberMatches, classMapping, classMatches, doer);
522 memberMatches.getUnmatchedSourceEntries().size(), 523 }
523 memberMatches.getUnmatchableSourceEntries().size() 524
524 )); 525 // get unmatched dest fields
525 526 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) {
526 return memberMatches; 527 if (!memberMatches.isMatchedDestEntry(destEntry)) {
527 } 528 memberMatches.addUnmatchedDestEntry(destEntry);
528 529 }
529 public static <T extends Entry> MemberMatches<T> computeMemberMatches(Deobfuscator destDeobfuscator, Mappings destMappings, ClassMatches classMatches, Doer<T> doer) { 530 }
530 531
531 MemberMatches<T> memberMatches = new MemberMatches<>(); 532 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries...");
532 533
533 // unmatched source fields are easy 534 // go through the unmatched source fields and try to pick out the easy matches
534 MappingsChecker checker = new MappingsChecker(destDeobfuscator.getJarIndex()); 535 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) {
535 checker.dropBrokenMappings(destMappings); 536 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) {
536 for (T destObfEntry : doer.getDroppedEntries(checker)) { 537
537 T srcObfEntry = translate(destObfEntry, classMatches.getUniqueMatches().inverse()); 538 // get the possible dest matches
538 memberMatches.addUnmatchedSourceEntry(srcObfEntry); 539 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass);
539 } 540
540 541 // filter by type/signature
541 // get matched fields (anything that's left after the checks/drops is matched( 542 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches);
542 for (ClassMapping classMapping : destMappings.classes()) { 543
543 collectMatchedFields(memberMatches, classMapping, classMatches, doer); 544 if (obfDestEntries.size() == 1) {
544 } 545 // make the easy match
545 546 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next());
546 // get unmatched dest fields 547 } else if (obfDestEntries.isEmpty()) {
547 for (T destEntry : doer.getObfEntries(destDeobfuscator.getJarIndex())) { 548 // no match is possible =(
548 if (!memberMatches.isMatchedDestEntry(destEntry)) { 549 memberMatches.makeSourceUnmatchable(obfSourceEntry, null);
549 memberMatches.addUnmatchedDestEntry(destEntry); 550 }
550 } 551 }
551 } 552 }
552 553
553 System.out.println("Automatching " + memberMatches.getUnmatchedSourceEntries().size() + " unmatched source entries..."); 554 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries",
554 555 memberMatches.getUnmatchedSourceEntries().size(),
555 // go through the unmatched source fields and try to pick out the easy matches 556 memberMatches.getUnmatchableSourceEntries().size()
556 for (ClassEntry obfSourceClass : Lists.newArrayList(memberMatches.getSourceClassesWithUnmatchedEntries())) { 557 ));
557 for (T obfSourceEntry : Lists.newArrayList(memberMatches.getUnmatchedSourceEntries(obfSourceClass))) { 558
558 559 return memberMatches;
559 // get the possible dest matches 560 }
560 ClassEntry obfDestClass = classMatches.getUniqueMatches().get(obfSourceClass); 561
561 562 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) {
562 // filter by type/signature 563
563 Set<T> obfDestEntries = doer.filterEntries(memberMatches.getUnmatchedDestEntries(obfDestClass), obfSourceEntry, classMatches); 564 // get the fields for this class
564 565 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) {
565 if (obfDestEntries.size() == 1) { 566 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry());
566 // make the easy match 567 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse());
567 memberMatches.makeMatch(obfSourceEntry, obfDestEntries.iterator().next()); 568 memberMatches.addMatch(srcObfField, destObfField);
568 } else if (obfDestEntries.isEmpty()) { 569 }
569 // no match is possible =( 570
570 memberMatches.makeSourceUnmatchable(obfSourceEntry, null); 571 // recurse
571 } 572 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) {
572 } 573 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer);
573 } 574 }
574 575 }
575 System.out.println(String.format("Ended up with %d ambiguous and %d unmatchable source entries", 576
576 memberMatches.getUnmatchedSourceEntries().size(), 577 @SuppressWarnings("unchecked")
577 memberMatches.getUnmatchableSourceEntries().size() 578 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) {
578 )); 579 if (in instanceof FieldEntry) {
579 580 return (T) new FieldEntry(
580 return memberMatches; 581 map.get(in.getClassEntry()),
581 } 582 in.getName(),
582 583 translate(((FieldEntry) in).getType(), map)
583 private static <T extends Entry> void collectMatchedFields(MemberMatches<T> memberMatches, ClassMapping destClassMapping, ClassMatches classMatches, Doer<T> doer) { 584 );
584 585 } else if (in instanceof MethodEntry) {
585 // get the fields for this class 586 return (T) new MethodEntry(
586 for (MemberMapping<T> destEntryMapping : doer.getMappings(destClassMapping)) { 587 map.get(in.getClassEntry()),
587 T destObfField = destEntryMapping.getObfEntry(destClassMapping.getObfEntry()); 588 in.getName(),
588 T srcObfField = translate(destObfField, classMatches.getUniqueMatches().inverse()); 589 translate(((MethodEntry) in).getSignature(), map)
589 memberMatches.addMatch(srcObfField, destObfField); 590 );
590 } 591 } else if (in instanceof ConstructorEntry) {
591 592 return (T) new ConstructorEntry(
592 // recurse 593 map.get(in.getClassEntry()),
593 for (ClassMapping destInnerClassMapping : destClassMapping.innerClasses()) { 594 translate(((ConstructorEntry) in).getSignature(), map)
594 collectMatchedFields(memberMatches, destInnerClassMapping, classMatches, doer); 595 );
595 } 596 }
596 } 597 throw new Error("Unhandled entry type: " + in.getClass());
597 598 }
598 @SuppressWarnings("unchecked") 599
599 private static <T extends Entry> T translate(T in, BiMap<ClassEntry, ClassEntry> map) { 600 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) {
600 if (in instanceof FieldEntry) { 601 return new Type(type, inClassName ->
601 return (T) new FieldEntry( 602 {
602 map.get(in.getClassEntry()), 603 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
603 in.getName(), 604 if (outClassEntry == null) {
604 translate(((FieldEntry) in).getType(), map) 605 return null;
605 ); 606 }
606 } else if (in instanceof MethodEntry) { 607 return outClassEntry.getName();
607 return (T) new MethodEntry( 608 });
608 map.get(in.getClassEntry()), 609 }
609 in.getName(), 610
610 translate(((MethodEntry) in).getSignature(), map) 611 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) {
611 ); 612 if (signature == null) {
612 } else if (in instanceof ConstructorEntry) { 613 return null;
613 return (T) new ConstructorEntry( 614 }
614 map.get(in.getClassEntry()), 615 return new Signature(signature, inClassName ->
615 translate(((ConstructorEntry) in).getSignature(), map) 616 {
616 ); 617 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName));
617 } 618 if (outClassEntry == null) {
618 throw new Error("Unhandled entry type: " + in.getClass()); 619 return null;
619 } 620 }
620 621 return outClassEntry.getName();
621 private static Type translate(Type type, final BiMap<ClassEntry, ClassEntry> map) { 622 });
622 return new Type(type, inClassName -> 623 }
623 { 624
624 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); 625 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
625 if (outClassEntry == null) { 626 for (ClassMapping classMapping : mappings.classes()) {
626 return null; 627 applyMemberMatches(classMapping, classMatches, memberMatches, doer);
627 } 628 }
628 return outClassEntry.getName(); 629 }
629 }); 630
630 } 631 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) {
631 632
632 private static Signature translate(Signature signature, final BiMap<ClassEntry, ClassEntry> map) { 633 // get the classes
633 if (signature == null) { 634 ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName());
634 return null; 635
635 } 636 // make a map of all the renames we need to make
636 return new Signature(signature, inClassName -> 637 Map<T, T> renames = Maps.newHashMap();
637 { 638 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
638 ClassEntry outClassEntry = map.get(new ClassEntry(inClassName)); 639 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
639 if (outClassEntry == null) { 640 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches);
640 return null; 641
641 } 642 // but drop the unmatchable things
642 return outClassEntry.getName(); 643 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) {
643 }); 644 doer.removeMemberByObf(classMapping, obfOldDestEntry);
644 } 645 continue;
645 646 }
646 public static <T extends Entry> void applyMemberMatches(Mappings mappings, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { 647
647 for (ClassMapping classMapping : mappings.classes()) { 648 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry);
648 applyMemberMatches(classMapping, classMatches, memberMatches, doer); 649 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) {
649 } 650 renames.put(obfOldDestEntry, obfNewDestEntry);
650 } 651 }
651 652 }
652 private static <T extends Entry> void applyMemberMatches(ClassMapping classMapping, ClassMatches classMatches, MemberMatches<T> memberMatches, Doer<T> doer) { 653
653 654 if (!renames.isEmpty()) {
654 // get the classes 655
655 ClassEntry obfDestClass = new ClassEntry(classMapping.getObfFullName()); 656 // apply to this class (should never need more than n passes)
656 657 int numRenamesAppliedThisRound;
657 // make a map of all the renames we need to make 658 do {
658 Map<T, T> renames = Maps.newHashMap(); 659 numRenamesAppliedThisRound = 0;
659 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { 660
660 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); 661 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) {
661 T obfSourceEntry = getSourceEntryFromDestMapping(memberMapping, obfDestClass, classMatches); 662 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass);
662 663 T obfNewDestEntry = renames.get(obfOldDestEntry);
663 // but drop the unmatchable things 664 if (obfNewDestEntry != null) {
664 if (memberMatches.isUnmatchableSourceEntry(obfSourceEntry)) { 665 // make sure this rename won't cause a collision
665 doer.removeMemberByObf(classMapping, obfOldDestEntry); 666 // otherwise, save it for the next round and try again next time
666 continue; 667 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) {
667 } 668 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry);
668 669 renames.remove(obfOldDestEntry);
669 T obfNewDestEntry = memberMatches.matches().get(obfSourceEntry); 670 numRenamesAppliedThisRound++;
670 if (obfNewDestEntry != null && !obfOldDestEntry.getName().equals(obfNewDestEntry.getName())) { 671 }
671 renames.put(obfOldDestEntry, obfNewDestEntry); 672 }
672 } 673 }
673 } 674 } while (numRenamesAppliedThisRound > 0);
674 675
675 if (!renames.isEmpty()) { 676 if (!renames.isEmpty()) {
676 677 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.",
677 // apply to this class (should never need more than n passes) 678 classMapping.getObfFullName(), renames.size()
678 int numRenamesAppliedThisRound; 679 ));
679 do { 680 for (Map.Entry<T, T> entry : renames.entrySet()) {
680 numRenamesAppliedThisRound = 0; 681 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName()));
681 682 }
682 for (MemberMapping<T> memberMapping : Lists.newArrayList(doer.getMappings(classMapping))) { 683 }
683 T obfOldDestEntry = memberMapping.getObfEntry(obfDestClass); 684 }
684 T obfNewDestEntry = renames.get(obfOldDestEntry); 685
685 if (obfNewDestEntry != null) { 686 // recurse
686 // make sure this rename won't cause a collision 687 for (ClassMapping innerClassMapping : classMapping.innerClasses()) {
687 // otherwise, save it for the next round and try again next time 688 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer);
688 if (!doer.hasObfMember(classMapping, obfNewDestEntry)) { 689 }
689 doer.setUpdateObfMember(classMapping, memberMapping, obfNewDestEntry); 690 }
690 renames.remove(obfOldDestEntry); 691
691 numRenamesAppliedThisRound++; 692 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
692 } 693 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
693 } 694 }
694 } 695
695 } while (numRenamesAppliedThisRound > 0); 696 public interface Doer<T extends Entry> {
696 697 Collection<T> getDroppedEntries(MappingsChecker checker);
697 if (!renames.isEmpty()) { 698
698 System.err.println(String.format("WARNING: Couldn't apply all the renames for class %s. %d renames left.", 699 Collection<T> getObfEntries(JarIndex jarIndex);
699 classMapping.getObfFullName(), renames.size() 700
700 )); 701 Collection<? extends MemberMapping<T>> getMappings(ClassMapping destClassMapping);
701 for (Map.Entry<T, T> entry : renames.entrySet()) { 702
702 System.err.println(String.format("\t%s -> %s", entry.getKey().getName(), entry.getValue().getName())); 703 Set<T> filterEntries(Collection<T> obfEntries, T obfSourceEntry, ClassMatches classMatches);
703 } 704
704 } 705 void setUpdateObfMember(ClassMapping classMapping, MemberMapping<T> memberMapping, T newEntry);
705 } 706
706 707 boolean hasObfMember(ClassMapping classMapping, T obfEntry);
707 // recurse 708
708 for (ClassMapping innerClassMapping : classMapping.innerClasses()) { 709 void removeMemberByObf(ClassMapping classMapping, T obfEntry);
709 applyMemberMatches(innerClassMapping, classMatches, memberMatches, doer); 710 }
710 }
711 }
712
713 private static <T extends Entry> T getSourceEntryFromDestMapping(MemberMapping<T> destMemberMapping, ClassEntry obfDestClass, ClassMatches classMatches) {
714 return translate(destMemberMapping.getObfEntry(obfDestClass), classMatches.getUniqueMatches().inverse());
715 }
716} 711}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesReader.java b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
index d86d6c2..1cf50fa 100644
--- a/src/main/java/cuchaz/enigma/convert/MatchesReader.java
+++ b/src/main/java/cuchaz/enigma/convert/MatchesReader.java
@@ -8,99 +8,98 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.Lists; 14import com.google.common.collect.Lists;
15import cuchaz.enigma.mapping.*;
14 16
15import java.io.*; 17import java.io.*;
16import java.nio.charset.Charset; 18import java.nio.charset.Charset;
17import java.util.Collection; 19import java.util.Collection;
18import java.util.List; 20import java.util.List;
19 21
20import cuchaz.enigma.mapping.*;
21
22
23public class MatchesReader { 22public class MatchesReader {
24 23
25 public static ClassMatches readClasses(File file) 24 public static ClassMatches readClasses(File file)
26 throws IOException { 25 throws IOException {
27 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { 26 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) {
28 ClassMatches matches = new ClassMatches(); 27 ClassMatches matches = new ClassMatches();
29 String line; 28 String line;
30 while ((line = in.readLine()) != null) { 29 while ((line = in.readLine()) != null) {
31 matches.add(readClassMatch(line)); 30 matches.add(readClassMatch(line));
32 } 31 }
33 return matches; 32 return matches;
34 } 33 }
35 } 34 }
36 35
37 private static ClassMatch readClassMatch(String line) { 36 private static ClassMatch readClassMatch(String line) {
38 String[] sides = line.split(":", 2); 37 String[] sides = line.split(":", 2);
39 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1])); 38 return new ClassMatch(readClasses(sides[0]), readClasses(sides[1]));
40 } 39 }
41 40
42 private static Collection<ClassEntry> readClasses(String in) { 41 private static Collection<ClassEntry> readClasses(String in) {
43 List<ClassEntry> entries = Lists.newArrayList(); 42 List<ClassEntry> entries = Lists.newArrayList();
44 for (String className : in.split(",")) { 43 for (String className : in.split(",")) {
45 className = className.trim(); 44 className = className.trim();
46 if (className.length() > 0) { 45 if (!className.isEmpty()) {
47 entries.add(new ClassEntry(className)); 46 entries.add(new ClassEntry(className));
48 } 47 }
49 } 48 }
50 return entries; 49 return entries;
51 } 50 }
52 51
53 public static <T extends Entry> MemberMatches<T> readMembers(File file) 52 public static <T extends Entry> MemberMatches<T> readMembers(File file)
54 throws IOException { 53 throws IOException {
55 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) { 54 try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8")))) {
56 MemberMatches<T> matches = new MemberMatches<>(); 55 MemberMatches<T> matches = new MemberMatches<>();
57 String line; 56 String line;
58 while ((line = in.readLine()) != null) { 57 while ((line = in.readLine()) != null) {
59 readMemberMatch(matches, line); 58 readMemberMatch(matches, line);
60 } 59 }
61 return matches; 60 return matches;
62 } 61 }
63 } 62 }
64 63
65 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) { 64 private static <T extends Entry> void readMemberMatch(MemberMatches<T> matches, String line) {
66 if (line.startsWith("!")) { 65 if (line.startsWith("!")) {
67 T source = readEntry(line.substring(1)); 66 T source = readEntry(line.substring(1));
68 matches.addUnmatchableSourceEntry(source); 67 matches.addUnmatchableSourceEntry(source);
69 } else { 68 } else {
70 String[] parts = line.split(":", 2); 69 String[] parts = line.split(":", 2);
71 T source = readEntry(parts[0]); 70 T source = readEntry(parts[0]);
72 T dest = readEntry(parts[1]); 71 T dest = readEntry(parts[1]);
73 if (source != null && dest != null) { 72 if (source != null && dest != null) {
74 matches.addMatch(source, dest); 73 matches.addMatch(source, dest);
75 } else if (source != null) { 74 } else if (source != null) {
76 matches.addUnmatchedSourceEntry(source); 75 matches.addUnmatchedSourceEntry(source);
77 } else if (dest != null) { 76 } else if (dest != null) {
78 matches.addUnmatchedDestEntry(dest); 77 matches.addUnmatchedDestEntry(dest);
79 } 78 }
80 } 79 }
81 } 80 }
82 81
83 @SuppressWarnings("unchecked") 82 @SuppressWarnings("unchecked")
84 private static <T extends Entry> T readEntry(String in) { 83 private static <T extends Entry> T readEntry(String in) {
85 if (in.length() <= 0) { 84 if (in.length() <= 0) {
86 return null; 85 return null;
87 } 86 }
88 String[] parts = in.split(" "); 87 String[] parts = in.split(" ");
89 if (parts.length == 3 && parts[2].indexOf('(') < 0) { 88 if (parts.length == 3 && parts[2].indexOf('(') < 0) {
90 return (T) new FieldEntry( 89 return (T) new FieldEntry(
91 new ClassEntry(parts[0]), 90 new ClassEntry(parts[0]),
92 parts[1], 91 parts[1],
93 new Type(parts[2]) 92 new Type(parts[2])
94 ); 93 );
95 } else { 94 } else {
96 assert (parts.length == 2 || parts.length == 3); 95 assert (parts.length == 2 || parts.length == 3);
97 if (parts.length == 2) { 96 if (parts.length == 2) {
98 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]); 97 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1]);
99 } else if (parts.length == 3) { 98 } else if (parts.length == 3) {
100 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]); 99 return (T) EntryFactory.getBehaviorEntry(parts[0], parts[1], parts[2]);
101 } else { 100 } else {
102 throw new Error("Malformed behavior entry: " + in); 101 throw new Error("Malformed behavior entry: " + in);
103 } 102 }
104 } 103 }
105 } 104 }
106} 105}
diff --git a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
index dccbf6f..8fe7326 100644
--- a/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
+++ b/src/main/java/cuchaz/enigma/convert/MatchesWriter.java
@@ -8,113 +8,116 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.convert;
12 11
13import java.io.*; 12package cuchaz.enigma.convert;
14import java.nio.charset.Charset;
15import java.util.Map;
16 13
17import cuchaz.enigma.mapping.BehaviorEntry; 14import cuchaz.enigma.mapping.BehaviorEntry;
18import cuchaz.enigma.mapping.ClassEntry; 15import cuchaz.enigma.mapping.ClassEntry;
19import cuchaz.enigma.mapping.Entry; 16import cuchaz.enigma.mapping.Entry;
20import cuchaz.enigma.mapping.FieldEntry; 17import cuchaz.enigma.mapping.FieldEntry;
21 18
19import java.io.File;
20import java.io.FileOutputStream;
21import java.io.IOException;
22import java.io.OutputStreamWriter;
23import java.nio.charset.Charset;
24import java.util.Map;
22 25
23public class MatchesWriter { 26public class MatchesWriter {
24 27
25 public static void writeClasses(ClassMatches matches, File file) 28 public static void writeClasses(ClassMatches matches, File file)
26 throws IOException { 29 throws IOException {
27 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { 30 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) {
28 for (ClassMatch match : matches) { 31 for (ClassMatch match : matches) {
29 writeClassMatch(out, match); 32 writeClassMatch(out, match);
30 } 33 }
31 } 34 }
32 } 35 }
33 36
34 private static void writeClassMatch(OutputStreamWriter out, ClassMatch match) 37 private static void writeClassMatch(OutputStreamWriter out, ClassMatch match)
35 throws IOException { 38 throws IOException {
36 writeClasses(out, match.sourceClasses); 39 writeClasses(out, match.sourceClasses);
37 out.write(":"); 40 out.write(":");
38 writeClasses(out, match.destClasses); 41 writeClasses(out, match.destClasses);
39 out.write("\n"); 42 out.write("\n");
40 } 43 }
41 44
42 private static void writeClasses(OutputStreamWriter out, Iterable<ClassEntry> classes) 45 private static void writeClasses(OutputStreamWriter out, Iterable<ClassEntry> classes)
43 throws IOException { 46 throws IOException {
44 boolean isFirst = true; 47 boolean isFirst = true;
45 for (ClassEntry entry : classes) { 48 for (ClassEntry entry : classes) {
46 if (isFirst) { 49 if (isFirst) {
47 isFirst = false; 50 isFirst = false;
48 } else { 51 } else {
49 out.write(","); 52 out.write(",");
50 } 53 }
51 out.write(entry.toString()); 54 out.write(entry.toString());
52 } 55 }
53 } 56 }
54 57
55 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file) 58 public static <T extends Entry> void writeMembers(MemberMatches<T> matches, File file)
56 throws IOException { 59 throws IOException {
57 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) { 60 try (OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"))) {
58 for (Map.Entry<T, T> match : matches.matches().entrySet()) { 61 for (Map.Entry<T, T> match : matches.matches().entrySet()) {
59 writeMemberMatch(out, match.getKey(), match.getValue()); 62 writeMemberMatch(out, match.getKey(), match.getValue());
60 } 63 }
61 for (T entry : matches.getUnmatchedSourceEntries()) { 64 for (T entry : matches.getUnmatchedSourceEntries()) {
62 writeMemberMatch(out, entry, null); 65 writeMemberMatch(out, entry, null);
63 } 66 }
64 for (T entry : matches.getUnmatchedDestEntries()) { 67 for (T entry : matches.getUnmatchedDestEntries()) {
65 writeMemberMatch(out, null, entry); 68 writeMemberMatch(out, null, entry);
66 } 69 }
67 for (T entry : matches.getUnmatchableSourceEntries()) { 70 for (T entry : matches.getUnmatchableSourceEntries()) {
68 writeUnmatchableEntry(out, entry); 71 writeUnmatchableEntry(out, entry);
69 } 72 }
70 } 73 }
71 } 74 }
72 75
73 private static <T extends Entry> void writeMemberMatch(OutputStreamWriter out, T source, T dest) 76 private static <T extends Entry> void writeMemberMatch(OutputStreamWriter out, T source, T dest)
74 throws IOException { 77 throws IOException {
75 if (source != null) { 78 if (source != null) {
76 writeEntry(out, source); 79 writeEntry(out, source);
77 } 80 }
78 out.write(":"); 81 out.write(":");
79 if (dest != null) { 82 if (dest != null) {
80 writeEntry(out, dest); 83 writeEntry(out, dest);
81 } 84 }
82 out.write("\n"); 85 out.write("\n");
83 } 86 }
84 87
85 private static <T extends Entry> void writeUnmatchableEntry(OutputStreamWriter out, T entry) 88 private static <T extends Entry> void writeUnmatchableEntry(OutputStreamWriter out, T entry)
86 throws IOException { 89 throws IOException {
87 out.write("!"); 90 out.write("!");
88 writeEntry(out, entry); 91 writeEntry(out, entry);
89 out.write("\n"); 92 out.write("\n");
90 } 93 }
91 94
92 private static <T extends Entry> void writeEntry(OutputStreamWriter out, T entry) 95 private static <T extends Entry> void writeEntry(OutputStreamWriter out, T entry)
93 throws IOException { 96 throws IOException {
94 if (entry instanceof FieldEntry) { 97 if (entry instanceof FieldEntry) {
95 writeField(out, (FieldEntry) entry); 98 writeField(out, (FieldEntry) entry);
96 } else if (entry instanceof BehaviorEntry) { 99 } else if (entry instanceof BehaviorEntry) {
97 writeBehavior(out, (BehaviorEntry) entry); 100 writeBehavior(out, (BehaviorEntry) entry);
98 } 101 }
99 } 102 }
100 103
101 private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry) 104 private static void writeField(OutputStreamWriter out, FieldEntry fieldEntry)
102 throws IOException { 105 throws IOException {
103 out.write(fieldEntry.getClassName()); 106 out.write(fieldEntry.getClassName());
104 out.write(" "); 107 out.write(" ");
105 out.write(fieldEntry.getName()); 108 out.write(fieldEntry.getName());
106 out.write(" "); 109 out.write(" ");
107 out.write(fieldEntry.getType().toString()); 110 out.write(fieldEntry.getType().toString());
108 } 111 }
109 112
110 private static void writeBehavior(OutputStreamWriter out, BehaviorEntry behaviorEntry) 113 private static void writeBehavior(OutputStreamWriter out, BehaviorEntry behaviorEntry)
111 throws IOException { 114 throws IOException {
112 out.write(behaviorEntry.getClassName()); 115 out.write(behaviorEntry.getClassName());
113 out.write(" "); 116 out.write(" ");
114 out.write(behaviorEntry.getName()); 117 out.write(behaviorEntry.getName());
115 out.write(" "); 118 out.write(" ");
116 if (behaviorEntry.getSignature() != null) { 119 if (behaviorEntry.getSignature() != null) {
117 out.write(behaviorEntry.getSignature().toString()); 120 out.write(behaviorEntry.getSignature().toString());
118 } 121 }
119 } 122 }
120} 123}
diff --git a/src/main/java/cuchaz/enigma/convert/MemberMatches.java b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
index 51cee85..bd74311 100644
--- a/src/main/java/cuchaz/enigma/convert/MemberMatches.java
+++ b/src/main/java/cuchaz/enigma/convert/MemberMatches.java
@@ -8,6 +8,7 @@
8 * Contributors: 8 * Contributors:
9 * Jeff Martin - initial API and implementation 9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/ 10 ******************************************************************************/
11
11package cuchaz.enigma.convert; 12package cuchaz.enigma.convert;
12 13
13import com.google.common.collect.*; 14import com.google.common.collect.*;
@@ -18,165 +19,161 @@ import cuchaz.enigma.mapping.Entry;
18import java.util.Collection; 19import java.util.Collection;
19import java.util.Set; 20import java.util.Set;
20 21
21
22public class MemberMatches<T extends Entry> { 22public class MemberMatches<T extends Entry> {
23 23
24 private BiMap<T, T> matches; 24 private BiMap<T, T> matches;
25 private Multimap<ClassEntry, T> matchedSourceEntries; 25 private Multimap<ClassEntry, T> matchedSourceEntries;
26 private Multimap<ClassEntry, T> unmatchedSourceEntries; 26 private Multimap<ClassEntry, T> unmatchedSourceEntries;
27 private Multimap<ClassEntry, T> unmatchedDestEntries; 27 private Multimap<ClassEntry, T> unmatchedDestEntries;
28 private Multimap<ClassEntry, T> unmatchableSourceEntries; 28 private Multimap<ClassEntry, T> unmatchableSourceEntries;
29 29
30 public MemberMatches() { 30 public MemberMatches() {
31 matches = HashBiMap.create(); 31 matches = HashBiMap.create();
32 matchedSourceEntries = HashMultimap.create(); 32 matchedSourceEntries = HashMultimap.create();
33 unmatchedSourceEntries = HashMultimap.create(); 33 unmatchedSourceEntries = HashMultimap.create();
34 unmatchedDestEntries = HashMultimap.create(); 34 unmatchedDestEntries = HashMultimap.create();
35 unmatchableSourceEntries = HashMultimap.create(); 35 unmatchableSourceEntries = HashMultimap.create();
36 } 36 }
37 37
38 public void addMatch(T srcEntry, T destEntry) { 38 public void addMatch(T srcEntry, T destEntry) {
39 boolean wasAdded = matches.put(srcEntry, destEntry) == null; 39 boolean wasAdded = matches.put(srcEntry, destEntry) == null;
40 assert (wasAdded); 40 assert (wasAdded);
41 wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry); 41 wasAdded = matchedSourceEntries.put(srcEntry.getClassEntry(), srcEntry);
42 assert (wasAdded); 42 assert (wasAdded);
43 } 43 }
44 44
45 public void addUnmatchedSourceEntry(T sourceEntry) { 45 public void addUnmatchedSourceEntry(T sourceEntry) {
46 boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); 46 boolean wasAdded = unmatchedSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
47 assert (wasAdded); 47 assert (wasAdded);
48 } 48 }
49 49
50 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) { 50 public void addUnmatchedSourceEntries(Iterable<T> sourceEntries) {
51 for (T sourceEntry : sourceEntries) { 51 for (T sourceEntry : sourceEntries) {
52 addUnmatchedSourceEntry(sourceEntry); 52 addUnmatchedSourceEntry(sourceEntry);
53 } 53 }
54 } 54 }
55 55
56 public void addUnmatchedDestEntry(T destEntry) { 56 public void addUnmatchedDestEntry(T destEntry) {
57 if (destEntry.getName().equals("<clinit>") || destEntry.getName().equals("<init>")) 57 if (destEntry.getName().equals("<clinit>") || destEntry.getName().equals("<init>"))
58 return; 58 return;
59 boolean wasAdded = unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry); 59 boolean wasAdded = unmatchedDestEntries.put(destEntry.getClassEntry(), destEntry);
60 assert (wasAdded); 60 assert (wasAdded);
61 } 61 }
62 62
63 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) { 63 public void addUnmatchedDestEntries(Iterable<T> destEntriesntries) {
64 for (T entry : destEntriesntries) { 64 for (T entry : destEntriesntries) {
65 addUnmatchedDestEntry(entry); 65 addUnmatchedDestEntry(entry);
66 } 66 }
67 } 67 }
68 68
69 public void addUnmatchableSourceEntry(T sourceEntry) { 69 public void addUnmatchableSourceEntry(T sourceEntry) {
70 boolean wasAdded = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry); 70 boolean wasAdded = unmatchableSourceEntries.put(sourceEntry.getClassEntry(), sourceEntry);
71 assert (wasAdded); 71 assert (wasAdded);
72 } 72 }
73 73
74 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() { 74 public Set<ClassEntry> getSourceClassesWithUnmatchedEntries() {
75 return unmatchedSourceEntries.keySet(); 75 return unmatchedSourceEntries.keySet();
76 } 76 }
77 77
78 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() { 78 public Collection<ClassEntry> getSourceClassesWithoutUnmatchedEntries() {
79 Set<ClassEntry> out = Sets.newHashSet(); 79 Set<ClassEntry> out = Sets.newHashSet();
80 out.addAll(matchedSourceEntries.keySet()); 80 out.addAll(matchedSourceEntries.keySet());
81 out.removeAll(unmatchedSourceEntries.keySet()); 81 out.removeAll(unmatchedSourceEntries.keySet());
82 return out; 82 return out;
83 } 83 }
84 84
85 public Collection<T> getUnmatchedSourceEntries() { 85 public Collection<T> getUnmatchedSourceEntries() {
86 return unmatchedSourceEntries.values(); 86 return unmatchedSourceEntries.values();
87 } 87 }
88 88
89 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) { 89 public Collection<T> getUnmatchedSourceEntries(ClassEntry sourceClass) {
90 return unmatchedSourceEntries.get(sourceClass); 90 return unmatchedSourceEntries.get(sourceClass);
91 } 91 }
92 92
93 public Collection<T> getUnmatchedDestEntries() { 93 public Collection<T> getUnmatchedDestEntries() {
94 return unmatchedDestEntries.values(); 94 return unmatchedDestEntries.values();
95 } 95 }
96 96
97 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) { 97 public Collection<T> getUnmatchedDestEntries(ClassEntry destClass) {
98 return unmatchedDestEntries.get(destClass); 98 return unmatchedDestEntries.get(destClass);
99 } 99 }
100 100
101 public Collection<T> getUnmatchableSourceEntries() { 101 public Collection<T> getUnmatchableSourceEntries() {
102 return unmatchableSourceEntries.values(); 102 return unmatchableSourceEntries.values();
103 } 103 }
104 104
105 public boolean hasSource(T sourceEntry) { 105 public boolean hasSource(T sourceEntry) {
106 return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry); 106 return matches.containsKey(sourceEntry) || unmatchedSourceEntries.containsValue(sourceEntry);
107 } 107 }
108 108
109 public boolean hasDest(T destEntry) { 109 public boolean hasDest(T destEntry) {
110 return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry); 110 return matches.containsValue(destEntry) || unmatchedDestEntries.containsValue(destEntry);
111 } 111 }
112 112
113 public BiMap<T, T> matches() { 113 public BiMap<T, T> matches() {
114 return matches; 114 return matches;
115 } 115 }
116 116
117 public boolean isMatchedSourceEntry(T sourceEntry) { 117 public boolean isMatchedSourceEntry(T sourceEntry) {
118 return matches.containsKey(sourceEntry); 118 return matches.containsKey(sourceEntry);
119 } 119 }
120 120
121 public boolean isMatchedDestEntry(T destEntry) { 121 public boolean isMatchedDestEntry(T destEntry) {
122 return matches.containsValue(destEntry); 122 return matches.containsValue(destEntry);
123 } 123 }
124 124
125 public boolean isUnmatchableSourceEntry(T sourceEntry) { 125 public boolean isUnmatchableSourceEntry(T sourceEntry) {
126 return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry); 126 return unmatchableSourceEntries.containsEntry(sourceEntry.getClassEntry(), sourceEntry);
127 } 127 }
128 public void makeMatch(T sourceEntry, T destEntry) { 128
129 makeMatch(sourceEntry, destEntry, null, null); 129 public void makeMatch(T sourceEntry, T destEntry) {
130 } 130 makeMatch(sourceEntry, destEntry, null, null);
131 131 }
132 public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) { 132
133 if (sourceDeobfuscator != null && destDeobfuscator != null) 133 public void makeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
134 { 134 if (sourceDeobfuscator != null && destDeobfuscator != null) {
135 makeMatch(sourceEntry, destEntry); 135 makeMatch(sourceEntry, destEntry);
136 sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); 136 sourceEntry = (T) sourceEntry.cloneToNewClass(sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
137 destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); 137 destEntry = (T) destEntry.cloneToNewClass(destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true));
138 } 138 }
139 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); 139 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
140 assert (wasRemoved); 140 assert (wasRemoved);
141 wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry); 141 wasRemoved = unmatchedDestEntries.remove(destEntry.getClassEntry(), destEntry);
142 assert (wasRemoved); 142 assert (wasRemoved);
143 addMatch(sourceEntry, destEntry); 143 addMatch(sourceEntry, destEntry);
144 } 144 }
145 145
146 public boolean isMatched(T sourceEntry, T destEntry) { 146 public boolean isMatched(T sourceEntry, T destEntry) {
147 T match = matches.get(sourceEntry); 147 T match = matches.get(sourceEntry);
148 return match != null && match.equals(destEntry); 148 return match != null && match.equals(destEntry);
149 } 149 }
150 150
151 public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) 151 public void unmakeMatch(T sourceEntry, T destEntry, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
152 { 152 if (sourceDeobfuscator != null && destDeobfuscator != null) {
153 if (sourceDeobfuscator != null && destDeobfuscator != null) 153 unmakeMatch(sourceEntry, destEntry, null, null);
154 { 154 sourceEntry = (T) sourceEntry.cloneToNewClass(
155 unmakeMatch(sourceEntry, destEntry, null, null); 155 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
156 sourceEntry = (T) sourceEntry.cloneToNewClass( 156 destEntry = (T) destEntry.cloneToNewClass(
157 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); 157 destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true));
158 destEntry = (T) destEntry.cloneToNewClass( 158 }
159 destDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(destEntry, true)); 159
160 } 160 boolean wasRemoved = matches.remove(sourceEntry) != null;
161 161 assert (wasRemoved);
162 boolean wasRemoved = matches.remove(sourceEntry) != null; 162 wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
163 assert (wasRemoved); 163 assert (wasRemoved);
164 wasRemoved = matchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); 164 addUnmatchedSourceEntry(sourceEntry);
165 assert (wasRemoved); 165 addUnmatchedDestEntry(destEntry);
166 addUnmatchedSourceEntry(sourceEntry); 166 }
167 addUnmatchedDestEntry(destEntry); 167
168 } 168 public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) {
169 169 if (sourceDeobfuscator != null) {
170 public void makeSourceUnmatchable(T sourceEntry, Deobfuscator sourceDeobfuscator) { 170 makeSourceUnmatchable(sourceEntry, null);
171 if (sourceDeobfuscator != null) 171 sourceEntry = (T) sourceEntry.cloneToNewClass(
172 { 172 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true));
173 makeSourceUnmatchable(sourceEntry, null); 173 }
174 sourceEntry = (T) sourceEntry.cloneToNewClass( 174 assert (!isMatchedSourceEntry(sourceEntry));
175 sourceDeobfuscator.getJarIndex().getTranslationIndex().resolveEntryClass(sourceEntry, true)); 175 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry);
176 } 176 assert (wasRemoved);
177 assert (!isMatchedSourceEntry(sourceEntry)); 177 addUnmatchableSourceEntry(sourceEntry);
178 boolean wasRemoved = unmatchedSourceEntries.remove(sourceEntry.getClassEntry(), sourceEntry); 178 }
179 assert (wasRemoved);
180 addUnmatchableSourceEntry(sourceEntry);
181 }
182} 179}