From c0a81978bfde052f3323225870a68df259c47b8d Mon Sep 17 00:00:00 2001 From: Skylot Date: Thu, 1 Oct 2020 16:01:34 +0100 Subject: [PATCH] fix(gui): allow to rename packages (#987) --- .../java/jadx/gui/treemodel/JPackage.java | 10 +- .../main/java/jadx/gui/treemodel/JRoot.java | 1 - .../java/jadx/gui/ui/JPackagePopupMenu.java | 115 ++++++++++++++++++ .../src/main/java/jadx/gui/ui/MainWindow.java | 24 +--- .../main/java/jadx/gui/ui/RenameDialog.java | 74 +++++++---- .../jadx/gui/ui/codearea/RenameAction.java | 2 +- 6 files changed, 179 insertions(+), 47 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java index b66ba0e2..ef01cc91 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -21,7 +21,7 @@ public class JPackage extends JNode implements Comparable { private String name; private boolean enabled; private final List classes; - private final List innerPackages = new ArrayList<>(1); + private final List innerPackages = new ArrayList<>(); public JPackage(JavaPackage pkg, JadxWrapper wrapper) { this.fullName = pkg.getName(); @@ -39,7 +39,13 @@ public class JPackage extends JNode implements Comparable { this.fullName = name; this.name = name; setEnabled(wrapper); - this.classes = new ArrayList<>(1); + this.classes = new ArrayList<>(); + } + + public JPackage(String fullName, String name) { + this.fullName = fullName; + this.name = name; + this.classes = new ArrayList<>(); } private void setEnabled(JadxWrapper wrapper) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java index 4274df10..706c537d 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -25,7 +25,6 @@ public class JRoot extends JNode { public JRoot(JadxWrapper wrapper) { this.wrapper = wrapper; - update(); } public final void update() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java b/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java new file mode 100644 index 00000000..a1dc456d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java @@ -0,0 +1,115 @@ +package jadx.gui.ui; + +import java.util.Arrays; +import java.util.List; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +import org.jetbrains.annotations.Nullable; + +import jadx.gui.JadxWrapper; +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JPackage; +import jadx.gui.utils.NLS; + +class JPackagePopupMenu extends JPopupMenu { + private static final long serialVersionUID = -7781009781149224131L; + + private final transient MainWindow mainWindow; + + public JPackagePopupMenu(MainWindow mainWindow, JPackage pkg) { + this.mainWindow = mainWindow; + + add(makeExcludeItem(pkg)); + JMenuItem menuItem = makeRenameMenuItem(pkg); + if (menuItem != null) { + add(menuItem); + } + } + + @Nullable + private JMenuItem makeRenameMenuItem(JPackage pkg) { + List aliasParts = splitPackage(pkg.getName()); + int count = aliasParts.size(); + if (count == 0) { + return null; + } + String rawPackage = getRawPackage(pkg); + if (rawPackage == null) { + return null; + } + if (count == 1) { + // single case => no submenu + String aliasPkg = aliasParts.get(0); + JPackage renamePkg = new JPackage(rawPackage, aliasPkg); + JMenuItem pkgItem = new JMenuItem(NLS.str("popup.rename")); + pkgItem.addActionListener(e -> rename(renamePkg)); + return pkgItem; + } + List rawParts = splitPackage(rawPackage); // can be longer then alias + JMenuItem renameSubMenu = new JMenu(NLS.str("popup.rename")); + for (int i = 0; i < count; i++) { + String rawPkg = concat(rawParts, i); + String aliasShortPkg = aliasParts.get(i); + JPackage pkgPart = new JPackage(rawPkg, aliasShortPkg); + JMenuItem pkgPartItem = new JMenuItem(aliasShortPkg); + pkgPartItem.addActionListener(e -> rename(pkgPart)); + renameSubMenu.add(pkgPartItem); + } + return renameSubMenu; + } + + private String concat(List parts, int n) { + if (n == 0) { + return parts.get(0); + } + StringBuilder sb = new StringBuilder(); + sb.append(parts.get(0)); + int count = parts.size(); + for (int i = 1; i < count && i <= n; i++) { + sb.append('.'); + sb.append(parts.get(i)); + } + return sb.toString(); + } + + private void rename(JPackage pkgPart) { + new RenameDialog(mainWindow, pkgPart).setVisible(true); + } + + private List splitPackage(String rawPackage) { + return Arrays.asList(rawPackage.split("\\.")); + } + + private String getRawPackage(JPackage pkg) { + for (JClass cls : pkg.getClasses()) { + return cls.getRootClass().getCls().getClassNode().getClassInfo().getPackage(); + } + for (JPackage innerPkg : pkg.getInnerPackages()) { + String rawPackage = getRawPackage(innerPkg); + if (rawPackage != null) { + return rawPackage; + } + } + return null; + } + + private JMenuItem makeExcludeItem(JPackage pkg) { + JMenuItem excludeItem = new JCheckBoxMenuItem(NLS.str("popup.exclude")); + excludeItem.setSelected(!pkg.isEnabled()); + excludeItem.addItemListener(e -> { + JadxWrapper wrapper = mainWindow.getWrapper(); + String fullName = pkg.getFullName(); + if (excludeItem.isSelected()) { + wrapper.addExcludedPackage(fullName); + } else { + wrapper.removeExcludedPackage(fullName); + } + mainWindow.reOpenFile(); + }); + return excludeItem; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 29418dac..38d1c898 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -46,7 +46,6 @@ import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JToggleButton; @@ -503,10 +502,11 @@ public class MainWindow extends JFrame { } } - private void initTree() { + public void initTree() { treeRoot = new JRoot(wrapper); treeRoot.setFlatPackages(isFlattenPackage); treeModel.setRoot(treeRoot); + treeRoot.update(); reloadTree(); } @@ -606,7 +606,7 @@ public class MainWindow extends JFrame { private void treeRightClickAction(MouseEvent e) { Object obj = getJNodeUnderMouse(e); if (obj instanceof JPackage) { - JPackagePopUp menu = new JPackagePopUp((JPackage) obj); + JPackagePopupMenu menu = new JPackagePopupMenu(this, (JPackage) obj); menu.show(e.getComponent(), e.getX(), e.getY()); } } @@ -1170,22 +1170,4 @@ public class MainWindow extends JFrame { public void menuCanceled(MenuEvent e) { } } - - private class JPackagePopUp extends JPopupMenu { - JMenuItem excludeItem = new JCheckBoxMenuItem(NLS.str("popup.exclude")); - - public JPackagePopUp(JPackage pkg) { - excludeItem.setSelected(!pkg.isEnabled()); - add(excludeItem); - excludeItem.addItemListener(e -> { - String fullName = pkg.getFullName(); - if (excludeItem.isSelected()) { - wrapper.addExcludedPackage(fullName); - } else { - wrapper.removeExcludedPackage(fullName); - } - reOpenFile(); - }); - } - } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index e1f61b5a..116f5eaa 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -8,8 +8,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -27,6 +29,7 @@ import jadx.core.codegen.CodeWriter; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.RenameVisitor; import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.jobs.IndexJob; import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; @@ -35,7 +38,6 @@ import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JPackage; import jadx.gui.ui.codearea.ClassCodeContentPanel; -import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.codearea.CodePanel; import jadx.gui.utils.CacheObject; import jadx.gui.utils.JNodeCache; @@ -52,9 +54,9 @@ public class RenameDialog extends JDialog { private final transient JNode node; private transient JTextField renameField; - public RenameDialog(CodeArea codeArea, JNode node) { - super(codeArea.getMainWindow()); - this.mainWindow = codeArea.getMainWindow(); + public RenameDialog(MainWindow mainWindow, JNode node) { + super(mainWindow); + this.mainWindow = mainWindow; this.cache = mainWindow.getCacheObject(); this.node = node; if (checkSettings()) { @@ -117,17 +119,12 @@ public class RenameDialog extends JDialog { type = "f"; id = javaField.getFieldNode().getFieldInfo().getRawFullId(); } else if (node instanceof JClass) { + JavaClass javaClass = (JavaClass) node.getJavaNode(); type = "c"; - JavaNode javaNode = node.getJavaNode(); - id = javaNode.getFullName(); - if (javaNode instanceof JavaClass) { - JavaClass javaClass = (JavaClass) javaNode; - id = javaClass.getRawName(); - } - + id = javaClass.getRawName(); } else if (node instanceof JPackage) { type = "p"; - id = node.getJavaNode().getFullName(); + id = ((JPackage) node).getFullName(); } return String.format("%s %s = %s", type, id, renameText); } @@ -158,18 +155,17 @@ public class RenameDialog extends JDialog { } private List updateDeobfMap(List deobfMap, String alias) { - LOG.trace("updateDeobfMap(): alias = " + alias); - String id = alias.split("=")[0]; + String id = alias.substring(0, alias.indexOf('=') + 1); int i = 0; while (i < deobfMap.size()) { if (deobfMap.get(i).startsWith(id)) { - LOG.info("updateDeobfMap(): Removing entry " + deobfMap.get(i)); + LOG.debug("updateDeobfMap(): Removing entry " + deobfMap.get(i)); deobfMap.remove(i); } else { i++; } } - LOG.trace("updateDeobfMap(): Placing alias = " + alias); + LOG.debug("updateDeobfMap(): Placing alias = " + alias); deobfMap.add(alias); return deobfMap; } @@ -222,19 +218,53 @@ public class RenameDialog extends JDialog { renameVisitor.init(rootNode); JNodeCache nodeCache = cache.getNodeCache(); - Set updatedClasses = node.getJavaNode().getUseIn() + JavaNode javaNode = node.getJavaNode(); + + List toUpdate = new ArrayList<>(); + if (javaNode != null) { + toUpdate.add(javaNode); + toUpdate.addAll(javaNode.getUseIn()); + } else if (node instanceof JPackage) { + processPackage(toUpdate); + } else { + throw new JadxRuntimeException("Unexpected node type: " + node); + } + Set updatedTopClasses = toUpdate .stream() .map(nodeCache::makeFrom) .map(JNode::getRootClass) + .filter(Objects::nonNull) .collect(Collectors.toSet()); - updatedClasses.add(node.getRootClass()); - refreshTabs(mainWindow.getTabbedPane(), updatedClasses); + LOG.debug("Classes to update: {}", updatedTopClasses); - if (!updatedClasses.isEmpty()) { + refreshTabs(mainWindow.getTabbedPane(), updatedTopClasses); + + if (!updatedTopClasses.isEmpty()) { mainWindow.getBackgroundExecutor().execute("Refreshing", - Utils.collectionMap(updatedClasses, cls -> () -> refreshJClass(cls)), - mainWindow::reloadTree); + Utils.collectionMap(updatedTopClasses, cls -> () -> refreshJClass(cls)), + () -> { + if (node instanceof JPackage) { + // reinit tree + mainWindow.initTree(); + } else { + mainWindow.reloadTree(); + } + }); + } + } + + private void processPackage(List toUpdate) { + String rawFullPkg = ((JPackage) node).getFullName(); + String rawFullPkgDot = rawFullPkg + "."; + for (JavaClass cls : mainWindow.getWrapper().getClasses()) { + String clsPkg = cls.getClassNode().getClassInfo().getPackage(); + // search all classes in package + if (clsPkg.equals(rawFullPkg) || clsPkg.startsWith(rawFullPkgDot)) { + toUpdate.add(cls); + // also include all usages (for import fix) + toUpdate.addAll(cls.getUseIn()); + } } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java index 80697a2c..0bf175b9 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java @@ -25,7 +25,7 @@ public final class RenameAction extends JNodeMenuAction { LOG.info("node == null!"); return; } - RenameDialog renameDialog = new RenameDialog(codeArea, node); + RenameDialog renameDialog = new RenameDialog(codeArea.getMainWindow(), node); renameDialog.setVisible(true); } -- GitLab