From 63a571306cb0971e80602fc1f2af61b5eaac2e25 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 19 Aug 2022 22:15:04 +0100 Subject: [PATCH] refactor: load resource table nodes in one change (#1648) --- .../providers/ResourceSearchProvider.java | 77 +++++++-------- .../java/jadx/gui/treemodel/JResource.java | 91 ++++++----------- .../main/java/jadx/gui/treemodel/JRoot.java | 5 +- .../src/main/java/jadx/gui/utils/UiUtils.java | 2 +- .../jadx/gui/utils/res/ResTableHelper.java | 98 +++++++++++++++++++ 5 files changed, 171 insertions(+), 102 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java diff --git a/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java b/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java index 552c8a4d2ea..ecb9b209250 100644 --- a/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java +++ b/jadx-gui/src/main/java/jadx/gui/search/providers/ResourceSearchProvider.java @@ -52,7 +52,7 @@ public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings) { if (cancelable.isCanceled()) { return null; } - JResource resNode = getNextNode(); + JResource resNode = getNextResFile(cancelable); if (resNode == null) { return null; } @@ -62,7 +62,7 @@ public ResourceSearchProvider(MainWindow mw, SearchSettings searchSettings) { } pos = 0; resQueue.removeLast(); - addChildren(resQueue, resNode); + addChildren(resNode); if (resQueue.isEmpty()) { return null; } @@ -90,39 +90,37 @@ private JNode search(JResource resNode) { return new JResSearchNode(resNode, line.trim(), newPos); } - private @Nullable JResource getNextNode() { - JResource node = resQueue.peekLast(); - if (node == null) { - return null; - } - try { - node.loadNode(); - } catch (Exception e) { - LOG.error("Error load resource node: {}", node, e); - resQueue.removeLast(); - return getNextNode(); - } - if (node.getType() == JResource.JResType.FILE) { - if (shouldProcess(node)) { - return node; + private @Nullable JResource getNextResFile(Cancelable cancelable) { + while (true) { + JResource node = resQueue.peekLast(); + if (node == null) { + return null; + } + try { + node.loadNode(); + } catch (Exception e) { + LOG.error("Error load resource node: {}", node, e); + resQueue.removeLast(); + continue; + } + if (cancelable.isCanceled()) { + return null; + } + if (node.getType() == JResource.JResType.FILE) { + if (shouldProcess(node)) { + return node; + } + resQueue.removeLast(); + } else { + // dir + resQueue.removeLast(); + addChildren(node); } - resQueue.removeLast(); - return getNextNode(); } - // dit or root - resQueue.removeLast(); - addChildren(resQueue, node); - return getNextNode(); } - private void addChildren(Deque deque, JResource resNode) { - Enumeration children = resNode.children(); - while (children.hasMoreElements()) { - TreeNode node = children.nextElement(); - if (node instanceof JResource) { - deque.add((JResource) node); - } - } + private void addChildren(JResource resNode) { + resQueue.addAll(resNode.getSubNodes()); } private static Deque initResQueue(MainWindow mw) { @@ -155,16 +153,15 @@ private Set buildAllowedFilesExtensions(String srhResourceFileExt) { } private boolean shouldProcess(JResource resNode) { + ResourceFile resFile = resNode.getResFile(); + if (resFile.getType() == ResourceType.ARSC) { + // don't check size of generated resource table, it will also skip all sub files + return anyExt || extSet.contains("xml"); + } if (!anyExt) { - String fileExt; - ResourceFile resFile = resNode.getResFile(); - if (resFile.getType() == ResourceType.ARSC) { - fileExt = "xml"; - } else { - fileExt = CommonFileUtils.getFileExtension(resFile.getOriginalName()); - if (fileExt == null) { - return false; - } + String fileExt = CommonFileUtils.getFileExtension(resFile.getOriginalName()); + if (fileExt == null) { + return false; } if (!extSet.contains(fileExt)) { return false; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 4ac9916a937..92a05672779 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -1,6 +1,6 @@ package jadx.gui.treemodel; -import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -14,10 +14,10 @@ import jadx.api.ICodeInfo; import jadx.api.ICodeWriter; import jadx.api.ResourceFile; -import jadx.api.ResourceFileContent; import jadx.api.ResourceType; import jadx.api.ResourcesLoader; import jadx.api.impl.SimpleCodeInfo; +import jadx.core.utils.ListUtils; import jadx.core.utils.Utils; import jadx.core.xmlgen.ResContainer; import jadx.gui.ui.TabbedPane; @@ -26,6 +26,7 @@ import jadx.gui.ui.panel.ImagePanel; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; +import jadx.gui.utils.res.ResTableHelper; public class JResource extends JLoadableNode { private static final long serialVersionUID = -201018424302612434L; @@ -41,6 +42,10 @@ public class JResource extends JLoadableNode { private static final ImageIcon JAVA_ICON = UiUtils.openSvgIcon("nodes/java"); private static final ImageIcon UNKNOWN_ICON = UiUtils.openSvgIcon("nodes/unknown"); + public static final Comparator RESOURCES_COMPARATOR = + Comparator.comparingInt(r -> r.type.ordinal()) + .thenComparing(JResource::getName, String.CASE_INSENSITIVE_ORDER); + public enum JResType { ROOT, DIR, @@ -49,11 +54,11 @@ public enum JResType { private final transient String name; private final transient String shortName; - private final transient List files = new ArrayList<>(1); private final transient JResType type; private final transient ResourceFile resFile; - private transient boolean loaded; + private transient volatile boolean loaded; + private transient List subNodes = Collections.emptyList(); private transient ICodeInfo content; public JResource(ResourceFile resFile, String name, JResType type) { @@ -69,7 +74,8 @@ public JResource(ResourceFile resFile, String name, String shortName, JResType t } public final void update() { - if (files.isEmpty()) { + removeAllChildren(); + if (Utils.isEmpty(subNodes)) { if (type == JResType.DIR || type == JResType.ROOT || resFile.getType() == ResourceType.ARSC) { // fake leaf to force show expand button @@ -77,14 +83,7 @@ public final void update() { add(new TextNode(NLS.str("tree.loading"))); } } else { - removeAllChildren(); - - Comparator typeComparator = Comparator.comparingInt(r -> r.type.ordinal()); - Comparator nameComparator = Comparator.comparing(JResource::getName, String.CASE_INSENSITIVE_ORDER); - - files.sort(typeComparator.thenComparing(nameComparator)); - - for (JResource res : files) { + for (JResource res : subNodes) { res.update(); add(res); } @@ -106,8 +105,23 @@ public JResType getType() { return type; } - public List getFiles() { - return files; + public List getSubNodes() { + return subNodes; + } + + public void addSubNode(JResource node) { + subNodes = ListUtils.safeAdd(subNodes, node); + } + + public void sortSubNodes() { + sortResNodes(subNodes); + } + + private static void sortResNodes(List nodes) { + if (Utils.notEmpty(nodes)) { + nodes.forEach(JResource::sortSubNodes); + nodes.sort(RESOURCES_COMPARATOR); + } } @Override @@ -145,9 +159,9 @@ private ICodeInfo loadContent() { } if (rc.getDataType() == ResContainer.DataType.RES_TABLE) { ICodeInfo codeInfo = loadCurrentSingleRes(rc); - for (ResContainer subFile : rc.getSubFiles()) { - loadSubNodes(this, subFile, 1); - } + List nodes = ResTableHelper.buildTree(rc); + sortResNodes(nodes); + subNodes = nodes; return codeInfo; } // single node @@ -178,47 +192,6 @@ private ICodeInfo loadCurrentSingleRes(ResContainer rc) { } } - private void loadSubNodes(JResource root, ResContainer rc, int depth) { - String resName = rc.getName(); - String[] path = resName.split("/"); - String resShortName = path.length == 0 ? resName : path[path.length - 1]; - ICodeInfo code = rc.getText(); - ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, code); - addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE)); - - for (ResContainer subFile : rc.getSubFiles()) { - loadSubNodes(root, subFile, depth + 1); - } - } - - private static void addPath(String[] path, JResource root, JResource jResource) { - if (path.length == 1) { - root.getFiles().add(jResource); - return; - } - JResource currentRoot = root; - int last = path.length - 1; - for (int i = 0; i <= last; i++) { - String f = path[i]; - if (i == last) { - currentRoot.getFiles().add(jResource); - } else { - currentRoot = getResDir(currentRoot, f); - } - } - } - - private static JResource getResDir(JResource root, String dirName) { - for (JResource file : root.getFiles()) { - if (file.getName().equals(dirName)) { - return file; - } - } - JResource resDir = new JResource(null, dirName, JResType.DIR); - root.getFiles().add(resDir); - return resDir; - } - @Override public String getSyntaxName() { if (resFile == null) { 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 5e668120793..cb4e3cb3963 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JRoot.java @@ -68,17 +68,18 @@ private JResource getHierarchyResources(List resources) { } else { subRF = new JResource(rf, rf.getDeobfName(), name, JResType.FILE); } - curRf.getFiles().add(subRF); + curRf.addSubNode(subRF); } curRf = subRF; } } + root.sortSubNodes(); root.update(); return root; } private JResource getResourceByName(JResource rf, String name) { - for (JResource sub : rf.getFiles()) { + for (JResource sub : rf.getSubNodes()) { if (sub.getName().equals(name)) { return sub; } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index 6fbdef8bb0c..be436549d53 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -375,7 +375,7 @@ public static void uiRunAndWait(Runnable runnable) { try { SwingUtilities.invokeAndWait(runnable); } catch (InterruptedException e) { - LOG.warn("UI thread interrupted", e); + LOG.warn("UI thread interrupted, runnable: {}", runnable, e); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java b/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java new file mode 100644 index 00000000000..9473e7383a6 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/res/ResTableHelper.java @@ -0,0 +1,98 @@ +package jadx.gui.utils.res; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.ICodeInfo; +import jadx.api.ResourceFileContent; +import jadx.api.ResourceType; +import jadx.core.xmlgen.ResContainer; +import jadx.gui.treemodel.JResource; + +public class ResTableHelper { + + /** + * Build UI tree for resource table container. + * + * @return root nodes + */ + public static List buildTree(ResContainer resTable) { + ResTableHelper resTableHelper = new ResTableHelper(); + resTableHelper.process(resTable); + return resTableHelper.roots; + } + + private final List roots = new ArrayList<>(); + private final Map dirs = new HashMap<>(); + + private ResTableHelper() { + } + + private void process(ResContainer resTable) { + for (ResContainer subFile : resTable.getSubFiles()) { + loadSubNodes(subFile); + } + } + + private void loadSubNodes(ResContainer rc) { + String resName = rc.getName(); + int split = resName.lastIndexOf('/'); + String dir; + String name; + if (split == -1) { + dir = null; + name = resName; + } else { + dir = resName.substring(0, split); + name = resName.substring(split + 1); + } + ICodeInfo code = rc.getText(); + ResourceFileContent fileContent = new ResourceFileContent(name, ResourceType.XML, code); + JResource resFile = new JResource(fileContent, resName, name, JResource.JResType.FILE); + addResFile(dir, resFile); + + for (ResContainer subFile : rc.getSubFiles()) { + loadSubNodes(subFile); + } + } + + private void addResFile(@Nullable String dir, JResource resFile) { + if (dir == null) { + roots.add(resFile); + return; + } + JResource dirRes = dirs.get(dir); + if (dirRes != null) { + dirRes.addSubNode(resFile); + return; + } + JResource parentDir = null; + int splitPos = -1; + while (true) { + int prevStart = splitPos + 1; + splitPos = dir.indexOf('/', prevStart); + boolean last = splitPos == -1; + String path = last ? dir : dir.substring(0, splitPos); + JResource curDir = dirs.get(path); + if (curDir == null) { + String dirName = last ? dir.substring(prevStart) : dir.substring(prevStart, splitPos); + curDir = new JResource(null, dirName, JResource.JResType.DIR); + dirs.put(path, curDir); + if (parentDir == null) { + roots.add(curDir); + } else { + parentDir.addSubNode(curDir); + } + } + if (last) { + curDir.addSubNode(resFile); + return; + } + parentDir = curDir; + } + } +}