diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
index f19b9b2d6..bf88d1ce3 100644
--- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
+++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
@@ -29,7 +29,7 @@ public class JadxCLIArgs implements IJadxArgs {
protected String outDirName;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
- protected int threadsCount = Runtime.getRuntime().availableProcessors();
+ protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml
index 6a1e61641..670c71320 100644
--- a/jadx-cli/src/main/resources/logback.xml
+++ b/jadx-cli/src/main/resources/logback.xml
@@ -5,7 +5,7 @@
INFO
- %-5level - %msg%n
+ %d{HH:mm:ss} %-5level - %msg%n
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 0614e7cfe..e0f3992d0 100644
--- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
+++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java
@@ -2,7 +2,7 @@ package jadx.gui.jobs;
import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.CacheObject;
-import jadx.gui.utils.TextSearchIndex;
+import jadx.gui.utils.search.TextSearchIndex;
import jadx.gui.utils.Utils;
import javax.swing.SwingUtilities;
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 6039f9a21..83022ec59 100644
--- a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
+++ b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java
@@ -3,12 +3,15 @@ package jadx.gui.jobs;
import jadx.api.JavaClass;
import jadx.core.codegen.CodeWriter;
import jadx.gui.JadxWrapper;
-import jadx.gui.settings.JadxSettings;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
-import jadx.gui.utils.TextSearchIndex;
+import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.Utils;
+import jadx.gui.utils.search.StringRef;
+import jadx.gui.utils.search.TextSearchIndex;
+
+import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
@@ -20,15 +23,16 @@ public class IndexJob extends BackgroundJob {
private final CacheObject cache;
private final boolean useFastSearch;
- public IndexJob(JadxWrapper wrapper, JadxSettings settings, CacheObject cache) {
- super(wrapper, settings.getThreadsCount());
- this.useFastSearch = settings.isUseFastSearch();
+ public IndexJob(JadxWrapper wrapper, CacheObject cache, int threadsCount, boolean useFastSearch) {
+ super(wrapper, threadsCount);
+ this.useFastSearch = useFastSearch;
this.cache = cache;
}
protected void runJob() {
- final TextSearchIndex index = new TextSearchIndex();
- final CodeUsageInfo usageInfo = new CodeUsageInfo();
+ JNodeCache nodeCache = cache.getNodeCache();
+ final TextSearchIndex index = new TextSearchIndex(nodeCache, useFastSearch);
+ final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
cache.setTextIndex(index);
cache.setUsageInfo(usageInfo);
for (final JavaClass cls : wrapper.getClasses()) {
@@ -39,10 +43,10 @@ public class IndexJob extends BackgroundJob {
index.indexNames(cls);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
- String[] lines = splitIntoLines(cls);
+ List lines = splitLines(cls);
usageInfo.processClass(cls, linesInfo, lines);
- if (useFastSearch && Utils.isFreeMemoryAvailable()) {
+ if (Utils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines);
} else {
index.classCodeIndexSkipped(cls);
@@ -56,11 +60,11 @@ public class IndexJob extends BackgroundJob {
}
@NotNull
- protected String[] splitIntoLines(JavaClass cls) {
- String[] lines = cls.getCode().split(CodeWriter.NL);
- int count = lines.length;
- for (int i = 0; i < count; i++) {
- lines[i] = lines[i].trim();
+ protected List splitLines(JavaClass cls) {
+ List lines = StringRef.split(cls.getCode(), CodeWriter.NL);
+ int size = lines.size();
+ for (int i = 0; i < size; i++) {
+ lines.set(i, lines.get(i).trim());
}
return lines;
}
diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
index 1986fa1ef..73eeda752 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
@@ -29,6 +29,11 @@ public class JadxSettings extends JadxCLIArgs {
private List recentFiles = new ArrayList();
private String fontStr = "";
private boolean useFastSearch = false;
+ private boolean autoStartJobs = true;
+
+ public JadxSettings() {
+ setSkipResources(true);
+ }
public void sync() {
JadxSettingsAdapter.store(this);
@@ -137,13 +142,22 @@ public class JadxSettings extends JadxCLIArgs {
}
public boolean isUseFastSearch() {
- return useFastSearch;
+ return false;
+// return useFastSearch;
}
public void setUseFastSearch(boolean useFastSearch) {
this.useFastSearch = useFastSearch;
}
+ public boolean isAutoStartJobs() {
+ return autoStartJobs;
+ }
+
+ public void setAutoStartJobs(boolean autoStartJobs) {
+ this.autoStartJobs = autoStartJobs;
+ }
+
public Font getFont() {
if (fontStr.isEmpty()) {
return DEFAULT_FONT;
diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
index 89f281725..001d0bd9a 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
@@ -24,7 +24,6 @@ import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
-import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -57,7 +56,7 @@ public class JadxSettingsWindow extends JDialog {
private void initUI() {
JPanel panel = new JPanel();
- panel.setLayout(new GridLayout(0, 1, 10, 5));
+ panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(makeDeobfuscationGroup());
panel.add(makeOtherGroup());
@@ -102,7 +101,6 @@ public class JadxSettingsWindow extends JDialog {
}
private SettingsGroup makeDeobfuscationGroup() {
-
JCheckBox deobfOn = new JCheckBox();
deobfOn.setSelected(settings.isDeobfuscationOn());
deobfOn.addItemListener(new ItemListener() {
@@ -239,7 +237,16 @@ public class JadxSettingsWindow extends JDialog {
}
});
+ JCheckBox autoStartJobs = new JCheckBox();
+ autoStartJobs.setSelected(settings.isAutoStartJobs());
+ autoStartJobs.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED);
+ }
+ });
+
JCheckBox fastSearch = new JCheckBox();
+ fastSearch.setEnabled(false);
fastSearch.setSelected(settings.isUseFastSearch());
fastSearch.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
@@ -257,6 +264,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
other.addRow(NLS.str("preferences.font"), fontBtn);
other.addRow(NLS.str("preferences.fast_search"), fastSearch);
+ other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
return other;
}
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 192299791..592d7023b 100644
--- a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java
+++ b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java
@@ -1,6 +1,7 @@
package jadx.gui.treemodel;
import jadx.api.JavaNode;
+import jadx.gui.utils.search.StringRef;
import javax.swing.Icon;
@@ -10,12 +11,12 @@ public class CodeNode extends JNode {
private final JNode jNode;
private final JClass jParent;
- private final String line;
+ private final StringRef line;
private final int lineNum;
- public CodeNode(JavaNode javaNode, int lineNum, String line) {
- this.jNode = makeFrom(javaNode);
- this.jParent = jNode.getJParent();
+ public CodeNode(JNode jNode, int lineNum, StringRef line) {
+ this.jNode = jNode;
+ this.jParent = this.jNode.getJParent();
this.line = line;
this.lineNum = lineNum;
}
@@ -54,7 +55,7 @@ public class CodeNode extends JNode {
@Override
public String makeDescString() {
- return line;
+ return line.toString();
}
@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 82e6ecb15..5017fe81f 100644
--- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java
+++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java
@@ -1,10 +1,6 @@
package jadx.gui.treemodel;
-import jadx.api.JavaClass;
-import jadx.api.JavaField;
-import jadx.api.JavaMethod;
import jadx.api.JavaNode;
-import jadx.core.utils.exceptions.JadxRuntimeException;
import javax.swing.Icon;
import javax.swing.tree.DefaultMutableTreeNode;
@@ -12,24 +8,6 @@ import javax.swing.tree.DefaultMutableTreeNode;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
public abstract class JNode extends DefaultMutableTreeNode {
- public static JNode makeFrom(JavaNode node) {
- if (node == null) {
- return null;
- }
- if (node instanceof JavaClass) {
- JClass p = (JClass) makeFrom(node.getDeclaringClass());
- return new JClass((JavaClass) node, p);
- }
- if (node instanceof JavaMethod) {
- JavaMethod mth = (JavaMethod) node;
- return new JMethod(mth, (JClass) makeFrom(mth.getDeclaringClass()));
- }
- if (node instanceof JavaField) {
- JavaField fld = (JavaField) node;
- return new JField(fld, (JClass) makeFrom(fld.getDeclaringClass()));
- }
- throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
- }
public abstract JClass getJParent();
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java
index d4c6b4852..194dc6a1c 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java
@@ -7,7 +7,7 @@ import jadx.gui.treemodel.TextNode;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Position;
-import jadx.gui.utils.TextSearchIndex;
+import jadx.gui.utils.search.TextSearchIndex;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -56,7 +56,7 @@ public abstract class CommonSearchDialog extends JDialog {
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
private static final long serialVersionUID = 8939332306115370276L;
- public static final int MAX_RESULTS_COUNT = 1000;
+ public static final int MAX_RESULTS_COUNT = 100;
protected final TabbedPane tabbedPane;
protected final CacheObject cache;
@@ -328,7 +328,10 @@ public abstract class CommonSearchDialog extends JDialog {
textArea.setRows(1);
textArea.setColumns(textArea.getText().length());
if (highlightText != null) {
- SearchEngine.markAll(textArea, new SearchContext(highlightText));
+ SearchContext searchContext = new SearchContext(highlightText);
+ searchContext.setMatchCase(true);
+ searchContext.setMarkAll(true);
+ SearchEngine.markAll(textArea, searchContext);
}
return textArea;
}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java b/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java
index b79654ddb..18cae067b 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java
@@ -214,7 +214,8 @@ class ContentArea extends RSyntaxTextArea {
return;
}
MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow();
- UsageDialog usageDialog = new UsageDialog(mainWindow, JNode.makeFrom(node));
+ JNode jNode = mainWindow.getCacheObject().getNodeCache().makeFrom(node);
+ UsageDialog usageDialog = new UsageDialog(mainWindow, jNode);
usageDialog.setVisible(true);
}
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 00937d071..fec78a77c 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
@@ -194,20 +194,23 @@ public class MainWindow extends JFrame {
protected void resetCache() {
cacheObject.reset();
- int threadsCount = settings.getThreadsCount();
+ // TODO: decompilation freezes sometime with several threads
+ int threadsCount = 1; // settings.getThreadsCount();
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
- cacheObject.setIndexJob(new IndexJob(wrapper, settings, cacheObject));
+ cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount, settings.isUseFastSearch()));
}
private synchronized void runBackgroundJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
- new Timer().schedule(new TimerTask() {
- @Override
- public void run() {
- backgroundWorker.exec();
- }
- }, 1000);
+ if (settings.isAutoStartJobs()) {
+ new Timer().schedule(new TimerTask() {
+ @Override
+ public void run() {
+ backgroundWorker.exec();
+ }
+ }, 1000);
+ }
}
public synchronized void cancelBackgroundJobs() {
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java
index 564cba57f..6ba405b78 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java
@@ -1,7 +1,7 @@
package jadx.gui.ui;
import jadx.gui.utils.NLS;
-import jadx.gui.utils.TextSearchIndex;
+import jadx.gui.utils.search.TextSearchIndex;
import jadx.gui.utils.TextStandardActions;
import javax.swing.BorderFactory;
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 63f00a33f..1fd4955be 100644
--- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java
+++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java
@@ -2,6 +2,7 @@ package jadx.gui.utils;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
+import jadx.gui.utils.search.TextSearchIndex;
import org.jetbrains.annotations.Nullable;
@@ -13,10 +14,12 @@ public class CacheObject {
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
private String lastSearch;
+ private JNodeCache jNodeCache = new JNodeCache();
public void reset() {
textIndex = null;
lastSearch = null;
+ jNodeCache = new JNodeCache();
usageInfo = null;
}
@@ -62,4 +65,8 @@ public class CacheObject {
public void setIndexJob(IndexJob indexJob) {
this.indexJob = indexJob;
}
+
+ public JNodeCache getNodeCache() {
+ return jNodeCache;
+ }
}
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 29a665141..ee6213ace 100644
--- a/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java
+++ b/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java
@@ -5,6 +5,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
+import jadx.gui.utils.search.StringRef;
import java.util.ArrayList;
import java.util.Collections;
@@ -22,19 +23,25 @@ public class CodeUsageInfo {
}
}
+ private final JNodeCache nodeCache;
+
+ public CodeUsageInfo(JNodeCache nodeCache) {
+ this.nodeCache = nodeCache;
+ }
+
private final Map usageMap = new HashMap();
- public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, String[] lines) {
+ public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List lines) {
Map usage = javaClass.getUsageMap();
for (Map.Entry entry : usage.entrySet()) {
CodePosition codePosition = entry.getKey();
JavaNode javaNode = entry.getValue();
- addUsage(JNode.makeFrom(javaNode), javaClass, linesInfo, codePosition, lines);
+ addUsage(nodeCache.makeFrom(javaNode), javaClass, linesInfo, codePosition, lines);
}
}
private void addUsage(JNode jNode, JavaClass javaClass,
- CodeLinesInfo linesInfo, CodePosition codePosition, String[] lines) {
+ CodeLinesInfo linesInfo, CodePosition codePosition, List lines) {
UsageInfo usageInfo = usageMap.get(jNode);
if (usageInfo == null) {
usageInfo = new UsageInfo();
@@ -42,8 +49,9 @@ public class CodeUsageInfo {
}
int line = codePosition.getLine();
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
- String codeLine = lines[line - 1].trim();
- CodeNode codeNode = new CodeNode(javaNodeByLine == null ? javaClass : javaNodeByLine, line, codeLine);
+ StringRef codeLine = lines.get(line - 1);
+ JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
+ CodeNode codeNode = new CodeNode(node, line, codeLine);
usageInfo.getUsageList().add(codeNode);
}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java
new file mode 100644
index 000000000..1a88411c0
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java
@@ -0,0 +1,50 @@
+package jadx.gui.utils;
+
+import jadx.api.JavaClass;
+import jadx.api.JavaField;
+import jadx.api.JavaMethod;
+import jadx.api.JavaNode;
+import jadx.core.utils.exceptions.JadxRuntimeException;
+import jadx.gui.treemodel.JClass;
+import jadx.gui.treemodel.JField;
+import jadx.gui.treemodel.JMethod;
+import jadx.gui.treemodel.JNode;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class JNodeCache {
+
+ private final Map cache = new HashMap();
+
+ public JNode makeFrom(JavaNode javaNode) {
+ if (javaNode == null) {
+ return null;
+ }
+ JNode jNode = cache.get(javaNode);
+ if (jNode == null) {
+ jNode = convert(javaNode);
+ cache.put(javaNode, jNode);
+ }
+ return jNode;
+ }
+
+ private JNode convert(JavaNode node) {
+ if (node == null) {
+ return null;
+ }
+ if (node instanceof JavaClass) {
+ JClass p = (JClass) makeFrom(node.getDeclaringClass());
+ return new JClass((JavaClass) node, p);
+ }
+ if (node instanceof JavaMethod) {
+ JavaMethod mth = (JavaMethod) node;
+ return new JMethod(mth, (JClass) makeFrom(mth.getDeclaringClass()));
+ }
+ if (node instanceof JavaField) {
+ JavaField fld = (JavaField) node;
+ return new JField(fld, (JClass) makeFrom(fld.getDeclaringClass()));
+ }
+ throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/TextSearchIndex.java
deleted file mode 100644
index d4b7278b7..000000000
--- a/jadx-gui/src/main/java/jadx/gui/utils/TextSearchIndex.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package jadx.gui.utils;
-
-import jadx.api.JavaClass;
-import jadx.api.JavaField;
-import jadx.api.JavaMethod;
-import jadx.api.JavaNode;
-import jadx.core.codegen.CodeWriter;
-import jadx.gui.treemodel.CodeNode;
-import jadx.gui.treemodel.JNode;
-import jadx.gui.ui.CommonSearchDialog;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class TextSearchIndex {
-
- private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class);
-
- private SuffixTree clsNamesTree;
- private SuffixTree mthNamesTree;
- private SuffixTree fldNamesTree;
- private SuffixTree codeTree;
-
- private List skippedClasses = new ArrayList();
-
- public TextSearchIndex() {
- clsNamesTree = new SuffixTree();
- mthNamesTree = new SuffixTree();
- fldNamesTree = new SuffixTree();
- codeTree = new SuffixTree();
- }
-
- public void indexNames(JavaClass cls) {
- clsNamesTree.put(cls.getFullName(), JNode.makeFrom(cls));
- for (JavaMethod mth : cls.getMethods()) {
- mthNamesTree.put(mth.getFullName(), JNode.makeFrom(mth));
- }
- for (JavaField fld : cls.getFields()) {
- fldNamesTree.put(fld.getFullName(), JNode.makeFrom(fld));
- }
- for (JavaClass innerCls : cls.getInnerClasses()) {
- indexNames(innerCls);
- }
- }
-
- public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, String[] lines) {
- try {
- int count = lines.length;
- for (int i = 0; i < count; i++) {
- String line = lines[i];
- if (!line.isEmpty()) {
- int lineNum = i + 1;
- JavaNode node = linesInfo.getJavaNodeByLine(lineNum);
- codeTree.put(line, new CodeNode(node == null ? cls : node, lineNum, line));
- }
- }
- } catch (Exception e) {
- LOG.warn("Failed to index class: {}", cls, e);
- }
- }
-
- public Iterable searchClsName(String text) {
- return clsNamesTree.getValuesForKeysContaining(text);
- }
-
- public Iterable searchMthName(String text) {
- return mthNamesTree.getValuesForKeysContaining(text);
- }
-
- public Iterable searchFldName(String text) {
- return fldNamesTree.getValuesForKeysContaining(text);
- }
-
- public Iterable searchCode(String text) {
- Iterable items;
- if (codeTree.size() > 0) {
- items = codeTree.getValuesForKeysContaining(text);
- if (skippedClasses.isEmpty()) {
- return items;
- }
- } else {
- items = null;
- }
- List list = new ArrayList();
- if (items != null) {
- for (CodeNode item : items) {
- list.add(item);
- }
- }
- addSkippedClasses(list, text);
- return list;
- }
-
- private void addSkippedClasses(List list, String text) {
- for (JavaClass javaClass : skippedClasses) {
- String code = javaClass.getCode();
- int pos = 0;
- while (pos != -1) {
- pos = searchNext(list, text, javaClass, code, pos);
- }
- if (list.size() > CommonSearchDialog.MAX_RESULTS_COUNT) {
- return;
- }
- }
- }
-
- private int searchNext(List list, String text, JavaNode javaClass, String code, int startPos) {
- int pos = code.indexOf(text, startPos);
- if (pos == -1) {
- return -1;
- }
- int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
- int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length());
- String line = code.substring(lineStart, lineEnd == -1 ? code.length() : lineEnd);
- list.add(new CodeNode(javaClass, -pos, line.trim()));
- return lineEnd;
- }
-
- public void classCodeIndexSkipped(JavaClass cls) {
- this.skippedClasses.add(cls);
- }
-
- public List getSkippedClasses() {
- return skippedClasses;
- }
-
- public int getSkippedCount() {
- return skippedClasses.size();
- }
-}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java
index c51aa64d7..8886b7455 100644
--- a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java
+++ b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java
@@ -99,10 +99,11 @@ public class Utils {
long allocatedMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
- sb.append("free: ").append(format(freeMemory));
+ sb.append("heap: ").append(format(allocatedMemory - freeMemory));
sb.append(", allocated: ").append(format(allocatedMemory));
- sb.append(", max: ").append(format(maxMemory));
+ sb.append(", free: ").append(format(freeMemory));
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
+ sb.append(", max: ").append(format(maxMemory));
return sb.toString();
}
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
new file mode 100644
index 000000000..64dc19cc7
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java
@@ -0,0 +1,51 @@
+package jadx.gui.utils.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CodeIndex extends SearchIndex {
+
+ private final List keys = 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 void put(StringRef str, T value) {
+ if (str == null || str.length() == 0) {
+ return;
+ }
+ keys.add(str);
+ values.add(value);
+ }
+
+ @Override
+ public boolean isStringRefSupported() {
+ return true;
+ }
+
+ @Override
+ public List getValuesForKeysContaining(String str) {
+ int size = size();
+ if (size == 0) {
+ return Collections.emptyList();
+ }
+ List results = new ArrayList();
+ for (int i = 0; i < size; i++) {
+ StringRef key = keys.get(i);
+ if (key.indexOf(str) != -1) {
+ results.add(values.get(i));
+ }
+ }
+ return results;
+ }
+
+ @Override
+ public int size() {
+ return keys.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
new file mode 100644
index 000000000..fbd6619b3
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java
@@ -0,0 +1,20 @@
+package jadx.gui.utils.search;
+
+import java.util.List;
+
+public abstract class SearchIndex {
+
+ public abstract void put(String str, V value);
+
+ public void put(StringRef str, V value) {
+ throw new UnsupportedOperationException("StringRef put not supported");
+ }
+
+ public boolean isStringRefSupported() {
+ return false;
+ }
+
+ public abstract List getValuesForKeysContaining(String str);
+
+ public abstract 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
new file mode 100644
index 000000000..ab27b7d46
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java
@@ -0,0 +1,38 @@
+package jadx.gui.utils.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class SimpleIndex extends SearchIndex {
+
+ private final List keys = new ArrayList();
+ private final List values = new ArrayList();
+
+ @Override
+ public void put(String str, T value) {
+ keys.add(str);
+ values.add(value);
+ }
+
+ @Override
+ public List getValuesForKeysContaining(String str) {
+ int size = size();
+ if (size == 0) {
+ return Collections.emptyList();
+ }
+ List results = new ArrayList();
+ for (int i = 0; i < size; i++) {
+ String key = keys.get(i);
+ if (key.contains(str)) {
+ results.add(values.get(i));
+ }
+ }
+ return results;
+ }
+
+ @Override
+ public int size() {
+ return keys.size();
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/StringRef.java b/jadx-gui/src/main/java/jadx/gui/utils/search/StringRef.java
new file mode 100644
index 000000000..37db49605
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/StringRef.java
@@ -0,0 +1,181 @@
+package jadx.gui.utils.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.jetbrains.annotations.NotNull;
+
+public class StringRef implements CharSequence {
+
+ private final String refStr;
+ private final int offset;
+ private final int length;
+
+ private int hash;
+
+ public static StringRef subString(String str, int from, int to) {
+ return new StringRef(str, from, to - from);
+ }
+
+ public static StringRef subString(String str, int from) {
+ return subString(str, from, str.length());
+ }
+
+ public static StringRef fromStr(String str) {
+ return new StringRef(str, 0, str.length());
+ }
+
+ private StringRef(String str, int from, int length) {
+ this.refStr = str;
+ this.offset = from;
+ this.length = length;
+ }
+
+ @Override
+ public int length() {
+ return length;
+ }
+
+ @Override
+ public char charAt(int index) {
+ return refStr.charAt(offset + index);
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return subString(refStr, start, end);
+ }
+
+ public StringRef trim() {
+ int start = offset;
+ int end = start + length;
+ String str = refStr;
+
+ while ((start < end) && (str.charAt(start) <= ' ')) {
+ start++;
+ }
+ while ((start < end) && (str.charAt(end - 1) <= ' ')) {
+ end--;
+ }
+ if ((start > offset) || (end < offset + length)) {
+ return subString(str, start, end);
+ }
+ return this;
+ }
+
+ public int indexOf(String str) {
+ return indexOf(str, 0);
+ }
+
+ public int indexOf(String str, int from) {
+ return indexOf(refStr, offset, length, str, 0, str.length(), from);
+ }
+
+ private static int indexOf(String source, int sourceOffset, int sourceCount,
+ String target, int targetOffset, int targetCount,
+ int fromIndex) {
+ if (fromIndex >= sourceCount) {
+ return (targetCount == 0 ? sourceCount : -1);
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ if (targetCount == 0) {
+ return -1;
+ }
+ char first = target.charAt(targetOffset);
+ int max = sourceOffset + (sourceCount - targetCount);
+ for (int i = sourceOffset + fromIndex; i <= max; i++) {
+ if (source.charAt(i) != first) {
+ while (++i <= max && source.charAt(i) != first) {
+ }
+ }
+ if (i <= max) {
+ int j = i + 1;
+ int end = j + targetCount - 1;
+ int k = targetOffset + 1;
+ while (j < end && source.charAt(j) == target.charAt(k)) {
+ j++;
+ k++;
+ }
+ if (j == end) {
+ return i - sourceOffset;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public static List split(String str, String splitBy) {
+ int len = str.length();
+ int targetLen = splitBy.length();
+ if (len == 0 || targetLen == 0) {
+ return Collections.emptyList();
+ }
+ int pos = -targetLen;
+ List list = new ArrayList();
+ while (true) {
+ int start = pos + targetLen;
+ pos = indexOf(str, 0, len, splitBy, 0, targetLen, start);
+ if (pos == -1) {
+ if (start != len) {
+ list.add(subString(str, start, len));
+ }
+ break;
+ } else {
+ list.add(subString(str, start, pos));
+ }
+ }
+ return list;
+ }
+
+ public int hashCode() {
+ int h = hash;
+ int len = length;
+ if (h == 0 && len > 0) {
+ int off = offset;
+ String str = this.refStr;
+ for (int i = 0; i < len; i++) {
+ h = 31 * h + str.charAt(off++);
+ }
+ hash = h;
+ }
+ return h;
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof StringRef)) {
+ return false;
+ }
+ StringRef otherSlice = (StringRef) other;
+ int len = this.length;
+ if (len != otherSlice.length) {
+ return false;
+ }
+ int i = offset;
+ int j = otherSlice.offset;
+ String refStr = this.refStr;
+ String otherRefStr = otherSlice.refStr;
+ while (len-- != 0) {
+ if (refStr.charAt(i++) != otherRefStr.charAt(j++)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @NotNull
+ @Override
+ public String toString() {
+ int len = this.length;
+ if (len == 0) {
+ return "";
+ }
+ int offset = this.offset;
+ return refStr.substring(offset, offset + len);
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SuffixTree.java
similarity index 55%
rename from jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java
rename to jadx-gui/src/main/java/jadx/gui/utils/search/SuffixTree.java
index d5caf651b..8a04f8edd 100644
--- a/jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SuffixTree.java
@@ -1,9 +1,12 @@
-package jadx.gui.utils;
+package jadx.gui.utils.search;
+
+import java.util.ArrayList;
+import java.util.List;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
import com.googlecode.concurrenttrees.suffix.ConcurrentSuffixTree;
-public class SuffixTree {
+public class SuffixTree extends SearchIndex {
private final ConcurrentSuffixTree tree;
@@ -11,6 +14,7 @@ public class SuffixTree {
this.tree = new ConcurrentSuffixTree(new DefaultCharArrayNodeFactory());
}
+ @Override
public void put(String str, V value) {
if (str == null || str.isEmpty()) {
return;
@@ -18,10 +22,17 @@ public class SuffixTree {
tree.putIfAbsent(str, value);
}
- public Iterable getValuesForKeysContaining(String str) {
- return tree.getValuesForKeysContaining(str);
+ @Override
+ public List getValuesForKeysContaining(String str) {
+ Iterable resultsIt = tree.getValuesForKeysContaining(str);
+ List list = new ArrayList();
+ for (V v : resultsIt) {
+ list.add(v);
+ }
+ return list;
}
+ @Override
public int size() {
return tree.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
new file mode 100644
index 000000000..b4515282e
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java
@@ -0,0 +1,144 @@
+package jadx.gui.utils.search;
+
+import jadx.api.JavaClass;
+import jadx.api.JavaField;
+import jadx.api.JavaMethod;
+import jadx.api.JavaNode;
+import jadx.core.codegen.CodeWriter;
+import jadx.gui.treemodel.CodeNode;
+import jadx.gui.treemodel.JNode;
+import jadx.gui.ui.CommonSearchDialog;
+import jadx.gui.utils.CodeLinesInfo;
+import jadx.gui.utils.JNodeCache;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TextSearchIndex {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class);
+
+ private final JNodeCache nodeCache;
+ private final boolean useFastSearch;
+
+ private SearchIndex clsNamesIndex;
+ private SearchIndex mthNamesIndex;
+ private SearchIndex fldNamesIndex;
+ private SearchIndex codeIndex;
+
+ private List skippedClasses = new ArrayList();
+
+ public TextSearchIndex(JNodeCache nodeCache, boolean useFastSearch) {
+ this.nodeCache = nodeCache;
+ this.useFastSearch = useFastSearch;
+ this.clsNamesIndex = initIndex();
+ this.mthNamesIndex = initIndex();
+ this.fldNamesIndex = initIndex();
+ this.codeIndex = useFastSearch ? new SuffixTree() : new CodeIndex();
+ }
+
+ private SearchIndex initIndex() {
+ return useFastSearch ? new SuffixTree() : new SimpleIndex();
+ }
+
+ public void indexNames(JavaClass cls) {
+ clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls));
+ for (JavaMethod mth : cls.getMethods()) {
+ mthNamesIndex.put(mth.getFullName(), this.nodeCache.makeFrom(mth));
+ }
+ for (JavaField fld : cls.getFields()) {
+ fldNamesIndex.put(fld.getFullName(), nodeCache.makeFrom(fld));
+ }
+ for (JavaClass innerCls : cls.getInnerClasses()) {
+ indexNames(innerCls);
+ }
+ }
+
+ 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);
+ if (line.length() != 0 && line.charAt(0) != '}') {
+ 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);
+ }
+ }
+ }
+ } catch (Exception e) {
+ LOG.warn("Failed to index class: {}", cls, e);
+ }
+ }
+
+ public List searchClsName(String text) {
+ return clsNamesIndex.getValuesForKeysContaining(text);
+ }
+
+ public List searchMthName(String text) {
+ return mthNamesIndex.getValuesForKeysContaining(text);
+ }
+
+ public List searchFldName(String text) {
+ return fldNamesIndex.getValuesForKeysContaining(text);
+ }
+
+ public List searchCode(String text) {
+ List items;
+ if (codeIndex.size() > 0) {
+ items = codeIndex.getValuesForKeysContaining(text);
+ if (skippedClasses.isEmpty()) {
+ return items;
+ }
+ } else {
+ items = new ArrayList();
+ }
+ addSkippedClasses(items, text);
+ return items;
+ }
+
+ private void addSkippedClasses(List list, String text) {
+ for (JavaClass javaClass : skippedClasses) {
+ String code = javaClass.getCode();
+ int pos = 0;
+ while (pos != -1) {
+ pos = searchNext(list, text, javaClass, code, pos);
+ }
+ if (list.size() > CommonSearchDialog.MAX_RESULTS_COUNT) {
+ return;
+ }
+ }
+ }
+
+ private int searchNext(List list, String text, JavaNode javaClass, String code, int startPos) {
+ int pos = code.indexOf(text, startPos);
+ if (pos == -1) {
+ return -1;
+ }
+ int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
+ int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length());
+ StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd);
+ list.add(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()));
+ return lineEnd;
+ }
+
+ public void classCodeIndexSkipped(JavaClass cls) {
+ this.skippedClasses.add(cls);
+ }
+
+ public List getSkippedClasses() {
+ return skippedClasses;
+ }
+
+ public int getSkippedCount() {
+ return skippedClasses.size();
+ }
+}
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 a6905633a..dcb59db5a 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
@@ -62,6 +62,7 @@ preferences.cfg=Generate methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font
preferences.fast_search=Fast search (uses more memory)
+preferences.start_jobs=Auto start background decompilation
preferences.select_font=Select
preferences.deobfuscation_on=Enable deobfuscation
preferences.deobfuscation_force=Force rewrite deobfuscation map file
diff --git a/jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy b/jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy
new file mode 100644
index 000000000..352978ab8
--- /dev/null
+++ b/jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy
@@ -0,0 +1,70 @@
+package jadx.gui.tests
+import jadx.gui.utils.search.StringRef
+import spock.lang.Specification
+
+import static jadx.gui.utils.search.StringRef.fromStr
+import static jadx.gui.utils.search.StringRef.subString
+
+class TestStringRef extends Specification {
+
+ def "test substring"() {
+ expect:
+ s1.toString() == expected
+ s1 == fromStr(expected)
+ where:
+ s1 | expected
+ fromStr("a") | "a"
+ subString("a", 0) | "a"
+ subString("a", 1) | ""
+ subString("a", 0, 0) | ""
+ subString("a", 0, 1) | "a"
+ subString("abc", 1, 2) | "b"
+ subString("abc", 2) | "c"
+ subString("abc", 2, 3) | "c"
+ }
+
+ def "compare with original substring"() {
+ expect:
+ s == expected
+ where:
+ s | expected
+ "a".substring(0) | "a"
+ "a".substring(1) | ""
+ "a".substring(0, 0) | ""
+ "a".substring(0, 1) | "a"
+ }
+
+ def "test trim"() {
+ expect:
+ s.trim().toString() == expected
+ where:
+ s | expected
+ fromStr("a") | "a"
+ fromStr(" a ") | "a"
+ fromStr("\ta") | "a"
+ subString("a b c", 1) | "b c"
+ subString("a b\tc", 1, 4) | "b"
+ subString("a b\tc", 2, 3) | "b"
+ }
+
+ def "test split"() {
+ expect:
+ StringRef.split(s, d) == (expected as String[]).collect { fromStr(it) }
+ if (!Arrays.equals(s.split(d), (expected).toArray(new String[0]))) {
+ throw new IllegalArgumentException("Don't match with original split: "
+ + " s='" + s + "' d='" + d
+ + "', expected:" + expected + ", got: " + Arrays.toString(s.split(d)));
+ }
+ where:
+ s | d | expected
+ "abc" | "b" | ["a", "c"]
+ "abc" | "a" | ["", "bc"]
+ "abc" | "c" | ["ab"]
+ "abc" | "d" | ["abc"]
+ "abbbc" | "b" | ["a", "", "", "c"]
+ "abbbc" | "bb" | ["a", "bc"]
+ "abbbc" | "bbb" | ["a", "c"]
+ "abbbc" | "bbc" | ["ab"]
+ "abbbc" | "bbbc" | ["a"]
+ }
+}