diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index eee71f46d..09226c838 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -587,6 +587,8 @@ public final class JadxDecompiler implements Closeable { return convertMethodNode((MethodNode) ann); case FIELD: return convertFieldNode((FieldNode) ann); + case PKG: + return convertPackageNode((PackageNode) ann); case DECLARATION: return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode()); case VAR: 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 497e41698..3431cb789 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -9,6 +9,7 @@ import javax.swing.JPopupMenu; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import jadx.api.ICodeInfo; import jadx.api.JavaClass; @@ -30,6 +31,7 @@ import jadx.gui.ui.popupmenu.JClassPopupMenu; import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.CacheObject; import jadx.gui.utils.Icons; +import jadx.gui.utils.JNodeCache; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -46,12 +48,18 @@ public class JClass extends JLoadableNode implements JRenameNode { private final transient JavaClass cls; private final transient JClass jParent; + private final transient JNodeCache nodeCache; + private transient boolean loaded; - public JClass(JavaClass cls, JClass parent) { + /** + * Should be called only from JNodeCache! + */ + public JClass(JavaClass cls, JClass parent, JNodeCache nodeCache) { this.cls = cls; this.jParent = parent; this.loaded = parent != null; + this.nodeCache = nodeCache; } public JavaClass getCls() { @@ -69,7 +77,10 @@ public class JClass extends JLoadableNode implements JRenameNode { } @Override - public SimpleTask getLoadTask() { + public synchronized @Nullable SimpleTask getLoadTask() { + if (loaded) { + return null; + } JClass rootClass = getRootClass(); return new SimpleTask(NLS.str("progress.decompile"), () -> rootClass.getCls().getClassNode().decompile(), // run decompilation in background @@ -106,15 +117,15 @@ public class JClass extends JLoadableNode implements JRenameNode { add(new TextNode(NLS.str("tree.loading"))); } else { for (JavaClass javaClass : cls.getInnerClasses()) { - JClass innerCls = new JClass(javaClass, this); + JClass innerCls = nodeCache.makeFrom(javaClass); add(innerCls); innerCls.update(); } for (JavaField f : cls.getFields()) { - add(new JField(f, this)); + add(nodeCache.makeFrom(f)); } for (JavaMethod m : cls.getMethods()) { - add(new JMethod(m, this)); + add(nodeCache.makeFrom(m)); } } } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java index 648166391..ba8c9f710 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JField.java @@ -34,6 +34,9 @@ public class JField extends JNode implements JRenameNode { private final transient JavaField field; private final transient JClass jParent; + /** + * Should be called only from JNodeCache! + */ public JField(JavaField javaField, JClass jClass) { this.field = javaField; this.jParent = jClass; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java index 2afe63793..2168ae720 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java @@ -40,6 +40,9 @@ public class JMethod extends JNode implements JRenameNode { private final transient JavaMethod mth; private final transient JClass jParent; + /** + * Should be called only from JNodeCache! + */ public JMethod(JavaMethod javaMethod, JClass jClass) { this.mth = javaMethod; this.jParent = jClass; 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 d6eb136c1..8d6abf1e2 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -1,5 +1,7 @@ package jadx.gui.treemodel; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.swing.Icon; @@ -33,6 +35,9 @@ public class JPackage extends JNode { private String name; + /** + * Should be called only from JNodeCache! + */ public JPackage(JavaPackage pkg, boolean enabled, List classes, List subPackages, boolean synthetic) { this.pkg = pkg; this.enabled = enabled; @@ -41,6 +46,10 @@ public class JPackage extends JNode { this.synthetic = synthetic; } + public static JPackage makeTmpRoot() { + return new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true); + } + public void update() { removeAllChildren(); if (isEnabled()) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java index 1129dd3e8..6130382b7 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java @@ -27,10 +27,6 @@ public class JSources extends JNode { public final void update() { removeAllChildren(); PackageHelper packageHelper = wrapper.getCache().getPackageHelper(); - if (packageHelper == null) { - packageHelper = new PackageHelper(wrapper); - wrapper.getCache().setPackageHelper(packageHelper); - } List roots = packageHelper.getRoots(flatPackages); for (JPackage rootPkg : roots) { rootPkg.update(); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index de7673485..adbd154fb 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -14,29 +14,30 @@ import jadx.gui.utils.pkgs.PackageHelper; public class CacheObject { private final JadxWrapper wrapper; + private final JNodeCache jNodeCache; + private final PackageHelper packageHelper; private String lastSearch; - private JNodeCache jNodeCache; private Map> lastSearchOptions; private String lastSearchPackage; private List> decompileBatches; - private PackageHelper packageHelper; private volatile boolean fullDecompilationFinished; public CacheObject(JadxWrapper wrapper) { this.wrapper = wrapper; + this.jNodeCache = new JNodeCache(wrapper); + this.packageHelper = new PackageHelper(wrapper, jNodeCache); reset(); } public void reset() { lastSearch = null; - jNodeCache = new JNodeCache(wrapper); + jNodeCache.reset(); lastSearchOptions = new HashMap<>(); lastSearchPackage = null; decompileBatches = null; - packageHelper = null; fullDecompilationFinished = false; } @@ -78,10 +79,6 @@ public class CacheObject { return packageHelper; } - public void setPackageHelper(PackageHelper packageHelper) { - this.packageHelper = packageHelper; - } - public boolean isFullDecompilationFinished() { return fullDecompilationFinished; } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java index ac63f650d..aa0e96545 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java @@ -1,5 +1,7 @@ package jadx.gui.utils; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -7,6 +9,7 @@ import jadx.api.JavaClass; import jadx.api.JavaField; import jadx.api.JavaMethod; import jadx.api.JavaNode; +import jadx.api.JavaPackage; import jadx.api.JavaVariable; import jadx.api.metadata.ICodeNodeRef; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -15,6 +18,7 @@ import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JNode; +import jadx.gui.treemodel.JPackage; import jadx.gui.treemodel.JVariable; public class JNodeCache { @@ -38,6 +42,14 @@ public class JNodeCache { return jNode; } + public void put(ICodeNodeRef nodeRef, JNode jNode) { + cache.put(nodeRef, jNode); + } + + public void put(JavaNode javaNode, JNode jNode) { + cache.put(javaNode.getCodeNodeRef(), jNode); + } + public JNode makeFrom(JavaNode javaNode) { if (javaNode == null) { return null; @@ -58,6 +70,12 @@ public class JNodeCache { return jCls; } + public JPackage newJPackage(JavaPackage javaPkg, boolean synthetic, boolean pkgEnabled, List classes) { + JPackage jPackage = new JPackage(javaPkg, pkgEnabled, classes, new ArrayList<>(), synthetic); + put(javaPkg, jPackage); + return jPackage; + } + public void remove(JavaNode javaNode) { cache.remove(javaNode.getCodeNodeRef()); } @@ -70,12 +88,16 @@ public class JNodeCache { javaCls.getInlinedClasses().forEach(this::remove); } + public void reset() { + cache.clear(); + } + private JClass convert(JavaClass cls) { JavaClass parentCls = cls.getDeclaringClass(); if (parentCls == cls) { - return new JClass(cls, null); + return new JClass(cls, null, this); } - return new JClass(cls, makeFrom(parentCls)); + return new JClass(cls, makeFrom(parentCls), this); } private JNode convert(ICodeNodeRef nodeRef) { @@ -101,6 +123,9 @@ public class JNodeCache { JMethod jMth = (JMethod) makeFrom(javaVar.getMth()); return new JVariable(jMth, javaVar); } + if (node instanceof JavaPackage) { + throw new JadxRuntimeException("Unexpected JPackage (missing from cache): " + node); + } throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass()); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/pkgs/PackageHelper.java b/jadx-gui/src/main/java/jadx/gui/utils/pkgs/PackageHelper.java index 6d7320060..9a249782e 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/pkgs/PackageHelper.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/pkgs/PackageHelper.java @@ -28,18 +28,18 @@ public class PackageHelper { private static final Comparator PKG_COMPARATOR = Comparator.comparing(JPackage::getName, String.CASE_INSENSITIVE_ORDER); private final JadxWrapper wrapper; + private final JNodeCache nodeCache; private List excludedPackages; - private JNodeCache nodeCache; private final Map pkgInfoMap = new HashMap<>(); - public PackageHelper(JadxWrapper wrapper) { + public PackageHelper(JadxWrapper wrapper, JNodeCache jNodeCache) { this.wrapper = wrapper; + this.nodeCache = jNodeCache; } public List getRoots(boolean flatPackages) { excludedPackages = wrapper.getExcludedPackages(); - nodeCache = wrapper.getCache().getNodeCache(); pkgInfoMap.clear(); if (flatPackages) { return prepareFlatPackages(); @@ -87,7 +87,7 @@ public class PackageHelper { } private List prepareHierarchyPackages() { - JPackage root = new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true); + JPackage root = JPackage.makeTmpRoot(); List packages = wrapper.getPackages(); List jPackages = new ArrayList<>(packages.size()); // create nodes for exists packages @@ -176,12 +176,21 @@ public class PackageHelper { classes = Utils.collectionMap(javaPkg.getClasses(), nodeCache::makeFrom); classes.sort(CLASS_COMPARATOR); } - return new JPackage(javaPkg, pkgEnabled, classes, new ArrayList<>(), synthetic); + return nodeCache.newJPackage(javaPkg, synthetic, pkgEnabled, classes); } private static boolean isPkgEnabled(String fullPkgName, List excludedPackages) { - return excludedPackages.isEmpty() - || excludedPackages.stream() - .noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.')); + return excludedPackages.isEmpty() || excludedPackages.stream().noneMatch(p -> isPkgMatch(fullPkgName, p)); + } + + private static boolean isPkgMatch(String fullPkgName, String filterPkg) { + if (fullPkgName.equals(filterPkg)) { + return true; + } + // optimized check, same as `fullPkgName.startsWith(filterPkg + '.')` + int filterPkgLen = filterPkg.length(); + return fullPkgName.length() > filterPkgLen + && fullPkgName.charAt(filterPkgLen) == '.' + && fullPkgName.startsWith(filterPkg); } }