summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorGravatar Runemoro2019-11-08 17:36:33 -0500
committerGravatar modmuss502019-11-08 22:36:33 +0000
commit0e67256a2a66b2d0a0c6b3491ab2db63884bd55f (patch)
tree1d489fca6212d0df928cc0c8036099ab450baea5 /src/main
parentFix inner class renaming (#176) (diff)
downloadenigma-0e67256a2a66b2d0a0c6b3491ab2db63884bd55f.tar.gz
enigma-0e67256a2a66b2d0a0c6b3491ab2db63884bd55f.tar.xz
enigma-0e67256a2a66b2d0a0c6b3491ab2db63884bd55f.zip
Add stats generation (#177)
* Add stats generation * Parameters and inner classes too * Fixes
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java29
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java43
-rw-r--r--src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java188
-rw-r--r--src/main/java/cuchaz/enigma/gui/stats/StatsMember.java8
-rw-r--r--src/main/resources/stats.html34
6 files changed, 305 insertions, 4 deletions
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
index 54b5c92a..69f12e2e 100644
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -20,6 +20,8 @@ import cuchaz.enigma.api.service.ObfuscationTestService;
20import cuchaz.enigma.bytecode.translators.SourceFixVisitor; 20import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
21import cuchaz.enigma.config.Config; 21import cuchaz.enigma.config.Config;
22import cuchaz.enigma.gui.dialog.ProgressDialog; 22import cuchaz.enigma.gui.dialog.ProgressDialog;
23import cuchaz.enigma.gui.stats.StatsGenerator;
24import cuchaz.enigma.gui.stats.StatsMember;
23import cuchaz.enigma.gui.util.History; 25import cuchaz.enigma.gui.util.History;
24import cuchaz.enigma.throwables.MappingParseException; 26import cuchaz.enigma.throwables.MappingParseException;
25import cuchaz.enigma.translation.Translator; 27import cuchaz.enigma.translation.Translator;
@@ -31,17 +33,19 @@ import cuchaz.enigma.translation.representation.entry.Entry;
31import cuchaz.enigma.translation.representation.entry.FieldEntry; 33import cuchaz.enigma.translation.representation.entry.FieldEntry;
32import cuchaz.enigma.translation.representation.entry.MethodEntry; 34import cuchaz.enigma.translation.representation.entry.MethodEntry;
33import cuchaz.enigma.utils.ReadableToken; 35import cuchaz.enigma.utils.ReadableToken;
36import cuchaz.enigma.utils.Utils;
34import org.objectweb.asm.Opcodes; 37import org.objectweb.asm.Opcodes;
35 38
36import javax.annotation.Nullable; 39import javax.annotation.Nullable;
37import javax.swing.*; 40import javax.swing.*;
41import java.awt.*;
38import java.awt.event.ItemEvent; 42import java.awt.event.ItemEvent;
39import java.io.PrintWriter; 43import java.io.*;
40import java.io.StringWriter;
41import java.nio.file.Path; 44import java.nio.file.Path;
42import java.util.Collection; 45import java.util.Collection;
43import java.util.List; 46import java.util.List;
44import java.util.Optional; 47import java.util.Optional;
48import java.util.Set;
45import java.util.concurrent.CompletableFuture; 49import java.util.concurrent.CompletableFuture;
46import java.util.concurrent.ExecutorService; 50import java.util.concurrent.ExecutorService;
47import java.util.concurrent.Executors; 51import java.util.concurrent.Executors;
@@ -527,4 +531,25 @@ public class GuiController {
527 531
528 refreshCurrentClass(reference); 532 refreshCurrentClass(reference);
529 } 533 }
534
535 public void openStats(Set<StatsMember> includedMembers) {
536 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
537 String data = new StatsGenerator(project).generate(progress, includedMembers);
538
539 try {
540 File statsFile = File.createTempFile("stats", ".html");
541
542 try (FileWriter w = new FileWriter(statsFile)) {
543 w.write(
544 Utils.readResourceToString("/stats.html")
545 .replace("/*data*/", data)
546 );
547 }
548
549 Desktop.getDesktop().open(statsFile);
550 } catch (IOException e) {
551 throw new Error(e);
552 }
553 });
554 }
530} 555}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
index cf5c2c86..ae30667c 100644
--- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
+++ b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
@@ -89,7 +89,12 @@ public class ProgressDialog implements ProgressListener, AutoCloseable {
89 @Override 89 @Override
90 public void step(int numDone, String message) { 90 public void step(int numDone, String message) {
91 this.labelText.setText(message); 91 this.labelText.setText(message);
92 this.progress.setValue(numDone); 92 if (numDone != -1) {
93 this.progress.setValue(numDone);
94 this.progress.setIndeterminate(false);
95 } else {
96 this.progress.setIndeterminate(true);
97 }
93 98
94 // update the frame 99 // update the frame
95 this.frame.validate(); 100 this.frame.validate();
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
index 2e10bfb5..cdcad05c 100644
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
@@ -5,6 +5,7 @@ import cuchaz.enigma.config.Themes;
5import cuchaz.enigma.gui.Gui; 5import cuchaz.enigma.gui.Gui;
6import cuchaz.enigma.gui.dialog.AboutDialog; 6import cuchaz.enigma.gui.dialog.AboutDialog;
7import cuchaz.enigma.gui.dialog.SearchDialog; 7import cuchaz.enigma.gui.dialog.SearchDialog;
8import cuchaz.enigma.gui.stats.StatsMember;
8import cuchaz.enigma.translation.mapping.serde.MappingFormat; 9import cuchaz.enigma.translation.mapping.serde.MappingFormat;
9import cuchaz.enigma.utils.Utils; 10import cuchaz.enigma.utils.Utils;
10 11
@@ -19,8 +20,9 @@ import java.net.URL;
19import java.nio.file.Files; 20import java.nio.file.Files;
20import java.nio.file.Path; 21import java.nio.file.Path;
21import java.nio.file.Paths; 22import java.nio.file.Paths;
22import java.util.ArrayList;
23import java.util.List; 23import java.util.List;
24import java.util.*;
25import java.util.stream.Collectors;
24 26
25public class MenuBar extends JMenuBar { 27public class MenuBar extends JMenuBar {
26 28
@@ -154,6 +156,45 @@ public class MenuBar extends JMenuBar {
154 } 156 }
155 menu.addSeparator(); 157 menu.addSeparator();
156 { 158 {
159 JMenuItem stats = new JMenuItem("Mapping Stats...");
160
161 stats.addActionListener(event -> {
162 JFrame frame = new JFrame("Choose Included Members");
163 Container pane = frame.getContentPane();
164 pane.setLayout(new FlowLayout());
165
166 Map<StatsMember, JCheckBox> checkboxes = Arrays
167 .stream(StatsMember.values())
168 .collect(Collectors.toMap(m -> m, m -> {
169 JCheckBox checkbox = new JCheckBox(Utils.caplisiseCamelCase(m.name()));
170 pane.add(checkbox);
171 return checkbox;
172 }));
173
174 JButton button = new JButton("Generate Stats");
175
176 button.addActionListener(e -> {
177 Set<StatsMember> includedMembers = checkboxes
178 .entrySet()
179 .stream()
180 .filter(entry -> entry.getValue().isSelected())
181 .map(Map.Entry::getKey)
182 .collect(Collectors.toSet());
183
184 frame.setVisible(false);
185 frame.dispose();
186 gui.getController().openStats(includedMembers);
187 });
188
189 pane.add(button);
190 frame.pack();
191 frame.setVisible(true);
192 });
193
194 menu.add(stats);
195 }
196 menu.addSeparator();
197 {
157 JMenuItem item = new JMenuItem("Exit"); 198 JMenuItem item = new JMenuItem("Exit");
158 menu.add(item); 199 menu.add(item);
159 item.addActionListener(event -> this.gui.close()); 200 item.addActionListener(event -> this.gui.close());
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
new file mode 100644
index 00000000..485daf55
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
@@ -0,0 +1,188 @@
1package cuchaz.enigma.gui.stats;
2
3import com.google.gson.GsonBuilder;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.analysis.index.EntryIndex;
7import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.api.service.ObfuscationTestService;
9import cuchaz.enigma.translation.mapping.EntryRemapper;
10import cuchaz.enigma.translation.mapping.EntryResolver;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.TypeDescriptor;
13import cuchaz.enigma.translation.representation.entry.*;
14
15import java.util.*;
16
17public class StatsGenerator {
18 private final EntryIndex entryIndex;
19 private final EntryRemapper mapper;
20 private final EntryResolver entryResolver;
21 private final ObfuscationTestService obfuscationTestService;
22 private final NameProposalService nameProposalService;
23
24 public StatsGenerator(EnigmaProject project) {
25 entryIndex = project.getJarIndex().getEntryIndex();
26 mapper = project.getMapper();
27 entryResolver = project.getJarIndex().getEntryResolver();
28 obfuscationTestService = project.getEnigma().getServices().get(ObfuscationTestService.TYPE).orElse(null);
29 nameProposalService = project.getEnigma().getServices().get(NameProposalService.TYPE).orElse(null);
30 }
31
32 public String generate(ProgressListener progress, Set<StatsMember> includedMembers) {
33 includedMembers = EnumSet.copyOf(includedMembers);
34 int totalWork = 0;
35
36 if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
37 totalWork += entryIndex.getMethods().size();
38 }
39
40 if (includedMembers.contains(StatsMember.FIELDS)) {
41 totalWork += entryIndex.getFields().size();
42 }
43
44 if (includedMembers.contains(StatsMember.CLASSES)) {
45 totalWork += entryIndex.getClasses().size();
46 }
47
48 progress.init(totalWork, "Generating stats");
49
50 Map<String, Integer> counts = new HashMap<>();
51
52 int numDone = 0;
53 if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
54 for (MethodEntry method : entryIndex.getMethods()) {
55 progress.step(numDone++, "Methods");
56 MethodEntry root = entryResolver
57 .resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT)
58 .stream()
59 .findFirst()
60 .orElseThrow(AssertionError::new);
61
62 if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) {
63 if (includedMembers.contains(StatsMember.METHODS)) {
64 update(counts, method);
65 }
66
67 if (includedMembers.contains(StatsMember.PARAMETERS)) {
68 int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1;
69 for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) {
70 update(counts, new LocalVariableEntry(method, index, "", true));
71 index += argument.getSize();
72 }
73 }
74 }
75 }
76 }
77
78 if (includedMembers.contains(StatsMember.FIELDS)) {
79 for (FieldEntry field : entryIndex.getFields()) {
80 progress.step(numDone++, "Fields");
81 update(counts, field);
82 }
83 }
84
85 if (includedMembers.contains(StatsMember.CLASSES)) {
86 for (ClassEntry clazz : entryIndex.getClasses()) {
87 progress.step(numDone++, "Classes");
88 update(counts, clazz);
89 }
90 }
91
92 progress.step(-1, "Generating data");
93
94 Tree<Integer> tree = new Tree<>();
95
96 for (Map.Entry<String, Integer> entry : counts.entrySet()) {
97 if (entry.getKey().startsWith("com.mojang")) continue; // just a few unmapped names, no point in having a subsection
98 tree.getNode(entry.getKey()).value = entry.getValue();
99 }
100
101 tree.collapse(tree.root);
102 return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root);
103 }
104
105 private void update(Map<String, Integer> counts, Entry<?> entry) {
106 if (isObfuscated(entry)) {
107 String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.');
108 counts.put(parent, counts.getOrDefault(parent, 0) + 1);
109 }
110 }
111
112 private boolean isObfuscated(Entry<?> entry) {
113 String name = entry.getName();
114
115 if (obfuscationTestService != null && obfuscationTestService.testDeobfuscated(entry)) {
116 return false;
117 }
118
119 if (nameProposalService != null && nameProposalService.proposeName(entry, mapper).isPresent()) {
120 return false;
121 }
122
123 String mappedName = mapper.deobfuscate(entry).getName();
124 if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) {
125 return false;
126 }
127
128 return true;
129 }
130
131 private static class Tree<T> {
132 public final Node<T> root;
133 private final Map<String, Node<T>> nodes = new HashMap<>();
134
135 public static class Node<T> {
136 public String name;
137 public T value;
138 public List<Node<T>> children = new ArrayList<>();
139 private final transient Map<String, Node<T>> namedChildren = new HashMap<>();
140
141 public Node(String name, T value) {
142 this.name = name;
143 this.value = value;
144 }
145 }
146
147 public Tree() {
148 root = new Node<>("", null);
149 }
150
151 public Node<T> getNode(String name) {
152 Node<T> node = nodes.get(name);
153
154 if (node == null) {
155 node = root;
156
157 for (String part : name.split("\\.")) {
158 Node<T> child = node.namedChildren.get(part);
159
160 if (child == null) {
161 child = new Node<>(part, null);
162 node.namedChildren.put(part, child);
163 node.children.add(child);
164 }
165
166 node = child;
167 }
168
169 nodes.put(name, node);
170 }
171
172 return node;
173 }
174
175 public void collapse(Node<T> node) {
176 while (node.children.size() == 1) {
177 Node<T> child = node.children.get(0);
178 node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name;
179 node.children = child.children;
180 node.value = child.value;
181 }
182
183 for (Node<T> child : node.children) {
184 collapse(child);
185 }
186 }
187 }
188}
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java b/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java
new file mode 100644
index 00000000..70b4f40d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java
@@ -0,0 +1,8 @@
1package cuchaz.enigma.gui.stats;
2
3public enum StatsMember {
4 METHODS,
5 FIELDS,
6 PARAMETERS,
7 CLASSES
8}
diff --git a/src/main/resources/stats.html b/src/main/resources/stats.html
new file mode 100644
index 00000000..fcff7c0f
--- /dev/null
+++ b/src/main/resources/stats.html
@@ -0,0 +1,34 @@
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta content="width=device-width, initial-scale=1" name="viewport">
6 <title>Stats</title>
7 <link href="https://cdn.anychart.com/releases/v8/css/anychart-ui.min.css" rel="stylesheet" type="text/css">
8 <style>
9 html, body, #container {
10 width: 100%;
11 height: 100%;
12 margin: 0;
13 padding: 0;
14 }
15 </style>
16</head>
17
18<body>
19<div id="container"></div>
20<script src="https://cdn.anychart.com/releases/v8/js/anychart-base.min.js"></script>
21<script src="https://cdn.anychart.com/releases/v8/js/anychart-sunburst.min.js"></script>
22<script src="https://cdn.anychart.com/releases/v8/js/anychart-exports.min.js"></script>
23<script src="https://cdn.anychart.com/releases/v8/js/anychart-ui.min.js"></script>
24<script>
25 anychart.onDocumentReady(function () {
26 var chart = anychart.sunburst([/*data*/], "as-tree");
27 chart.sort("desc");
28 chart.calculationMode("parent-independent");
29 chart.container("container");
30 chart.draw();
31 });
32</script>
33</body>
34</html>