summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.gradle2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java8
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java65
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java14
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java98
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java43
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java32
-rw-r--r--enigma-swing/src/main/resources/icons/class.pngbin0 -> 768 bytes
-rw-r--r--enigma-swing/src/main/resources/icons/constructor.pngbin0 -> 751 bytes
-rw-r--r--enigma-swing/src/main/resources/icons/field.pngbin0 -> 534 bytes
-rw-r--r--enigma-swing/src/main/resources/icons/method.pngbin0 -> 633 bytes
-rw-r--r--enigma/src/main/java/cuchaz/enigma/EnigmaProject.java30
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java123
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java23
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java27
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java3
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java7
-rw-r--r--enigma/src/main/resources/lang/en_us.json2
-rw-r--r--enigma/src/main/resources/lang/fr_fr.json2
19 files changed, 397 insertions, 82 deletions
diff --git a/build.gradle b/build.gradle
index 3dfbae41..8b0a2c50 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,7 @@ subprojects {
24 } 24 }
25 25
26 group = 'cuchaz' 26 group = 'cuchaz'
27 version = '0.21.8' 27 version = '0.22.0'
28 28
29 version = version + (System.getenv("GITHUB_ACTIONS") ? "" : "+local") 29 version = version + (System.getenv("GITHUB_ACTIONS") ? "" : "+local")
30 30
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java
index 488d04ed..b27832be 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java
@@ -27,6 +27,7 @@ import com.google.common.collect.Maps;
27import com.google.common.collect.Multimap; 27import com.google.common.collect.Multimap;
28import cuchaz.enigma.gui.node.ClassSelectorClassNode; 28import cuchaz.enigma.gui.node.ClassSelectorClassNode;
29import cuchaz.enigma.gui.node.ClassSelectorPackageNode; 29import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
30import cuchaz.enigma.gui.util.GuiUtil;
30import cuchaz.enigma.translation.Translator; 31import cuchaz.enigma.translation.Translator;
31import cuchaz.enigma.translation.representation.entry.ClassEntry; 32import cuchaz.enigma.translation.representation.entry.ClassEntry;
32import cuchaz.enigma.utils.validation.ValidationContext; 33import cuchaz.enigma.utils.validation.ValidationContext;
@@ -69,10 +70,13 @@ public class ClassSelector extends JTree {
69 } 70 }
70 }); 71 });
71 72
73 final DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
74 renderer.setLeafIcon(GuiUtil.CLASS_ICON);
75 setCellRenderer(renderer);
76
72 final JTree tree = this; 77 final JTree tree = this;
73 78
74 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, 79 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree, renderer) {
75 (DefaultTreeCellRenderer) tree.getCellRenderer()) {
76 @Override 80 @Override
77 public boolean isCellEditable(EventObject event) { 81 public boolean isCellEditable(EventObject event) {
78 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event); 82 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
index c56731dc..d74fefd9 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -51,10 +51,7 @@ import cuchaz.enigma.network.packet.RemoveMappingC2SPacket;
51import cuchaz.enigma.network.packet.RenameC2SPacket; 51import cuchaz.enigma.network.packet.RenameC2SPacket;
52import cuchaz.enigma.source.Token; 52import cuchaz.enigma.source.Token;
53import cuchaz.enigma.translation.mapping.EntryRemapper; 53import cuchaz.enigma.translation.mapping.EntryRemapper;
54import cuchaz.enigma.translation.representation.entry.ClassEntry; 54import cuchaz.enigma.translation.representation.entry.*;
55import cuchaz.enigma.translation.representation.entry.Entry;
56import cuchaz.enigma.translation.representation.entry.FieldEntry;
57import cuchaz.enigma.translation.representation.entry.MethodEntry;
58import cuchaz.enigma.utils.I18n; 55import cuchaz.enigma.utils.I18n;
59import cuchaz.enigma.utils.validation.ParameterizedMessage; 56import cuchaz.enigma.utils.validation.ParameterizedMessage;
60import cuchaz.enigma.utils.validation.ValidationContext; 57import cuchaz.enigma.utils.validation.ValidationContext;
@@ -82,6 +79,7 @@ public class Gui implements LanguageChangeListener {
82 private JPanel classesPanel; 79 private JPanel classesPanel;
83 private JSplitPane splitClasses; 80 private JSplitPane splitClasses;
84 private IdentifierPanel infoPanel; 81 private IdentifierPanel infoPanel;
82 private StructurePanel structurePanel;
85 private JTree inheritanceTree; 83 private JTree inheritanceTree;
86 private JTree implementationsTree; 84 private JTree implementationsTree;
87 private JTree callsTree; 85 private JTree callsTree;
@@ -163,13 +161,17 @@ public class Gui implements LanguageChangeListener {
163 // init info panel 161 // init info panel
164 infoPanel = new IdentifierPanel(this); 162 infoPanel = new IdentifierPanel(this);
165 163
164 // init structure panel
165 this.structurePanel = new StructurePanel(this);
166
166 // init inheritance panel 167 // init inheritance panel
167 inheritanceTree = new JTree(); 168 inheritanceTree = new JTree();
168 inheritanceTree.setModel(null); 169 inheritanceTree.setModel(null);
170 inheritanceTree.setShowsRootHandles(true);
169 inheritanceTree.addMouseListener(new MouseAdapter() { 171 inheritanceTree.addMouseListener(new MouseAdapter() {
170 @Override 172 @Override
171 public void mouseClicked(MouseEvent event) { 173 public void mouseClicked(MouseEvent event) {
172 if (event.getClickCount() >= 2) { 174 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
173 // get the selected node 175 // get the selected node
174 TreePath path = inheritanceTree.getSelectionPath(); 176 TreePath path = inheritanceTree.getSelectionPath();
175 if (path == null) { 177 if (path == null) {
@@ -199,10 +201,11 @@ public class Gui implements LanguageChangeListener {
199 // init implementations panel 201 // init implementations panel
200 implementationsTree = new JTree(); 202 implementationsTree = new JTree();
201 implementationsTree.setModel(null); 203 implementationsTree.setModel(null);
204 implementationsTree.setShowsRootHandles(true);
202 implementationsTree.addMouseListener(new MouseAdapter() { 205 implementationsTree.addMouseListener(new MouseAdapter() {
203 @Override 206 @Override
204 public void mouseClicked(MouseEvent event) { 207 public void mouseClicked(MouseEvent event) {
205 if (event.getClickCount() >= 2) { 208 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
206 // get the selected node 209 // get the selected node
207 TreePath path = implementationsTree.getSelectionPath(); 210 TreePath path = implementationsTree.getSelectionPath();
208 if (path == null) { 211 if (path == null) {
@@ -227,11 +230,12 @@ public class Gui implements LanguageChangeListener {
227 // init call panel 230 // init call panel
228 callsTree = new JTree(); 231 callsTree = new JTree();
229 callsTree.setModel(null); 232 callsTree.setModel(null);
233 callsTree.setShowsRootHandles(true);
230 callsTree.addMouseListener(new MouseAdapter() { 234 callsTree.addMouseListener(new MouseAdapter() {
231 @SuppressWarnings("unchecked") 235 @SuppressWarnings("unchecked")
232 @Override 236 @Override
233 public void mouseClicked(MouseEvent event) { 237 public void mouseClicked(MouseEvent event) {
234 if (event.getClickCount() >= 2) { 238 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
235 // get the selected node 239 // get the selected node
236 TreePath path = callsTree.getSelectionPath(); 240 TreePath path = callsTree.getSelectionPath();
237 if (path == null) { 241 if (path == null) {
@@ -287,6 +291,8 @@ public class Gui implements LanguageChangeListener {
287 editorTabPopupMenu.show(openFiles, e.getX(), e.getY(), EditorPanel.byUi(openFiles.getComponentAt(i))); 291 editorTabPopupMenu.show(openFiles, e.getX(), e.getY(), EditorPanel.byUi(openFiles.getComponentAt(i)));
288 } 292 }
289 } 293 }
294
295 showStructure(getActiveEditor());
290 } 296 }
291 }); 297 });
292 298
@@ -311,6 +317,7 @@ public class Gui implements LanguageChangeListener {
311 centerPanel.add(openFiles, BorderLayout.CENTER); 317 centerPanel.add(openFiles, BorderLayout.CENTER);
312 tabs = new JTabbedPane(); 318 tabs = new JTabbedPane();
313 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); 319 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0));
320 tabs.addTab(I18n.translate("info_panel.tree.structure"), structurePanel);
314 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); 321 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel);
315 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel); 322 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel);
316 tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel); 323 tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel);
@@ -482,11 +489,14 @@ public class Gui implements LanguageChangeListener {
482 } 489 }
483 }); 490 });
484 491
492 showStructure(ed);
493
485 return ed; 494 return ed;
486 }); 495 });
487 if (editorPanel != null) { 496 if (editorPanel != null) {
488 openFiles.setSelectedComponent(editors.get(entry).getUi()); 497 openFiles.setSelectedComponent(editors.get(entry).getUi());
489 } 498 }
499
490 return editorPanel; 500 return editorPanel;
491 } 501 }
492 502
@@ -506,6 +516,7 @@ public class Gui implements LanguageChangeListener {
506 public void closeEditor(EditorPanel ed) { 516 public void closeEditor(EditorPanel ed) {
507 openFiles.remove(ed.getUi()); 517 openFiles.remove(ed.getUi());
508 editors.inverse().remove(ed); 518 editors.inverse().remove(ed);
519 showStructure(getActiveEditor());
509 ed.destroy(); 520 ed.destroy();
510 } 521 }
511 522
@@ -595,6 +606,32 @@ public class Gui implements LanguageChangeListener {
595 infoPanel.startRenaming(); 606 infoPanel.startRenaming();
596 } 607 }
597 608
609 public void showStructure(EditorPanel editor) {
610 JTree structureTree = this.structurePanel.getStructureTree();
611 structureTree.setModel(null);
612
613 if (editor == null) {
614 this.structurePanel.getSortingPanel().setVisible(false);
615 return;
616 }
617
618 ClassEntry classEntry = editor.getClassHandle().getRef();
619 if (classEntry == null) return;
620
621 this.structurePanel.getSortingPanel().setVisible(true);
622
623 // get the class structure
624 StructureTreeNode node = this.controller.getClassStructure(classEntry, this.structurePanel.shouldHideDeobfuscated());
625
626 // show the tree at the root
627 TreePath path = getPathToRoot(node);
628 structureTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
629 structureTree.expandPath(path);
630 structureTree.setSelectionRow(structureTree.getRowForPath(path));
631
632 redraw();
633 }
634
598 public void showInheritance(EditorPanel editor) { 635 public void showInheritance(EditorPanel editor) {
599 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); 636 EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference();
600 if (cursorReference == null) return; 637 if (cursorReference == null) return;
@@ -621,7 +658,7 @@ public class Gui implements LanguageChangeListener {
621 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path)); 658 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
622 } 659 }
623 660
624 tabs.setSelectedIndex(0); 661 tabs.setSelectedIndex(1);
625 662
626 redraw(); 663 redraw();
627 } 664 }
@@ -649,7 +686,7 @@ public class Gui implements LanguageChangeListener {
649 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path)); 686 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path));
650 } 687 }
651 688
652 tabs.setSelectedIndex(1); 689 tabs.setSelectedIndex(2);
653 690
654 redraw(); 691 redraw();
655 } 692 }
@@ -669,7 +706,7 @@ public class Gui implements LanguageChangeListener {
669 callsTree.setModel(new DefaultTreeModel(node)); 706 callsTree.setModel(new DefaultTreeModel(node));
670 } 707 }
671 708
672 tabs.setSelectedIndex(2); 709 tabs.setSelectedIndex(3);
673 710
674 redraw(); 711 redraw();
675 } 712 }
@@ -893,9 +930,10 @@ public class Gui implements LanguageChangeListener {
893 public void retranslateUi() { 930 public void retranslateUi() {
894 this.jarFileChooser.setTitle(I18n.translate("menu.file.jar.open")); 931 this.jarFileChooser.setTitle(I18n.translate("menu.file.jar.open"));
895 this.exportJarFileChooser.setTitle(I18n.translate("menu.file.export.jar")); 932 this.exportJarFileChooser.setTitle(I18n.translate("menu.file.export.jar"));
896 this.tabs.setTitleAt(0, I18n.translate("info_panel.tree.inheritance")); 933 this.tabs.setTitleAt(0, I18n.translate("info_panel.tree.structure"));
897 this.tabs.setTitleAt(1, I18n.translate("info_panel.tree.implementations")); 934 this.tabs.setTitleAt(1, I18n.translate("info_panel.tree.inheritance"));
898 this.tabs.setTitleAt(2, I18n.translate("info_panel.tree.calls")); 935 this.tabs.setTitleAt(2, I18n.translate("info_panel.tree.implementations"));
936 this.tabs.setTitleAt(3, I18n.translate("info_panel.tree.calls"));
899 this.logTabs.setTitleAt(0, I18n.translate("log_panel.users")); 937 this.logTabs.setTitleAt(0, I18n.translate("log_panel.users"));
900 this.logTabs.setTitleAt(1, I18n.translate("log_panel.messages")); 938 this.logTabs.setTitleAt(1, I18n.translate("log_panel.messages"));
901 this.connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected")); 939 this.connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected"));
@@ -907,6 +945,7 @@ public class Gui implements LanguageChangeListener {
907 this.deobfPanel.retranslateUi(); 945 this.deobfPanel.retranslateUi();
908 this.deobfPanelPopupMenu.retranslateUi(); 946 this.deobfPanelPopupMenu.retranslateUi();
909 this.infoPanel.retranslateUi(); 947 this.infoPanel.retranslateUi();
948 this.structurePanel.retranslateUi();
910 this.editors.values().forEach(EditorPanel::retranslateUi); 949 this.editors.values().forEach(EditorPanel::retranslateUi);
911 } 950 }
912 951
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
index 4f7819e4..5217b25c 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -57,10 +57,7 @@ import cuchaz.enigma.translation.mapping.serde.MappingParseException;
57import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; 57import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
58import cuchaz.enigma.translation.mapping.tree.EntryTree; 58import cuchaz.enigma.translation.mapping.tree.EntryTree;
59import cuchaz.enigma.translation.mapping.tree.HashEntryTree; 59import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
60import cuchaz.enigma.translation.representation.entry.ClassEntry; 60import cuchaz.enigma.translation.representation.entry.*;
61import cuchaz.enigma.translation.representation.entry.Entry;
62import cuchaz.enigma.translation.representation.entry.FieldEntry;
63import cuchaz.enigma.translation.representation.entry.MethodEntry;
64import cuchaz.enigma.utils.I18n; 61import cuchaz.enigma.utils.I18n;
65import cuchaz.enigma.utils.Utils; 62import cuchaz.enigma.utils.Utils;
66import cuchaz.enigma.utils.validation.ValidationContext; 63import cuchaz.enigma.utils.validation.ValidationContext;
@@ -395,6 +392,12 @@ public class GuiController implements ClientPacketHandler {
395 chp.invalidateMapped(); 392 chp.invalidateMapped();
396 } 393 }
397 394
395 public StructureTreeNode getClassStructure(ClassEntry entry, boolean hideDeobfuscated) {
396 StructureTreeNode rootNode = new StructureTreeNode(this.project, entry, entry);
397 rootNode.load(this.project, hideDeobfuscated);
398 return rootNode;
399 }
400
398 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { 401 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
399 Translator translator = project.getMapper().getDeobfuscator(); 402 Translator translator = project.getMapper().getDeobfuscator();
400 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry); 403 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry);
@@ -454,6 +457,7 @@ public class GuiController implements ClientPacketHandler {
454 Entry<?> entry = reference.getNameableEntry(); 457 Entry<?> entry = reference.getNameableEntry();
455 EntryMapping previous = project.getMapper().getDeobfMapping(entry); 458 EntryMapping previous = project.getMapper().getDeobfMapping(entry);
456 project.getMapper().mapFromObf(vc, entry, previous != null ? previous.withName(newName) : new EntryMapping(newName), true, validateOnly); 459 project.getMapper().mapFromObf(vc, entry, previous != null ? previous.withName(newName) : new EntryMapping(newName), true, validateOnly);
460 gui.showStructure(gui.getActiveEditor());
457 461
458 if (validateOnly || !vc.canProceed()) return; 462 if (validateOnly || !vc.canProceed()) return;
459 463
@@ -466,6 +470,7 @@ public class GuiController implements ClientPacketHandler {
466 @Override 470 @Override
467 public void removeMapping(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference) { 471 public void removeMapping(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference) {
468 project.getMapper().removeByObf(vc, reference.getNameableEntry()); 472 project.getMapper().removeByObf(vc, reference.getNameableEntry());
473 gui.showStructure(gui.getActiveEditor());
469 474
470 if (!vc.canProceed()) return; 475 if (!vc.canProceed()) return;
471 476
@@ -504,6 +509,7 @@ public class GuiController implements ClientPacketHandler {
504 EntryRemapper mapper = project.getMapper(); 509 EntryRemapper mapper = project.getMapper();
505 Entry<?> entry = reference.getNameableEntry(); 510 Entry<?> entry = reference.getNameableEntry();
506 mapper.mapFromObf(vc, entry, new EntryMapping(mapper.deobfuscate(entry).getName())); 511 mapper.mapFromObf(vc, entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
512 gui.showStructure(gui.getActiveEditor());
507 513
508 if (!vc.canProceed()) return; 514 if (!vc.canProceed()) return;
509 515
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java
new file mode 100644
index 00000000..139923c5
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/StructurePanel.java
@@ -0,0 +1,98 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.analysis.StructureTreeNode;
4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.gui.util.GuiUtil;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7import cuchaz.enigma.translation.representation.entry.FieldEntry;
8import cuchaz.enigma.translation.representation.entry.MethodEntry;
9import cuchaz.enigma.translation.representation.entry.ParentedEntry;
10import cuchaz.enigma.utils.I18n;
11
12import javax.swing.*;
13import javax.swing.tree.DefaultTreeCellRenderer;
14import javax.swing.tree.TreePath;
15import java.awt.*;
16import java.awt.event.MouseAdapter;
17import java.awt.event.MouseEvent;
18
19public class StructurePanel extends JPanel {
20 private JPanel sortingPanel;
21 private JCheckBox hideDeobfuscated;
22
23 private JTree structureTree;
24
25 public StructurePanel(Gui gui) {
26 this.sortingPanel = new JPanel();
27 this.hideDeobfuscated = new JCheckBox(I18n.translate("info_panel.tree.structure.hide_deobfuscated"));
28 this.hideDeobfuscated.addActionListener(event -> gui.showStructure(gui.getActiveEditor()));
29 this.sortingPanel.add(this.hideDeobfuscated);
30 this.sortingPanel.setVisible(false);
31
32 this.structureTree = new JTree();
33 this.structureTree.setModel(null);
34 this.structureTree.setCellRenderer(new StructureTreeCellRenderer());
35 this.structureTree.setShowsRootHandles(true);
36 this.structureTree.addMouseListener(new MouseAdapter() {
37 @Override
38 public void mouseClicked(MouseEvent event) {
39 if (event.getClickCount() >= 2 && event.getButton() == MouseEvent.BUTTON1) {
40 // get the selected node
41 TreePath path = structureTree.getSelectionPath();
42 if (path == null) {
43 return;
44 }
45
46 Object node = path.getLastPathComponent();
47 if (node instanceof StructureTreeNode) {
48 gui.getController().navigateTo(((StructureTreeNode) node).getEntry());
49 }
50 }
51 }
52 });
53
54 this.setLayout(new BorderLayout());
55 this.add(this.sortingPanel, BorderLayout.NORTH);
56 this.add(new JScrollPane(this.structureTree));
57 }
58
59 public JPanel getSortingPanel() {
60 return this.sortingPanel;
61 }
62
63 /**
64 * Returns whether the "Hide Deobfuscated" option of this structure panel is selected.
65 */
66 public boolean shouldHideDeobfuscated() {
67 return this.hideDeobfuscated.isSelected();
68 }
69
70 public JTree getStructureTree() {
71 return this.structureTree;
72 }
73
74 public void retranslateUi() {
75 this.hideDeobfuscated.setText(I18n.translate("info_panel.tree.structure.hide_deobfuscated"));
76 }
77
78 class StructureTreeCellRenderer extends DefaultTreeCellRenderer {
79
80 @Override
81 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
82 JComponent c = (JComponent) super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
83 ParentedEntry entry = ((StructureTreeNode) value).getEntry();
84
85 if (entry instanceof ClassEntry) {
86 this.setIcon(GuiUtil.CLASS_ICON);
87 } else if (entry instanceof MethodEntry) {
88 this.setIcon(((MethodEntry) entry).isConstructor() ? GuiUtil.CONSTRUCTOR_ICON : GuiUtil.METHOD_ICON);
89 } else if (entry instanceof FieldEntry) {
90 this.setIcon(GuiUtil.FIELD_ICON);
91 }
92
93 this.setText(value.toString());
94
95 return c;
96 }
97 }
98}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
index b898eac3..20d6a0eb 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
@@ -3,8 +3,6 @@ package cuchaz.enigma.gui.stats;
3import cuchaz.enigma.EnigmaProject; 3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener; 4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.analysis.index.EntryIndex; 5import cuchaz.enigma.analysis.index.EntryIndex;
6import cuchaz.enigma.api.service.NameProposalService;
7import cuchaz.enigma.api.service.ObfuscationTestService;
8import cuchaz.enigma.translation.mapping.EntryRemapper; 6import cuchaz.enigma.translation.mapping.EntryRemapper;
9import cuchaz.enigma.translation.mapping.EntryResolver; 7import cuchaz.enigma.translation.mapping.EntryResolver;
10import cuchaz.enigma.translation.mapping.ResolutionStrategy; 8import cuchaz.enigma.translation.mapping.ResolutionStrategy;
@@ -15,18 +13,16 @@ import cuchaz.enigma.utils.I18n;
15import java.util.*; 13import java.util.*;
16 14
17public class StatsGenerator { 15public class StatsGenerator {
16 private final EnigmaProject project;
18 private final EntryIndex entryIndex; 17 private final EntryIndex entryIndex;
19 private final EntryRemapper mapper; 18 private final EntryRemapper mapper;
20 private final EntryResolver entryResolver; 19 private final EntryResolver entryResolver;
21 private final List<ObfuscationTestService> obfuscationTestServices;
22 private final List<NameProposalService> nameProposalServices;
23 20
24 public StatsGenerator(EnigmaProject project) { 21 public StatsGenerator(EnigmaProject project) {
25 entryIndex = project.getJarIndex().getEntryIndex(); 22 this.project = project;
26 mapper = project.getMapper(); 23 this.entryIndex = project.getJarIndex().getEntryIndex();
27 entryResolver = project.getJarIndex().getEntryResolver(); 24 this.mapper = project.getMapper();
28 obfuscationTestServices = project.getEnigma().getServices().get(ObfuscationTestService.TYPE); 25 this.entryResolver = project.getJarIndex().getEntryResolver();
29 nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE);
30 } 26 }
31 27
32 public StatsResult generate(ProgressListener progress, Set<StatsMember> includedMembers, String topLevelPackage, boolean includeSynthetic) { 28 public StatsResult generate(ProgressListener progress, Set<StatsMember> includedMembers, String topLevelPackage, boolean includeSynthetic) {
@@ -111,36 +107,9 @@ public class StatsGenerator {
111 } 107 }
112 108
113 private void update(Map<String, Integer> counts, Entry<?> entry) { 109 private void update(Map<String, Integer> counts, Entry<?> entry) {
114 if (isObfuscated(entry)) { 110 if (project.isObfuscated(entry)) {
115 String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.'); 111 String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.');
116 counts.put(parent, counts.getOrDefault(parent, 0) + 1); 112 counts.put(parent, counts.getOrDefault(parent, 0) + 1);
117 } 113 }
118 } 114 }
119
120 private boolean isObfuscated(Entry<?> entry) {
121 String name = entry.getName();
122
123 if (!obfuscationTestServices.isEmpty()) {
124 for (ObfuscationTestService service : obfuscationTestServices) {
125 if (service.testDeobfuscated(entry)) {
126 return false;
127 }
128 }
129 }
130
131 if (!nameProposalServices.isEmpty()) {
132 for (NameProposalService service : nameProposalServices) {
133 if (service.proposeName(entry, mapper).isPresent()) {
134 return false;
135 }
136 }
137 }
138
139 String mappedName = mapper.deobfuscate(entry).getName();
140 if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) {
141 return false;
142 }
143
144 return true;
145 }
146} 115}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java
index 7fe942d0..6393913d 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java
@@ -1,24 +1,25 @@
1package cuchaz.enigma.gui.util; 1package cuchaz.enigma.gui.util;
2 2
3import java.awt.Color; 3import cuchaz.enigma.utils.Os;
4import java.awt.Cursor; 4
5import java.awt.Desktop; 5import javax.imageio.ImageIO;
6import java.awt.Font; 6import javax.swing.*;
7import java.awt.*;
7import java.awt.event.MouseAdapter; 8import java.awt.event.MouseAdapter;
8import java.awt.event.MouseEvent; 9import java.awt.event.MouseEvent;
9import java.awt.font.TextAttribute; 10import java.awt.font.TextAttribute;
10import java.io.IOException; 11import java.io.IOException;
12import java.io.InputStream;
11import java.net.URI; 13import java.net.URI;
12import java.net.URISyntaxException; 14import java.net.URISyntaxException;
13import java.util.Map; 15import java.util.Map;
14 16
15import javax.swing.JComponent;
16import javax.swing.JLabel;
17import javax.swing.ToolTipManager;
18
19import cuchaz.enigma.utils.Os;
20
21public class GuiUtil { 17public class GuiUtil {
18 public static final Icon CLASS_ICON = loadIcon("class");
19 public static final Icon METHOD_ICON = loadIcon("method");
20 public static final Icon FIELD_ICON = loadIcon("field");
21 public static final Icon CONSTRUCTOR_ICON = loadIcon("constructor");
22
22 public static void openUrl(String url) { 23 public static void openUrl(String url) {
23 try { 24 try {
24 switch (Os.getOs()) { 25 switch (Os.getOs()) {
@@ -70,4 +71,15 @@ public class GuiUtil {
70 return link; 71 return link;
71 } 72 }
72 73
74 public static Icon loadIcon(String name) {
75 try {
76 InputStream inputStream = GuiUtil.class.getResourceAsStream("/icons/" + name + ".png");
77 Image image = ImageIO.read(inputStream).getScaledInstance(ScaleUtil.scale(16), ScaleUtil.scale(16), Image.SCALE_DEFAULT);
78 return new ImageIcon(image);
79 } catch (IOException e) {
80 e.printStackTrace();
81 }
82
83 return null;
84 }
73} 85}
diff --git a/enigma-swing/src/main/resources/icons/class.png b/enigma-swing/src/main/resources/icons/class.png
new file mode 100644
index 00000000..17d82ecd
--- /dev/null
+++ b/enigma-swing/src/main/resources/icons/class.png
Binary files differ
diff --git a/enigma-swing/src/main/resources/icons/constructor.png b/enigma-swing/src/main/resources/icons/constructor.png
new file mode 100644
index 00000000..3728b554
--- /dev/null
+++ b/enigma-swing/src/main/resources/icons/constructor.png
Binary files differ
diff --git a/enigma-swing/src/main/resources/icons/field.png b/enigma-swing/src/main/resources/icons/field.png
new file mode 100644
index 00000000..4ac1aab9
--- /dev/null
+++ b/enigma-swing/src/main/resources/icons/field.png
Binary files differ
diff --git a/enigma-swing/src/main/resources/icons/method.png b/enigma-swing/src/main/resources/icons/method.png
new file mode 100644
index 00000000..e4e96797
--- /dev/null
+++ b/enigma-swing/src/main/resources/icons/method.png
Binary files differ
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
index fcd2c96a..a01eca19 100644
--- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
+++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java
@@ -18,6 +18,7 @@ import java.util.stream.Stream;
18 18
19import com.google.common.base.Functions; 19import com.google.common.base.Functions;
20import com.google.common.base.Preconditions; 20import com.google.common.base.Preconditions;
21import cuchaz.enigma.api.service.ObfuscationTestService;
21import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; 22import cuchaz.enigma.classprovider.ObfuscationFixClassProvider;
22import org.objectweb.asm.ClassWriter; 23import org.objectweb.asm.ClassWriter;
23import org.objectweb.asm.tree.ClassNode; 24import org.objectweb.asm.tree.ClassNode;
@@ -158,6 +159,35 @@ public class EnigmaProject {
158 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry()); 159 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
159 } 160 }
160 161
162 public boolean isObfuscated(Entry<?> entry) {
163 String name = entry.getName();
164
165 List<ObfuscationTestService> obfuscationTestServices = this.getEnigma().getServices().get(ObfuscationTestService.TYPE);
166 if (!obfuscationTestServices.isEmpty()) {
167 for (ObfuscationTestService service : obfuscationTestServices) {
168 if (service.testDeobfuscated(entry)) {
169 return false;
170 }
171 }
172 }
173
174 List<NameProposalService> nameProposalServices = this.getEnigma().getServices().get(NameProposalService.TYPE);
175 if (!nameProposalServices.isEmpty()) {
176 for (NameProposalService service : nameProposalServices) {
177 if (service.proposeName(entry, mapper).isPresent()) {
178 return false;
179 }
180 }
181 }
182
183 String mappedName = mapper.deobfuscate(entry).getName();
184 if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) {
185 return false;
186 }
187
188 return true;
189 }
190
161 public JarExport exportRemappedJar(ProgressListener progress) { 191 public JarExport exportRemappedJar(ProgressListener progress) {
162 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses(); 192 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
163 ClassProvider fixingClassProvider = new ObfuscationFixClassProvider(classProvider, jarIndex); 193 ClassProvider fixingClassProvider = new ObfuscationFixClassProvider(classProvider, jarIndex);
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java
new file mode 100644
index 00000000..f310aa75
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java
@@ -0,0 +1,123 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.api.service.NameProposalService;
5import cuchaz.enigma.translation.TranslateResult;
6import cuchaz.enigma.translation.mapping.EntryRemapper;
7import cuchaz.enigma.translation.representation.TypeDescriptor;
8import cuchaz.enigma.translation.representation.entry.*;
9
10import javax.swing.tree.DefaultMutableTreeNode;
11import java.util.List;
12
13public class StructureTreeNode extends DefaultMutableTreeNode {
14 private final List<NameProposalService> nameProposalServices;
15 private final EntryRemapper mapper;
16 private final ClassEntry parentEntry;
17 private final ParentedEntry entry;
18
19 public StructureTreeNode(EnigmaProject project, ClassEntry parentEntry, ParentedEntry entry) {
20 this.nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE);
21 this.mapper = project.getMapper();
22 this.parentEntry = parentEntry;
23 this.entry = entry;
24 }
25
26 /**
27 * Returns the parented entry corresponding to this tree node.
28 */
29 public ParentedEntry getEntry() {
30 return this.entry;
31 }
32
33 public void load(EnigmaProject project, boolean hideDeobfuscated) {
34 List<ParentedEntry> children = project.getJarIndex().getChildrenByClass().get(this.parentEntry);
35
36 for (ParentedEntry child : children) {
37 StructureTreeNode childNode = new StructureTreeNode(project, this.parentEntry, child);
38
39 if (child instanceof ClassEntry) {
40 childNode = new StructureTreeNode(project, (ClassEntry) child, child);
41 childNode.load(project, hideDeobfuscated);
42 }
43
44 // don't add deobfuscated members if hideDeobfuscated is true, unless it's an inner class
45 if (hideDeobfuscated && !project.isObfuscated(child) && !(child instanceof ClassEntry)) {
46 continue;
47 }
48
49 // don't add constructor methods if hideDeobfuscated is true
50 if (hideDeobfuscated && (child instanceof MethodEntry) && ((MethodEntry) child).isConstructor()) {
51 continue;
52 }
53
54 this.add(childNode);
55 }
56 }
57
58 @Override
59 public String toString() {
60 TranslateResult<ParentedEntry> translateResult = this.mapper.extendedDeobfuscate(this.entry);
61 String result = translateResult.getValue().getName();
62
63 if (translateResult.isObfuscated()) {
64 if (!this.nameProposalServices.isEmpty()) {
65 for (NameProposalService service : this.nameProposalServices) {
66 if (service.proposeName(this.entry, this.mapper).isPresent()) {
67 result = service.proposeName(this.entry, this.mapper).get();
68 }
69 }
70 }
71 }
72
73 if (this.entry instanceof FieldDefEntry) {
74 FieldDefEntry field = (FieldDefEntry) translateResult.getValue();
75 String returnType = this.parseDesc(field.getDesc());
76
77 result = result + ": " + returnType;
78 } else if (this.entry instanceof MethodDefEntry) {
79 MethodDefEntry method = (MethodDefEntry) translateResult.getValue();
80 String args = this.parseArgs(method.getDesc().getArgumentDescs());
81 String returnType = this.parseDesc(method.getDesc().getReturnDesc());
82
83 if (method.isConstructor()) {
84 result = method.getParent().getSimpleName() + args;
85 } else {
86 result = result + args + ": " + returnType;
87 }
88 }
89
90 return result;
91 }
92
93 private String parseArgs(List<TypeDescriptor> args) {
94 if (args.size() > 0) {
95 String result = "(";
96
97 for (int i = 0; i < args.size(); i++) {
98 if (i > 0) {
99 result += ", ";
100 }
101
102 result += this.parseDesc(args.get(i));
103 }
104
105 return result + ")";
106 }
107
108 return "()";
109 }
110
111 private String parseDesc(TypeDescriptor desc) {
112 if (desc.isVoid()) return "void";
113 if (desc.isPrimitive()) return desc.getPrimitive().getKeyword();
114 if (desc.isType()) return desc.getTypeEntry().getSimpleName();
115
116 if (desc.isArray()) {
117 if (desc.getArrayType().isPrimitive()) return desc.getArrayType().getPrimitive().getKeyword() + "[]";
118 if (desc.getArrayType().isType()) return desc.getArrayType().getTypeEntry().getSimpleName() + "[]";
119 }
120
121 return null;
122 }
123}
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
index b5ad91a8..aa360cfd 100644
--- a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
+++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
@@ -11,8 +11,7 @@
11 11
12package cuchaz.enigma.analysis.index; 12package cuchaz.enigma.analysis.index;
13 13
14import com.google.common.collect.HashMultimap; 14import com.google.common.collect.*;
15import com.google.common.collect.Multimap;
16import cuchaz.enigma.Enigma; 15import cuchaz.enigma.Enigma;
17import cuchaz.enigma.ProgressListener; 16import cuchaz.enigma.ProgressListener;
18import cuchaz.enigma.analysis.ReferenceTargetType; 17import cuchaz.enigma.analysis.ReferenceTargetType;
@@ -23,10 +22,7 @@ import cuchaz.enigma.translation.representation.Lambda;
23import cuchaz.enigma.translation.representation.entry.*; 22import cuchaz.enigma.translation.representation.entry.*;
24import cuchaz.enigma.utils.I18n; 23import cuchaz.enigma.utils.I18n;
25 24
26import java.util.Arrays; 25import java.util.*;
27import java.util.Collection;
28import java.util.HashSet;
29import java.util.Set;
30 26
31public class JarIndex implements JarIndexer { 27public class JarIndex implements JarIndexer {
32 private final Set<String> indexedClasses = new HashSet<>(); 28 private final Set<String> indexedClasses = new HashSet<>();
@@ -40,6 +36,7 @@ public class JarIndex implements JarIndexer {
40 private final Collection<JarIndexer> indexers; 36 private final Collection<JarIndexer> indexers;
41 37
42 private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); 38 private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create();
39 private final ListMultimap<ClassEntry, ParentedEntry> childrenByClass;
43 40
44 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { 41 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) {
45 this.entryIndex = entryIndex; 42 this.entryIndex = entryIndex;
@@ -49,6 +46,7 @@ public class JarIndex implements JarIndexer {
49 this.packageVisibilityIndex = packageVisibilityIndex; 46 this.packageVisibilityIndex = packageVisibilityIndex;
50 this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); 47 this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
51 this.entryResolver = new IndexEntryResolver(this); 48 this.entryResolver = new IndexEntryResolver(this);
49 this.childrenByClass = ArrayListMultimap.create();
52 } 50 }
53 51
54 public static JarIndex empty() { 52 public static JarIndex empty() {
@@ -101,6 +99,9 @@ public class JarIndex implements JarIndexer {
101 } 99 }
102 100
103 indexers.forEach(indexer -> indexer.indexClass(classEntry)); 101 indexers.forEach(indexer -> indexer.indexClass(classEntry));
102 if (classEntry.isInnerClass() && !classEntry.getAccess().isSynthetic()) {
103 childrenByClass.put(classEntry.getParent(), classEntry);
104 }
104 } 105 }
105 106
106 @Override 107 @Override
@@ -110,6 +111,9 @@ public class JarIndex implements JarIndexer {
110 } 111 }
111 112
112 indexers.forEach(indexer -> indexer.indexField(fieldEntry)); 113 indexers.forEach(indexer -> indexer.indexField(fieldEntry));
114 if (!fieldEntry.getAccess().isSynthetic()) {
115 childrenByClass.put(fieldEntry.getParent(), fieldEntry);
116 }
113 } 117 }
114 118
115 @Override 119 @Override
@@ -119,6 +123,9 @@ public class JarIndex implements JarIndexer {
119 } 123 }
120 124
121 indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); 125 indexers.forEach(indexer -> indexer.indexMethod(methodEntry));
126 if (!methodEntry.getAccess().isSynthetic() && !methodEntry.getName().equals("<clinit>")) {
127 childrenByClass.put(methodEntry.getParent(), methodEntry);
128 }
122 129
123 if (!methodEntry.isConstructor()) { 130 if (!methodEntry.isConstructor()) {
124 methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); 131 methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry);
@@ -176,6 +183,10 @@ public class JarIndex implements JarIndexer {
176 return entryResolver; 183 return entryResolver;
177 } 184 }
178 185
186 public ListMultimap<ClassEntry, ParentedEntry> getChildrenByClass() {
187 return this.childrenByClass;
188 }
189
179 public boolean isIndexed(String internalName) { 190 public boolean isIndexed(String internalName) {
180 return indexedClasses.contains(internalName); 191 return indexedClasses.contains(internalName);
181 } 192 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
index a7dccfcf..6a1b82f0 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
@@ -235,14 +235,14 @@ public class TypeDescriptor implements Translatable {
235 } 235 }
236 236
237 public enum Primitive { 237 public enum Primitive {
238 BYTE('B'), 238 BYTE('B', "byte"),
239 CHARACTER('C'), 239 CHARACTER('C', "char"),
240 SHORT('S'), 240 SHORT('S', "short"),
241 INTEGER('I'), 241 INTEGER('I', "int"),
242 LONG('J'), 242 LONG('J', "long"),
243 FLOAT('F'), 243 FLOAT('F', "float"),
244 DOUBLE('D'), 244 DOUBLE('D', "double"),
245 BOOLEAN('Z'); 245 BOOLEAN('Z', "boolean");
246 246
247 private static final Map<Character, Primitive> lookup; 247 private static final Map<Character, Primitive> lookup;
248 248
@@ -254,9 +254,11 @@ public class TypeDescriptor implements Translatable {
254 } 254 }
255 255
256 private char code; 256 private char code;
257 private String keyword;
257 258
258 Primitive(char code) { 259 Primitive(char code, String keyword) {
259 this.code = code; 260 this.code = code;
261 this.keyword = keyword;
260 } 262 }
261 263
262 public static Primitive get(char code) { 264 public static Primitive get(char code) {
@@ -266,5 +268,12 @@ public class TypeDescriptor implements Translatable {
266 public char getCode() { 268 public char getCode() {
267 return this.code; 269 return this.code;
268 } 270 }
271
272 /**
273 * Returns the Java keyword corresponding to this primitive.
274 */
275 public String getKeyword() {
276 return this.keyword;
277 }
269 } 278 }
270} 279}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
index 4a50021c..b4a22f1e 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
@@ -134,6 +134,9 @@ public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<
134 return name; 134 return name;
135 } 135 }
136 136
137 /**
138 * Returns whether this class entry has a parent, and therefore is an inner class.
139 */
137 public boolean isInnerClass() { 140 public boolean isInnerClass() {
138 return parent != null; 141 return parent != null;
139 } 142 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
index ff392fee..6fd412a0 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
@@ -29,6 +29,13 @@ public interface Entry<P extends Entry<?>> extends Translatable {
29 return getName(); 29 return getName();
30 } 30 }
31 31
32 /**
33 * Returns the parent entry of this entry.
34 *
35 * <p>The parent entry should be a {@linkplain MethodEntry method} for local variables,
36 * a {@linkplain ClassEntry class} for methods, fields and inner classes, and {@code null}
37 * for other classes.</p>
38 */
32 @Nullable 39 @Nullable
33 P getParent(); 40 P getParent();
34 41
diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json
index 9db4e1fc..8195bb1f 100644
--- a/enigma/src/main/resources/lang/en_us.json
+++ b/enigma/src/main/resources/lang/en_us.json
@@ -101,6 +101,8 @@
101 "info_panel.identifier.index": "Index", 101 "info_panel.identifier.index": "Index",
102 "info_panel.editor.class.decompiling": "(decompiling...)", 102 "info_panel.editor.class.decompiling": "(decompiling...)",
103 "info_panel.editor.class.not_found": "Unable to find class:", 103 "info_panel.editor.class.not_found": "Unable to find class:",
104 "info_panel.tree.structure": "Structure",
105 "info_panel.tree.structure.hide_deobfuscated": "Hide deobfuscated members",
104 "info_panel.tree.inheritance": "Inheritance", 106 "info_panel.tree.inheritance": "Inheritance",
105 "info_panel.tree.implementations": "Implementations", 107 "info_panel.tree.implementations": "Implementations",
106 "info_panel.tree.calls": "Call Graph", 108 "info_panel.tree.calls": "Call Graph",
diff --git a/enigma/src/main/resources/lang/fr_fr.json b/enigma/src/main/resources/lang/fr_fr.json
index 127b9c88..43bea4de 100644
--- a/enigma/src/main/resources/lang/fr_fr.json
+++ b/enigma/src/main/resources/lang/fr_fr.json
@@ -101,6 +101,8 @@
101 "info_panel.identifier.index": "Index", 101 "info_panel.identifier.index": "Index",
102 "info_panel.editor.class.decompiling": "(décompilation...)", 102 "info_panel.editor.class.decompiling": "(décompilation...)",
103 "info_panel.editor.class.not_found": "Impossible de trouver la classe :", 103 "info_panel.editor.class.not_found": "Impossible de trouver la classe :",
104 "info_panel.tree.structure": "Structure",
105 "info_panel.tree.structure.hide_deobfuscated": "Masquer les membres déobfusqués",
104 "info_panel.tree.inheritance": "Héritage", 106 "info_panel.tree.inheritance": "Héritage",
105 "info_panel.tree.implementations": "Implémentations", 107 "info_panel.tree.implementations": "Implémentations",
106 "info_panel.tree.calls": "Graphique des appels", 108 "info_panel.tree.calls": "Graphique des appels",