summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar jeff2015-03-07 20:54:44 -0500
committerGravatar jeff2015-03-07 20:54:44 -0500
commitd1a041362a164e4469a4b725608c631bd0961c2e (patch)
tree211903a263b5206dacab6ed2a99e40fadac8b08c /src
parentmatch/unmatch button works (diff)
downloadenigma-d1a041362a164e4469a4b725608c631bd0961c2e.tar.gz
enigma-d1a041362a164e4469a4b725608c631bd0961c2e.tar.xz
enigma-d1a041362a164e4469a4b725608c631bd0961c2e.zip
ui improvements
Diffstat (limited to 'src')
-rw-r--r--src/cuchaz/enigma/convert/ClassForest.java8
-rw-r--r--src/cuchaz/enigma/convert/ClassIdentifier.java6
-rw-r--r--src/cuchaz/enigma/convert/ClassMatching.java12
-rw-r--r--src/cuchaz/enigma/convert/MappingsConverter.java10
-rw-r--r--src/cuchaz/enigma/convert/Matches.java2
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorClassNode.java12
-rw-r--r--src/cuchaz/enigma/gui/ClassSelectorPackageNode.java12
-rw-r--r--src/cuchaz/enigma/gui/MatchingGui.java141
8 files changed, 155 insertions, 48 deletions
diff --git a/src/cuchaz/enigma/convert/ClassForest.java b/src/cuchaz/enigma/convert/ClassForest.java
index 36731393..a5ea0568 100644
--- a/src/cuchaz/enigma/convert/ClassForest.java
+++ b/src/cuchaz/enigma/convert/ClassForest.java
@@ -24,8 +24,12 @@ public class ClassForest {
24 } 24 }
25 } 25 }
26 26
27 private void add(ClassEntry entry) { 27 public void add(ClassEntry entry) {
28 m_forest.put(m_identifier.identify(entry), entry); 28 try {
29 m_forest.put(m_identifier.identify(entry), entry);
30 } catch (ClassNotFoundException ex) {
31 throw new Error("Unable to find class " + entry.getName());
32 }
29 } 33 }
30 34
31 public Collection<ClassIdentity> identities() { 35 public Collection<ClassIdentity> identities() {
diff --git a/src/cuchaz/enigma/convert/ClassIdentifier.java b/src/cuchaz/enigma/convert/ClassIdentifier.java
index bdbf11b2..da799cd0 100644
--- a/src/cuchaz/enigma/convert/ClassIdentifier.java
+++ b/src/cuchaz/enigma/convert/ClassIdentifier.java
@@ -29,10 +29,14 @@ public class ClassIdentifier {
29 m_cache = Maps.newHashMap(); 29 m_cache = Maps.newHashMap();
30 } 30 }
31 31
32 public ClassIdentity identify(ClassEntry classEntry) { 32 public ClassIdentity identify(ClassEntry classEntry)
33 throws ClassNotFoundException {
33 ClassIdentity identity = m_cache.get(classEntry); 34 ClassIdentity identity = m_cache.get(classEntry);
34 if (identity == null) { 35 if (identity == null) {
35 CtClass c = m_loader.loadClass(classEntry.getName()); 36 CtClass c = m_loader.loadClass(classEntry.getName());
37 if (c == null) {
38 throw new ClassNotFoundException(classEntry.getName());
39 }
36 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences); 40 identity = new ClassIdentity(c, m_namer, m_index, m_useReferences);
37 m_cache.put(classEntry, identity); 41 m_cache.put(classEntry, identity);
38 } 42 }
diff --git a/src/cuchaz/enigma/convert/ClassMatching.java b/src/cuchaz/enigma/convert/ClassMatching.java
index 9f931300..d8973ac5 100644
--- a/src/cuchaz/enigma/convert/ClassMatching.java
+++ b/src/cuchaz/enigma/convert/ClassMatching.java
@@ -40,8 +40,16 @@ public class ClassMatching {
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 m_sourceClasses.addAll(sourceClasses); 43 for (ClassEntry sourceClass : sourceClasses) {
44 m_destClasses.addAll(destClasses); 44 if (!m_knownMatches.containsKey(sourceClass)) {
45 m_sourceClasses.add(sourceClass);
46 }
47 }
48 for (ClassEntry destClass : destClasses) {
49 if (!m_knownMatches.containsValue(destClass)) {
50 m_destClasses.add(destClass);
51 }
52 }
45 } 53 }
46 54
47 public Collection<ClassMatch> matches() { 55 public Collection<ClassMatch> matches() {
diff --git a/src/cuchaz/enigma/convert/MappingsConverter.java b/src/cuchaz/enigma/convert/MappingsConverter.java
index aa067d4d..f38723f7 100644
--- a/src/cuchaz/enigma/convert/MappingsConverter.java
+++ b/src/cuchaz/enigma/convert/MappingsConverter.java
@@ -45,11 +45,11 @@ public class MappingsConverter {
45 destIndex.indexJar(destJar, false); 45 destIndex.indexJar(destJar, false);
46 46
47 // compute the matching 47 // compute the matching
48 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex); 48 ClassMatching matching = computeMatching(sourceJar, sourceIndex, destJar, destIndex, null);
49 return new Matches(matching.matches()); 49 return new Matches(matching.matches());
50 } 50 }
51 51
52 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex) { 52 public static ClassMatching computeMatching(JarFile sourceJar, JarIndex sourceIndex, JarFile destJar, JarIndex destIndex, BiMap<ClassEntry,ClassEntry> knownMatches) {
53 53
54 System.out.println("Iteratively matching classes"); 54 System.out.println("Iteratively matching classes");
55 55
@@ -74,11 +74,15 @@ public class MappingsConverter {
74 new ClassIdentifier(destJar, destIndex, destNamer, useReferences) 74 new ClassIdentifier(destJar, destIndex, destNamer, useReferences)
75 ); 75 );
76 76
77 if (knownMatches != null) {
78 matching.addKnownMatches(knownMatches);
79 }
80
77 if (lastMatching == null) { 81 if (lastMatching == null) {
78 // search all classes 82 // search all classes
79 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries()); 83 matching.match(sourceIndex.getObfClassEntries(), destIndex.getObfClassEntries());
80 } else { 84 } else {
81 // we already know about these matches 85 // we already know about these matches from last time
82 matching.addKnownMatches(lastMatching.uniqueMatches()); 86 matching.addKnownMatches(lastMatching.uniqueMatches());
83 87
84 // search unmatched and ambiguously-matched classes 88 // search unmatched and ambiguously-matched classes
diff --git a/src/cuchaz/enigma/convert/Matches.java b/src/cuchaz/enigma/convert/Matches.java
index 0b00b29e..19bb155f 100644
--- a/src/cuchaz/enigma/convert/Matches.java
+++ b/src/cuchaz/enigma/convert/Matches.java
@@ -56,7 +56,7 @@ public class Matches implements Iterable<ClassMatch> {
56 m_ambiguousMatchesBySource.remove(sourceClass); 56 m_ambiguousMatchesBySource.remove(sourceClass);
57 m_unmatchedSourceClasses.remove(sourceClass); 57 m_unmatchedSourceClasses.remove(sourceClass);
58 } 58 }
59 for (ClassEntry destClass : match.sourceClasses) { 59 for (ClassEntry destClass : match.destClasses) {
60 m_matchesByDest.remove(destClass); 60 m_matchesByDest.remove(destClass);
61 m_uniqueMatches.inverse().remove(destClass); 61 m_uniqueMatches.inverse().remove(destClass);
62 m_ambiguousMatchesByDest.remove(destClass); 62 m_ambiguousMatchesByDest.remove(destClass);
diff --git a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
index b3d7b899..f0541888 100644
--- a/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
+++ b/src/cuchaz/enigma/gui/ClassSelectorClassNode.java
@@ -35,4 +35,16 @@ public class ClassSelectorClassNode extends DefaultMutableTreeNode {
35 } 35 }
36 return m_classEntry.getSimpleName(); 36 return m_classEntry.getSimpleName();
37 } 37 }
38
39 @Override
40 public boolean equals(Object other) {
41 if (other instanceof ClassSelectorClassNode) {
42 return equals((ClassSelectorClassNode)other);
43 }
44 return false;
45 }
46
47 public boolean equals(ClassSelectorClassNode other) {
48 return m_classEntry.equals(other.m_classEntry);
49 }
38} 50}
diff --git a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
index 451d3809..5685abbf 100644
--- a/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
+++ b/src/cuchaz/enigma/gui/ClassSelectorPackageNode.java
@@ -30,4 +30,16 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
30 public String toString() { 30 public String toString() {
31 return m_packageName; 31 return m_packageName;
32 } 32 }
33
34 @Override
35 public boolean equals(Object other) {
36 if (other instanceof ClassSelectorPackageNode) {
37 return equals((ClassSelectorPackageNode)other);
38 }
39 return false;
40 }
41
42 public boolean equals(ClassSelectorPackageNode other) {
43 return m_packageName.equals(other.m_packageName);
44 }
33} 45}
diff --git a/src/cuchaz/enigma/gui/MatchingGui.java b/src/cuchaz/enigma/gui/MatchingGui.java
index e2d517ee..04dbd7a7 100644
--- a/src/cuchaz/enigma/gui/MatchingGui.java
+++ b/src/cuchaz/enigma/gui/MatchingGui.java
@@ -11,10 +11,12 @@ import java.util.Arrays;
11import java.util.Collection; 11import java.util.Collection;
12import java.util.Collections; 12import java.util.Collections;
13import java.util.List; 13import java.util.List;
14import java.util.Map;
14 15
15import javax.swing.BoxLayout; 16import javax.swing.BoxLayout;
16import javax.swing.ButtonGroup; 17import javax.swing.ButtonGroup;
17import javax.swing.JButton; 18import javax.swing.JButton;
19import javax.swing.JCheckBox;
18import javax.swing.JEditorPane; 20import javax.swing.JEditorPane;
19import javax.swing.JFrame; 21import javax.swing.JFrame;
20import javax.swing.JLabel; 22import javax.swing.JLabel;
@@ -22,9 +24,12 @@ import javax.swing.JPanel;
22import javax.swing.JRadioButton; 24import javax.swing.JRadioButton;
23import javax.swing.JScrollPane; 25import javax.swing.JScrollPane;
24import javax.swing.JSplitPane; 26import javax.swing.JSplitPane;
27import javax.swing.SwingConstants;
25import javax.swing.WindowConstants; 28import javax.swing.WindowConstants;
29import javax.swing.tree.TreePath;
26 30
27import com.beust.jcommander.internal.Lists; 31import com.beust.jcommander.internal.Lists;
32import com.beust.jcommander.internal.Maps;
28import com.google.common.collect.ArrayListMultimap; 33import com.google.common.collect.ArrayListMultimap;
29import com.google.common.collect.BiMap; 34import com.google.common.collect.BiMap;
30import com.google.common.collect.Multimap; 35import com.google.common.collect.Multimap;
@@ -34,6 +39,7 @@ import cuchaz.enigma.Deobfuscator;
34import cuchaz.enigma.convert.ClassIdentifier; 39import cuchaz.enigma.convert.ClassIdentifier;
35import cuchaz.enigma.convert.ClassIdentity; 40import cuchaz.enigma.convert.ClassIdentity;
36import cuchaz.enigma.convert.ClassMatch; 41import cuchaz.enigma.convert.ClassMatch;
42import cuchaz.enigma.convert.ClassMatching;
37import cuchaz.enigma.convert.ClassNamer; 43import cuchaz.enigma.convert.ClassNamer;
38import cuchaz.enigma.convert.MappingsConverter; 44import cuchaz.enigma.convert.MappingsConverter;
39import cuchaz.enigma.convert.Matches; 45import cuchaz.enigma.convert.Matches;
@@ -95,6 +101,8 @@ public class MatchingGui {
95 private JLabel m_sourceClassLabel; 101 private JLabel m_sourceClassLabel;
96 private JLabel m_destClassLabel; 102 private JLabel m_destClassLabel;
97 private JButton m_matchButton; 103 private JButton m_matchButton;
104 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
105 private JCheckBox m_advanceCheck;
98 106
99 private Matches m_matches; 107 private Matches m_matches;
100 private Deobfuscator m_sourceDeobfuscator; 108 private Deobfuscator m_sourceDeobfuscator;
@@ -133,8 +141,11 @@ public class MatchingGui {
133 } 141 }
134 }; 142 };
135 ButtonGroup sourceTypeButtons = new ButtonGroup(); 143 ButtonGroup sourceTypeButtons = new ButtonGroup();
144 m_sourceTypeButtons = Maps.newHashMap();
136 for (SourceType sourceType : SourceType.values()) { 145 for (SourceType sourceType : SourceType.values()) {
137 sourceTypePanel.add(sourceType.newRadio(sourceTypeListener, sourceTypeButtons)); 146 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
147 m_sourceTypeButtons.put(sourceType, button);
148 sourceTypePanel.add(button);
138 } 149 }
139 150
140 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator); 151 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
@@ -164,6 +175,15 @@ public class MatchingGui {
164 JScrollPane destScroller = new JScrollPane(m_destClasses); 175 JScrollPane destScroller = new JScrollPane(m_destClasses);
165 destPanel.add(destScroller); 176 destPanel.add(destScroller);
166 177
178 JButton autoMatchButton = new JButton("AutoMatch");
179 autoMatchButton.addActionListener(new ActionListener() {
180 @Override
181 public void actionPerformed(ActionEvent event) {
182 autoMatch();
183 }
184 });
185 destPanel.add(autoMatchButton);
186
167 // init source panels 187 // init source panels
168 DefaultSyntaxKit.initKit(); 188 DefaultSyntaxKit.initKit();
169 m_sourceReader = new JEditorPane(); 189 m_sourceReader = new JEditorPane();
@@ -188,18 +208,21 @@ public class MatchingGui {
188 bottomPanel.setLayout(new FlowLayout()); 208 bottomPanel.setLayout(new FlowLayout());
189 209
190 m_sourceClassLabel = new JLabel(); 210 m_sourceClassLabel = new JLabel();
191 m_sourceClassLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); 211 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
192 m_sourceClassLabel.setPreferredSize(new Dimension(300, 24)); 212 m_sourceClassLabel.setPreferredSize(new Dimension(300, 24));
193 m_destClassLabel = new JLabel(); 213 m_destClassLabel = new JLabel();
194 m_destClassLabel.setAlignmentX(JLabel.CENTER_ALIGNMENT); 214 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
195 m_destClassLabel.setPreferredSize(new Dimension(300, 24)); 215 m_destClassLabel.setPreferredSize(new Dimension(300, 24));
196 216
197 m_matchButton = new JButton(); 217 m_matchButton = new JButton();
198 m_matchButton.setPreferredSize(new Dimension(140, 24)); 218 m_matchButton.setPreferredSize(new Dimension(140, 24));
199 219
220 m_advanceCheck = new JCheckBox("Advance to next likely match");
221
200 bottomPanel.add(m_sourceClassLabel); 222 bottomPanel.add(m_sourceClassLabel);
201 bottomPanel.add(m_matchButton); 223 bottomPanel.add(m_matchButton);
202 bottomPanel.add(m_destClassLabel); 224 bottomPanel.add(m_destClassLabel);
225 bottomPanel.add(m_advanceCheck);
203 pane.add(bottomPanel, BorderLayout.SOUTH); 226 pane.add(bottomPanel, BorderLayout.SOUTH);
204 227
205 // show the frame 228 // show the frame
@@ -210,7 +233,7 @@ public class MatchingGui {
210 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 233 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
211 234
212 // init state 235 // init state
213 updateMappings(); 236 updateDestMappings();
214 setSourceType(SourceType.getDefault()); 237 setSourceType(SourceType.getDefault());
215 updateMatchButton(); 238 updateMatchButton();
216 m_saveListener = null; 239 m_saveListener = null;
@@ -220,7 +243,7 @@ public class MatchingGui {
220 m_saveListener = val; 243 m_saveListener = val;
221 } 244 }
222 245
223 private void updateMappings() { 246 private void updateDestMappings() {
224 m_destDeobfuscator.setMappings(MappingsConverter.newMappings( 247 m_destDeobfuscator.setMappings(MappingsConverter.newMappings(
225 m_matches, 248 m_matches,
226 m_sourceDeobfuscator.getMappings(), 249 m_sourceDeobfuscator.getMappings(),
@@ -230,9 +253,18 @@ public class MatchingGui {
230 } 253 }
231 254
232 protected void setSourceType(SourceType val) { 255 protected void setSourceType(SourceType val) {
256
233 // show the source classes 257 // show the source classes
234 m_sourceType = val; 258 m_sourceType = val;
235 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator)); 259 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator));
260
261 // update counts
262 for (SourceType sourceType : SourceType.values()) {
263 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
264 sourceType.name(),
265 sourceType.getSourceClasses(m_matches).size()
266 ));
267 }
236 } 268 }
237 269
238 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) { 270 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
@@ -296,31 +328,37 @@ public class MatchingGui {
296 namer.getDestNamer(), true 328 namer.getDestNamer(), true
297 ); 329 );
298 330
299 // rank all the unmatched dest classes against the source class 331 try {
300 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass); 332
301 Multimap<Float,ClassEntry> scoredDestClasses = ArrayListMultimap.create(); 333 // rank all the unmatched dest classes against the source class
302 for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) { 334 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
303 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass); 335 Multimap<Float,ClassEntry> scoredDestClasses = ArrayListMultimap.create();
304 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity)) 336 for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) {
305 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore()); 337 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
306 scoredDestClasses.put(score, unmatchedDestClass); 338 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
307 } 339 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
340 scoredDestClasses.put(score, unmatchedDestClass);
341 }
308 342
309 // sort by scores 343 // sort by scores
310 List<Float> scores = new ArrayList<Float>(scoredDestClasses.keySet()); 344 List<Float> scores = new ArrayList<Float>(scoredDestClasses.keySet());
311 Collections.sort(scores, Collections.reverseOrder()); 345 Collections.sort(scores, Collections.reverseOrder());
312 346
313 // collect the scored classes in order 347 // collect the scored classes in order
314 List<ClassEntry> scoredClasses = Lists.newArrayList(); 348 List<ClassEntry> scoredClasses = Lists.newArrayList();
315 for (float score : scores) { 349 for (float score : scores) {
316 for (ClassEntry classEntry : scoredDestClasses.get(score)) { 350 for (ClassEntry classEntry : scoredDestClasses.get(score)) {
317 scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%.0f%% ", score))); 351 scoredClasses.add(new DecoratedClassEntry(classEntry, String.format("%2.0f%% ", score)));
318 if (scoredClasses.size() > 10) { 352 if (scoredClasses.size() > 10) {
319 return scoredClasses; 353 return scoredClasses;
354 }
320 } 355 }
321 } 356 }
357 return scoredClasses;
358
359 } catch (ClassNotFoundException ex) {
360 throw new Error("Unable to find class " + ex.getMessage());
322 } 361 }
323 return scoredClasses;
324 } 362 }
325 363
326 protected void setDestClass(ClassEntry classEntry) { 364 protected void setDestClass(ClassEntry classEntry) {
@@ -419,15 +457,18 @@ public class MatchingGui {
419 // add them as matched classes 457 // add them as matched classes
420 m_matches.add(new ClassMatch(obfSource, obfDest)); 458 m_matches.add(new ClassMatch(obfSource, obfDest));
421 459
422 // TEMP 460 // remember where we were in the source tree
423 System.out.println("Match: " + obfSource + " <-> " + obfDest); 461 TreePath path = m_sourceClasses.getSelectionPath();
424 462
425 //save(); 463 save();
426 updateMappings(); 464 updateMatches();
427 setDestClass(null); 465
428 m_destClasses.setClasses(null); 466 // put the tree back to where it was
429 updateMatchButton(); 467 m_sourceClasses.expandPath(path);
430 setSourceType(m_sourceType); 468
469 if (m_advanceCheck.isSelected()) {
470
471 }
431 } 472 }
432 473
433 private void onUnmatchClick() { 474 private void onUnmatchClick() {
@@ -439,11 +480,12 @@ public class MatchingGui {
439 m_matches.removeSource(obfSource); 480 m_matches.removeSource(obfSource);
440 m_matches.add(new ClassMatch(obfSource, null)); 481 m_matches.add(new ClassMatch(obfSource, null));
441 482
442 // TEMP 483 save();
443 System.out.println("Unmatch: " + obfSource + " <-> " + m_destDeobfuscator.obfuscateEntry(m_destClass)); 484 updateMatches();
444 485 }
445 //save(); 486
446 updateMappings(); 487 private void updateMatches() {
488 updateDestMappings();
447 setDestClass(null); 489 setDestClass(null);
448 m_destClasses.setClasses(null); 490 m_destClasses.setClasses(null);
449 updateMatchButton(); 491 updateMatchButton();
@@ -455,4 +497,25 @@ public class MatchingGui {
455 m_saveListener.save(m_matches); 497 m_saveListener.save(m_matches);
456 } 498 }
457 } 499 }
500
501 private void autoMatch() {
502
503 System.out.println("Automatching...");
504
505 // compute a new matching
506 ClassMatching matching = MappingsConverter.computeMatching(
507 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
508 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
509 m_matches.getUniqueMatches()
510 );
511 Matches newMatches = new Matches(matching.matches());
512 System.out.println(String.format("Automatch found %d new matches",
513 newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size()
514 ));
515
516 // update the current matches
517 m_matches = newMatches;
518 save();
519 updateMatches();
520 }
458} 521}