diff --git a/jadx-gui/src/main/java/jadx/gui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/MainWindow.java index dd16bf19f..447bfdece 100644 --- a/jadx-gui/src/main/java/jadx/gui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/MainWindow.java @@ -6,6 +6,8 @@ import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JRoot; +import javax.swing.ImageIcon; +import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; @@ -14,6 +16,8 @@ import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; +import javax.swing.JToggleButton; +import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; @@ -22,6 +26,7 @@ import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.awt.BorderLayout; import java.awt.Color; @@ -40,8 +45,12 @@ import org.slf4j.LoggerFactory; public class MainWindow extends JFrame { private static final Logger LOG = LoggerFactory.getLogger(MainWindow.class); - public static final String DEFAULT_TITLE = "jadx-gui"; - public static final Color BACKGROUND = new Color(0xf7f7f7); + private static final String DEFAULT_TITLE = "jadx-gui"; + private static final Color BACKGROUND = new Color(0xf7f7f7); + + private static final ImageIcon ICON_OPEN = Utils.openIcon("folder"); + private static final ImageIcon ICON_CLOSE = Utils.openIcon("cross"); + private static final ImageIcon ICON_FLAT_PKG = Utils.openIcon("empty_logical_package_obj"); private final JadxWrapper wrapper; private JPanel mainPanel; @@ -53,46 +62,7 @@ public class MainWindow extends JFrame { this.wrapper = new JadxWrapper(jadxArgs); initUI(); - initMenu(); - } - - private void initMenu() { - JMenuBar menuBar = new JMenuBar(); - - JMenu file = new JMenu("File"); - file.setMnemonic(KeyEvent.VK_F); - - JMenuItem exit = new JMenuItem("Exit", Utils.openIcon("cross")); - exit.setMnemonic(KeyEvent.VK_E); - exit.setToolTipText("Exit application"); - exit.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - System.exit(0); - } - }); - - JMenuItem open = new JMenuItem("Open", Utils.openIcon("folder")); - open.setMnemonic(KeyEvent.VK_E); - open.setToolTipText("Open file"); - open.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent event) { - JFileChooser fileChooser = new JFileChooser(); - FileFilter filter = new FileNameExtensionFilter("dex files", "dex", "apk", "jar"); - fileChooser.addChoosableFileFilter(filter); - int ret = fileChooser.showDialog(mainPanel, "Open file"); - if (ret == JFileChooser.APPROVE_OPTION) { - File file = fileChooser.getSelectedFile(); - openFile(file); - } - } - }); - - file.add(open); - file.addSeparator(); - file.add(exit); - - menuBar.add(file); - setJMenuBar(menuBar); + initMenuAndToolbar(); } public void openFile(File file) { @@ -106,6 +76,80 @@ public class MainWindow extends JFrame { treeModel.setRoot(treeRoot); treeModel.reload(); tree.expandRow(0); +// expandTree(); + } + + private void toggleFlattenPackage() { + Object root = treeModel.getRoot(); + if (root instanceof JRoot) { + JRoot treeRoot = (JRoot) root; + treeRoot.setFlatPackages(!treeRoot.isFlatPackages()); + treeModel.reload(); + tree.expandRow(0); + } + } + + private void expandTree() { + DefaultMutableTreeNode currentNode = ((DefaultMutableTreeNode) tree.getModel().getRoot()).getNextNode(); + do { + if (currentNode.getLevel() == 1) { + tree.expandPath(new TreePath(currentNode.getPath())); + } + currentNode = currentNode.getNextNode(); + } + while (currentNode != null); + } + + private void initMenuAndToolbar() { + JMenuBar menuBar = new JMenuBar(); + + JMenu file = new JMenu("File"); + file.setMnemonic(KeyEvent.VK_F); + + JMenuItem exit = new JMenuItem("Exit", ICON_CLOSE); + exit.setMnemonic(KeyEvent.VK_E); + exit.setToolTipText("Exit application"); + exit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + System.exit(0); + } + }); + + JMenuItem open = new JMenuItem("Open", ICON_OPEN); + open.setMnemonic(KeyEvent.VK_E); + open.setToolTipText("Open file"); + open.addActionListener(new OpenListener()); + + file.add(open); + file.addSeparator(); + file.add(exit); + + menuBar.add(file); + setJMenuBar(menuBar); + + JToolBar toolbar = new JToolBar(); + toolbar.setFloatable(false); + + JButton openButton = new JButton(ICON_OPEN); + openButton.addActionListener(new OpenListener()); + openButton.setToolTipText(NLS.str("file.open")); + + toolbar.add(openButton); + toolbar.addSeparator(); + + JToggleButton flatPkgButton = new JToggleButton(ICON_FLAT_PKG); + flatPkgButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + toggleFlattenPackage(); + } + }); + flatPkgButton.setToolTipText(NLS.str("tree.flatten")); + toolbar.add(flatPkgButton); + + toolbar.addSeparator(); + + add(toolbar, BorderLayout.NORTH); } private void initUI() { @@ -166,4 +210,17 @@ public class MainWindow extends JFrame { pack(); setLocationRelativeTo(null); } + + private class OpenListener implements ActionListener { + public void actionPerformed(ActionEvent event) { + JFileChooser fileChooser = new JFileChooser(); + FileFilter filter = new FileNameExtensionFilter("dex files", "dex", "apk", "jar"); + fileChooser.addChoosableFileFilter(filter); + int ret = fileChooser.showDialog(mainPanel, "Open file"); + if (ret == JFileChooser.APPROVE_OPTION) { + File file = fileChooser.getSelectedFile(); + openFile(file); + } + } + } } diff --git a/jadx-gui/src/main/java/jadx/gui/NLS.java b/jadx-gui/src/main/java/jadx/gui/NLS.java new file mode 100644 index 000000000..c3bf3e2dc --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/NLS.java @@ -0,0 +1,21 @@ +package jadx.gui; + +import java.util.Locale; +import java.util.ResourceBundle; + +public class NLS { + + private static ResourceBundle messages; + + static { + load(new Locale("en", "US")); + } + + public static void load(Locale locale) { + messages = ResourceBundle.getBundle("i18n/Messages", locale); + } + + public static String str(String key) { + return messages.getString(key); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 252b15b40..fabf948e2 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -1,26 +1,60 @@ package jadx.gui.treemodel; import jadx.api.JavaClass; +import jadx.core.dex.info.AccessInfo; import jadx.gui.Utils; import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.tree.DefaultMutableTreeNode; public class JClass extends DefaultMutableTreeNode implements JNode { + private static final ImageIcon ICON_CLASS = Utils.openIcon("class_obj"); + private static final ImageIcon ICON_CLASS_DEFAULT = Utils.openIcon("class_default_obj"); + private static final ImageIcon ICON_CLASS_PRIVATE = Utils.openIcon("innerclass_private_obj"); + private static final ImageIcon ICON_CLASS_PROTECTED = Utils.openIcon("innerclass_protected_obj"); + private static final ImageIcon ICON_INTERFACE = Utils.openIcon("int_obj"); + private static final ImageIcon ICON_ENUM = Utils.openIcon("enum_obj"); + private static final ImageIcon ICON_ANNOTATION = Utils.openIcon("annotation_obj"); + private final JavaClass cls; public JClass(JavaClass cls) { this.cls = cls; + updateChilds(); } public JavaClass getCls() { return cls; } + @Override + public void updateChilds() { +// for (JavaClass javaClass : cls.getInnerClasses()) { +// add(new JClass(javaClass)); +// } + } + @Override public Icon getIcon() { - return Utils.openIcon("class_obj"); + AccessInfo accessInfo = cls.getAccessInfo(); + + if (accessInfo.isEnum()) { + return ICON_ENUM; + } else if (accessInfo.isAnnotation()) { + return ICON_ANNOTATION; + } else if (accessInfo.isInterface()) { + return ICON_INTERFACE; + } else if (accessInfo.isProtected()) { + return ICON_CLASS_PROTECTED; + } else if (accessInfo.isPrivate()) { + return ICON_CLASS_PRIVATE; + } else if (accessInfo.isPublic()) { + return ICON_CLASS; + } else { + return ICON_CLASS_DEFAULT; + } } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index b05409ed3..45a12aa87 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -3,5 +3,8 @@ package jadx.gui.treemodel; import javax.swing.Icon; public interface JNode { + + void updateChilds(); + Icon getIcon(); } 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 029c947e2..158f82854 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -7,22 +7,57 @@ import jadx.gui.Utils; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.tree.DefaultMutableTreeNode; +import java.util.ArrayList; +import java.util.List; -public class JPackage extends DefaultMutableTreeNode implements JNode { +public class JPackage extends DefaultMutableTreeNode implements JNode, Comparable { private static final ImageIcon PACKAGE_ICON = Utils.openIcon("package_obj"); - private final JavaPackage pkg; + private String name; + private List classes; + private List innerPackages = new ArrayList(1); public JPackage(JavaPackage pkg) { - this.pkg = pkg; + this.name = pkg.getName(); + List javaClasses = pkg.getClasses(); + this.classes = new ArrayList(javaClasses.size()); + for (JavaClass javaClass : javaClasses) { + classes.add(new JClass(javaClass)); + } + updateChilds(); + } - for (JavaClass javaClass : pkg.getClasses()) { - add(new JClass(javaClass)); + public JPackage(String name) { + this.name = name; + this.classes = new ArrayList(1); + } + + @Override + public void updateChilds() { + removeAllChildren(); + for (JPackage pkg : innerPackages) { + pkg.updateChilds(); + add(pkg); + } + for (JClass cls : classes) { + add(cls); } } - public JavaPackage getPkg() { - return pkg; + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getInnerPackages() { + return innerPackages; + } + + public List getClasses() { + return classes; } @Override @@ -30,8 +65,13 @@ public class JPackage extends DefaultMutableTreeNode implements JNode { return PACKAGE_ICON; } + @Override + public int compareTo(JPackage o) { + return name.compareTo(o.name); + } + @Override public String toString() { - return pkg.getName(); + return name; } } 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 dfc755e91..f00cabb72 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -8,6 +8,14 @@ import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.tree.DefaultMutableTreeNode; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; public class JRoot extends DefaultMutableTreeNode implements JNode { @@ -15,11 +23,93 @@ public class JRoot extends DefaultMutableTreeNode implements JNode { private final JadxWrapper wrapper; + private boolean flatPackages = false; + public JRoot(JadxWrapper wrapper) { this.wrapper = wrapper; + updateChilds(); + } - for (JavaPackage pkg : wrapper.getPackages()) { - add(new JPackage(pkg)); + @Override + public void updateChilds() { + removeAllChildren(); + if (flatPackages) { + for (JavaPackage pkg : wrapper.getPackages()) { + add(new JPackage(pkg)); + } + } else { + // build packages hierarchy + Map pkgMap = new HashMap(); + for (JavaPackage pkg : wrapper.getPackages()) { + addPackage(pkgMap, new JPackage(pkg)); + } + // merge packages without classes + for (JPackage pkg : pkgMap.values()) { + if (pkg.getInnerPackages().size() == 1 && pkg.getClasses().isEmpty()) { + JPackage innerPkg = pkg.getInnerPackages().get(0); + pkg.getInnerPackages().clear(); + pkg.getInnerPackages().addAll(innerPkg.getInnerPackages()); + pkg.getClasses().addAll(innerPkg.getClasses()); + pkg.setName(pkg.getName() + "." + innerPkg.getName()); + innerPkg.getInnerPackages().clear(); + innerPkg.getClasses().clear(); + } + } + // remove empty packages + for (Iterator> it = pkgMap.entrySet().iterator(); it.hasNext(); ) { + JPackage pkg = it.next().getValue(); + if (pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty()) { + it.remove(); + } + } + // find root packages + Set inners = new HashSet(); + for (JPackage pkg : pkgMap.values()) { + inners.addAll(pkg.getInnerPackages()); + } + List rootPkgs = new ArrayList(); + for (JPackage pkg : pkgMap.values()) { + if (!inners.contains(pkg)) { + rootPkgs.add(pkg); + } + } + Collections.sort(rootPkgs); + for (JPackage jPackage : rootPkgs) { + jPackage.updateChilds(); + add(jPackage); + } + } + } + + private void addPackage(Map pkgs, JPackage pkg) { + String pkgName = pkg.getName(); + JPackage replaced = pkgs.put(pkgName, pkg); + if (replaced != null) { + pkg.getInnerPackages().addAll(replaced.getInnerPackages()); + pkg.getClasses().addAll(replaced.getClasses()); + } + int dot = pkgName.lastIndexOf('.'); + if (dot > 0) { + String prevPart = pkgName.substring(0, dot); + String shortName = pkgName.substring(dot + 1); + pkg.setName(shortName); + JPackage prevPkg = pkgs.get(prevPart); + if (prevPkg == null) { + prevPkg = new JPackage(prevPart); + addPackage(pkgs, prevPkg); + } + prevPkg.getInnerPackages().add(pkg); + } + } + + public boolean isFlatPackages() { + return flatPackages; + } + + public void setFlatPackages(boolean flatPackages) { + if (this.flatPackages != flatPackages) { + this.flatPackages = flatPackages; + updateChilds(); } } diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties new file mode 100644 index 000000000..2f6124650 --- /dev/null +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -0,0 +1,2 @@ +file.open=Open file +tree.flatten=Flatten packages diff --git a/jadx-gui/src/main/resources/icons-16/abstract_co.png b/jadx-gui/src/main/resources/icons-16/abstract_co.png new file mode 100644 index 000000000..658b8fde5 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/abstract_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/constr_ovr.png b/jadx-gui/src/main/resources/icons-16/constr_ovr.png new file mode 100644 index 000000000..65964a92e Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/constr_ovr.png differ diff --git a/jadx-gui/src/main/resources/icons-16/empty_logical_package_obj.png b/jadx-gui/src/main/resources/icons-16/empty_logical_package_obj.png new file mode 100644 index 000000000..acfb2238a Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/empty_logical_package_obj.png differ diff --git a/jadx-gui/src/main/resources/icons-16/empty_pack_obj.png b/jadx-gui/src/main/resources/icons-16/empty_pack_obj.png new file mode 100644 index 000000000..a2d43103f Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/empty_pack_obj.png differ diff --git a/jadx-gui/src/main/resources/icons-16/field_private_obj.png b/jadx-gui/src/main/resources/icons-16/field_private_obj.png new file mode 100644 index 000000000..d3fa87ddd Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/field_private_obj.png differ diff --git a/jadx-gui/src/main/resources/icons-16/field_protected_obj.png b/jadx-gui/src/main/resources/icons-16/field_protected_obj.png new file mode 100644 index 000000000..dc80489bc Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/field_protected_obj.png differ diff --git a/jadx-gui/src/main/resources/icons-16/field_public_obj.png b/jadx-gui/src/main/resources/icons-16/field_public_obj.png new file mode 100644 index 000000000..40a5ef1c8 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/field_public_obj.png differ diff --git a/jadx-gui/src/main/resources/icons-16/final_co.png b/jadx-gui/src/main/resources/icons-16/final_co.png new file mode 100644 index 000000000..ec1b0346d Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/final_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/native_co.png b/jadx-gui/src/main/resources/icons-16/native_co.png new file mode 100644 index 000000000..bb410c523 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/native_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/run_co.png b/jadx-gui/src/main/resources/icons-16/run_co.png new file mode 100644 index 000000000..d253a17ce Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/run_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/static_co.png b/jadx-gui/src/main/resources/icons-16/static_co.png new file mode 100644 index 000000000..655c9c069 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/static_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/synch_co.png b/jadx-gui/src/main/resources/icons-16/synch_co.png new file mode 100644 index 000000000..2e950e988 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/synch_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/transient_co.png b/jadx-gui/src/main/resources/icons-16/transient_co.png new file mode 100644 index 000000000..4f54e545e Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/transient_co.png differ diff --git a/jadx-gui/src/main/resources/icons-16/volatile_co.png b/jadx-gui/src/main/resources/icons-16/volatile_co.png new file mode 100644 index 000000000..cd99249f5 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/volatile_co.png differ