summaryrefslogtreecommitdiff
path: root/src/cuchaz/enigma/gui/ClassMatchingGui.java
diff options
context:
space:
mode:
authorGravatar jeff2015-03-09 11:03:35 -0400
committerGravatar jeff2015-03-09 11:03:35 -0400
commit93c053803404382163d728e044d6dd49e76a5007 (patch)
treea5e8c610185f83550d6ae5df158fe6b619ca096c /src/cuchaz/enigma/gui/ClassMatchingGui.java
parentmore tweaks, improvements, and bug fixes (diff)
downloadenigma-fork-93c053803404382163d728e044d6dd49e76a5007.tar.gz
enigma-fork-93c053803404382163d728e044d6dd49e76a5007.tar.xz
enigma-fork-93c053803404382163d728e044d6dd49e76a5007.zip
add tracking for mismatched fields/methods
Diffstat (limited to 'src/cuchaz/enigma/gui/ClassMatchingGui.java')
-rw-r--r--src/cuchaz/enigma/gui/ClassMatchingGui.java661
1 files changed, 661 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/gui/ClassMatchingGui.java b/src/cuchaz/enigma/gui/ClassMatchingGui.java
new file mode 100644
index 0000000..ff7cda9
--- /dev/null
+++ b/src/cuchaz/enigma/gui/ClassMatchingGui.java
@@ -0,0 +1,661 @@
1package cuchaz.enigma.gui;
2
3import java.awt.BorderLayout;
4import java.awt.Container;
5import java.awt.Dimension;
6import java.awt.FlowLayout;
7import java.awt.event.ActionEvent;
8import java.awt.event.ActionListener;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.List;
12import java.util.Map;
13
14import javax.swing.BoxLayout;
15import javax.swing.ButtonGroup;
16import javax.swing.JButton;
17import javax.swing.JCheckBox;
18import javax.swing.JEditorPane;
19import javax.swing.JFrame;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JRadioButton;
23import javax.swing.JScrollPane;
24import javax.swing.JSplitPane;
25import javax.swing.SwingConstants;
26import javax.swing.WindowConstants;
27
28import com.beust.jcommander.internal.Lists;
29import com.beust.jcommander.internal.Maps;
30import com.google.common.collect.BiMap;
31import com.strobel.decompiler.languages.java.ast.CompilationUnit;
32
33import cuchaz.enigma.Constants;
34import cuchaz.enigma.Deobfuscator;
35import cuchaz.enigma.analysis.SourceIndex;
36import cuchaz.enigma.analysis.Token;
37import cuchaz.enigma.convert.ClassIdentifier;
38import cuchaz.enigma.convert.ClassIdentity;
39import cuchaz.enigma.convert.ClassMatch;
40import cuchaz.enigma.convert.ClassMatching;
41import cuchaz.enigma.convert.ClassNamer;
42import cuchaz.enigma.convert.MappingsConverter;
43import cuchaz.enigma.convert.Matches;
44import cuchaz.enigma.gui.ClassSelector.ClassSelectionListener;
45import cuchaz.enigma.mapping.ClassEntry;
46import cuchaz.enigma.mapping.Entry;
47import cuchaz.enigma.mapping.Mappings;
48import cuchaz.enigma.mapping.MappingsChecker;
49import de.sciss.syntaxpane.DefaultSyntaxKit;
50
51
52public class ClassMatchingGui {
53
54 private static enum SourceType {
55 Matched {
56
57 @Override
58 public Collection<ClassEntry> getSourceClasses(Matches matches) {
59 return matches.getUniqueMatches().keySet();
60 }
61 },
62 Unmatched {
63
64 @Override
65 public Collection<ClassEntry> getSourceClasses(Matches matches) {
66 return matches.getUnmatchedSourceClasses();
67 }
68 },
69 Ambiguous {
70
71 @Override
72 public Collection<ClassEntry> getSourceClasses(Matches matches) {
73 return matches.getAmbiguouslyMatchedSourceClasses();
74 }
75 };
76
77 public JRadioButton newRadio(ActionListener listener, ButtonGroup group) {
78 JRadioButton button = new JRadioButton(name(), this == getDefault());
79 button.setActionCommand(name());
80 button.addActionListener(listener);
81 group.add(button);
82 return button;
83 }
84
85 public abstract Collection<ClassEntry> getSourceClasses(Matches matches);
86
87 public static SourceType getDefault() {
88 return values()[0];
89 }
90 }
91
92 public static interface SaveListener {
93 public void save(Matches matches);
94 }
95
96 // controls
97 private JFrame m_frame;
98 private ClassSelector m_sourceClasses;
99 private ClassSelector m_destClasses;
100 private JEditorPane m_sourceReader;
101 private JEditorPane m_destReader;
102 private JLabel m_sourceClassLabel;
103 private JLabel m_destClassLabel;
104 private JButton m_matchButton;
105 private Map<SourceType,JRadioButton> m_sourceTypeButtons;
106 private JCheckBox m_advanceCheck;
107 private SelectionHighlightPainter m_selectionHighlightPainter;
108
109 private Matches m_matches;
110 private Deobfuscator m_sourceDeobfuscator;
111 private Deobfuscator m_destDeobfuscator;
112 private ClassEntry m_sourceClass;
113 private ClassEntry m_destClass;
114 private SourceType m_sourceType;
115 private SaveListener m_saveListener;
116
117 public ClassMatchingGui(Matches matches, Deobfuscator sourceDeobfuscator, Deobfuscator destDeobfuscator) {
118
119 m_matches = matches;
120 m_sourceDeobfuscator = sourceDeobfuscator;
121 m_destDeobfuscator = destDeobfuscator;
122
123 // init frame
124 m_frame = new JFrame(Constants.Name);
125 final Container pane = m_frame.getContentPane();
126 pane.setLayout(new BorderLayout());
127
128 // init source side
129 JPanel sourcePanel = new JPanel();
130 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.PAGE_AXIS));
131 sourcePanel.setPreferredSize(new Dimension(200, 0));
132 pane.add(sourcePanel, BorderLayout.WEST);
133 sourcePanel.add(new JLabel("Source Classes"));
134
135 // init source type radios
136 JPanel sourceTypePanel = new JPanel();
137 sourcePanel.add(sourceTypePanel);
138 sourceTypePanel.setLayout(new BoxLayout(sourceTypePanel, BoxLayout.PAGE_AXIS));
139 ActionListener sourceTypeListener = new ActionListener() {
140 @Override
141 public void actionPerformed(ActionEvent event) {
142 setSourceType(SourceType.valueOf(event.getActionCommand()));
143 }
144 };
145 ButtonGroup sourceTypeButtons = new ButtonGroup();
146 m_sourceTypeButtons = Maps.newHashMap();
147 for (SourceType sourceType : SourceType.values()) {
148 JRadioButton button = sourceType.newRadio(sourceTypeListener, sourceTypeButtons);
149 m_sourceTypeButtons.put(sourceType, button);
150 sourceTypePanel.add(button);
151 }
152
153 m_sourceClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
154 m_sourceClasses.setListener(new ClassSelectionListener() {
155 @Override
156 public void onSelectClass(ClassEntry classEntry) {
157 setSourceClass(classEntry);
158 }
159 });
160 JScrollPane sourceScroller = new JScrollPane(m_sourceClasses);
161 sourcePanel.add(sourceScroller);
162
163 // init dest side
164 JPanel destPanel = new JPanel();
165 destPanel.setLayout(new BoxLayout(destPanel, BoxLayout.PAGE_AXIS));
166 destPanel.setPreferredSize(new Dimension(200, 0));
167 pane.add(destPanel, BorderLayout.WEST);
168 destPanel.add(new JLabel("Destination Classes"));
169
170 m_destClasses = new ClassSelector(ClassSelector.DeobfuscatedClassEntryComparator);
171 m_destClasses.setListener(new ClassSelectionListener() {
172 @Override
173 public void onSelectClass(ClassEntry classEntry) {
174 setDestClass(classEntry);
175 }
176 });
177 JScrollPane destScroller = new JScrollPane(m_destClasses);
178 destPanel.add(destScroller);
179
180 JButton autoMatchButton = new JButton("AutoMatch");
181 autoMatchButton.addActionListener(new ActionListener() {
182 @Override
183 public void actionPerformed(ActionEvent event) {
184 autoMatch();
185 }
186 });
187 destPanel.add(autoMatchButton);
188
189 // init source panels
190 DefaultSyntaxKit.initKit();
191 m_sourceReader = makeReader();
192 m_destReader = makeReader();
193
194 // init all the splits
195 JSplitPane splitLeft = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, sourcePanel, new JScrollPane(m_sourceReader));
196 splitLeft.setResizeWeight(0); // let the right side take all the slack
197 JSplitPane splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, new JScrollPane(m_destReader), destPanel);
198 splitRight.setResizeWeight(1); // let the left side take all the slack
199 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, splitLeft, splitRight);
200 splitCenter.setResizeWeight(0.5); // resize 50:50
201 pane.add(splitCenter, BorderLayout.CENTER);
202 splitCenter.resetToPreferredSizes();
203
204 // init bottom panel
205 JPanel bottomPanel = new JPanel();
206 bottomPanel.setLayout(new FlowLayout());
207
208 m_sourceClassLabel = new JLabel();
209 m_sourceClassLabel.setHorizontalAlignment(SwingConstants.RIGHT);
210 m_sourceClassLabel.setPreferredSize(new Dimension(300, 24));
211 m_destClassLabel = new JLabel();
212 m_destClassLabel.setHorizontalAlignment(SwingConstants.LEFT);
213 m_destClassLabel.setPreferredSize(new Dimension(300, 24));
214
215 m_matchButton = new JButton();
216 m_matchButton.setPreferredSize(new Dimension(140, 24));
217
218 m_advanceCheck = new JCheckBox("Advance to next likely match");
219 m_advanceCheck.addActionListener(new ActionListener() {
220 @Override
221 public void actionPerformed(ActionEvent event) {
222 if (m_advanceCheck.isSelected()) {
223 advance();
224 }
225 }
226 });
227
228 bottomPanel.add(m_sourceClassLabel);
229 bottomPanel.add(m_matchButton);
230 bottomPanel.add(m_destClassLabel);
231 bottomPanel.add(m_advanceCheck);
232 pane.add(bottomPanel, BorderLayout.SOUTH);
233
234 // show the frame
235 pane.doLayout();
236 m_frame.setSize(1024, 576);
237 m_frame.setMinimumSize(new Dimension(640, 480));
238 m_frame.setVisible(true);
239 m_frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
240
241 m_selectionHighlightPainter = new SelectionHighlightPainter();
242
243 // init state
244 updateDestMappings();
245 setSourceType(SourceType.getDefault());
246 updateMatchButton();
247 m_saveListener = null;
248 }
249
250 private JEditorPane makeReader() {
251
252 JEditorPane reader = new JEditorPane();
253 reader.setEditable(false);
254 reader.setContentType("text/java");
255
256 // turn off token highlighting (it's wrong most of the time anyway...)
257 DefaultSyntaxKit kit = (DefaultSyntaxKit)reader.getEditorKit();
258 kit.toggleComponent(reader, "de.sciss.syntaxpane.components.TokenMarker");
259
260 return reader;
261 }
262
263 public void setSaveListener(SaveListener val) {
264 m_saveListener = val;
265 }
266
267 private void updateDestMappings() {
268
269 Mappings newMappings = MappingsConverter.newMappings(
270 m_matches,
271 m_sourceDeobfuscator.getMappings(),
272 m_sourceDeobfuscator,
273 m_destDeobfuscator
274 );
275
276 // look for dropped mappings
277 MappingsChecker checker = new MappingsChecker(m_destDeobfuscator.getJarIndex());
278 checker.dropBrokenMappings(newMappings);
279
280 // count them
281 int numDroppedFields = checker.getDroppedFieldMappings().size();
282 int numDroppedMethods = checker.getDroppedMethodMappings().size();
283 System.out.println(String.format(
284 "%d mappings from matched classes don't match the dest jar:\n\t%5d fields\n\t%5d methods",
285 numDroppedFields + numDroppedMethods,
286 numDroppedFields,
287 numDroppedMethods
288 ));
289
290 m_destDeobfuscator.setMappings(newMappings);
291 }
292
293 protected void setSourceType(SourceType val) {
294
295 // show the source classes
296 m_sourceType = val;
297 m_sourceClasses.setClasses(deobfuscateClasses(m_sourceType.getSourceClasses(m_matches), m_sourceDeobfuscator));
298
299 // update counts
300 for (SourceType sourceType : SourceType.values()) {
301 m_sourceTypeButtons.get(sourceType).setText(String.format("%s (%d)",
302 sourceType.name(),
303 sourceType.getSourceClasses(m_matches).size()
304 ));
305 }
306 }
307
308 private Collection<ClassEntry> deobfuscateClasses(Collection<ClassEntry> in, Deobfuscator deobfuscator) {
309 List<ClassEntry> out = Lists.newArrayList();
310 for (ClassEntry entry : in) {
311
312 ClassEntry deobf = deobfuscator.deobfuscateEntry(entry);
313
314 // make sure we preserve any scores
315 if (entry instanceof ScoredClassEntry) {
316 deobf = new ScoredClassEntry(deobf, ((ScoredClassEntry)entry).getScore());
317 }
318
319 out.add(deobf);
320 }
321 return out;
322 }
323
324 protected void setSourceClass(ClassEntry classEntry) {
325
326 Runnable onGetDestClasses = null;
327 if (m_advanceCheck.isSelected()) {
328 onGetDestClasses = new Runnable() {
329 @Override
330 public void run() {
331 pickBestDestClass();
332 }
333 };
334 }
335
336 setSourceClass(classEntry, onGetDestClasses);
337 }
338
339 protected void setSourceClass(ClassEntry classEntry, final Runnable onGetDestClasses) {
340
341 // update the current source class
342 m_sourceClass = classEntry;
343 m_sourceClassLabel.setText(m_sourceClass != null ? m_sourceClass.getName() : "");
344
345 if (m_sourceClass != null) {
346
347 // show the dest class(es)
348 ClassMatch match = m_matches.getMatchBySource(m_sourceDeobfuscator.obfuscateEntry(m_sourceClass));
349 assert(match != null);
350 if (match.destClasses.isEmpty()) {
351
352 m_destClasses.setClasses(null);
353
354 // run in a separate thread to keep ui responsive
355 new Thread() {
356 @Override
357 public void run() {
358 m_destClasses.setClasses(deobfuscateClasses(getLikelyMatches(m_sourceClass), m_destDeobfuscator));
359 m_destClasses.expandAll();
360
361 if (onGetDestClasses != null) {
362 onGetDestClasses.run();
363 }
364 }
365 }.start();
366
367 } else {
368
369 m_destClasses.setClasses(deobfuscateClasses(match.destClasses, m_destDeobfuscator));
370 m_destClasses.expandAll();
371
372 if (onGetDestClasses != null) {
373 onGetDestClasses.run();
374 }
375 }
376 }
377
378 setDestClass(null);
379 decompileClass(m_sourceClass, m_sourceDeobfuscator, m_sourceReader);
380
381 updateMatchButton();
382 }
383
384 private Collection<ClassEntry> getLikelyMatches(ClassEntry sourceClass) {
385
386 ClassEntry obfSourceClass = m_sourceDeobfuscator.obfuscateEntry(sourceClass);
387
388 // set up identifiers
389 ClassNamer namer = new ClassNamer(m_matches.getUniqueMatches());
390 ClassIdentifier sourceIdentifier = new ClassIdentifier(
391 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
392 namer.getSourceNamer(), true
393 );
394 ClassIdentifier destIdentifier = new ClassIdentifier(
395 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
396 namer.getDestNamer(), true
397 );
398
399 try {
400
401 // rank all the unmatched dest classes against the source class
402 ClassIdentity sourceIdentity = sourceIdentifier.identify(obfSourceClass);
403 List<ClassEntry> scoredDestClasses = Lists.newArrayList();
404 for (ClassEntry unmatchedDestClass : m_matches.getUnmatchedDestClasses()) {
405 ClassIdentity destIdentity = destIdentifier.identify(unmatchedDestClass);
406 float score = 100.0f*(sourceIdentity.getMatchScore(destIdentity) + destIdentity.getMatchScore(sourceIdentity))
407 /(sourceIdentity.getMaxMatchScore() + destIdentity.getMaxMatchScore());
408 scoredDestClasses.add(new ScoredClassEntry(unmatchedDestClass, score));
409 }
410 return scoredDestClasses;
411
412 } catch (ClassNotFoundException ex) {
413 throw new Error("Unable to find class " + ex.getMessage());
414 }
415 }
416
417 protected void setDestClass(ClassEntry classEntry) {
418
419 // update the current source class
420 m_destClass = classEntry;
421 m_destClassLabel.setText(m_destClass != null ? m_destClass.getName() : "");
422
423 decompileClass(m_destClass, m_destDeobfuscator, m_destReader);
424
425 updateMatchButton();
426 }
427
428 protected void decompileClass(final ClassEntry classEntry, final Deobfuscator deobfuscator, final JEditorPane reader) {
429
430 if (classEntry == null) {
431 reader.setText(null);
432 return;
433 }
434
435 reader.setText("(decompiling...)");
436
437 // run in a separate thread to keep ui responsive
438 new Thread() {
439 @Override
440 public void run() {
441
442 // get the outermost class
443 ClassEntry outermostClassEntry = classEntry;
444 while (outermostClassEntry.isInnerClass()) {
445 outermostClassEntry = outermostClassEntry.getOuterClassEntry();
446 }
447
448 // decompile it
449 CompilationUnit sourceTree = deobfuscator.getSourceTree(outermostClassEntry.getName());
450 String source = deobfuscator.getSource(sourceTree);
451 reader.setText(source);
452 SourceIndex sourceIndex = deobfuscator.getSourceIndex(sourceTree, source);
453
454 // navigate to the class declaration
455 Token token = sourceIndex.getDeclarationToken(classEntry);
456 if (token == null) {
457 // couldn't find the class declaration token, might be an anonymous class
458 // look for any declaration in that class instead
459 for (Entry entry : sourceIndex.declarations()) {
460 if (entry.getClassEntry().equals(classEntry)) {
461 token = sourceIndex.getDeclarationToken(entry);
462 break;
463 }
464 }
465 }
466
467 if (token != null) {
468 GuiTricks.navigateToToken(reader, token, m_selectionHighlightPainter);
469 } else {
470 // couldn't find anything =(
471 System.out.println("Unable to find declaration in source for " + classEntry);
472 }
473
474 }
475 }.start();
476 }
477
478 private void updateMatchButton() {
479
480 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
481 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
482
483 BiMap<ClassEntry,ClassEntry> uniqueMatches = m_matches.getUniqueMatches();
484 boolean twoSelected = m_sourceClass != null && m_destClass != null;
485 boolean isMatched = uniqueMatches.containsKey(obfSource) && uniqueMatches.containsValue(obfDest);
486 boolean canMatch = !uniqueMatches.containsKey(obfSource) && ! uniqueMatches.containsValue(obfDest);
487
488 deactivateButton(m_matchButton);
489 if (twoSelected) {
490 if (isMatched) {
491 activateButton(m_matchButton, "Unmatch", new ActionListener() {
492 @Override
493 public void actionPerformed(ActionEvent event) {
494 onUnmatchClick();
495 }
496 });
497 } else if (canMatch) {
498 activateButton(m_matchButton, "Match", new ActionListener() {
499 @Override
500 public void actionPerformed(ActionEvent event) {
501 onMatchClick();
502 }
503 });
504 }
505 }
506 }
507
508 private void deactivateButton(JButton button) {
509 button.setEnabled(false);
510 button.setText("");
511 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
512 button.removeActionListener(listener);
513 }
514 }
515
516 private void activateButton(JButton button, String text, ActionListener newListener) {
517 button.setText(text);
518 button.setEnabled(true);
519 for (ActionListener listener : Arrays.asList(button.getActionListeners())) {
520 button.removeActionListener(listener);
521 }
522 button.addActionListener(newListener);
523 }
524
525 private void onMatchClick() {
526 // precondition: source and dest classes are set correctly
527
528 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
529 ClassEntry obfDest = m_destDeobfuscator.obfuscateEntry(m_destClass);
530
531 // remove the classes from their match
532 m_matches.removeSource(obfSource);
533 m_matches.removeDest(obfDest);
534
535 // add them as matched classes
536 m_matches.add(new ClassMatch(obfSource, obfDest));
537
538 ClassEntry nextClass = null;
539 if (m_advanceCheck.isSelected()) {
540 nextClass = m_sourceClasses.getNextClass(m_sourceClass);
541 }
542
543 save();
544 updateMatches();
545
546 if (nextClass != null) {
547 advance(nextClass);
548 }
549 }
550
551 private void onUnmatchClick() {
552 // precondition: source and dest classes are set to a unique match
553
554 ClassEntry obfSource = m_sourceDeobfuscator.obfuscateEntry(m_sourceClass);
555
556 // remove the source to break the match, then add the source back as unmatched
557 m_matches.removeSource(obfSource);
558 m_matches.add(new ClassMatch(obfSource, null));
559
560 save();
561 updateMatches();
562 }
563
564 private void updateMatches() {
565 updateDestMappings();
566 setDestClass(null);
567 m_destClasses.setClasses(null);
568 updateMatchButton();
569
570 // remember where we were in the source tree
571 String packageName = m_sourceClasses.getSelectedPackage();
572
573 setSourceType(m_sourceType);
574
575 m_sourceClasses.expandPackage(packageName);
576 }
577
578 private void save() {
579 if (m_saveListener != null) {
580 m_saveListener.save(m_matches);
581 }
582 }
583
584 private void autoMatch() {
585
586 System.out.println("Automatching...");
587
588 // compute a new matching
589 ClassMatching matching = MappingsConverter.computeMatching(
590 m_sourceDeobfuscator.getJar(), m_sourceDeobfuscator.getJarIndex(),
591 m_destDeobfuscator.getJar(), m_destDeobfuscator.getJarIndex(),
592 m_matches.getUniqueMatches()
593 );
594 Matches newMatches = new Matches(matching.matches());
595 System.out.println(String.format("Automatch found %d new matches",
596 newMatches.getUniqueMatches().size() - m_matches.getUniqueMatches().size()
597 ));
598
599 // update the current matches
600 m_matches = newMatches;
601 save();
602 updateMatches();
603 }
604
605 private void advance() {
606 advance(null);
607 }
608
609 private void advance(ClassEntry sourceClass) {
610
611 // make sure we have a source class
612 if (sourceClass == null) {
613 sourceClass = m_sourceClasses.getSelectedClass();
614 if (sourceClass != null) {
615 sourceClass = m_sourceClasses.getNextClass(sourceClass);
616 } else {
617 sourceClass = m_sourceClasses.getFirstClass();
618 }
619 }
620
621 // set the source class
622 setSourceClass(sourceClass, new Runnable() {
623 @Override
624 public void run() {
625 pickBestDestClass();
626 }
627 });
628 m_sourceClasses.setSelectionClass(sourceClass);
629 }
630
631 private void pickBestDestClass() {
632
633 // then, pick the best dest class
634 ClassEntry firstClass = null;
635 ScoredClassEntry bestDestClass = null;
636 for (ClassSelectorPackageNode packageNode : m_destClasses.packageNodes()) {
637 for (ClassSelectorClassNode classNode : m_destClasses.classNodes(packageNode)) {
638 if (firstClass == null) {
639 firstClass = classNode.getClassEntry();
640 }
641 if (classNode.getClassEntry() instanceof ScoredClassEntry) {
642 ScoredClassEntry scoredClass = (ScoredClassEntry)classNode.getClassEntry();
643 if (bestDestClass == null || bestDestClass.getScore() < scoredClass.getScore()) {
644 bestDestClass = scoredClass;
645 }
646 }
647 }
648 }
649
650 // pick the entry to show
651 ClassEntry destClass = null;
652 if (bestDestClass != null) {
653 destClass = bestDestClass;
654 } else if (firstClass != null) {
655 destClass = firstClass;
656 }
657
658 setDestClass(destClass);
659 m_destClasses.setSelectionClass(destClass);
660 }
661}