summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jeff2015-02-28 18:00:25 -0500
committerGravatar jeff2015-02-28 18:00:25 -0500
commit741e3472f76d959645ee0e025547d69a03e5b6f2 (patch)
treeda17641de8b891721e435636edbf7a5077d9e6ec
parentignore more harmless exceptions from the buggy highlight painter system (diff)
downloadenigma-741e3472f76d959645ee0e025547d69a03e5b6f2.tar.gz
enigma-741e3472f76d959645ee0e025547d69a03e5b6f2.tar.xz
enigma-741e3472f76d959645ee0e025547d69a03e5b6f2.zip
fix up conversion tool to handle Minecraft 1.8.3
-rw-r--r--src/cuchaz/enigma/convert/ClassForest.java50
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentifier.java41
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentity.java125
-rw-r--r--src/cuchaz/enigma/convert/ClassMatch.java72
-rw-r--r--src/cuchaz/enigma/convert/ClassMatcher.java356
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java200
-rw-r--r--src/cuchaz/enigma/convert/ClassNamer.java10
-rw-r--r--src/cuchaz/enigma/mapping/Translator.java1
8 files changed, 486 insertions, 369 deletions
diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java
new file mode 100644
index 00000000..e113eeb9
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassForest.java
@@ -0,0 +1,50 @@
1package cuchaz.enigma.convert;
2
3import java.util.Collection;
4
5import com.google.common.collect.HashMultimap;
6import com.google.common.collect.Multimap;
7
8import cuchaz.enigma.mapping.ClassEntry;
9
10
11public class ClassForest {
12
13 private ClassIdentifier m_identifier;
14 private Multimap<ClassIdentity,ClassEntry> m_forest;
15
16 public ClassForest(ClassIdentifier identifier) {
17 m_identifier = identifier;
18 m_forest = HashMultimap.create();
19 }
20
21 public ClassIdentifier getIdentifier() {
22 return m_identifier;
23 }
24
25 public void addAll(Iterable<ClassEntry> entries) {
26 for (ClassEntry entry : entries) {
27 add(entry);
28 }
29 }
30
31 private void add(ClassEntry entry) {
32 m_forest.put(m_identifier.identify(entry), entry);
33 }
34
35 public Collection<ClassIdentity> identities() {
36 return m_forest.keySet();
37 }
38
39 public Collection<ClassEntry> classes() {
40 return m_forest.values();
41 }
42
43 public Collection<ClassEntry> getClasses(ClassIdentity identity) {
44 return m_forest.get(identity);
45 }
46
47 public boolean containsIdentity(ClassIdentity identity) {
48 return m_forest.containsKey(identity);
49 }
50}
diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java
new file mode 100644
index 00000000..bdbf11b2
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassIdentifier.java
@@ -0,0 +1,41 @@
1package cuchaz.enigma.convert;
2
3import java.util.Map;
4import java.util.jar.JarFile;
5
6import javassist.CtClass;
7
8import com.beust.jcommander.internal.Maps;
9
10import cuchaz.enigma.TranslatingTypeLoader;
11import cuchaz.enigma.analysis.JarIndex;
12import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
13import cuchaz.enigma.mapping.ClassEntry;
14
15
16public class ClassIdentifier {
17
18 private JarIndex m_index;
19 private SidedClassNamer m_namer;
20 private boolean m_useReferences;
21 private TranslatingTypeLoader m_loader;
22 private Map<ClassEntry,ClassIdentity> m_cache;
23
24 public ClassIdentifier(JarFile jar, JarIndex index, SidedClassNamer namer, boolean useReferences) {
25 m_index = index;
26 m_namer = namer;
27 m_useReferences = useReferences;
28 m_loader = new TranslatingTypeLoader(jar, index);
29 m_cache = Maps.newHashMap();
30 }
31
32 public ClassIdentity identify(ClassEntry classEntry) {
33 ClassIdentity identity = m_cache.get(classEntry);
34 if (identity == null) {
35 CtClass c = m_loader.loadClass(classEntry.getName());
36 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences);
37 m_cache.put(classEntry, identity);
38 }
39 return identity;
40 }
41}
diff --git a/src/cuchaz/enigma/convert/ClassIdentity.java b/src/cuchaz/enigma/convert/ClassIdentity.java
index b5140124..3736a533 100644
--- a/src/cuchaz/enigma/convert/ClassIdentity.java
+++ b/src/cuchaz/enigma/convert/ClassIdentity.java
@@ -52,9 +52,10 @@ import cuchaz.enigma.mapping.BehaviorEntry;
52import cuchaz.enigma.mapping.ClassEntry; 52import cuchaz.enigma.mapping.ClassEntry;
53import cuchaz.enigma.mapping.ClassNameReplacer; 53import cuchaz.enigma.mapping.ClassNameReplacer;
54import cuchaz.enigma.mapping.Entry; 54import cuchaz.enigma.mapping.Entry;
55import cuchaz.enigma.mapping.FieldEntry;
56import cuchaz.enigma.mapping.EntryFactory; 55import cuchaz.enigma.mapping.EntryFactory;
56import cuchaz.enigma.mapping.FieldEntry;
57import cuchaz.enigma.mapping.Signature; 57import cuchaz.enigma.mapping.Signature;
58import cuchaz.enigma.mapping.Type;
58 59
59public class ClassIdentity { 60public class ClassIdentity {
60 61
@@ -68,7 +69,45 @@ public class ClassIdentity {
68 private Multiset<String> m_implements; 69 private Multiset<String> m_implements;
69 private Multiset<String> m_implementations; 70 private Multiset<String> m_implementations;
70 private Multiset<String> m_references; 71 private Multiset<String> m_references;
71 72
73 private final ClassNameReplacer m_classNameReplacer = new ClassNameReplacer() {
74
75 private Map<String,String> m_classNames = Maps.newHashMap();
76
77 @Override
78 public String replace(String className) {
79
80 // classes not in the none package can be passed through
81 ClassEntry classEntry = new ClassEntry(className);
82 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
83 return className;
84 }
85
86 // is this class ourself?
87 if (className.equals(m_classEntry.getName())) {
88 return "CSelf";
89 }
90
91 // try the namer
92 if (m_namer != null) {
93 String newName = m_namer.getName(className);
94 if (newName != null) {
95 return newName;
96 }
97 }
98
99 // otherwise, use local naming
100 if (!m_classNames.containsKey(className)) {
101 m_classNames.put(className, getNewClassName());
102 }
103 return m_classNames.get(className);
104 }
105
106 private String getNewClassName() {
107 return String.format("C%03d", m_classNames.size());
108 }
109 };
110
72 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) { 111 public ClassIdentity(CtClass c, SidedClassNamer namer, JarIndex index, boolean useReferences) {
73 m_namer = namer; 112 m_namer = namer;
74 113
@@ -77,7 +116,7 @@ public class ClassIdentity {
77 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); 116 m_classEntry = new ClassEntry(Descriptor.toJvmName(c.getName()));
78 m_fields = HashMultiset.create(); 117 m_fields = HashMultiset.create();
79 for (CtField field : c.getDeclaredFields()) { 118 for (CtField field : c.getDeclaredFields()) {
80 m_fields.add(scrubSignature(field.getSignature())); 119 m_fields.add(scrubType(field.getSignature()));
81 } 120 }
82 m_methods = HashMultiset.create(); 121 m_methods = HashMultiset.create();
83 for (CtMethod method : c.getDeclaredMethods()) { 122 for (CtMethod method : c.getDeclaredMethods()) {
@@ -93,11 +132,11 @@ public class ClassIdentity {
93 } 132 }
94 m_extends = ""; 133 m_extends = "";
95 if (c.getClassFile().getSuperclass() != null) { 134 if (c.getClassFile().getSuperclass() != null) {
96 m_extends = scrubClassName(c.getClassFile().getSuperclass()); 135 m_extends = scrubClassName(Descriptor.toJvmName(c.getClassFile().getSuperclass()));
97 } 136 }
98 m_implements = HashMultiset.create(); 137 m_implements = HashMultiset.create();
99 for (String interfaceName : c.getClassFile().getInterfaces()) { 138 for (String interfaceName : c.getClassFile().getInterfaces()) {
100 m_implements.add(scrubClassName(interfaceName)); 139 m_implements.add(scrubClassName(Descriptor.toJvmName(interfaceName)));
101 } 140 }
102 141
103 // stuff from the jar index 142 // stuff from the jar index
@@ -132,9 +171,14 @@ public class ClassIdentity {
132 171
133 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) { 172 private void addReference(EntryReference<? extends Entry,BehaviorEntry> reference) {
134 if (reference.context.getSignature() != null) { 173 if (reference.context.getSignature() != null) {
135 m_references.add(String.format("%s_%s", scrubClassName(reference.context.getClassName()), scrubSignature(reference.context.getSignature()))); 174 m_references.add(String.format("%s_%s",
175 scrubClassName(reference.context.getClassName()),
176 scrubSignature(reference.context.getSignature())
177 ));
136 } else { 178 } else {
137 m_references.add(String.format("%s_<clinit>", scrubClassName(reference.context.getClassName()))); 179 m_references.add(String.format("%s_<clinit>",
180 scrubClassName(reference.context.getClassName())
181 ));
138 } 182 }
139 } 183 }
140 184
@@ -194,52 +238,27 @@ public class ClassIdentity {
194 } 238 }
195 239
196 private String scrubClassName(String className) { 240 private String scrubClassName(String className) {
197 return scrubSignature("L" + Descriptor.toJvmName(className) + ";"); 241 return m_classNameReplacer.replace(className);
242 }
243
244 private String scrubType(String typeName) {
245 return scrubType(new Type(typeName)).toString();
246 }
247
248 private Type scrubType(Type type) {
249 if (type.hasClass()) {
250 return new Type(type, m_classNameReplacer);
251 } else {
252 return type;
253 }
198 } 254 }
199 255
200 private String scrubSignature(String signature) { 256 private String scrubSignature(String signature) {
201 return scrubSignature(new Signature(signature)); 257 return scrubSignature(new Signature(signature)).toString();
202 } 258 }
203 259
204 private String scrubSignature(Signature signature) { 260 private Signature scrubSignature(Signature signature) {
205 261 return new Signature(signature, m_classNameReplacer);
206 return new Signature(signature, new ClassNameReplacer() {
207
208 private Map<String,String> m_classNames = Maps.newHashMap();
209
210 @Override
211 public String replace(String className) {
212
213 // classes not in the none package can be passed through
214 ClassEntry classEntry = new ClassEntry(className);
215 if (!classEntry.getPackageName().equals(Constants.NonePackage)) {
216 return className;
217 }
218
219 // is this class ourself?
220 if (className.equals(m_classEntry.getName())) {
221 return "CSelf";
222 }
223
224 // try the namer
225 if (m_namer != null) {
226 String newName = m_namer.getName(className);
227 if (newName != null) {
228 return newName;
229 }
230 }
231
232 // otherwise, use local naming
233 if (!m_classNames.containsKey(className)) {
234 m_classNames.put(className, getNewClassName());
235 }
236 return m_classNames.get(className);
237 }
238
239 private String getNewClassName() {
240 return String.format("C%03d", m_classNames.size());
241 }
242 }).toString();
243 } 262 }
244 263
245 private boolean isClassMatchedUniquely(String className) { 264 private boolean isClassMatchedUniquely(String className) {
@@ -284,7 +303,7 @@ public class ClassIdentity {
284 behavior.instrument(new ExprEditor() { 303 behavior.instrument(new ExprEditor() {
285 @Override 304 @Override
286 public void edit(MethodCall call) { 305 public void edit(MethodCall call) {
287 updateHashWithString(digest, scrubClassName(call.getClassName())); 306 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
288 updateHashWithString(digest, scrubSignature(call.getSignature())); 307 updateHashWithString(digest, scrubSignature(call.getSignature()));
289 if (isClassMatchedUniquely(call.getClassName())) { 308 if (isClassMatchedUniquely(call.getClassName())) {
290 updateHashWithString(digest, call.getMethodName()); 309 updateHashWithString(digest, call.getMethodName());
@@ -293,8 +312,8 @@ public class ClassIdentity {
293 312
294 @Override 313 @Override
295 public void edit(FieldAccess access) { 314 public void edit(FieldAccess access) {
296 updateHashWithString(digest, scrubClassName(access.getClassName())); 315 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(access.getClassName())));
297 updateHashWithString(digest, scrubSignature(access.getSignature())); 316 updateHashWithString(digest, scrubType(access.getSignature()));
298 if (isClassMatchedUniquely(access.getClassName())) { 317 if (isClassMatchedUniquely(access.getClassName())) {
299 updateHashWithString(digest, access.getFieldName()); 318 updateHashWithString(digest, access.getFieldName());
300 } 319 }
@@ -302,13 +321,13 @@ public class ClassIdentity {
302 321
303 @Override 322 @Override
304 public void edit(ConstructorCall call) { 323 public void edit(ConstructorCall call) {
305 updateHashWithString(digest, scrubClassName(call.getClassName())); 324 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(call.getClassName())));
306 updateHashWithString(digest, scrubSignature(call.getSignature())); 325 updateHashWithString(digest, scrubSignature(call.getSignature()));
307 } 326 }
308 327
309 @Override 328 @Override
310 public void edit(NewExpr expr) { 329 public void edit(NewExpr expr) {
311 updateHashWithString(digest, scrubClassName(expr.getClassName())); 330 updateHashWithString(digest, scrubClassName(Descriptor.toJvmName(expr.getClassName())));
312 } 331 }
313 }); 332 });
314 333
diff --git a/src/cuchaz/enigma/convert/ClassMatch.java b/src/cuchaz/enigma/convert/ClassMatch.java
new file mode 100644
index 00000000..9cecf701
--- /dev/null
+++ b/src/cuchaz/enigma/convert/ClassMatch.java
@@ -0,0 +1,72 @@
1package cuchaz.enigma.convert;
2
3import java.util.Collection;
4import java.util.Set;
5
6import com.google.common.collect.Sets;
7
8import cuchaz.enigma.Util;
9import cuchaz.enigma.mapping.ClassEntry;
10
11
12public class ClassMatch {
13
14 public Set<ClassEntry> sourceClasses;
15 public Set<ClassEntry> destClasses;
16
17 public ClassMatch(Collection<ClassEntry> sourceClasses, Collection<ClassEntry> destClasses) {
18 this.sourceClasses = Sets.newHashSet(sourceClasses);
19 this.destClasses = Sets.newHashSet(destClasses);
20 }
21
22 public ClassMatch(ClassEntry sourceClass, ClassEntry destClass) {
23 this.sourceClasses = Sets.newHashSet(sourceClass);
24 this.destClasses = Sets.newHashSet(destClass);
25 }
26
27 public boolean isMatched() {
28 return sourceClasses.size() > 0 && destClasses.size() > 0;
29 }
30
31 public boolean isAmbiguous() {
32 return sourceClasses.size() > 1 || destClasses.size() > 1;
33 }
34
35 public ClassEntry getUniqueSource() {
36 if (sourceClasses.size() != 1) {
37 throw new IllegalStateException("Match has ambiguous source!");
38 }
39 return sourceClasses.iterator().next();
40 }
41
42 public ClassEntry getUniqueDest() {
43 if (destClasses.size() != 1) {
44 throw new IllegalStateException("Match has ambiguous source!");
45 }
46 return destClasses.iterator().next();
47 }
48
49 public Set<ClassEntry> intersectSourceClasses(Set<ClassEntry> classes) {
50 Set<ClassEntry> intersection = Sets.newHashSet(sourceClasses);
51 intersection.retainAll(classes);
52 return intersection;
53 }
54
55 @Override
56 public int hashCode() {
57 return Util.combineHashesOrdered(sourceClasses, destClasses);
58 }
59
60 @Override
61 public boolean equals(Object other) {
62 if (other instanceof ClassMatch) {
63 return equals((ClassMatch)other);
64 }
65 return false;
66 }
67
68 public boolean equals(ClassMatch other) {
69 return this.sourceClasses.equals(other.sourceClasses)
70 && this.destClasses.equals(other.destClasses);
71 }
72}
diff --git a/src/cuchaz/enigma/convert/ClassMatcher.java b/src/cuchaz/enigma/convert/ClassMatcher.java
index 224d0048..f43f5b20 100644
--- a/src/cuchaz/enigma/convert/ClassMatcher.java
+++ b/src/cuchaz/enigma/convert/ClassMatcher.java
@@ -26,9 +26,6 @@ import java.util.Map;
26import java.util.Set; 26import java.util.Set;
27import java.util.jar.JarFile; 27import java.util.jar.JarFile;
28 28
29import javassist.CtBehavior;
30import javassist.CtClass;
31
32import com.google.common.collect.ArrayListMultimap; 29import com.google.common.collect.ArrayListMultimap;
33import com.google.common.collect.BiMap; 30import com.google.common.collect.BiMap;
34import com.google.common.collect.HashBiMap; 31import com.google.common.collect.HashBiMap;
@@ -37,12 +34,10 @@ import com.google.common.collect.Maps;
37import com.google.common.collect.Multimap; 34import com.google.common.collect.Multimap;
38import com.google.common.collect.Sets; 35import com.google.common.collect.Sets;
39 36
40import cuchaz.enigma.TranslatingTypeLoader;
41import cuchaz.enigma.analysis.JarIndex; 37import cuchaz.enigma.analysis.JarIndex;
42import cuchaz.enigma.convert.ClassNamer.SidedClassNamer; 38import cuchaz.enigma.convert.ClassNamer.SidedClassNamer;
43import cuchaz.enigma.mapping.ClassEntry; 39import cuchaz.enigma.mapping.ClassEntry;
44import cuchaz.enigma.mapping.ClassMapping; 40import cuchaz.enigma.mapping.ClassMapping;
45import cuchaz.enigma.mapping.EntryFactory;
46import cuchaz.enigma.mapping.MappingParseException; 41import cuchaz.enigma.mapping.MappingParseException;
47import cuchaz.enigma.mapping.Mappings; 42import cuchaz.enigma.mapping.Mappings;
48import cuchaz.enigma.mapping.MappingsReader; 43import cuchaz.enigma.mapping.MappingsReader;
@@ -52,18 +47,23 @@ import cuchaz.enigma.mapping.MethodMapping;
52 47
53public class ClassMatcher { 48public class ClassMatcher {
54 49
55 public static void main(String[] args) throws IOException, MappingParseException { 50 public static void main(String[] args)
56 // TEMP 51 throws IOException, MappingParseException {
57 JarFile sourceJar = new JarFile(new File("input/1.8-pre3.jar")); 52
58 JarFile destJar = new JarFile(new File("input/1.8.jar")); 53 // setup files
59 File inMappingsFile = new File("../Enigma Mappings/1.8-pre3.mappings"); 54 File home = new File(System.getProperty("user.home"));
60 File outMappingsFile = new File("../Enigma Mappings/1.8.mappings"); 55 JarFile sourceJar = new JarFile(new File(home, ".minecraft/versions/1.8/1.8.jar"));
56 JarFile destJar = new JarFile(new File(home, ".minecraft/versions/1.8.3/1.8.3.jar"));
57 File inMappingsFile = new File("../Enigma Mappings/1.8.mappings");
58 File outMappingsFile = new File("../Enigma Mappings/1.8.3.mappings");
61 59
62 // define a matching to use when the automated system cannot find a match 60 // define a matching to use when the automated system cannot find a match
63 Map<String,String> fallbackMatching = Maps.newHashMap(); 61 Map<String,String> fallbackMatching = Maps.newHashMap();
62 /*
64 fallbackMatching.put("none/ayb", "none/ayf"); 63 fallbackMatching.put("none/ayb", "none/ayf");
65 fallbackMatching.put("none/ayd", "none/ayd"); 64 fallbackMatching.put("none/ayd", "none/ayd");
66 fallbackMatching.put("none/bgk", "unknown/bgk"); 65 fallbackMatching.put("none/bgk", "unknown/bgk");
66 */
67 67
68 // do the conversion 68 // do the conversion
69 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile)); 69 Mappings mappings = new MappingsReader().read(new FileReader(inMappingsFile));
@@ -77,6 +77,7 @@ public class ClassMatcher {
77 } 77 }
78 78
79 private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) { 79 private static void convertMappings(JarFile sourceJar, JarFile destJar, Mappings mappings, Map<String,String> fallbackMatching) {
80
80 // index jars 81 // index jars
81 System.out.println("Indexing source jar..."); 82 System.out.println("Indexing source jar...");
82 JarIndex sourceIndex = new JarIndex(); 83 JarIndex sourceIndex = new JarIndex();
@@ -84,48 +85,88 @@ public class ClassMatcher {
84 System.out.println("Indexing dest jar..."); 85 System.out.println("Indexing dest jar...");
85 JarIndex destIndex = new JarIndex(); 86 JarIndex destIndex = new JarIndex();
86 destIndex.indexJar(destJar, false); 87 destIndex.indexJar(destJar, false);
87 TranslatingTypeLoader sourceLoader = new TranslatingTypeLoader(sourceJar, sourceIndex);
88 TranslatingTypeLoader destLoader = new TranslatingTypeLoader(destJar, destIndex);
89 88
90 // compute the matching 89 // compute the matching
91 ClassMatching matching = computeMatching(sourceIndex, sourceLoader, destIndex, destLoader); 90 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex);
92 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> matchingIndex = matching.getIndex();
93 91
94 // get all the obf class names used in the mappings 92 // get all the obf class names used in the mappings
95 Set<String> usedClassNames = mappings.getAllObfClassNames(); 93 Set<ClassEntry> usedClasses = Sets.newHashSet();
96 Set<String> allClassNames = Sets.newHashSet(); 94 for (String className : mappings.getAllObfClassNames()) {
97 for (ClassEntry classEntry : sourceIndex.getObfClassEntries()) { 95 usedClasses.add(new ClassEntry(className));
98 allClassNames.add(classEntry.getName());
99 } 96 }
100 usedClassNames.retainAll(allClassNames); 97 System.out.println("Mappings reference " + usedClasses.size() + " classes");
101 System.out.println("Used " + usedClassNames.size() + " classes in the mappings");
102 98
103 // probabilistically match the non-uniquely-matched source classes 99 // see what the used classes map to
104 for (Map.Entry<ClassIdentity,List<ClassIdentity>> entry : matchingIndex.values()) { 100 BiMap<ClassEntry,ClassEntry> uniqueUsedMatches = HashBiMap.create();
105 ClassIdentity sourceClass = entry.getKey(); 101 Map<ClassEntry,ClassMatch> ambiguousUsedMatches = Maps.newHashMap();
106 List<ClassIdentity> destClasses = entry.getValue(); 102 Set<ClassEntry> unmatchedUsedClasses = Sets.newHashSet();
107 103 for (ClassMatch match : matching.matches()) {
108 // skip classes that are uniquely matched 104 Set<ClassEntry> matchUsedClasses = match.intersectSourceClasses(usedClasses);
109 if (destClasses.size() == 1) { 105 if (matchUsedClasses.isEmpty()) {
110 continue; 106 continue;
111 } 107 }
112 108
113 // skip classes that aren't used in the mappings 109 // classify the match
114 if (!usedClassNames.contains(sourceClass.getClassEntry().getName())) { 110 if (!match.isMatched()) {
115 continue; 111 // unmatched
112 unmatchedUsedClasses.addAll(matchUsedClasses);
113 } else {
114 if (match.isAmbiguous()) {
115 // ambiguously matched
116 for (ClassEntry matchUsedClass : matchUsedClasses) {
117 ambiguousUsedMatches.put(matchUsedClass, match);
118 }
119 } else {
120 // uniquely matched
121 uniqueUsedMatches.put(match.getUniqueSource(), match.getUniqueDest());
122 }
116 } 123 }
124 }
125
126 // get unmatched dest classes
127 Set<ClassEntry> unmatchedDestClasses = Sets.newHashSet();
128 for (ClassMatch match : matching.matches()) {
129 if (!match.isMatched()) {
130 unmatchedDestClasses.addAll(match.destClasses);
131 }
132 }
133
134 // warn about the ambiguous used matches
135 if (ambiguousUsedMatches.size() > 0) {
136 System.out.println(String.format("%d source classes have ambiguous mappings", ambiguousUsedMatches.size()));
137 List<ClassMatch> ambiguousMatchesList = Lists.newArrayList(Sets.newHashSet(ambiguousUsedMatches.values()));
138 Collections.sort(ambiguousMatchesList, new Comparator<ClassMatch>() {
139 @Override
140 public int compare(ClassMatch a, ClassMatch b) {
141 String aName = a.sourceClasses.iterator().next().getName();
142 String bName = b.sourceClasses.iterator().next().getName();
143 return aName.compareTo(bName);
144 }
145 });
146 for (ClassMatch match : ambiguousMatchesList) {
147 System.out.println("Ambiguous matching:");
148 System.out.println("\tSource: " + getClassNames(match.sourceClasses));
149 System.out.println("\tDest: " + getClassNames(match.destClasses));
150 }
151 }
152
153 // warn about unmatched used classes
154 for (ClassEntry unmatchedUsedClass : unmatchedUsedClasses) {
155 System.out.println("No exact match for source class " + unmatchedUsedClass.getClassEntry());
117 156
118 System.out.println("No exact match for source class " + sourceClass.getClassEntry()); 157 // rank all the unmatched dest classes against the used class
119 158 ClassIdentity sourceIdentity = matching.getSourceIdentifier().identify(unmatchedUsedClass);
120 // find the closest classes 159 Multimap<Integer,ClassEntry> scoredDestClasses = ArrayListMultimap.create();
121 Multimap<Integer,ClassIdentity> scoredMatches = ArrayListMultimap.create(); 160 for (ClassEntry unmatchedDestClass : unmatchedDestClasses) {
122 for (ClassIdentity c : destClasses) { 161 ClassIdentity destIdentity = matching.getDestIdentifier().identify(unmatchedDestClass);
123 scoredMatches.put(sourceClass.getMatchScore(c), c); 162 scoredDestClasses.put(sourceIdentity.getMatchScore(destIdentity), unmatchedDestClass);
124 } 163 }
125 List<Integer> scores = new ArrayList<Integer>(scoredMatches.keySet()); 164
165 List<Integer> scores = new ArrayList<Integer>(scoredDestClasses.keySet());
126 Collections.sort(scores, Collections.reverseOrder()); 166 Collections.sort(scores, Collections.reverseOrder());
127 printScoredMatches(sourceClass.getMaxMatchScore(), scores, scoredMatches); 167 printScoredMatches(sourceIdentity.getMaxMatchScore(), scores, scoredDestClasses);
128 168
169 /* TODO: re-enable auto-pick logic
129 // does the best match have a non-zero score and the same name? 170 // does the best match have a non-zero score and the same name?
130 int bestScore = scores.get(0); 171 int bestScore = scores.get(0);
131 Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore); 172 Collection<ClassIdentity> bestMatches = scoredMatches.get(bestScore);
@@ -138,85 +179,45 @@ public class ClassMatcher {
138 destClasses.add(bestMatch); 179 destClasses.add(bestMatch);
139 } 180 }
140 } 181 }
182 */
141 } 183 }
142 184
143 // group the matching into unique and non-unique matches 185 // bail if there were unmatched classes
144 BiMap<String,String> matchedClassNames = HashBiMap.create(); 186 if (!unmatchedUsedClasses.isEmpty()) {
145 Set<String> unmatchedSourceClassNames = Sets.newHashSet(); 187 throw new Error("There were " + unmatchedUsedClasses.size() + " unmatched classes!");
146 for (String className : usedClassNames) {
147 // is there a match for this class?
148 Map.Entry<ClassIdentity,List<ClassIdentity>> entry = matchingIndex.get(className);
149 ClassIdentity sourceClass = entry.getKey();
150 List<ClassIdentity> matches = entry.getValue();
151
152 if (matches.size() == 1) {
153 // unique match! We're good to go!
154 matchedClassNames.put(sourceClass.getClassEntry().getName(), matches.get(0).getClassEntry().getName());
155 } else {
156 // no match, check the fallback matching
157 String fallbackMatch = fallbackMatching.get(className);
158 if (fallbackMatch != null) {
159 matchedClassNames.put(sourceClass.getClassEntry().getName(), fallbackMatch);
160 } else {
161 unmatchedSourceClassNames.add(className);
162 }
163 }
164 }
165
166 // report unmatched classes
167 if (!unmatchedSourceClassNames.isEmpty()) {
168 System.err.println("ERROR: there were unmatched classes!");
169 for (String className : unmatchedSourceClassNames) {
170 System.err.println("\t" + className);
171 }
172 return;
173 }
174
175 // get the class name changes from the matched class names
176 Map<String,String> classChanges = Maps.newHashMap();
177 for (Map.Entry<String,String> entry : matchedClassNames.entrySet()) {
178 if (!entry.getKey().equals(entry.getValue())) {
179 classChanges.put(entry.getKey(), entry.getValue());
180 System.out.println(String.format("Class change: %s -> %s", entry.getKey(), entry.getValue()));
181 /* DEBUG
182 System.out.println(String.format("\n%s\n%s",
183 new ClassIdentity(sourceLoader.loadClass(entry.getKey()), null, sourceIndex, false, false),
184 new ClassIdentity( destLoader.loadClass(entry.getValue()), null, destIndex, false, false)
185 ));
186 */
187 }
188 } 188 }
189 189
190 // sort the changes so classes are renamed in the correct order 190 // sort the changes so classes are renamed in the correct order
191 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b 191 // ie. if we have the mappings a->b, b->c, we have to apply b->c before a->b
192 LinkedHashMap<String,String> orderedClassChanges = Maps.newLinkedHashMap(); 192 BiMap<ClassEntry,ClassEntry> unsortedChanges = HashBiMap.create(uniqueUsedMatches);
193 int numChangesLeft = classChanges.size(); 193 LinkedHashMap<ClassEntry,ClassEntry> sortedChanges = Maps.newLinkedHashMap();
194 while (!classChanges.isEmpty()) { 194 int numChangesLeft = unsortedChanges.size();
195 Iterator<Map.Entry<String,String>> iter = classChanges.entrySet().iterator(); 195 while (!unsortedChanges.isEmpty()) {
196 Iterator<Map.Entry<ClassEntry,ClassEntry>> iter = unsortedChanges.entrySet().iterator();
196 while (iter.hasNext()) { 197 while (iter.hasNext()) {
197 Map.Entry<String,String> entry = iter.next(); 198 Map.Entry<ClassEntry,ClassEntry> change = iter.next();
198 if (classChanges.get(entry.getValue()) == null) { 199 if (unsortedChanges.containsKey(change.getValue())) {
199 orderedClassChanges.put(entry.getKey(), entry.getValue()); 200 sortedChanges.put(change.getKey(), change.getValue());
200 iter.remove(); 201 iter.remove();
201 } 202 }
202 } 203 }
203 204
204 // did we remove any changes? 205 // did we remove any changes?
205 if (numChangesLeft - classChanges.size() > 0) { 206 if (numChangesLeft - unsortedChanges.size() > 0) {
206 // keep going 207 // keep going
207 numChangesLeft = classChanges.size(); 208 numChangesLeft = unsortedChanges.size();
208 } else { 209 } else {
209 // can't sort anymore. There must be a loop 210 // can't sort anymore. There must be a loop
210 break; 211 break;
211 } 212 }
212 } 213 }
213 if (classChanges.size() > 0) { 214 if (!unsortedChanges.isEmpty()) {
214 throw new Error(String.format("Unable to sort %d/%d class changes!", classChanges.size(), matchedClassNames.size())); 215 throw new Error(String.format("Unable to sort %d/%d class changes!", unsortedChanges.size(), uniqueUsedMatches.size()));
215 } 216 }
216 217
217 // convert the mappings in the correct class order 218 // convert the mappings in the correct class order
218 for (Map.Entry<String,String> entry : orderedClassChanges.entrySet()) { 219 for (Map.Entry<ClassEntry,ClassEntry> entry : sortedChanges.entrySet()) {
219 mappings.renameObfClass(entry.getKey(), entry.getValue()); 220 mappings.renameObfClass(entry.getKey().getName(), entry.getValue().getName());
220 } 221 }
221 222
222 // check the method matches 223 // check the method matches
@@ -238,6 +239,7 @@ public class ClassMatcher {
238 if (!destIndex.containsObfBehavior(methodEntry)) { 239 if (!destIndex.containsObfBehavior(methodEntry)) {
239 System.err.println("WARNING: method doesn't match: " + methodEntry); 240 System.err.println("WARNING: method doesn't match: " + methodEntry);
240 241
242 /* TODO: show methods if needed
241 // show the available methods 243 // show the available methods
242 System.err.println("\tAvailable dest methods:"); 244 System.err.println("\tAvailable dest methods:");
243 CtClass c = destLoader.loadClass(classMapping.getObfFullName()); 245 CtClass c = destLoader.loadClass(classMapping.getObfFullName());
@@ -250,6 +252,7 @@ public class ClassMatcher {
250 for (CtBehavior behavior : c.getDeclaredBehaviors()) { 252 for (CtBehavior behavior : c.getDeclaredBehaviors()) {
251 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior)); 253 System.err.println("\t\t" + EntryFactory.getBehaviorEntry(behavior));
252 } 254 }
255 */
253 } 256 }
254 } 257 }
255 } 258 }
@@ -257,125 +260,72 @@ public class ClassMatcher {
257 System.out.println("Done!"); 260 System.out.println("Done!");
258 } 261 }
259 262
260 public static ClassMatching computeMatching(JarIndex sourceIndex, TranslatingTypeLoader sourceLoader, JarIndex destIndex, TranslatingTypeLoader destLoader) { 263 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) {
261 264
262 System.out.println("Matching classes..."); 265 System.out.println("Iteratively matching classes...");
263 266
264 ClassMatching matching = null; 267 ClassMatching lastMatching = null;
268 int round = 0;
269 SidedClassNamer sourceNamer = null;
270 SidedClassNamer destNamer = null;
265 for (boolean useReferences : Arrays.asList(false, true)) { 271 for (boolean useReferences : Arrays.asList(false, true)) {
266 int numMatches = 0; 272
267 do { 273 int numUniqueMatchesLastTime = 0;
268 SidedClassNamer sourceNamer = null; 274 if (lastMatching != null) {
269 SidedClassNamer destNamer = null; 275 numUniqueMatchesLastTime = lastMatching.uniqueMatches().size();
270 if (matching != null) { 276 }
271 // build a class namer 277
272 ClassNamer namer = new ClassNamer(matching.getUniqueMatches()); 278 while (true) {
273 sourceNamer = namer.getSourceNamer(); 279
274 destNamer = namer.getDestNamer(); 280 System.out.println("Round " + (++round) + " ...");
275 281
276 // note the number of matches 282 // init the matching with identity settings
277 numMatches = matching.getUniqueMatches().size(); 283 ClassMatching matching = new ClassMatching(
278 } 284 new ClassIdentifier(sourceJar, sourceIndex, sourceNamer, useReferences),
285 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
286 );
279 287
280 // get the entries left to match 288 if (lastMatching == null) {
281 Set<ClassEntry> sourceClassEntries = Sets.newHashSet(); 289 // search all classes
282 Set<ClassEntry> destClassEntries = Sets.newHashSet(); 290 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
283 if (matching == null) {
284 sourceClassEntries.addAll(sourceIndex.getObfClassEntries());
285 destClassEntries.addAll(destIndex.getObfClassEntries());
286 matching = new ClassMatching();
287 } else { 291 } else {
288 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : matching.getAmbiguousMatches().entrySet()) { 292 // we already know about these matches
289 for (ClassIdentity c : entry.getKey()) { 293 matching.addKnownMatches(lastMatching.uniqueMatches());
290 sourceClassEntries.add(c.getClassEntry()); 294
291 matching.removeSource(c); 295 // search unmatched and ambiguously-matched classes
292 } 296 matching.match(lastMatching.unmatchedSourceClasses(), lastMatching.unmatchedDestClasses());
293 for (ClassIdentity c : entry.getValue()) { 297 for (ClassMatch match : lastMatching.ambiguousMatches()) {
294 destClassEntries.add(c.getClassEntry()); 298 matching.match(match.sourceClasses, match.destClasses);
295 matching.removeDest(c);
296 }
297 }
298 for (ClassIdentity c : matching.getUnmatchedSourceClasses()) {
299 sourceClassEntries.add(c.getClassEntry());
300 matching.removeSource(c);
301 }
302 for (ClassIdentity c : matching.getUnmatchedDestClasses()) {
303 destClassEntries.add(c.getClassEntry());
304 matching.removeDest(c);
305 } 299 }
306 } 300 }
301 System.out.println(matching);
302 BiMap<ClassEntry,ClassEntry> uniqueMatches = matching.uniqueMatches();
307 303
308 // compute a matching for the classes 304 // did we match anything new this time?
309 for (ClassEntry classEntry : sourceClassEntries) { 305 if (uniqueMatches.size() > numUniqueMatchesLastTime) {
310 CtClass c = sourceLoader.loadClass(classEntry.getName()); 306 numUniqueMatchesLastTime = uniqueMatches.size();
311 ClassIdentity sourceClass = new ClassIdentity(c, sourceNamer, sourceIndex, useReferences); 307 lastMatching = matching;
312 matching.addSource(sourceClass); 308 } else {
313 } 309 break;
314 for (ClassEntry classEntry : destClassEntries) {
315 CtClass c = destLoader.loadClass(classEntry.getName());
316 ClassIdentity destClass = new ClassIdentity(c, destNamer, destIndex, useReferences);
317 matching.matchDestClass(destClass);
318 } 310 }
319 311
320 // TEMP 312 // update the namers
321 System.out.println(matching); 313 ClassNamer namer = new ClassNamer(uniqueMatches);
322 } while (matching.getUniqueMatches().size() - numMatches > 0); 314 sourceNamer = namer.getSourceNamer();
323 } 315 destNamer = namer.getDestNamer();
324
325 // check the class matches
326 System.out.println("Checking class matches...");
327 ClassNamer namer = new ClassNamer(matching.getUniqueMatches());
328 SidedClassNamer sourceNamer = namer.getSourceNamer();
329 SidedClassNamer destNamer = namer.getDestNamer();
330 for (Map.Entry<ClassIdentity,ClassIdentity> entry : matching.getUniqueMatches().entrySet()) {
331
332 // check source
333 ClassIdentity sourceClass = entry.getKey();
334 CtClass sourceC = sourceLoader.loadClass(sourceClass.getClassEntry().getName());
335 assert (sourceC != null) : "Unable to load source class " + sourceClass.getClassEntry();
336 assert (sourceClass.matches(sourceC)) : "Source " + sourceClass + " doesn't match " + new ClassIdentity(sourceC, sourceNamer, sourceIndex, false);
337
338 // check dest
339 ClassIdentity destClass = entry.getValue();
340 CtClass destC = destLoader.loadClass(destClass.getClassEntry().getName());
341 assert (destC != null) : "Unable to load dest class " + destClass.getClassEntry();
342 assert (destClass.matches(destC)) : "Dest " + destClass + " doesn't match " + new ClassIdentity(destC, destNamer, destIndex, false);
343 }
344
345 // warn about the ambiguous matchings
346 List<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>> ambiguousMatches = new ArrayList<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>(matching.getAmbiguousMatches().entrySet());
347 Collections.sort(ambiguousMatches, new Comparator<Map.Entry<List<ClassIdentity>,List<ClassIdentity>>>() {
348 @Override
349 public int compare(Map.Entry<List<ClassIdentity>,List<ClassIdentity>> a, Map.Entry<List<ClassIdentity>,List<ClassIdentity>> b) {
350 String aName = a.getKey().get(0).getClassEntry().getName();
351 String bName = b.getKey().get(0).getClassEntry().getName();
352 return aName.compareTo(bName);
353 } 316 }
354 });
355 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : ambiguousMatches) {
356 System.out.println("Ambiguous matching:");
357 System.out.println("\tSource: " + getClassNames(entry.getKey()));
358 System.out.println("\tDest: " + getClassNames(entry.getValue()));
359 } 317 }
360 318
361 /* DEBUG 319 return lastMatching;
362 Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry = ambiguousMatches.get( 7 );
363 for (ClassIdentity c : entry.getKey()) {
364 System.out.println(c);
365 }
366 for(ClassIdentity c : entry.getKey()) {
367 System.out.println(decompile(sourceLoader, c.getClassEntry()));
368 }
369 */
370
371 return matching;
372 } 320 }
373 321
374 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassIdentity> scoredMatches) { 322 private static void printScoredMatches(int maxScore, List<Integer> scores, Multimap<Integer,ClassEntry> scoredMatches) {
375 int numScoredMatchesShown = 0; 323 int numScoredMatchesShown = 0;
376 for (int score : scores) { 324 for (int score : scores) {
377 for (ClassIdentity scoredMatch : scoredMatches.get(score)) { 325 for (ClassEntry classEntry : scoredMatches.get(score)) {
378 System.out.println(String.format("\tScore: %3d %3.0f%% %s", score, 100.0 * score / maxScore, scoredMatch.getClassEntry().getName())); 326 System.out.println(String.format("\tScore: %3d %3.0f%% %s",
327 score, 100.0 * score / maxScore, classEntry.getName()
328 ));
379 if (numScoredMatchesShown++ > 10) { 329 if (numScoredMatchesShown++ > 10) {
380 return; 330 return;
381 } 331 }
@@ -383,10 +333,10 @@ public class ClassMatcher {
383 } 333 }
384 } 334 }
385 335
386 private static List<String> getClassNames(Collection<ClassIdentity> classes) { 336 private static List<String> getClassNames(Collection<ClassEntry> classes) {
387 List<String> out = Lists.newArrayList(); 337 List<String> out = Lists.newArrayList();
388 for (ClassIdentity c : classes) { 338 for (ClassEntry c : classes) {
389 out.add(c.getClassEntry().getName()); 339 out.add(c.getName());
390 } 340 }
391 Collections.sort(out); 341 Collections.sort(out);
392 return out; 342 return out;
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
index 53b6f7f4..b94fd8bd 100644
--- a/src/cuchaz/enigma/convert/ClassMatching.java
+++ b/src/cuchaz/enigma/convert/ClassMatching.java
@@ -10,164 +10,146 @@
10 ******************************************************************************/ 10 ******************************************************************************/
11package cuchaz.enigma.convert; 11package cuchaz.enigma.convert;
12 12
13import java.util.AbstractMap;
14import java.util.ArrayList; 13import java.util.ArrayList;
15import java.util.Arrays;
16import java.util.Collection; 14import java.util.Collection;
17import java.util.List; 15import java.util.List;
18import java.util.Map; 16import java.util.Map.Entry;
17import java.util.Set;
19 18
20import com.google.common.collect.ArrayListMultimap;
21import com.google.common.collect.BiMap; 19import com.google.common.collect.BiMap;
22import com.google.common.collect.HashBiMap; 20import com.google.common.collect.HashBiMap;
23import com.google.common.collect.Lists; 21import com.google.common.collect.Lists;
24import com.google.common.collect.Maps; 22import com.google.common.collect.Sets;
25import com.google.common.collect.Multimap; 23
24import cuchaz.enigma.mapping.ClassEntry;
26 25
27public class ClassMatching { 26public class ClassMatching {
28 27
29 private Multimap<ClassIdentity,ClassIdentity> m_sourceClasses; 28 private ClassForest m_sourceClasses;
30 private Multimap<ClassIdentity,ClassIdentity> m_matchedDestClasses; 29 private ClassForest m_destClasses;
31 private List<ClassIdentity> m_unmatchedDestClasses; 30 private BiMap<ClassEntry,ClassEntry> m_knownMatches;
32
33 public ClassMatching() {
34 m_sourceClasses = ArrayListMultimap.create();
35 m_matchedDestClasses = ArrayListMultimap.create();
36 m_unmatchedDestClasses = Lists.newArrayList();
37 }
38 31
39 public void addSource(ClassIdentity c) { 32 public ClassMatching(ClassIdentifier sourceIdentifier, ClassIdentifier destIdentifier) {
40 m_sourceClasses.put(c, c); 33 m_sourceClasses = new ClassForest(sourceIdentifier);
34 m_destClasses = new ClassForest(destIdentifier);
35 m_knownMatches = HashBiMap.create();
41 } 36 }
42 37
43 public void matchDestClass(ClassIdentity destClass) { 38 public void addKnownMatches(BiMap<ClassEntry,ClassEntry> knownMatches) {
44 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(destClass); 39 m_knownMatches.putAll(knownMatches);
45 if (matchedSourceClasses.isEmpty()) {
46 // no match
47 m_unmatchedDestClasses.add(destClass);
48 } else {
49 // found a match
50 m_matchedDestClasses.put(destClass, destClass);
51
52 // DEBUG
53 ClassIdentity sourceClass = matchedSourceClasses.iterator().next();
54 assert (sourceClass.hashCode() == destClass.hashCode());
55 assert (sourceClass.equals(destClass));
56 }
57 } 40 }
58 41
59 public void removeSource(ClassIdentity sourceClass) { 42 public void match(Iterable<ClassEntry> sourceClasses, Iterable<ClassEntry> destClasses) {
60 m_sourceClasses.remove(sourceClass, sourceClass); 43 m_sourceClasses.addAll(sourceClasses);
44 m_destClasses.addAll(destClasses);
61 } 45 }
62 46
63 public void removeDest(ClassIdentity destClass) { 47 public Collection<ClassMatch> matches() {
64 m_matchedDestClasses.remove(destClass, destClass); 48 List<ClassMatch> matches = Lists.newArrayList();
65 m_unmatchedDestClasses.remove(destClass); 49 for (Entry<ClassEntry,ClassEntry> entry : m_knownMatches.entrySet()) {
50 matches.add(new ClassMatch(
51 entry.getKey(),
52 entry.getValue()
53 ));
54 }
55 for (ClassIdentity identity : m_sourceClasses.identities()) {
56 matches.add(new ClassMatch(
57 m_sourceClasses.getClasses(identity),
58 m_destClasses.getClasses(identity)
59 ));
60 }
61 for (ClassIdentity identity : m_destClasses.identities()) {
62 if (!m_sourceClasses.containsIdentity(identity)) {
63 matches.add(new ClassMatch(
64 new ArrayList<ClassEntry>(),
65 m_destClasses.getClasses(identity)
66 ));
67 }
68 }
69 return matches;
66 } 70 }
67 71
68 public List<ClassIdentity> getSourceClasses() { 72 public Collection<ClassEntry> sourceClasses() {
69 return new ArrayList<ClassIdentity>(m_sourceClasses.values()); 73 Set<ClassEntry> classes = Sets.newHashSet();
74 for (ClassMatch match : matches()) {
75 classes.addAll(match.sourceClasses);
76 }
77 return classes;
70 } 78 }
71 79
72 public List<ClassIdentity> getDestClasses() { 80 public Collection<ClassEntry> destClasses() {
73 List<ClassIdentity> classes = Lists.newArrayList(); 81 Set<ClassEntry> classes = Sets.newHashSet();
74 classes.addAll(m_matchedDestClasses.values()); 82 for (ClassMatch match : matches()) {
75 classes.addAll(m_unmatchedDestClasses); 83 classes.addAll(match.destClasses);
84 }
76 return classes; 85 return classes;
77 } 86 }
78 87
79 public BiMap<ClassIdentity,ClassIdentity> getUniqueMatches() { 88 public BiMap<ClassEntry,ClassEntry> uniqueMatches() {
80 BiMap<ClassIdentity,ClassIdentity> uniqueMatches = HashBiMap.create(); 89 BiMap<ClassEntry,ClassEntry> uniqueMatches = HashBiMap.create();
81 for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { 90 for (ClassMatch match : matches()) {
82 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass); 91 if (match.isMatched() && !match.isAmbiguous()) {
83 Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass); 92 uniqueMatches.put(match.getUniqueSource(), match.getUniqueDest());
84 if (matchedSourceClasses.size() == 1 && matchedDestClasses.size() == 1) {
85 ClassIdentity matchedSourceClass = matchedSourceClasses.iterator().next();
86 ClassIdentity matchedDestClass = matchedDestClasses.iterator().next();
87 uniqueMatches.put(matchedSourceClass, matchedDestClass);
88 } 93 }
89 } 94 }
90 return uniqueMatches; 95 return uniqueMatches;
91 } 96 }
92 97
93 public BiMap<List<ClassIdentity>,List<ClassIdentity>> getAmbiguousMatches() { 98 public Collection<ClassMatch> ambiguousMatches() {
94 BiMap<List<ClassIdentity>,List<ClassIdentity>> ambiguousMatches = HashBiMap.create(); 99 List<ClassMatch> ambiguousMatches = Lists.newArrayList();
95 for (ClassIdentity sourceClass : m_sourceClasses.keySet()) { 100 for (ClassMatch match : matches()) {
96 Collection<ClassIdentity> matchedSourceClasses = m_sourceClasses.get(sourceClass); 101 if (match.isMatched() && match.isAmbiguous()) {
97 Collection<ClassIdentity> matchedDestClasses = m_matchedDestClasses.get(sourceClass); 102 ambiguousMatches.add(match);
98 if (matchedSourceClasses.size() > 1 && matchedDestClasses.size() > 1) {
99 ambiguousMatches.put(
100 new ArrayList<ClassIdentity>(matchedSourceClasses),
101 new ArrayList<ClassIdentity>(matchedDestClasses)
102 );
103 } 103 }
104 } 104 }
105 return ambiguousMatches; 105 return ambiguousMatches;
106 } 106 }
107 107
108 public int getNumAmbiguousSourceMatches() { 108 public Collection<ClassEntry> unmatchedSourceClasses() {
109 int num = 0; 109 List<ClassEntry> classes = Lists.newArrayList();
110 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) { 110 for (ClassMatch match : matches()) {
111 num += entry.getKey().size(); 111 if (!match.isMatched() && !match.sourceClasses.isEmpty()) {
112 } 112 classes.addAll(match.sourceClasses);
113 return num; 113 }
114 }
115
116 public int getNumAmbiguousDestMatches() {
117 int num = 0;
118 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
119 num += entry.getValue().size();
120 } 114 }
121 return num; 115 return classes;
122 } 116 }
123 117
124 public List<ClassIdentity> getUnmatchedSourceClasses() { 118 public Collection<ClassEntry> unmatchedDestClasses() {
125 List<ClassIdentity> classes = Lists.newArrayList(); 119 List<ClassEntry> classes = Lists.newArrayList();
126 for (ClassIdentity sourceClass : getSourceClasses()) { 120 for (ClassMatch match : matches()) {
127 if (m_matchedDestClasses.get(sourceClass).isEmpty()) { 121 if (!match.isMatched() && !match.destClasses.isEmpty()) {
128 classes.add(sourceClass); 122 classes.addAll(match.destClasses);
129 } 123 }
130 } 124 }
131 return classes; 125 return classes;
132 } 126 }
133 127
134 public List<ClassIdentity> getUnmatchedDestClasses() { 128 public ClassIdentifier getSourceIdentifier() {
135 return new ArrayList<ClassIdentity>(m_unmatchedDestClasses); 129 return m_sourceClasses.getIdentifier();
136 } 130 }
137 131
138 public Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> getIndex() { 132 public ClassIdentifier getDestIdentifier() {
139 Map<String,Map.Entry<ClassIdentity,List<ClassIdentity>>> conversion = Maps.newHashMap(); 133 return m_destClasses.getIdentifier();
140 for (Map.Entry<ClassIdentity,ClassIdentity> entry : getUniqueMatches().entrySet()) {
141 conversion.put(
142 entry.getKey().getClassEntry().getName(),
143 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(entry.getKey(), Arrays.asList(entry.getValue()))
144 );
145 }
146 for (Map.Entry<List<ClassIdentity>,List<ClassIdentity>> entry : getAmbiguousMatches().entrySet()) {
147 for (ClassIdentity sourceClass : entry.getKey()) {
148 conversion.put(
149 sourceClass.getClassEntry().getName(),
150 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, entry.getValue())
151 );
152 }
153 }
154 for (ClassIdentity sourceClass : getUnmatchedSourceClasses()) {
155 conversion.put(
156 sourceClass.getClassEntry().getName(),
157 new AbstractMap.SimpleEntry<ClassIdentity,List<ClassIdentity>>(sourceClass, getUnmatchedDestClasses())
158 );
159 }
160 return conversion;
161 } 134 }
162 135
163 @Override 136 @Override
164 public String toString() { 137 public String toString() {
138
139 // count the ambiguous classes
140 int numAmbiguousSource = 0;
141 int numAmbiguousDest = 0;
142 for (ClassMatch match : ambiguousMatches()) {
143 numAmbiguousSource += match.sourceClasses.size();
144 numAmbiguousDest += match.destClasses.size();
145 }
146
165 StringBuilder buf = new StringBuilder(); 147 StringBuilder buf = new StringBuilder();
166 buf.append(String.format("%12s%8s%8s\n", "", "Source", "Dest")); 148 buf.append(String.format("%20s%8s%8s\n", "", "Source", "Dest"));
167 buf.append(String.format("%12s%8d%8d\n", "Classes", getSourceClasses().size(), getDestClasses().size())); 149 buf.append(String.format("%20s%8d%8d\n", "Classes", sourceClasses().size(), destClasses().size()));
168 buf.append(String.format("%12s%8d%8d\n", "Unique", getUniqueMatches().size(), getUniqueMatches().size())); 150 buf.append(String.format("%20s%8d%8d\n", "Uniquely matched", uniqueMatches().size(), uniqueMatches().size()));
169 buf.append(String.format("%12s%8d%8d\n", "Ambiguous", getNumAmbiguousSourceMatches(), getNumAmbiguousDestMatches())); 151 buf.append(String.format("%20s%8d%8d\n", "Ambiguously matched", numAmbiguousSource, numAmbiguousDest));
170 buf.append(String.format("%12s%8d%8d\n", "Unmatched", getUnmatchedSourceClasses().size(), getUnmatchedDestClasses().size())); 152 buf.append(String.format("%20s%8d%8d\n", "Unmatched", unmatchedSourceClasses().size(), unmatchedDestClasses().size()));
171 return buf.toString(); 153 return buf.toString();
172 } 154 }
173} 155}
diff --git a/src/cuchaz/enigma/convert/ClassNamer.java b/src/cuchaz/enigma/convert/ClassNamer.java
index 1b6e81c8..6423a189 100644
--- a/src/cuchaz/enigma/convert/ClassNamer.java
+++ b/src/cuchaz/enigma/convert/ClassNamer.java
@@ -15,6 +15,8 @@ import java.util.Map;
15import com.google.common.collect.BiMap; 15import com.google.common.collect.BiMap;
16import com.google.common.collect.Maps; 16import com.google.common.collect.Maps;
17 17
18import cuchaz.enigma.mapping.ClassEntry;
19
18public class ClassNamer { 20public class ClassNamer {
19 21
20 public interface SidedClassNamer { 22 public interface SidedClassNamer {
@@ -24,15 +26,15 @@ public class ClassNamer {
24 private Map<String,String> m_sourceNames; 26 private Map<String,String> m_sourceNames;
25 private Map<String,String> m_destNames; 27 private Map<String,String> m_destNames;
26 28
27 public ClassNamer(BiMap<ClassIdentity,ClassIdentity> mappings) { 29 public ClassNamer(BiMap<ClassEntry,ClassEntry> mappings) {
28 // convert the identity mappings to name maps 30 // convert the identity mappings to name maps
29 m_sourceNames = Maps.newHashMap(); 31 m_sourceNames = Maps.newHashMap();
30 m_destNames = Maps.newHashMap(); 32 m_destNames = Maps.newHashMap();
31 int i = 0; 33 int i = 0;
32 for (Map.Entry<ClassIdentity,ClassIdentity> entry : mappings.entrySet()) { 34 for (Map.Entry<ClassEntry,ClassEntry> entry : mappings.entrySet()) {
33 String name = String.format("M%04d", i++); 35 String name = String.format("M%04d", i++);
34 m_sourceNames.put(entry.getKey().getClassEntry().getName(), name); 36 m_sourceNames.put(entry.getKey().getName(), name);
35 m_destNames.put(entry.getValue().getClassEntry().getName(), name); 37 m_destNames.put(entry.getValue().getName(), name);
36 } 38 }
37 } 39 }
38 40
diff --git a/src/cuchaz/enigma/mapping/Translator.java b/src/cuchaz/enigma/mapping/Translator.java
index d9850324..d3b6e771 100644
--- a/src/cuchaz/enigma/mapping/Translator.java
+++ b/src/cuchaz/enigma/mapping/Translator.java
@@ -27,6 +27,7 @@ public class Translator {
27 public Translator() { 27 public Translator() {
28 m_direction = null; 28 m_direction = null;
29 m_classes = Maps.newHashMap(); 29 m_classes = Maps.newHashMap();
30 m_index = new TranslationIndex();
30 } 31 }
31 32
32 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) { 33 public Translator(TranslationDirection direction, Map<String,ClassMapping> classes, TranslationIndex index) {