diff --git a/jadx-core/src/main/java/jadx/api/ICodeCache.java b/jadx-core/src/main/java/jadx/api/ICodeCache.java index a078f79cf..4dd8eab21 100644 --- a/jadx-core/src/main/java/jadx/api/ICodeCache.java +++ b/jadx-core/src/main/java/jadx/api/ICodeCache.java @@ -6,6 +6,8 @@ public interface ICodeCache { void add(String clsFullName, ICodeInfo codeInfo); + void remove(String clsFullName); + @Nullable ICodeInfo get(String clsFullName); } diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 6547fa41a..2939f9956 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -4,16 +4,19 @@ import java.io.Closeable; import java.io.File; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -422,6 +425,13 @@ public final class JadxDecompiler implements Closeable { throw new JadxRuntimeException("Unexpected node type: " + obj); } + List convertNodes(Collection nodesList) { + return nodesList.stream() + .map(this::convertNode) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + @Nullable public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) { Map map = codeInfo.getAnnotations(); diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 84470e89d..f125ab8fa 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -59,6 +59,8 @@ public final class JavaClass implements JavaNode { public synchronized void refresh() { listsLoaded = false; + cls.unload(); + cls.deepUnload(); cls.reRunDecompile(); } @@ -124,7 +126,7 @@ public final class JavaClass implements JavaNode { } } - private JadxDecompiler getRootDecompiler() { + protected JadxDecompiler getRootDecompiler() { if (parent != null) { return parent.getRootDecompiler(); } @@ -156,6 +158,11 @@ public final class JavaClass implements JavaNode { return resultMap; } + @Override + public List getUseIn() { + return getRootDecompiler().convertNodes(cls.getUseIn()); + } + @Nullable @Deprecated public JavaNode getJavaNodeAtPosition(int line, int offset) { diff --git a/jadx-core/src/main/java/jadx/api/JavaField.java b/jadx-core/src/main/java/jadx/api/JavaField.java index 582c66bb4..e616f0f8d 100644 --- a/jadx-core/src/main/java/jadx/api/JavaField.java +++ b/jadx-core/src/main/java/jadx/api/JavaField.java @@ -1,5 +1,7 @@ package jadx.api; +import java.util.List; + import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.FieldNode; @@ -47,6 +49,11 @@ public final class JavaField implements JavaNode { return field.getDecompiledLine(); } + @Override + public List getUseIn() { + return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn()); + } + /** * Internal API. Not Stable! */ diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java index 13e863fe8..acfc6e345 100644 --- a/jadx-core/src/main/java/jadx/api/JavaMethod.java +++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java @@ -56,6 +56,11 @@ public final class JavaMethod implements JavaNode { return ArgType.tryToResolveClassAlias(mth.root(), retType); } + @Override + public List getUseIn() { + return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn()); + } + public boolean isConstructor() { return mth.getMethodInfo().isConstructor(); } diff --git a/jadx-core/src/main/java/jadx/api/JavaNode.java b/jadx-core/src/main/java/jadx/api/JavaNode.java index 190bae579..1a34cc7cb 100644 --- a/jadx-core/src/main/java/jadx/api/JavaNode.java +++ b/jadx-core/src/main/java/jadx/api/JavaNode.java @@ -1,5 +1,7 @@ package jadx.api; +import java.util.List; + public interface JavaNode { String getName(); @@ -11,4 +13,6 @@ public interface JavaNode { JavaClass getTopParentClass(); int getDecompiledLine(); + + List getUseIn(); } diff --git a/jadx-core/src/main/java/jadx/api/JavaPackage.java b/jadx-core/src/main/java/jadx/api/JavaPackage.java index 11bd5ec0d..3f1799618 100644 --- a/jadx-core/src/main/java/jadx/api/JavaPackage.java +++ b/jadx-core/src/main/java/jadx/api/JavaPackage.java @@ -1,5 +1,6 @@ package jadx.api; +import java.util.Collections; import java.util.List; import org.jetbrains.annotations.NotNull; @@ -43,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable { return 0; } + @Override + public List getUseIn() { + return Collections.emptyList(); + } + @Override public int compareTo(@NotNull JavaPackage o) { return name.compareTo(o.name); diff --git a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java index 04d80fef8..a1dcdff43 100644 --- a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java @@ -17,6 +17,11 @@ public class InMemoryCodeCache implements ICodeCache { storage.put(clsFullName, codeInfo); } + @Override + public void remove(String clsFullName) { + storage.remove(clsFullName); + } + @Override public @Nullable ICodeInfo get(String clsFullName) { return storage.get(clsFullName); diff --git a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java index f764cde4e..4e3d4811a 100644 --- a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java @@ -12,6 +12,11 @@ public class NoOpCodeCache implements ICodeCache { // do nothing } + @Override + public void remove(String clsFullName) { + // do nothing + } + @Override public @Nullable ICodeInfo get(String clsFullName) { return null; diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index ebf34c5c5..6e63191dc 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -699,12 +699,12 @@ public class ClassGen { List deps = cls.getDependencies(); code.startLine("// deps - ").add(Integer.toString(deps.size())); for (ClassNode depCls : deps) { - code.startLine("// ").add(depCls.getFullName()); + code.startLine("// ").add(depCls.getClassInfo().getFullName()); } List useIn = cls.getUseIn(); code.startLine("// use in - ").add(Integer.toString(useIn.size())); for (ClassNode useCls : useIn) { - code.startLine("// ").add(useCls.getFullName()); + code.startLine("// ").add(useCls.getClassInfo().getFullName()); } List useInMths = cls.getUseInMth(); code.startLine("// use in methods - ").add(Integer.toString(useInMths.size())); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 35fa01b60..770ee5954 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -345,6 +345,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN @Override public void unload() { + if (state == NOT_LOADED) { + return; + } methods.forEach(MethodNode::unload); innerClasses.forEach(ClassNode::unload); fields.forEach(FieldNode::unloadAttributes); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java index edd577cf5..e131e2ed4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java @@ -3,7 +3,7 @@ package jadx.core.dex.nodes; import jadx.core.dex.attributes.IAttributeNode; import jadx.core.dex.info.AccessInfo; -public interface ICodeNode extends IDexNode, IAttributeNode { +public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode { AccessInfo getAccessFlags(); void setAccessFlags(AccessInfo newAccessFlags); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/IUsageInfoNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/IUsageInfoNode.java new file mode 100644 index 000000000..6858f4b99 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/IUsageInfoNode.java @@ -0,0 +1,7 @@ +package jadx.core.dex.nodes; + +import java.util.List; + +public interface IUsageInfoNode { + List getUseIn(); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java index c191100e6..d95bd4989 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory; import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.Opcode; -import jadx.core.dex.attributes.AType; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; @@ -49,9 +48,6 @@ public class UsageInfoVisitor extends AbstractVisitor { } // TODO: process annotations and generics for (MethodNode methodNode : cls.getMethods()) { - if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) { - continue; - } processMethod(methodNode, usageInfo); } } @@ -70,6 +66,9 @@ public class UsageInfoVisitor extends AbstractVisitor { } private static void processInstructions(MethodNode mth, UsageInfo usageInfo) { + if (mth.isNoCode()) { + return; + } ICodeReader codeReader = mth.getCodeReader(); if (codeReader == null) { return; diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java index 1a27560df..8510de5f6 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java @@ -7,7 +7,7 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javax.swing.SwingWorker; +import javax.swing.*; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -36,8 +36,10 @@ public class BackgroundExecutor { public Future execute(IBackgroundTask task) { TaskWorker taskWorker = new TaskWorker(task); - taskWorker.init(); - taskQueueExecutor.execute(taskWorker); + taskQueueExecutor.execute(() -> { + taskWorker.init(); + taskWorker.run(); + }); return taskWorker; } @@ -52,6 +54,14 @@ public class BackgroundExecutor { } } + public void execute(String title, List backgroundJobs, Runnable onFinishUiRunnable) { + execute(new SimpleTask(title, backgroundJobs, onFinishUiRunnable)); + } + + public void execute(String title, List backgroundJobs) { + execute(new SimpleTask(title, backgroundJobs, null)); + } + public void execute(String title, Runnable backgroundRunnable, Runnable onFinishUiRunnable) { execute(new SimpleTask(title, backgroundRunnable, onFinishUiRunnable)); } @@ -85,6 +95,7 @@ public class BackgroundExecutor { List jobs = task.scheduleJobs(); jobsCount = jobs.size(); + LOG.debug("Starting background task '{}', jobs count: {}", task.getTitle(), jobsCount); if (jobsCount == 1) { jobs.get(0).run(); return true; @@ -149,15 +160,19 @@ public class BackgroundExecutor { private static final class SimpleTask implements IBackgroundTask { private final String title; - private final Runnable runnable; + private final List jobs; private final Runnable onFinish; - public SimpleTask(String title, Runnable runnable, @Nullable Runnable onFinish) { + public SimpleTask(String title, List jobs, @Nullable Runnable onFinish) { this.title = title; - this.runnable = runnable; + this.jobs = jobs; this.onFinish = onFinish; } + public SimpleTask(String title, Runnable job, @Nullable Runnable onFinish) { + this(title, Collections.singletonList(job), onFinish); + } + @Override public String getTitle() { return title; @@ -165,7 +180,7 @@ public class BackgroundExecutor { @Override public List scheduleJobs() { - return Collections.singletonList(runnable); + return jobs; } @Override diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java index 5f4c04f79..837ca7418 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java @@ -54,23 +54,6 @@ public class BackgroundWorker extends SwingWorker { runJob(cache.getDecompileJob()); LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo()); - if (cache.getUnloadJob() != null) { - LOG.info("Memory usage: Before unload: {}", UiUtils.memoryInfo()); - long start = System.nanoTime(); - runJob(cache.getUnloadJob()); - cache.setUnloadJob(null); - LOG.info("Memory usage: After unload: {}, unload took {} ms", UiUtils.memoryInfo(), (System.nanoTime() - start) / 1000000); - } - - if (cache.getRefreshJob() != null) { - LOG.info("Memory usage: Before refresh: {}", UiUtils.memoryInfo()); - long start = System.nanoTime(); - runJob(cache.getRefreshJob()); - cache.setRefreshJob(null); - LOG.info("Memory usage: After refresh: {}, refresh took {} ms", UiUtils.memoryInfo(), - (System.nanoTime() - start) / 1000000); - } - LOG.debug("Memory usage: Before index: {}", UiUtils.memoryInfo()); runJob(cache.getIndexJob()); LOG.debug("Memory usage: After index: {}", UiUtils.memoryInfo()); diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java index 52c4bbb60..0b7689e5a 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java @@ -31,33 +31,53 @@ public class IndexJob extends BackgroundJob { @Override protected void runJob() { JNodeCache nodeCache = cache.getNodeCache(); - final TextSearchIndex index = new TextSearchIndex(nodeCache); - final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache); + TextSearchIndex index = new TextSearchIndex(nodeCache); + CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache); cache.setTextIndex(index); cache.setUsageInfo(usageInfo); + for (final JavaClass cls : wrapper.getIncludedClasses()) { - addTask(() -> { - try { - index.indexNames(cls); - - CodeLinesInfo linesInfo = new CodeLinesInfo(cls); - List lines = splitLines(cls); - - usageInfo.processClass(cls, linesInfo, lines); - if (UiUtils.isFreeMemoryAvailable()) { - index.indexCode(cls, linesInfo, lines); - } else { - index.classCodeIndexSkipped(cls); - } - } catch (Exception e) { - LOG.error("Index error in class: {}", cls.getFullName(), e); - } - }); + addTask(() -> indexCls(cache, cls)); } } + public static void indexCls(CacheObject cache, JavaClass cls) { + try { + TextSearchIndex index = cache.getTextIndex(); + CodeUsageInfo usageInfo = cache.getUsageInfo(); + if (index == null || usageInfo == null) { + return; + } + + index.indexNames(cls); + + CodeLinesInfo linesInfo = new CodeLinesInfo(cls); + List lines = splitLines(cls); + + usageInfo.processClass(cls, linesInfo, lines); + if (UiUtils.isFreeMemoryAvailable()) { + index.indexCode(cls, linesInfo, lines); + } else { + index.classCodeIndexSkipped(cls); + } + } catch (Exception e) { + LOG.error("Index error in class: {}", cls.getFullName(), e); + } + } + + public static void refreshIndex(CacheObject cache, JavaClass cls) { + TextSearchIndex index = cache.getTextIndex(); + CodeUsageInfo usageInfo = cache.getUsageInfo(); + if (index == null || usageInfo == null) { + return; + } + index.remove(cls); + usageInfo.remove(cls); + indexCls(cache, cls); + } + @NotNull - protected List splitLines(JavaClass cls) { + protected static List splitLines(JavaClass cls) { List lines = StringRef.split(cls.getCode(), CodeWriter.NL); int size = lines.size(); for (int i = 0; i < size; i++) { diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java deleted file mode 100644 index 830feab45..000000000 --- a/jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java +++ /dev/null @@ -1,28 +0,0 @@ -package jadx.gui.jobs; - -import java.util.Set; - -import jadx.api.JavaClass; -import jadx.gui.JadxWrapper; - -public class RefreshJob extends BackgroundJob { - - private Set refreshClasses; - - public RefreshJob(JadxWrapper jadxWrapper, int threadsCount, Set refreshClasses) { - super(jadxWrapper, threadsCount); - this.refreshClasses = refreshClasses; - } - - protected void runJob() { - for (final JavaClass cls : refreshClasses) { - addTask(cls::refresh); - } - } - - @Override - public String getInfoString() { - return "Refreshing: "; - } - -} diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java deleted file mode 100644 index e3c646d6e..000000000 --- a/jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java +++ /dev/null @@ -1,31 +0,0 @@ -package jadx.gui.jobs; - -import java.util.Set; - -import jadx.api.JavaClass; -import jadx.gui.JadxWrapper; - -public class UnloadJob extends BackgroundJob { - - private Set refreshClasses; - - public UnloadJob(JadxWrapper jadxWrapper, int threadsCount, Set refreshClasses) { - super(jadxWrapper, threadsCount); - this.refreshClasses = refreshClasses; - } - - protected void runJob() { - for (final JavaClass cls : refreshClasses) { - addTask(() -> { - cls.unload(); - cls.getClassNode().deepUnload(); - }); - } - } - - @Override - public String getInfoString() { - return "Refreshing: "; - } - -} diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java index 8a7e76fbb..3af2e97f5 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java @@ -48,6 +48,10 @@ public class CodeNode extends JNode { return null; } + public StringRef getLineStr() { + return line; + } + @Override public int getLine() { return lineNum; @@ -72,4 +76,21 @@ public class CodeNode extends JNode { public String makeLongString() { return makeString(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CodeNode)) { + return false; + } + CodeNode codeNode = (CodeNode) o; + return jNode.equals(codeNode.jNode); + } + + @Override + public int hashCode() { + return jNode.hashCode(); + } } 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 a659b19c9..29418daca 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -415,12 +415,6 @@ public class MainWindow extends JFrame { } } - synchronized void runBackgroundUnloadRefreshAndIndexJobs() { - cancelBackgroundJobs(); - backgroundWorker = new BackgroundWorker(cacheObject, progressPane); - backgroundWorker.exec(); - } - public synchronized void cancelBackgroundJobs() { backgroundExecutor.cancelAll(); if (backgroundWorker != null) { @@ -520,7 +514,7 @@ public class MainWindow extends JFrame { tabbedPane.closeAllTabs(); resetCache(); treeRoot = null; - treeModel.setRoot(treeRoot); + treeModel.setRoot(null); treeModel.reload(); } @@ -1136,6 +1130,10 @@ public class MainWindow extends JFrame { return backgroundWorker; } + public BackgroundExecutor getBackgroundExecutor() { + return backgroundExecutor; + } + public ProgressPanel getProgressPane() { return progressPane; } 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 76b1081e0..e4fdd0158 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -8,10 +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.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.swing.*; @@ -23,11 +23,11 @@ import jadx.api.JavaClass; import jadx.api.JavaField; import jadx.api.JavaMethod; import jadx.api.JavaNode; +import jadx.core.codegen.CodeWriter; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.RenameVisitor; +import jadx.core.utils.Utils; import jadx.gui.jobs.IndexJob; -import jadx.gui.jobs.RefreshJob; -import jadx.gui.jobs.UnloadJob; import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; @@ -37,77 +37,59 @@ 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.*; +import jadx.gui.utils.CacheObject; +import jadx.gui.utils.JNodeCache; +import jadx.gui.utils.NLS; +import jadx.gui.utils.TextStandardActions; -public class RenameDialog extends CommonSearchDialog { +public class RenameDialog extends JDialog { private static final long serialVersionUID = -3269715644416902410L; private static final Logger LOG = LoggerFactory.getLogger(RenameDialog.class); - protected final transient MainWindow mainWindow; - + private final transient MainWindow mainWindow; + private final transient CacheObject cache; private final transient JNode node; - - private JTextField renameField; - - private CodeArea codeArea; - - private JButton renameBtn; + private transient JTextField renameField; public RenameDialog(CodeArea codeArea, JNode node) { super(codeArea.getMainWindow()); - mainWindow = codeArea.getMainWindow(); - this.codeArea = codeArea; + this.mainWindow = codeArea.getMainWindow(); + this.cache = mainWindow.getCacheObject(); this.node = node; - if (isDeobfuscationSettingsValid()) { + if (checkSettings()) { initUI(); - registerInitOnOpen(); - loadWindowPos(); - } else { - LOG.error("Deobfuscation settings are invalid - please enable deobfuscation and disable force rewrite deobfuscation map"); } } - private boolean isDeobfuscationSettingsValid() { - boolean valid = true; - String errorMessage = null; + private boolean checkSettings() { + StringBuilder errorMessage = new StringBuilder(); + errorMessage.append(NLS.str("msg.rename_disabled")).append(CodeWriter.NL); + JadxSettings settings = mainWindow.getSettings(); - final LangLocale langLocale = settings.getLangLocale(); - if (settings.isDeobfuscationForceSave()) { - valid = false; - errorMessage = NLS.str("msg.rename_disabled_force_rewrite_enabled", langLocale); - } + boolean valid = true; if (!settings.isDeobfuscationOn()) { + errorMessage.append(" - ").append(NLS.str("msg.rename_disabled_deobfuscation_disabled")).append(CodeWriter.NL); valid = false; - errorMessage = NLS.str("msg.rename_disabled_deobfuscation_disabled", langLocale); } - if (errorMessage != null) { - showRenameDisabledErrorMessage(langLocale, errorMessage); + if (settings.isDeobfuscationForceSave()) { + errorMessage.append(" - ").append(NLS.str("msg.rename_disabled_force_rewrite_enabled")).append(CodeWriter.NL); + valid = false; } - return valid; - } + if (valid) { + return true; + } + int result = JOptionPane.showConfirmDialog(mainWindow, errorMessage.toString(), + NLS.str("msg.rename_disabled_title"), JOptionPane.OK_CANCEL_OPTION); + if (result != JOptionPane.OK_OPTION) { + return false; + } + settings.setDeobfuscationOn(true); + settings.setDeobfuscationForceSave(false); + settings.sync(); - private void showRenameDisabledErrorMessage(LangLocale langLocale, String message) { - JOptionPane.showMessageDialog( - mainWindow, - message, - NLS.str("msg.rename_disabled_title", langLocale), - JOptionPane.ERROR_MESSAGE); - } - - @Override - protected void openInit() { - prepare(); - } - - @Override - protected void loadStart() { - renameBtn.setEnabled(false); - } - - @Override - protected void loadFinished() { - renameBtn.setEnabled(true); + mainWindow.reOpenFile(); + return false; // TODO: can't open dialog, 'node' is replaced with new one after reopen } private Path getDeobfMapPath(RootNode root) { @@ -195,29 +177,26 @@ public class RenameDialog extends CommonSearchDialog { } private void rename() { - long start = System.nanoTime(); - String renameText = renameField.getText(); - if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) { - return; + try { + String renameText = renameField.getText(); + if (renameText == null || renameText.length() == 0) { + return; + } + RootNode root = mainWindow.getWrapper().getDecompiler().getRoot(); + if (node == null) { + LOG.error("rename(): rootNode is null!"); + dispose(); + return; + } + if (!refreshDeobfMapFile(renameText, root)) { + LOG.error("rename(): refreshDeobfMapFile() failed!"); + dispose(); + return; + } + refreshState(root); + } catch (Exception e) { + LOG.error("Rename failed", e); } - RootNode root = mainWindow.getWrapper().getDecompiler().getRoot(); - if (node == null) { - LOG.error("rename(): rootNode is null!"); - dispose(); - return; - } - if (!refreshDeobfMapFile(renameText, root)) { - LOG.error("rename(): refreshDeobfMapFile() failed!"); - dispose(); - return; - } - long refreshStart = System.nanoTime(); - int classes = refreshState(root); - long refreshTime = (System.nanoTime() - refreshStart) / 1000000; - long totalTime = (System.nanoTime() - start) / 1000000; - LOG.info("refreshState() took {} ms to update state, {} classes will be refreshed in background ({} ms total)", refreshTime, - classes, - totalTime); dispose(); } @@ -240,92 +219,60 @@ public class RenameDialog extends CommonSearchDialog { return true; } - private int refreshState(RootNode rootNode) { + private void refreshState(RootNode rootNode) { RenameVisitor renameVisitor = new RenameVisitor(); renameVisitor.init(rootNode); - cache.getNodeCache().refresh(node); + JNodeCache nodeCache = cache.getNodeCache(); + Set updatedClasses = node.getJavaNode().getUseIn() + .stream() + .map(nodeCache::makeFrom) + .map(JNode::getRootClass) + .collect(Collectors.toSet()); + updatedClasses.add(node.getRootClass()); - Set updatedClasses = getUpdatedClasses(); - - mainWindow.reloadTree(); refreshTabs(mainWindow.getTabbedPane(), updatedClasses); - if (updatedClasses.size() > 0) { - setRefreshTask(updatedClasses); + if (!updatedClasses.isEmpty()) { + mainWindow.getBackgroundExecutor().execute("Refreshing", + Utils.collectionMap(updatedClasses, cls -> () -> refreshJClass(cls)), + mainWindow::reloadTree); } - - return updatedClasses.size(); } - private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) { - for (Map.Entry panel : tabbedPane.getOpenTabs().entrySet()) { - ContentPanel contentPanel = panel.getValue(); + private void refreshJClass(JClass cls) { + cls.refresh(); + IndexJob.refreshIndex(cache, cls.getCls()); + } + + private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) { + for (Map.Entry entry : tabbedPane.getOpenTabs().entrySet()) { + ContentPanel contentPanel = entry.getValue(); if (contentPanel instanceof ClassCodeContentPanel) { - JNode node = panel.getKey(); + JNode node = entry.getKey(); JClass rootClass = node.getRootClass(); - JavaClass javaClass = rootClass.getCls(); - if (updatedClasses.contains(javaClass) || node.getRootClass().getCls() == javaClass) { - LOG.info("Refreshing rootClass " + javaClass.getRawName()); - javaClass.unload(); - javaClass.getClassNode().deepUnload(); - rootClass.refresh(); // Update code cache + if (updatedClasses.contains(rootClass)) { + refreshJClass(rootClass); ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel; CodePanel javaPanel = codePanel.getJavaCodePanel(); javaPanel.refresh(); - tabbedPane.refresh(node); - updatedClasses.remove(javaClass); + tabbedPane.refresh(rootClass); } } } } - private Set getUpdatedClasses() { - Set usageClasses = new HashSet<>(); - CodeUsageInfo usageInfo = cache.getUsageInfo(); - if (usageInfo != null) { - usageInfo.getUsageList(node).forEach((node) -> { - JavaClass rootClass = node.getRootClass().getCls(); - // LOG.info("updateUsages(): Going to update class {}", rootClass.getRealFullName()); - usageClasses.add(rootClass); - }); - // usageClasses.parallelStream().forEach(JavaClass::refresh); - } - return usageClasses; - } - - private void setRefreshTask(Set refreshClasses) { - UnloadJob unloadJob = new UnloadJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); - RefreshJob refreshJob = new RefreshJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); - LOG.info("Waiting for old unloadJob and refreshJob"); - while (cache.getUnloadJob() != null || cache.getRefreshJob() != null) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - return; - } - } - LOG.info("Old unloadJob and refreshJob finished"); - cache.setUnloadJob(unloadJob); - cache.setRefreshJob(refreshJob); - cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), cache, mainWindow.getSettings().getThreadsCount())); - mainWindow.runBackgroundUnloadRefreshAndIndexJobs(); - } - @NotNull protected JPanel initButtonsPanel() { JButton cancelButton = new JButton(NLS.str("search_dialog.cancel")); cancelButton.addActionListener(event -> dispose()); - renameBtn = new JButton(NLS.str("popup.rename")); + JButton renameBtn = new JButton(NLS.str("popup.rename")); renameBtn.addActionListener(event -> rename()); getRootPane().setDefaultButton(renameBtn); - progressPane = new ProgressPanel(mainWindow, false); - JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); - buttonPane.add(progressPane); buttonPane.add(Box.createRigidArea(new Dimension(5, 0))); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(renameBtn); @@ -351,18 +298,12 @@ public class RenameDialog extends CommonSearchDialog { renamePane.add(nodeLabel); renamePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - warnLabel = new JLabel(); - warnLabel.setForeground(Color.RED); - warnLabel.setVisible(false); - JPanel textPane = new JPanel(); textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS)); - textPane.add(warnLabel); textPane.add(Box.createRigidArea(new Dimension(0, 5))); textPane.add(renameField); textPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); - initCommon(); JPanel buttonPane = initButtonsPanel(); Container contentPane = getContentPane(); @@ -376,5 +317,7 @@ public class RenameDialog extends CommonSearchDialog { setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); setModalityType(ModalityType.MODELESS); + + mainWindow.getSettings().loadWindowPos(this); } } 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 b367114bb..fc47dd81c 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -7,8 +7,6 @@ import org.jetbrains.annotations.Nullable; import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.IndexJob; -import jadx.gui.jobs.RefreshJob; -import jadx.gui.jobs.UnloadJob; import jadx.gui.ui.SearchDialog; import jadx.gui.utils.search.TextSearchIndex; @@ -16,8 +14,6 @@ public class CacheObject { private DecompileJob decompileJob; private IndexJob indexJob; - private UnloadJob unloadJob; - private RefreshJob refreshJob; private TextSearchIndex textIndex; private CodeUsageInfo usageInfo; @@ -93,20 +89,4 @@ public class CacheObject { public Set getLastSearchOptions() { return lastSearchOptions; } - - public RefreshJob getRefreshJob() { - return refreshJob; - } - - public void setRefreshJob(RefreshJob refreshJob) { - this.refreshJob = refreshJob; - } - - public UnloadJob getUnloadJob() { - return unloadJob; - } - - public void setUnloadJob(UnloadJob unloadJob) { - this.unloadJob = unloadJob; - } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java b/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java index 7927fb147..c9ca31e7f 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java @@ -70,4 +70,14 @@ public class CodeUsageInfo { } return usageInfo.getUsageList(); } + + public void remove(JavaClass cls) { + usageMap.entrySet().removeIf(e -> { + if (e.getKey().getJavaNode().getTopParentClass().equals(cls)) { + return true; + } + e.getValue().getUsageList().removeIf(node -> node.getJavaNode().getTopParentClass().equals(cls)); + return false; + }); + } } 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 1185c3489..e16ac30bf 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java @@ -29,12 +29,6 @@ public class JNodeCache { return jNode; } - public void refresh(JNode node) { - JavaNode javaNode = node.getJavaNode(); - cache.remove(javaNode); - makeFrom(javaNode); - } - private JNode convert(JavaNode node) { if (node == null) { return null; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java index 2ef4ec94f..bb2252cc8 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java @@ -9,46 +9,34 @@ import org.slf4j.LoggerFactory; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; +import jadx.api.JavaClass; +import jadx.gui.treemodel.CodeNode; import jadx.gui.utils.UiUtils; -public class CodeIndex implements SearchIndex { +public class CodeIndex { private static final Logger LOG = LoggerFactory.getLogger(CodeIndex.class); - private final List keys = new ArrayList<>(); - private final List values = new ArrayList<>(); + private final List values = new ArrayList<>(); - @Override - public void put(String str, T value) { - throw new UnsupportedOperationException("CodeIndex.put for string not supported"); - } - - @Override - public synchronized void put(StringRef str, T value) { - if (str == null || str.length() == 0) { - return; - } - keys.add(str); + public synchronized void put(CodeNode value) { values.add(value); } - @Override - public boolean isStringRefSupported() { - return true; + public void removeForCls(JavaClass cls) { + values.removeIf(v -> v.getJavaNode().getTopParentClass().equals(cls)); } private boolean isMatched(StringRef key, String str, boolean caseInsensitive) { return key.indexOf(str, caseInsensitive) != -1; } - @Override - public Flowable search(final String searchStr, final boolean caseInsensitive) { + public Flowable search(final String searchStr, final boolean caseInsensitive) { return Flowable.create(emitter -> { - int size = size(); LOG.debug("Code search started: {} ...", searchStr); - for (int i = 0; i < size; i++) { - if (isMatched(keys.get(i), searchStr, caseInsensitive)) { - emitter.onNext(values.get(i)); + for (CodeNode node : values) { + if (isMatched(node.getLineStr(), searchStr, caseInsensitive)) { + emitter.onNext(node); } if (emitter.isCancelled()) { LOG.debug("Code search canceled: {}", searchStr); @@ -60,8 +48,7 @@ public class CodeIndex implements SearchIndex { }, BackpressureStrategy.LATEST); } - @Override public int size() { - return keys.size(); + return values.size(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java index 75edf104b..4bf2e962b 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java @@ -6,10 +6,6 @@ public interface SearchIndex { void put(String str, V value); - void put(StringRef str, V value); - - boolean isStringRefSupported(); - Flowable search(String searchStr, boolean caseInsensitive); int size(); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java index 49d9fb546..6e995c90b 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java @@ -1,36 +1,25 @@ package jadx.gui.utils.search; -import java.util.ArrayList; -import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; -public class SimpleIndex implements SearchIndex { +import jadx.api.JavaClass; +import jadx.gui.treemodel.JNode; - private final List keys = new ArrayList<>(); - private final List values = new ArrayList<>(); +public class SimpleIndex { + private final Map data = new ConcurrentHashMap<>(); - private final Object syncData = new Object(); - - @Override - public void put(String str, T value) { - synchronized (syncData) { - keys.add(str); - values.add(value); - } + public void put(String str, JNode value) { + data.put(value, str); } - @Override - public void put(StringRef str, T value) { - throw new UnsupportedOperationException("StringRef not supported"); - } - - @Override - public boolean isStringRefSupported() { - return false; + public void removeForCls(JavaClass cls) { + data.entrySet().removeIf(e -> e.getKey().getJavaNode().getTopParentClass().equals(cls)); } private boolean isMatched(String str, String searchStr, boolean caseInsensitive) { @@ -41,28 +30,22 @@ public class SimpleIndex implements SearchIndex { } } - @Override - public Flowable search(final String searchStr, final boolean caseInsensitive) { + public Flowable search(final String searchStr, final boolean caseInsensitive) { return Flowable.create(emitter -> { - synchronized (syncData) { - int size = keys.size(); - for (int i = 0; i < size; i++) { - if (isMatched(keys.get(i), searchStr, caseInsensitive)) { - emitter.onNext(values.get(i)); - } - if (emitter.isCancelled()) { - return; - } + for (Map.Entry entry : data.entrySet()) { + + if (isMatched(entry.getValue(), searchStr, caseInsensitive)) { + emitter.onNext(entry.getKey()); + } + if (emitter.isCancelled()) { + return; } } emitter.onComplete(); }, BackpressureStrategy.LATEST); } - @Override public int size() { - synchronized (syncData) { - return keys.size(); - } + return data.size(); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java index 8c8bce0b1..b2ba28535 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java @@ -36,19 +36,19 @@ public class TextSearchIndex { private final JNodeCache nodeCache; - private SearchIndex clsNamesIndex; - private SearchIndex mthSignaturesIndex; - private SearchIndex fldSignaturesIndex; - private SearchIndex codeIndex; + private final SimpleIndex clsNamesIndex; + private final SimpleIndex mthSignaturesIndex; + private final SimpleIndex fldSignaturesIndex; + private final CodeIndex codeIndex; - private List skippedClasses = new ArrayList<>(); + private final List skippedClasses = new ArrayList<>(); public TextSearchIndex(JNodeCache nodeCache) { this.nodeCache = nodeCache; - this.clsNamesIndex = new SimpleIndex<>(); - this.mthSignaturesIndex = new SimpleIndex<>(); - this.fldSignaturesIndex = new SimpleIndex<>(); - this.codeIndex = new CodeIndex<>(); + this.clsNamesIndex = new SimpleIndex(); + this.mthSignaturesIndex = new SimpleIndex(); + this.fldSignaturesIndex = new SimpleIndex(); + this.codeIndex = new CodeIndex(); } public void indexNames(JavaClass cls) { @@ -68,7 +68,6 @@ public class TextSearchIndex { public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, List lines) { try { - boolean strRefSupported = codeIndex.isStringRefSupported(); int count = lines.size(); for (int i = 0; i < count; i++) { StringRef line = lines.get(i); @@ -78,18 +77,21 @@ public class TextSearchIndex { } int lineNum = i + 1; JavaNode node = linesInfo.getJavaNodeByLine(lineNum); - CodeNode codeNode = new CodeNode(nodeCache.makeFrom(node == null ? cls : node), lineNum, line); - if (strRefSupported) { - codeIndex.put(line, codeNode); - } else { - codeIndex.put(line.toString(), codeNode); - } + JNode nodeAtLine = nodeCache.makeFrom(node == null ? cls : node); + codeIndex.put(new CodeNode(nodeAtLine, lineNum, line)); } } catch (Exception e) { LOG.warn("Failed to index class: {}", cls, e); } } + public void remove(JavaClass cls) { + this.clsNamesIndex.removeForCls(cls); + this.mthSignaturesIndex.removeForCls(cls); + this.fldSignaturesIndex.removeForCls(cls); + this.codeIndex.removeForCls(cls); + } + public Flowable buildSearch(String text, Set options) { boolean ignoreCase = options.contains(IGNORE_CASE); LOG.debug("Building search, ignoreCase: {}", ignoreCase); diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 9a4e7be22..7aa16f5e1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -137,9 +137,9 @@ msg.index_not_initialized=Index not initialized, search will be disabled! msg.project_error_title=Error msg.project_error=Project could not be loaded msg.rename_disabled_title=Rename disabled -msg.rename_disabled=Some of rename settings are disabled, please take this into consideration -msg.rename_disabled_force_rewrite_enabled=Please disable "Force rewrite deobfuscation map file" option to rename. -msg.rename_disabled_deobfuscation_disabled=Please enable deobfuscation to rename. +msg.rename_disabled=Some of rename settings are disabled, next options will be changed: +msg.rename_disabled_force_rewrite_enabled=Disable "Force rewrite deobfuscation map file" option. +msg.rename_disabled_deobfuscation_disabled=Enable deobfuscation. msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. popup.undo=Undo