gui: improve memory usage (#79)
- don't use suffix tree in search - decrease default working threads count (only 1 for background jobs) - use string refs for store only one code string without duplicates - use cache for creating UI nodes - allow to disable autostart for background jobs (decompilation and index)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<StringRef> 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<StringRef> splitLines(JavaClass cls) {
|
||||
List<StringRef> 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;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
private List<String> recentFiles = new ArrayList<String>();
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JNode, UsageInfo> usageMap = new HashMap<JNode, UsageInfo>();
|
||||
|
||||
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, String[] lines) {
|
||||
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List<StringRef> lines) {
|
||||
Map<CodePosition, JavaNode> usage = javaClass.getUsageMap();
|
||||
for (Map.Entry<CodePosition, JavaNode> 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<StringRef> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<JavaNode, JNode> cache = new HashMap<JavaNode, JNode>();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -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<JNode> clsNamesTree;
|
||||
private SuffixTree<JNode> mthNamesTree;
|
||||
private SuffixTree<JNode> fldNamesTree;
|
||||
private SuffixTree<CodeNode> codeTree;
|
||||
|
||||
private List<JavaClass> skippedClasses = new ArrayList<JavaClass>();
|
||||
|
||||
public TextSearchIndex() {
|
||||
clsNamesTree = new SuffixTree<JNode>();
|
||||
mthNamesTree = new SuffixTree<JNode>();
|
||||
fldNamesTree = new SuffixTree<JNode>();
|
||||
codeTree = new SuffixTree<CodeNode>();
|
||||
}
|
||||
|
||||
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<JNode> searchClsName(String text) {
|
||||
return clsNamesTree.getValuesForKeysContaining(text);
|
||||
}
|
||||
|
||||
public Iterable<JNode> searchMthName(String text) {
|
||||
return mthNamesTree.getValuesForKeysContaining(text);
|
||||
}
|
||||
|
||||
public Iterable<JNode> searchFldName(String text) {
|
||||
return fldNamesTree.getValuesForKeysContaining(text);
|
||||
}
|
||||
|
||||
public Iterable<CodeNode> searchCode(String text) {
|
||||
Iterable<CodeNode> items;
|
||||
if (codeTree.size() > 0) {
|
||||
items = codeTree.getValuesForKeysContaining(text);
|
||||
if (skippedClasses.isEmpty()) {
|
||||
return items;
|
||||
}
|
||||
} else {
|
||||
items = null;
|
||||
}
|
||||
List<CodeNode> list = new ArrayList<CodeNode>();
|
||||
if (items != null) {
|
||||
for (CodeNode item : items) {
|
||||
list.add(item);
|
||||
}
|
||||
}
|
||||
addSkippedClasses(list, text);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void addSkippedClasses(List<CodeNode> 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<CodeNode> 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<JavaClass> getSkippedClasses() {
|
||||
return skippedClasses;
|
||||
}
|
||||
|
||||
public int getSkippedCount() {
|
||||
return skippedClasses.size();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package jadx.gui.utils.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class CodeIndex<T> extends SearchIndex<T> {
|
||||
|
||||
private final List<StringRef> keys = new ArrayList<StringRef>();
|
||||
private final List<T> values = new ArrayList<T>();
|
||||
|
||||
@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<T> getValuesForKeysContaining(String str) {
|
||||
int size = size();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<T> results = new ArrayList<T>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package jadx.gui.utils.search;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SearchIndex<V> {
|
||||
|
||||
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<V> getValuesForKeysContaining(String str);
|
||||
|
||||
public abstract int size();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.gui.utils.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SimpleIndex<T> extends SearchIndex<T> {
|
||||
|
||||
private final List<String> keys = new ArrayList<String>();
|
||||
private final List<T> values = new ArrayList<T>();
|
||||
|
||||
@Override
|
||||
public void put(String str, T value) {
|
||||
keys.add(str);
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getValuesForKeysContaining(String str) {
|
||||
int size = size();
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<T> results = new ArrayList<T>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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<StringRef> 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<StringRef> list = new ArrayList<StringRef>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
+15
-4
@@ -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<V> {
|
||||
public class SuffixTree<V> extends SearchIndex<V> {
|
||||
|
||||
private final ConcurrentSuffixTree<V> tree;
|
||||
|
||||
@@ -11,6 +14,7 @@ public class SuffixTree<V> {
|
||||
this.tree = new ConcurrentSuffixTree<V>(new DefaultCharArrayNodeFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String str, V value) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return;
|
||||
@@ -18,10 +22,17 @@ public class SuffixTree<V> {
|
||||
tree.putIfAbsent(str, value);
|
||||
}
|
||||
|
||||
public Iterable<V> getValuesForKeysContaining(String str) {
|
||||
return tree.getValuesForKeysContaining(str);
|
||||
@Override
|
||||
public List<V> getValuesForKeysContaining(String str) {
|
||||
Iterable<V> resultsIt = tree.getValuesForKeysContaining(str);
|
||||
List<V> list = new ArrayList<V>();
|
||||
for (V v : resultsIt) {
|
||||
list.add(v);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return tree.size();
|
||||
}
|
||||
@@ -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<JNode> clsNamesIndex;
|
||||
private SearchIndex<JNode> mthNamesIndex;
|
||||
private SearchIndex<JNode> fldNamesIndex;
|
||||
private SearchIndex<CodeNode> codeIndex;
|
||||
|
||||
private List<JavaClass> skippedClasses = new ArrayList<JavaClass>();
|
||||
|
||||
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<CodeNode>() : new CodeIndex<CodeNode>();
|
||||
}
|
||||
|
||||
private <T> SearchIndex<T> initIndex() {
|
||||
return useFastSearch ? new SuffixTree<T>() : new SimpleIndex<T>();
|
||||
}
|
||||
|
||||
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<StringRef> 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<JNode> searchClsName(String text) {
|
||||
return clsNamesIndex.getValuesForKeysContaining(text);
|
||||
}
|
||||
|
||||
public List<JNode> searchMthName(String text) {
|
||||
return mthNamesIndex.getValuesForKeysContaining(text);
|
||||
}
|
||||
|
||||
public List<JNode> searchFldName(String text) {
|
||||
return fldNamesIndex.getValuesForKeysContaining(text);
|
||||
}
|
||||
|
||||
public List<CodeNode> searchCode(String text) {
|
||||
List<CodeNode> items;
|
||||
if (codeIndex.size() > 0) {
|
||||
items = codeIndex.getValuesForKeysContaining(text);
|
||||
if (skippedClasses.isEmpty()) {
|
||||
return items;
|
||||
}
|
||||
} else {
|
||||
items = new ArrayList<CodeNode>();
|
||||
}
|
||||
addSkippedClasses(items, text);
|
||||
return items;
|
||||
}
|
||||
|
||||
private void addSkippedClasses(List<CodeNode> 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<CodeNode> 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<JavaClass> getSkippedClasses() {
|
||||
return skippedClasses;
|
||||
}
|
||||
|
||||
public int getSkippedCount() {
|
||||
return skippedClasses.size();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user