fix(gui): improve JNode caching (#2400)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<JClass> classes, List<JPackage> 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()) {
|
||||
|
||||
@@ -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<JPackage> roots = packageHelper.getRoots(flatPackages);
|
||||
for (JPackage rootPkg : roots) {
|
||||
rootPkg.update();
|
||||
|
||||
@@ -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<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
|
||||
private String lastSearchPackage;
|
||||
|
||||
private List<List<JavaClass>> 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;
|
||||
}
|
||||
|
||||
@@ -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<JClass> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,18 +28,18 @@ public class PackageHelper {
|
||||
private static final Comparator<JPackage> PKG_COMPARATOR = Comparator.comparing(JPackage::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private final JadxWrapper wrapper;
|
||||
private final JNodeCache nodeCache;
|
||||
private List<String> excludedPackages;
|
||||
private JNodeCache nodeCache;
|
||||
|
||||
private final Map<PackageInfo, JPackage> pkgInfoMap = new HashMap<>();
|
||||
|
||||
public PackageHelper(JadxWrapper wrapper) {
|
||||
public PackageHelper(JadxWrapper wrapper, JNodeCache jNodeCache) {
|
||||
this.wrapper = wrapper;
|
||||
this.nodeCache = jNodeCache;
|
||||
}
|
||||
|
||||
public List<JPackage> 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<JPackage> prepareHierarchyPackages() {
|
||||
JPackage root = new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true);
|
||||
JPackage root = JPackage.makeTmpRoot();
|
||||
List<JavaPackage> packages = wrapper.getPackages();
|
||||
List<JPackage> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user