Skip to content

Commit f6ba9ee

Browse files
authored
show mapping status in class tree (#60)
* initial really broken impl * temporarily remove shadowing warning * better icons and fix names * real time updates * cleanup * use StatsGenerator instead of @NebelNidas ' code * fix StatsGenerator * remove debug message * fix all classes docker not properly reloading when classes are moved to new packages * helpful comment * fix massive lag * fix some issues with StatsGenerator that I seem to have caused for no good reason * automatically reload icons * fix nodes calculating their stats repeatedly * run icon reloading off thread to prevent lag when renaming an entry * fix isObfuscated in EnigmaProject * remove superfluous reload in ClassSelector * fix all classes docker losing its expansion state on rename (I already caused this exact bug once, we stay silly :iea:) * StatsGenerator fix for anonymous classes always showing as a mapped entry * fix class selector exploding sometimes
1 parent aa05df2 commit f6ba9ee

File tree

12 files changed

+412
-95
lines changed

12 files changed

+412
-95
lines changed

enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717
import cuchaz.enigma.translation.representation.entry.ClassEntry;
1818
import cuchaz.enigma.utils.validation.ValidationContext;
1919

20-
import javax.swing.*;
20+
import javax.swing.BoxLayout;
21+
import javax.swing.JLabel;
22+
import javax.swing.JPanel;
23+
import javax.swing.JTree;
2124
import javax.swing.event.CellEditorListener;
2225
import javax.swing.event.ChangeEvent;
23-
import javax.swing.tree.*;
24-
import java.awt.*;
26+
import javax.swing.tree.DefaultMutableTreeNode;
27+
import javax.swing.tree.DefaultTreeCellEditor;
28+
import javax.swing.tree.DefaultTreeCellRenderer;
29+
import javax.swing.tree.DefaultTreeModel;
30+
import javax.swing.tree.TreeNode;
31+
import javax.swing.tree.TreePath;
32+
import java.awt.Component;
2533
import java.awt.event.MouseEvent;
34+
import java.util.ArrayList;
35+
import java.util.Collection;
36+
import java.util.Comparator;
37+
import java.util.EventObject;
2638
import java.util.List;
27-
import java.util.*;
2839

2940
public class ClassSelector extends JTree {
3041
public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
@@ -88,7 +99,21 @@ public Component getTreeCellRendererComponent(JTree tree, Object value, boolean
8899
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
89100

90101
if (gui.getController().project != null && leaf && value instanceof ClassSelectorClassNode node) {
91-
this.setIcon(GuiUtil.getClassIcon(gui, node.getObfEntry()));
102+
JPanel panel = new JPanel();
103+
panel.setOpaque(false);
104+
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
105+
panel.add(new JLabel(GuiUtil.getClassIcon(gui, node.getObfEntry())));
106+
107+
if (node.getStats() == null) {
108+
// calculate stats on a separate thread for performance reasons
109+
this.setIcon(GuiUtil.PENDING_STATUS_ICON);
110+
node.reloadStats(gui, ClassSelector.this, false);
111+
} else {
112+
this.setIcon(GuiUtil.getDeobfuscationIcon(node.getStats()));
113+
}
114+
115+
panel.add(this);
116+
return panel;
92117
}
93118

94119
return this;
@@ -111,8 +136,7 @@ public void editingStopped(ChangeEvent e) {
111136
String data = editor.getCellEditorValue().toString();
112137
TreePath path = ClassSelector.this.getSelectionPath();
113138

114-
Object realPath = path.getLastPathComponent();
115-
if (realPath instanceof DefaultMutableTreeNode node && data != null) {
139+
if (path != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode node && data != null) {
116140
TreeNode parentNode = node.getParent();
117141
if (parentNode == null)
118142
return;
@@ -180,7 +204,7 @@ public void setClasses(Collection<ClassEntry> classEntries) {
180204
}
181205

182206
public ClassEntry getSelectedClass() {
183-
if (!this.isSelectionEmpty()) {
207+
if (!this.isSelectionEmpty() && this.getSelectionPath() != null) {
184208
Object selectedNode = this.getSelectionPath().getLastPathComponent();
185209

186210
if (selectedNode instanceof ClassSelectorClassNode classNode) {
@@ -266,9 +290,20 @@ public void removeEntry(ClassEntry classEntry) {
266290
this.packageManager.removeClassNode(classEntry);
267291
}
268292

269-
public void reload() {
293+
public void reloadEntry(ClassEntry classEntry) {
294+
this.removeEntry(classEntry);
295+
this.moveClassIn(classEntry);
296+
ClassSelectorClassNode node = this.packageManager.getClassNode(classEntry);
297+
node.reloadStats(controller.getGui(), this, true);
298+
}
299+
300+
public void reload(TreeNode node) {
270301
DefaultTreeModel model = (DefaultTreeModel) this.getModel();
271-
model.reload(this.packageManager.getRoot());
302+
model.reload(node);
303+
}
304+
305+
public void reload() {
306+
this.reload(this.packageManager.getRoot());
272307
}
273308

274309
public interface ClassSelectionListener {

enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,6 @@ public void moveClassTree(Entry<?> obfEntry, boolean isOldOb, boolean isNewOb) {
571571

572572
ClassSelector deobfuscatedClassSelector = Docker.getDocker(DeobfuscatedClassesDocker.class).getClassSelector();
573573
ClassSelector obfuscatedClassSelector = Docker.getDocker(ObfuscatedClassesDocker.class).getClassSelector();
574-
ClassSelector allClassesClassSelector = Docker.getDocker(AllClassesDocker.class).getClassSelector();
575574

576575
List<ClassSelector.StateEntry> deobfuscatedPanelExpansionState = deobfuscatedClassSelector.getExpansionState();
577576
List<ClassSelector.StateEntry> obfuscatedPanelExpansionState = obfuscatedClassSelector.getExpansionState();
@@ -594,14 +593,23 @@ public void moveClassTree(Entry<?> obfEntry, boolean isOldOb, boolean isNewOb) {
594593
deobfuscatedClassSelector.reload();
595594
}
596595

597-
allClassesClassSelector.removeEntry(classEntry);
598-
allClassesClassSelector.moveClassIn(classEntry);
599-
allClassesClassSelector.reload();
596+
this.reloadClassEntry(classEntry);
600597

601598
deobfuscatedClassSelector.restoreExpansionState(deobfuscatedPanelExpansionState);
602599
obfuscatedClassSelector.restoreExpansionState(obfuscatedPanelExpansionState);
603600
}
604601

602+
public void reloadClassEntry(ClassEntry classEntry) {
603+
Docker.getDocker(DeobfuscatedClassesDocker.class).getClassSelector().reloadEntry(classEntry);
604+
Docker.getDocker(ObfuscatedClassesDocker.class).getClassSelector().reloadEntry(classEntry);
605+
606+
ClassSelector allClassesClassSelector = Docker.getDocker(AllClassesDocker.class).getClassSelector();
607+
List<ClassSelector.StateEntry> expansionState = allClassesClassSelector.getExpansionState();
608+
allClassesClassSelector.reloadEntry(classEntry);
609+
allClassesClassSelector.reload();
610+
allClassesClassSelector.restoreExpansionState(expansionState);
611+
}
612+
605613
public SearchDialog getSearchDialog() {
606614
if (this.searchDialog == null) {
607615
this.searchDialog = new SearchDialog(this);

enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -510,10 +510,7 @@ private void applyChange0(ValidationContext vc, EntryChange<?> change) {
510510
EntryMapping mapping = EntryUtil.applyChange(vc, this.project.getMapper(), change);
511511

512512
boolean renamed = !change.getDeobfName().isUnchanged();
513-
514-
if (renamed && target instanceof ClassEntry classEntry && !classEntry.isInnerClass()) {
515-
this.gui.moveClassTree(target, prev.targetName() == null, mapping.targetName() == null);
516-
}
513+
this.gui.updateStructure(this.gui.getActiveEditor());
517514

518515
if (!Objects.equals(prev.targetName(), mapping.targetName())) {
519516
this.chp.invalidateMapped();
@@ -523,7 +520,12 @@ private void applyChange0(ValidationContext vc, EntryChange<?> change) {
523520
this.chp.invalidateJavadoc(target.getTopLevelClass());
524521
}
525522

526-
this.gui.updateStructure(this.gui.getActiveEditor());
523+
if (renamed && target instanceof ClassEntry classEntry && !classEntry.isInnerClass()) {
524+
this.gui.moveClassTree(target, prev.targetName() == null, mapping.targetName() == null);
525+
return;
526+
}
527+
528+
this.gui.reloadClassEntry(change.getTarget().getTopLevelClass());
527529
}
528530

529531
public void openStats(Set<StatsMember> includedMembers, String topLevelPackage, boolean includeSynthetic) {

enigma-swing/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,27 @@
1111

1212
package cuchaz.enigma.gui.node;
1313

14+
import cuchaz.enigma.ProgressListener;
15+
import cuchaz.enigma.gui.ClassSelector;
16+
import cuchaz.enigma.gui.Gui;
17+
import cuchaz.enigma.gui.stats.StatsGenerator;
18+
import cuchaz.enigma.gui.stats.StatsResult;
19+
import cuchaz.enigma.gui.util.GuiUtil;
1420
import cuchaz.enigma.translation.representation.entry.ClassEntry;
1521

22+
import javax.swing.SwingWorker;
1623
import javax.swing.tree.DefaultMutableTreeNode;
24+
import javax.swing.tree.DefaultTreeCellRenderer;
1725

1826
public class ClassSelectorClassNode extends DefaultMutableTreeNode {
1927
private final ClassEntry obfEntry;
2028
private ClassEntry classEntry;
29+
private StatsResult stats;
2130

2231
public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) {
2332
this.obfEntry = obfEntry;
2433
this.classEntry = classEntry;
34+
this.stats = null;
2535
this.setUserObject(classEntry);
2636
}
2737

@@ -33,14 +43,50 @@ public ClassEntry getClassEntry() {
3343
return this.classEntry;
3444
}
3545

46+
public StatsResult getStats() {
47+
return this.stats;
48+
}
49+
50+
public void setStats(StatsResult stats) {
51+
this.stats = stats;
52+
}
53+
54+
/**
55+
* Reloads the stats for this class node and updates the icon in the provided class selector.
56+
* @param gui the current gui instance
57+
* @param selector the class selector to reload on
58+
* @param updateIfPresent whether to update the stats if they have already been generated for this node
59+
*/
60+
public void reloadStats(Gui gui, ClassSelector selector, boolean updateIfPresent) {
61+
SwingWorker<ClassSelectorClassNode, Void> iconUpdateWorker = new SwingWorker<>() {
62+
@Override
63+
protected ClassSelectorClassNode doInBackground() {
64+
if (ClassSelectorClassNode.this.getStats() == null || updateIfPresent) {
65+
StatsResult newStats = new StatsGenerator(gui.getController().project).generateForClassTree(ProgressListener.none(), ClassSelectorClassNode.this.getObfEntry(), false);
66+
ClassSelectorClassNode.this.setStats(newStats);
67+
}
68+
69+
return ClassSelectorClassNode.this;
70+
}
71+
72+
@Override
73+
public void done() {
74+
((DefaultTreeCellRenderer) selector.getCellRenderer()).setIcon(GuiUtil.getDeobfuscationIcon(ClassSelectorClassNode.this.getStats()));
75+
selector.reload(ClassSelectorClassNode.this);
76+
}
77+
};
78+
79+
iconUpdateWorker.execute();
80+
}
81+
3682
@Override
3783
public String toString() {
3884
return this.classEntry.getSimpleName();
3985
}
4086

4187
@Override
4288
public boolean equals(Object other) {
43-
return other instanceof ClassSelectorClassNode && this.equals((ClassSelectorClassNode) other);
89+
return other instanceof ClassSelectorClassNode node && this.equals(node);
4490
}
4591

4692
@Override
@@ -60,8 +106,8 @@ public void setUserObject(Object userObject) {
60106
packageName = this.classEntry.getPackageName() + "/";
61107
if (userObject instanceof String)
62108
this.classEntry = new ClassEntry(packageName + userObject);
63-
else if (userObject instanceof ClassEntry)
64-
this.classEntry = (ClassEntry) userObject;
109+
else if (userObject instanceof ClassEntry entry)
110+
this.classEntry = entry;
65111
super.setUserObject(this.classEntry);
66112
}
67113

enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,24 @@ public StatsGenerator(EnigmaProject project) {
3434
this.entryResolver = project.getJarIndex().getEntryResolver();
3535
}
3636

37+
public StatsResult generateForClassTree(ProgressListener progress, ClassEntry entry, boolean includeSynthetic) {
38+
return generate(progress, EnumSet.allOf(StatsMember.class), entry.getFullName(), true, includeSynthetic);
39+
}
40+
3741
public StatsResult generate(ProgressListener progress, Set<StatsMember> includedMembers, String topLevelPackage, boolean includeSynthetic) {
42+
return generate(progress, includedMembers, topLevelPackage, false, includeSynthetic);
43+
}
44+
45+
/**
46+
* Generates stats for the given package or class.
47+
* @param progress a listener to update with current progress
48+
* @param includedMembers the types of entry to include in the stats
49+
* @param topLevelPackage the package or class to generate stats for
50+
* @param forClassTree if true, the stats will be generated for the class tree - this means that non-mappable obfuscated entries will be ignored for correctness
51+
* @param includeSynthetic whether to include synthetic methods
52+
* @return the generated {@link StatsResult} for the provided class or package.
53+
*/
54+
public StatsResult generate(ProgressListener progress, Set<StatsMember> includedMembers, String topLevelPackage, boolean forClassTree, boolean includeSynthetic) {
3855
includedMembers = EnumSet.copyOf(includedMembers);
3956
int totalWork = 0;
4057
int totalMappable = 0;
@@ -61,27 +78,30 @@ public StatsResult generate(ProgressListener progress, Set<StatsMember> included
6178
if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
6279
for (MethodEntry method : this.entryIndex.getMethods()) {
6380
progress.step(numDone++, I18n.translate("type.methods"));
81+
82+
// we don't want constructors or otherwise non-mappable things to show as a mapped method!
83+
if (!project.isRenamable(method)) {
84+
continue;
85+
}
86+
6487
MethodEntry root = this.entryResolver
6588
.resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT)
6689
.stream()
6790
.findFirst()
6891
.orElseThrow(AssertionError::new);
6992

7093
ClassEntry clazz = root.getParent();
71-
String deobfuscatedPackageName = this.mapper.deobfuscate(clazz).getPackageName();
7294

73-
if (root == method && (topLevelPackageSlash.isBlank() || (deobfuscatedPackageName != null && deobfuscatedPackageName.startsWith(topLevelPackageSlash)))) {
95+
if (root == method && checkPackage(clazz, topLevelPackageSlash, forClassTree)) {
7496
if (includedMembers.contains(StatsMember.METHODS) && !((MethodDefEntry) method).getAccess().isSynthetic()) {
75-
this.update(counts, method);
76-
totalMappable++;
97+
totalMappable += this.update(counts, method, forClassTree);
7798
}
7899

79100
if (includedMembers.contains(StatsMember.PARAMETERS) && (!((MethodDefEntry) method).getAccess().isSynthetic() || includeSynthetic)) {
80101
int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1;
81102
for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) {
82-
this.update(counts, new LocalVariableEntry(method, index, "", true, null));
103+
totalMappable += this.update(counts, new LocalVariableEntry(method, index, "", true, null), forClassTree);
83104
index += argument.getSize();
84-
totalMappable++;
85105
}
86106
}
87107
}
@@ -92,11 +112,9 @@ public StatsResult generate(ProgressListener progress, Set<StatsMember> included
92112
for (FieldEntry field : this.entryIndex.getFields()) {
93113
progress.step(numDone++, I18n.translate("type.fields"));
94114
ClassEntry clazz = field.getParent();
95-
String deobfuscatedPackageName = this.mapper.deobfuscate(clazz).getPackageName();
96115

97-
if (!((FieldDefEntry) field).getAccess().isSynthetic() && (topLevelPackageSlash.isBlank() || (deobfuscatedPackageName != null && deobfuscatedPackageName.startsWith(topLevelPackageSlash)))) {
98-
this.update(counts, field);
99-
totalMappable++;
116+
if (!((FieldDefEntry) field).getAccess().isSynthetic() && checkPackage(clazz, topLevelPackageSlash, forClassTree)) {
117+
totalMappable += this.update(counts, field, forClassTree);
100118
}
101119
}
102120
}
@@ -105,11 +123,8 @@ public StatsResult generate(ProgressListener progress, Set<StatsMember> included
105123
for (ClassEntry clazz : this.entryIndex.getClasses()) {
106124
progress.step(numDone++, I18n.translate("type.classes"));
107125

108-
String deobfuscatedPackageName = this.mapper.deobfuscate(clazz).getPackageName();
109-
110-
if (topLevelPackageSlash.isBlank() || (deobfuscatedPackageName != null && deobfuscatedPackageName.startsWith(topLevelPackageSlash))) {
111-
this.update(counts, clazz);
112-
totalMappable++;
126+
if (checkPackage(clazz, topLevelPackageSlash, forClassTree)) {
127+
totalMappable += this.update(counts, clazz, forClassTree);
113128
}
114129
}
115130
}
@@ -128,10 +143,33 @@ public StatsResult generate(ProgressListener progress, Set<StatsMember> included
128143
return new StatsResult(totalMappable, counts.values().stream().mapToInt(i -> i).sum(), tree);
129144
}
130145

131-
private void update(Map<String, Integer> counts, Entry<?> entry) {
132-
if (this.project.isObfuscated(entry) && this.project.isRenamable(entry) && !this.project.isSynthetic(entry)) {
146+
private boolean checkPackage(ClassEntry clazz, String topLevelPackage, boolean singleClass) {
147+
String deobfuscatedName = this.mapper.deobfuscate(clazz).getPackageName();
148+
if (singleClass) {
149+
return (deobfuscatedName != null && deobfuscatedName.startsWith(topLevelPackage)) || clazz.getFullName().startsWith(topLevelPackage);
150+
}
151+
152+
return topLevelPackage.isBlank() || (deobfuscatedName != null && deobfuscatedName.startsWith(topLevelPackage));
153+
}
154+
155+
/**
156+
* @return whether to increment the total mappable entry count - 0 if no, 1 if yes
157+
*/
158+
private int update(Map<String, Integer> counts, Entry<?> entry, boolean forClassTree) {
159+
boolean obfuscated = this.project.isObfuscated(entry);
160+
boolean renamable = this.project.isRenamable(entry);
161+
boolean synthetic = this.project.isSynthetic(entry);
162+
163+
if (forClassTree && obfuscated && !renamable) {
164+
return 0;
165+
}
166+
167+
if (obfuscated && renamable && !synthetic) {
133168
String parent = this.mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.');
134169
counts.put(parent, counts.getOrDefault(parent, 0) + 1);
170+
return 1;
135171
}
172+
173+
return 1;
136174
}
137175
}

enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsResult.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ public int getUnmapped() {
2727
}
2828

2929
public int getMapped() {
30-
return this.total - this.unmapped;
30+
return this.getTotal() - this.getUnmapped();
3131
}
3232

3333
public double getPercentage() {
34+
// avoid showing "Nan%" when there are no entries to map
35+
// if there are none, you've mapped them all!
36+
if (this.total == 0) {
37+
return 100.0f;
38+
}
39+
3440
return (this.getMapped() * 100.0f) / this.total;
3541
}
3642

@@ -51,7 +57,7 @@ public static class Node<T> {
5157
public String name;
5258
public T value;
5359
public List<Node<T>> children = new ArrayList<>();
54-
private final transient Map<String, Node<T>> namedChildren = new HashMap<>();
60+
private final Map<String, Node<T>> namedChildren = new HashMap<>();
5561

5662
public Node(String name, T value) {
5763
this.name = name;

0 commit comments

Comments
 (0)