diff options
Diffstat (limited to 'enigma-swing/src/main/java/cuchaz')
5 files changed, 212 insertions, 102 deletions
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 b62e9cf9..124ad07e 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java | |||
| @@ -492,7 +492,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 492 | 492 | ||
| 493 | public void openStats(Set<StatsMember> includedMembers, String topLevelPackage) { | 493 | public void openStats(Set<StatsMember> includedMembers, String topLevelPackage) { |
| 494 | ProgressDialog.runOffThread(gui.getFrame(), progress -> { | 494 | ProgressDialog.runOffThread(gui.getFrame(), progress -> { |
| 495 | String data = new StatsGenerator(project).generate(progress, includedMembers, topLevelPackage); | 495 | String data = new StatsGenerator(project).generate(progress, includedMembers, topLevelPackage).getTreeJson(); |
| 496 | 496 | ||
| 497 | try { | 497 | try { |
| 498 | File statsFile = File.createTempFile("stats", ".html"); | 498 | File statsFile = File.createTempFile("stats", ".html"); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java index d8d3acd2..c691d753 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java | |||
| @@ -1,74 +1,132 @@ | |||
| 1 | package cuchaz.enigma.gui.dialog; | 1 | package cuchaz.enigma.gui.dialog; |
| 2 | 2 | ||
| 3 | import java.awt.*; | 3 | import java.awt.Container; |
| 4 | import java.util.Arrays; | 4 | import java.awt.Dimension; |
| 5 | import java.awt.GridBagConstraints; | ||
| 6 | import java.awt.GridBagLayout; | ||
| 7 | import java.util.Collections; | ||
| 8 | import java.util.HashMap; | ||
| 5 | import java.util.Locale; | 9 | import java.util.Locale; |
| 6 | import java.util.Map; | 10 | import java.util.Map; |
| 7 | import java.util.Set; | 11 | import java.util.Set; |
| 8 | import java.util.stream.Collectors; | 12 | import java.util.stream.Collectors; |
| 9 | 13 | ||
| 10 | import javax.swing.*; | 14 | import javax.swing.JButton; |
| 15 | import javax.swing.JCheckBox; | ||
| 16 | import javax.swing.JDialog; | ||
| 17 | import javax.swing.JFrame; | ||
| 18 | import javax.swing.JLabel; | ||
| 19 | import javax.swing.JPanel; | ||
| 20 | import javax.swing.JTextField; | ||
| 21 | import javax.swing.SwingUtilities; | ||
| 11 | 22 | ||
| 12 | import cuchaz.enigma.gui.Gui; | 23 | import cuchaz.enigma.gui.Gui; |
| 24 | import cuchaz.enigma.gui.stats.StatsGenerator; | ||
| 13 | import cuchaz.enigma.gui.stats.StatsMember; | 25 | import cuchaz.enigma.gui.stats.StatsMember; |
| 26 | import cuchaz.enigma.gui.stats.StatsResult; | ||
| 14 | import cuchaz.enigma.gui.util.ScaleUtil; | 27 | import cuchaz.enigma.gui.util.ScaleUtil; |
| 15 | import cuchaz.enigma.utils.I18n; | 28 | import cuchaz.enigma.utils.I18n; |
| 16 | 29 | ||
| 17 | public class StatsDialog { | 30 | public class StatsDialog { |
| 18 | 31 | ||
| 19 | public static void show(Gui gui) { | 32 | public static void show(Gui gui) { |
| 33 | ProgressDialog.runOffThread(gui.getFrame(), listener -> { | ||
| 34 | final StatsGenerator statsGenerator = new StatsGenerator(gui.getController().project); | ||
| 35 | final Map<StatsMember, StatsResult> results = new HashMap<>(); | ||
| 36 | for (StatsMember member : StatsMember.values()) { | ||
| 37 | results.put(member, statsGenerator.generate(listener, Collections.singleton(member), "")); | ||
| 38 | } | ||
| 39 | SwingUtilities.invokeLater(() -> show(gui, results)); | ||
| 40 | }); | ||
| 41 | } | ||
| 42 | |||
| 43 | public static void show(Gui gui, Map<StatsMember, StatsResult> results) { | ||
| 20 | // init frame | 44 | // init frame |
| 21 | JFrame frame = new JFrame(I18n.translate("menu.file.stats.title")); | 45 | JDialog dialog = new JDialog(gui.getFrame(), I18n.translate("menu.file.stats.title"), true); |
| 22 | JPanel checkboxesPanel = new JPanel(); | 46 | Container contentPane = dialog.getContentPane(); |
| 23 | JPanel topLevelPackagePanel = new JPanel(); | 47 | contentPane.setLayout(new GridBagLayout()); |
| 24 | JPanel buttonPanel = new JPanel(); | 48 | |
| 25 | frame.setLayout(new GridLayout(3, 0)); | 49 | GridBagConstraints c = new GridBagConstraints(); |
| 26 | frame.add(checkboxesPanel); | 50 | c.insets = ScaleUtil.getInsets(4, 4, 4, 4); |
| 27 | frame.add(topLevelPackagePanel); | 51 | c.gridy = 0; |
| 28 | frame.add(buttonPanel); | 52 | |
| 29 | 53 | Map<StatsMember, JCheckBox> checkboxes = new HashMap<>(); | |
| 30 | // show checkboxes | 54 | |
| 31 | Map<StatsMember, JCheckBox> checkboxes = Arrays | 55 | results.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(e -> { |
| 32 | .stream(StatsMember.values()) | 56 | StatsMember m = e.getKey(); |
| 33 | .collect(Collectors.toMap(m -> m, m -> { | 57 | StatsResult result = e.getValue(); |
| 34 | JCheckBox checkbox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT))); | 58 | |
| 35 | checkboxesPanel.add(checkbox); | 59 | c.gridx = 0; |
| 36 | return checkbox; | 60 | c.weightx = 1.0; |
| 37 | })); | 61 | c.anchor = GridBagConstraints.WEST; |
| 62 | JCheckBox checkBox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT))); | ||
| 63 | checkboxes.put(m, checkBox); | ||
| 64 | contentPane.add(checkBox, c); | ||
| 65 | |||
| 66 | c.gridx = 1; | ||
| 67 | c.weightx = 0.0; | ||
| 68 | c.anchor = GridBagConstraints.EAST; | ||
| 69 | contentPane.add(new JLabel(Integer.toString(result.getMapped())), c); | ||
| 70 | |||
| 71 | c.gridx = 2; | ||
| 72 | contentPane.add(new JLabel("/"), c); | ||
| 73 | |||
| 74 | c.gridx = 3; | ||
| 75 | contentPane.add(new JLabel(Integer.toString(result.getTotal())), c); | ||
| 76 | |||
| 77 | c.gridx = 4; | ||
| 78 | contentPane.add(new JLabel(String.format("%.2f%%", result.getPercentage())), c); | ||
| 79 | |||
| 80 | c.gridy += 1; | ||
| 81 | }); | ||
| 82 | |||
| 83 | c.gridx = 0; | ||
| 84 | c.gridwidth = 5; | ||
| 85 | c.weightx = 1.0; | ||
| 86 | c.anchor = GridBagConstraints.WEST; | ||
| 38 | 87 | ||
| 39 | // show top-level package option | 88 | // show top-level package option |
| 40 | JLabel topLevelPackageOption = new JLabel(I18n.translate("menu.file.stats.top_level_package")); | 89 | JLabel topLevelPackageOption = new JLabel(I18n.translate("menu.file.stats.top_level_package")); |
| 90 | contentPane.add(topLevelPackageOption, c); | ||
| 91 | |||
| 92 | c.gridy += 1; | ||
| 93 | c.weightx = 1.0; | ||
| 94 | c.fill = GridBagConstraints.HORIZONTAL; | ||
| 41 | JTextField topLevelPackage = new JTextField(); | 95 | JTextField topLevelPackage = new JTextField(); |
| 42 | topLevelPackage.setPreferredSize(ScaleUtil.getDimension(200, 25)); | 96 | contentPane.add(topLevelPackage, c); |
| 43 | topLevelPackagePanel.add(topLevelPackageOption); | 97 | |
| 44 | topLevelPackagePanel.add(topLevelPackage); | 98 | c.gridy += 1; |
| 99 | c.weighty = 1.0; | ||
| 100 | c.fill = GridBagConstraints.NONE; | ||
| 101 | c.anchor = GridBagConstraints.SOUTHEAST; | ||
| 45 | 102 | ||
| 46 | // show generate button | 103 | // show generate button |
| 47 | JButton button = new JButton(I18n.translate("menu.file.stats.generate")); | 104 | JButton button = new JButton(I18n.translate("menu.file.stats.generate")); |
| 48 | buttonPanel.add(button); | ||
| 49 | button.setEnabled(false); | 105 | button.setEnabled(false); |
| 50 | button.addActionListener(action -> { | 106 | button.addActionListener(action -> { |
| 51 | frame.dispose(); | 107 | dialog.dispose(); |
| 52 | generateStats(gui, checkboxes, topLevelPackage.getText()); | 108 | generateStats(gui, checkboxes, topLevelPackage.getText()); |
| 53 | }); | 109 | }); |
| 54 | 110 | ||
| 111 | contentPane.add(button, c); | ||
| 112 | |||
| 55 | // add action listener to each checkbox | 113 | // add action listener to each checkbox |
| 56 | checkboxes.entrySet().forEach(checkbox -> { | 114 | checkboxes.forEach((key, value) -> value.addActionListener(action -> { |
| 57 | checkbox.getValue().addActionListener(action -> { | 115 | if (!button.isEnabled()) { |
| 58 | if (!button.isEnabled()) { | 116 | button.setEnabled(true); |
| 59 | button.setEnabled(true); | 117 | } else if (checkboxes.entrySet().stream().noneMatch(entry -> entry.getValue().isSelected())) { |
| 60 | } else if (checkboxes.entrySet().stream().allMatch(entry -> !entry.getValue().isSelected())) { | 118 | button.setEnabled(false); |
| 61 | button.setEnabled(false); | 119 | } |
| 62 | } | 120 | })); |
| 63 | }); | ||
| 64 | }); | ||
| 65 | 121 | ||
| 66 | // show the frame | 122 | // show the frame |
| 67 | frame.pack(); | 123 | dialog.pack(); |
| 68 | frame.setVisible(true); | 124 | Dimension size = dialog.getSize(); |
| 69 | frame.setSize(ScaleUtil.getDimension(500, 150)); | 125 | dialog.setMinimumSize(size); |
| 70 | frame.setResizable(false); | 126 | size.width = ScaleUtil.scale(350); |
| 71 | frame.setLocationRelativeTo(gui.getFrame()); | 127 | dialog.setSize(size); |
| 128 | dialog.setLocationRelativeTo(gui.getFrame()); | ||
| 129 | dialog.setVisible(true); | ||
| 72 | } | 130 | } |
| 73 | 131 | ||
| 74 | private static void generateStats(Gui gui, Map<StatsMember, JCheckBox> checkboxes, String topLevelPackage) { | 132 | private static void generateStats(Gui gui, Map<StatsMember, JCheckBox> checkboxes, String topLevelPackage) { |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 1481a9dc..768bc410 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java | |||
| @@ -145,6 +145,7 @@ public class MenuBar { | |||
| 145 | this.closeMappingsItem.setEnabled(jarOpen); | 145 | this.closeMappingsItem.setEnabled(jarOpen); |
| 146 | this.exportSourceItem.setEnabled(jarOpen); | 146 | this.exportSourceItem.setEnabled(jarOpen); |
| 147 | this.exportJarItem.setEnabled(jarOpen); | 147 | this.exportJarItem.setEnabled(jarOpen); |
| 148 | this.statsItem.setEnabled(jarOpen); | ||
| 148 | } | 149 | } |
| 149 | 150 | ||
| 150 | public JMenuBar getUi() { | 151 | public JMenuBar getUi() { |
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 3d031a72..a5eea56e 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 | |||
| @@ -1,6 +1,5 @@ | |||
| 1 | package cuchaz.enigma.gui.stats; | 1 | package cuchaz.enigma.gui.stats; |
| 2 | 2 | ||
| 3 | import com.google.gson.GsonBuilder; | ||
| 4 | import cuchaz.enigma.EnigmaProject; | 3 | import cuchaz.enigma.EnigmaProject; |
| 5 | import cuchaz.enigma.ProgressListener; | 4 | import cuchaz.enigma.ProgressListener; |
| 6 | import cuchaz.enigma.analysis.index.EntryIndex; | 5 | import cuchaz.enigma.analysis.index.EntryIndex; |
| @@ -30,9 +29,10 @@ public class StatsGenerator { | |||
| 30 | nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE); | 29 | nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE); |
| 31 | } | 30 | } |
| 32 | 31 | ||
| 33 | public String generate(ProgressListener progress, Set<StatsMember> includedMembers, String topLevelPackage) { | 32 | public StatsResult generate(ProgressListener progress, Set<StatsMember> includedMembers, String topLevelPackage) { |
| 34 | includedMembers = EnumSet.copyOf(includedMembers); | 33 | includedMembers = EnumSet.copyOf(includedMembers); |
| 35 | int totalWork = 0; | 34 | int totalWork = 0; |
| 35 | int totalMappable = 0; | ||
| 36 | 36 | ||
| 37 | if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { | 37 | if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) { |
| 38 | totalWork += entryIndex.getMethods().size(); | 38 | totalWork += entryIndex.getMethods().size(); |
| @@ -63,6 +63,7 @@ public class StatsGenerator { | |||
| 63 | if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) { | 63 | if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) { |
| 64 | if (includedMembers.contains(StatsMember.METHODS)) { | 64 | if (includedMembers.contains(StatsMember.METHODS)) { |
| 65 | update(counts, method); | 65 | update(counts, method); |
| 66 | totalMappable ++; | ||
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | if (includedMembers.contains(StatsMember.PARAMETERS)) { | 69 | if (includedMembers.contains(StatsMember.PARAMETERS)) { |
| @@ -70,6 +71,7 @@ public class StatsGenerator { | |||
| 70 | for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) { | 71 | for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) { |
| 71 | update(counts, new LocalVariableEntry(method, index, "", true,null)); | 72 | update(counts, new LocalVariableEntry(method, index, "", true,null)); |
| 72 | index += argument.getSize(); | 73 | index += argument.getSize(); |
| 74 | totalMappable ++; | ||
| 73 | } | 75 | } |
| 74 | } | 76 | } |
| 75 | } | 77 | } |
| @@ -81,6 +83,7 @@ public class StatsGenerator { | |||
| 81 | progress.step(numDone++, I18n.translate("type.fields")); | 83 | progress.step(numDone++, I18n.translate("type.fields")); |
| 82 | if (!((FieldDefEntry)field).getAccess().isSynthetic()) { | 84 | if (!((FieldDefEntry)field).getAccess().isSynthetic()) { |
| 83 | update(counts, field); | 85 | update(counts, field); |
| 86 | totalMappable ++; | ||
| 84 | } | 87 | } |
| 85 | } | 88 | } |
| 86 | } | 89 | } |
| @@ -89,12 +92,13 @@ public class StatsGenerator { | |||
| 89 | for (ClassEntry clazz : entryIndex.getClasses()) { | 92 | for (ClassEntry clazz : entryIndex.getClasses()) { |
| 90 | progress.step(numDone++, I18n.translate("type.classes")); | 93 | progress.step(numDone++, I18n.translate("type.classes")); |
| 91 | update(counts, clazz); | 94 | update(counts, clazz); |
| 95 | totalMappable ++; | ||
| 92 | } | 96 | } |
| 93 | } | 97 | } |
| 94 | 98 | ||
| 95 | progress.step(-1, I18n.translate("progress.stats.data")); | 99 | progress.step(-1, I18n.translate("progress.stats.data")); |
| 96 | 100 | ||
| 97 | Tree<Integer> tree = new Tree<>(); | 101 | StatsResult.Tree<Integer> tree = new StatsResult.Tree<>(); |
| 98 | 102 | ||
| 99 | for (Map.Entry<String, Integer> entry : counts.entrySet()) { | 103 | for (Map.Entry<String, Integer> entry : counts.entrySet()) { |
| 100 | if (entry.getKey().startsWith(topLevelPackage)) { | 104 | if (entry.getKey().startsWith(topLevelPackage)) { |
| @@ -103,7 +107,7 @@ public class StatsGenerator { | |||
| 103 | } | 107 | } |
| 104 | 108 | ||
| 105 | tree.collapse(tree.root); | 109 | tree.collapse(tree.root); |
| 106 | return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root); | 110 | return new StatsResult(totalMappable, counts.values().stream().mapToInt(i -> i).sum(), tree); |
| 107 | } | 111 | } |
| 108 | 112 | ||
| 109 | private void update(Map<String, Integer> counts, Entry<?> entry) { | 113 | private void update(Map<String, Integer> counts, Entry<?> entry) { |
| @@ -139,62 +143,4 @@ public class StatsGenerator { | |||
| 139 | 143 | ||
| 140 | return true; | 144 | return true; |
| 141 | } | 145 | } |
| 142 | |||
| 143 | private static class Tree<T> { | ||
| 144 | public final Node<T> root; | ||
| 145 | private final Map<String, Node<T>> nodes = new HashMap<>(); | ||
| 146 | |||
| 147 | public static class Node<T> { | ||
| 148 | public String name; | ||
| 149 | public T value; | ||
| 150 | public List<Node<T>> children = new ArrayList<>(); | ||
| 151 | private final transient Map<String, Node<T>> namedChildren = new HashMap<>(); | ||
| 152 | |||
| 153 | public Node(String name, T value) { | ||
| 154 | this.name = name; | ||
| 155 | this.value = value; | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | public Tree() { | ||
| 160 | root = new Node<>("", null); | ||
| 161 | } | ||
| 162 | |||
| 163 | public Node<T> getNode(String name) { | ||
| 164 | Node<T> node = nodes.get(name); | ||
| 165 | |||
| 166 | if (node == null) { | ||
| 167 | node = root; | ||
| 168 | |||
| 169 | for (String part : name.split("\\.")) { | ||
| 170 | Node<T> child = node.namedChildren.get(part); | ||
| 171 | |||
| 172 | if (child == null) { | ||
| 173 | child = new Node<>(part, null); | ||
| 174 | node.namedChildren.put(part, child); | ||
| 175 | node.children.add(child); | ||
| 176 | } | ||
| 177 | |||
| 178 | node = child; | ||
| 179 | } | ||
| 180 | |||
| 181 | nodes.put(name, node); | ||
| 182 | } | ||
| 183 | |||
| 184 | return node; | ||
| 185 | } | ||
| 186 | |||
| 187 | public void collapse(Node<T> node) { | ||
| 188 | while (node.children.size() == 1) { | ||
| 189 | Node<T> child = node.children.get(0); | ||
| 190 | node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name; | ||
| 191 | node.children = child.children; | ||
| 192 | node.value = child.value; | ||
| 193 | } | ||
| 194 | |||
| 195 | for (Node<T> child : node.children) { | ||
| 196 | collapse(child); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | 146 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsResult.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsResult.java new file mode 100644 index 00000000..0a71a647 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/stats/StatsResult.java | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | package cuchaz.enigma.gui.stats; | ||
| 2 | |||
| 3 | import com.google.gson.GsonBuilder; | ||
| 4 | |||
| 5 | import java.util.ArrayList; | ||
| 6 | import java.util.HashMap; | ||
| 7 | import java.util.List; | ||
| 8 | import java.util.Map; | ||
| 9 | |||
| 10 | public final class StatsResult { | ||
| 11 | |||
| 12 | private final int total; | ||
| 13 | private final int unmapped; | ||
| 14 | private final Tree<Integer> tree; | ||
| 15 | |||
| 16 | public StatsResult(int total, int unmapped, Tree<Integer> tree) { | ||
| 17 | this.total = total; | ||
| 18 | this.unmapped = unmapped; | ||
| 19 | this.tree = tree; | ||
| 20 | } | ||
| 21 | |||
| 22 | public int getTotal() { | ||
| 23 | return total; | ||
| 24 | } | ||
| 25 | |||
| 26 | public int getUnmapped() { | ||
| 27 | return unmapped; | ||
| 28 | } | ||
| 29 | |||
| 30 | public int getMapped() { | ||
| 31 | return total - unmapped; | ||
| 32 | } | ||
| 33 | |||
| 34 | public double getPercentage() { | ||
| 35 | return (getMapped() * 100.0f) / total; | ||
| 36 | } | ||
| 37 | |||
| 38 | public String getTreeJson() { | ||
| 39 | return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public String toString() { | ||
| 44 | return String.format("%s/%s %.1f%%", getMapped(), total, getPercentage()); | ||
| 45 | } | ||
| 46 | |||
| 47 | public static class Tree<T> { | ||
| 48 | public final Node<T> root; | ||
| 49 | private final Map<String, Node<T>> nodes = new HashMap<>(); | ||
| 50 | |||
| 51 | public static class Node<T> { | ||
| 52 | public String name; | ||
| 53 | public T value; | ||
| 54 | public List<Node<T>> children = new ArrayList<>(); | ||
| 55 | private final transient Map<String, Node<T>> namedChildren = new HashMap<>(); | ||
| 56 | |||
| 57 | public Node(String name, T value) { | ||
| 58 | this.name = name; | ||
| 59 | this.value = value; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | public Tree() { | ||
| 64 | root = new Node<>("", null); | ||
| 65 | } | ||
| 66 | |||
| 67 | public Node<T> getNode(String name) { | ||
| 68 | Node<T> node = nodes.get(name); | ||
| 69 | |||
| 70 | if (node == null) { | ||
| 71 | node = root; | ||
| 72 | |||
| 73 | for (String part : name.split("\\.")) { | ||
| 74 | Node<T> child = node.namedChildren.get(part); | ||
| 75 | |||
| 76 | if (child == null) { | ||
| 77 | child = new Node<>(part, null); | ||
| 78 | node.namedChildren.put(part, child); | ||
| 79 | node.children.add(child); | ||
| 80 | } | ||
| 81 | |||
| 82 | node = child; | ||
| 83 | } | ||
| 84 | |||
| 85 | nodes.put(name, node); | ||
| 86 | } | ||
| 87 | |||
| 88 | return node; | ||
| 89 | } | ||
| 90 | |||
| 91 | public void collapse(Node<T> node) { | ||
| 92 | while (node.children.size() == 1) { | ||
| 93 | Node<T> child = node.children.get(0); | ||
| 94 | node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name; | ||
| 95 | node.children = child.children; | ||
| 96 | node.value = child.value; | ||
| 97 | } | ||
| 98 | |||
| 99 | for (Node<T> child : node.children) { | ||
| 100 | collapse(child); | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | } | ||