提交 7216635d 编写于 作者: S Skylot

gui: run text search in background thread (#269)

上级 98ef7c39
...@@ -14,6 +14,11 @@ dependencies { ...@@ -14,6 +14,11 @@ dependencies {
compile 'com.google.code.gson:gson:2.8.2' compile 'com.google.code.gson:gson:2.8.2'
compile files('libs/jfontchooser-1.0.5.jar') compile files('libs/jfontchooser-1.0.5.jar')
compile 'hu.kazocsaba:image-viewer:1.2.3' compile 'hu.kazocsaba:image-viewer:1.2.3'
compile 'org.apache.commons:commons-lang3:3.7'
compile 'io.reactivex.rxjava2:rxjava:2.1.13'
compile "com.github.akarnokd:rxjava2-swing:0.2.12"
} }
applicationDistribution.with { applicationDistribution.with {
...@@ -35,7 +40,7 @@ jar { ...@@ -35,7 +40,7 @@ jar {
} }
startScripts { startScripts {
defaultJvmOpts = [ '-Xms128M', '-Xmx4g' ] defaultJvmOpts = ['-Xms128M', '-Xmx4g']
doLast { doLast {
def str = windowsScript.text def str = windowsScript.text
str = str.replaceAll('java.exe', 'javaw.exe') str = str.replaceAll('java.exe', 'javaw.exe')
......
...@@ -12,12 +12,12 @@ public class CodeNode extends JNode { ...@@ -12,12 +12,12 @@ public class CodeNode extends JNode {
private final transient JNode jNode; private final transient JNode jNode;
private final transient JClass jParent; private final transient JClass jParent;
private final transient StringRef line; private final transient StringRef line;
private final int lineNum; private final transient int lineNum;
public CodeNode(JNode jNode, int lineNum, StringRef line) { public CodeNode(JNode jNode, int lineNum, StringRef lineStr) {
this.jNode = jNode; this.jNode = jNode;
this.jParent = this.jNode.getJParent(); this.jParent = this.jNode.getJParent();
this.line = line; this.line = lineStr;
this.lineNum = lineNum; this.lineNum = lineNum;
} }
......
...@@ -215,8 +215,12 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -215,8 +215,12 @@ public abstract class CommonSearchDialog extends JDialog {
} }
protected void updateProgressLabel() { protected void updateProgressLabel() {
String statusText = String.format(NLS.str("search_dialog.info_label"), resultsModel.getDisplayedResultsStart(), String statusText = String.format(
resultsModel.getDisplayedResultsEnd(), resultsModel.getResultCount()); NLS.str("search_dialog.info_label"),
resultsModel.getDisplayedResultsStart(),
resultsModel.getDisplayedResultsEnd(),
resultsModel.getResultCount()
);
resultsInfoLabel.setText(statusText); resultsInfoLabel.setText(statusText);
} }
...@@ -283,16 +287,15 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -283,16 +287,15 @@ public abstract class CommonSearchDialog extends JDialog {
protected void addAll(Collection<? extends JNode> nodes) { protected void addAll(Collection<? extends JNode> nodes) {
rows.ensureCapacity(rows.size() + nodes.size()); rows.ensureCapacity(rows.size() + nodes.size());
for (JNode node : nodes) { rows.addAll(nodes);
add(node); if (!addDescColumn) {
} for (JNode row : rows) {
} if (row.hasDescString()) {
addDescColumn = true;
private void add(JNode node) { break;
if (node.hasDescString()) { }
addDescColumn = true; }
} }
rows.add(node);
} }
public void clear() { public void clear() {
...@@ -339,7 +342,10 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -339,7 +342,10 @@ public abstract class CommonSearchDialog extends JDialog {
@Override @Override
public int getRowCount() { public int getRowCount() {
return rows.size() - start; if (rows.isEmpty()) {
return 0;
}
return getDisplayedResultsEnd() - getDisplayedResultsStart();
} }
@Override @Override
......
...@@ -24,7 +24,6 @@ import java.awt.event.MouseAdapter; ...@@ -24,7 +24,6 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
...@@ -43,7 +42,6 @@ import jadx.gui.treemodel.JLoadableNode; ...@@ -43,7 +42,6 @@ import jadx.gui.treemodel.JLoadableNode;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource; import jadx.gui.treemodel.JResource;
import jadx.gui.treemodel.JRoot; import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.SearchDialog.SearchOptions;
import jadx.gui.update.JadxUpdate; import jadx.gui.update.JadxUpdate;
import jadx.gui.update.JadxUpdate.IUpdateCallback; import jadx.gui.update.JadxUpdate.IUpdateCallback;
import jadx.gui.update.data.Release; import jadx.gui.update.data.Release;
...@@ -385,7 +383,7 @@ public class MainWindow extends JFrame { ...@@ -385,7 +383,7 @@ public class MainWindow extends JFrame {
Action textSearchAction = new AbstractAction(NLS.str("menu.text_search"), ICON_SEARCH) { Action textSearchAction = new AbstractAction(NLS.str("menu.text_search"), ICON_SEARCH) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
new SearchDialog(MainWindow.this, EnumSet.of(SearchOptions.CODE)).setVisible(true); new SearchDialog(MainWindow.this, true).setVisible(true);
} }
}; };
textSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.text_search")); textSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.text_search"));
...@@ -395,7 +393,7 @@ public class MainWindow extends JFrame { ...@@ -395,7 +393,7 @@ public class MainWindow extends JFrame {
Action clsSearchAction = new AbstractAction(NLS.str("menu.class_search"), ICON_FIND) { Action clsSearchAction = new AbstractAction(NLS.str("menu.class_search"), ICON_FIND) {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
new SearchDialog(MainWindow.this, EnumSet.of(SearchOptions.CLASS)).setVisible(true); new SearchDialog(MainWindow.this, false).setVisible(true);
} }
}; };
clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search")); clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search"));
......
...@@ -4,35 +4,60 @@ import javax.swing.*; ...@@ -4,35 +4,60 @@ import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter; import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import hu.akarnokd.rxjava2.swing.SwingSchedulers;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Emitter;
import io.reactivex.Flowable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions; import jadx.gui.utils.TextStandardActions;
import jadx.gui.utils.search.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
public class SearchDialog extends CommonSearchDialog { public class SearchDialog extends CommonSearchDialog {
private static final Logger LOG = LoggerFactory.getLogger(SearchDialog.class);
private static final long serialVersionUID = -5105405456969134105L; private static final long serialVersionUID = -5105405456969134105L;
private final boolean textSearch;
enum SearchOptions { public enum SearchOptions {
CLASS, CLASS,
METHOD, METHOD,
FIELD, FIELD,
CODE CODE,
IGNORE_CASE
} }
private Set<SearchOptions> options; private transient Set<SearchOptions> options;
private transient JTextField searchField;
private JTextField searchField; private transient Disposable searchDisposable;
private JCheckBox caseChBox; private transient SearchEventEmitter searchEmitter;
public SearchDialog(MainWindow mainWindow, Set<SearchOptions> options) { public SearchDialog(MainWindow mainWindow, boolean textSearch) {
super(mainWindow); super(mainWindow);
this.options = options; this.textSearch = textSearch;
if (textSearch) {
Set<SearchOptions> lastSearchOptions = cache.getLastSearchOptions();
if (!lastSearchOptions.isEmpty()) {
this.options = lastSearchOptions;
} else {
this.options = EnumSet.of(SearchOptions.CODE, SearchOptions.IGNORE_CASE);
}
} else {
this.options = EnumSet.of(SearchOptions.CLASS);
}
initUI(); initUI();
registerInitOnOpen(); registerInitOnOpen();
...@@ -46,83 +71,19 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -46,83 +71,19 @@ public class SearchDialog extends CommonSearchDialog {
if (lastSearch != null) { if (lastSearch != null) {
searchField.setText(lastSearch); searchField.setText(lastSearch);
searchField.selectAll(); searchField.selectAll();
searchEmitter.emitSearch();
} }
searchField.requestFocus(); searchField.requestFocus();
} }
@Override
protected synchronized void performSearch() {
resultsModel.clear();
String text = searchField.getText();
if (text == null || text.isEmpty() || options.isEmpty()) {
return;
}
try {
cache.setLastSearch(text);
TextSearchIndex index = cache.getTextIndex();
if (index == null) {
return;
}
boolean caseInsensitive = caseChBox.isSelected();
if (options.contains(SearchOptions.CLASS)) {
resultsModel.addAll(index.searchClsName(text, caseInsensitive));
}
if (options.contains(SearchOptions.METHOD)) {
resultsModel.addAll(index.searchMthName(text, caseInsensitive));
}
if (options.contains(SearchOptions.FIELD)) {
resultsModel.addAll(index.searchFldName(text, caseInsensitive));
}
if (options.contains(SearchOptions.CODE)) {
resultsModel.addAll(index.searchCode(text, caseInsensitive));
}
highlightText = text;
highlightTextCaseInsensitive = caseInsensitive;
} finally {
super.performSearch();
}
}
private class SearchFieldListener implements DocumentListener, ActionListener {
private Timer timer;
private synchronized void change() {
if (timer != null) {
timer.restart();
} else {
timer = new Timer(400, this);
timer.setRepeats(false);
timer.start();
}
}
@Override
public void actionPerformed(ActionEvent e) {
performSearch();
}
public void changedUpdate(DocumentEvent e) {
change();
}
public void removeUpdate(DocumentEvent e) {
change();
}
public void insertUpdate(DocumentEvent e) {
change();
}
}
private void initUI() { private void initUI() {
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name")); JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
searchField = new JTextField(); searchField = new JTextField();
searchField.setAlignmentX(LEFT_ALIGNMENT); searchField.setAlignmentX(LEFT_ALIGNMENT);
searchField.getDocument().addDocumentListener(new SearchFieldListener());
new TextStandardActions(searchField); new TextStandardActions(searchField);
searchFieldSubscribe();
caseChBox = new JCheckBox(NLS.str("search_dialog.ignorecase")); JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE);
caseChBox.addItemListener(e -> performSearch());
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS); JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD); JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
...@@ -184,6 +145,122 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -184,6 +145,122 @@ public class SearchDialog extends CommonSearchDialog {
setModalityType(ModalityType.MODELESS); setModalityType(ModalityType.MODELESS);
} }
private class SearchEventEmitter {
private final Flowable<String> flowable;
private Emitter<String> emitter;
public SearchEventEmitter() {
flowable = Flowable.create(this::saveEmitter, BackpressureStrategy.LATEST);
}
public Flowable<String> getFlowable() {
return flowable;
}
private void saveEmitter(Emitter<String> emitter) {
this.emitter = emitter;
}
public synchronized void emitSearch() {
this.emitter.onNext(searchField.getText());
}
}
private void searchFieldSubscribe() {
searchEmitter = new SearchEventEmitter();
Flowable<String> textChanges = onTextFieldChanges(searchField);
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
searchDisposable = searchEvents
.filter(text -> text.length() > 0)
.subscribeOn(Schedulers.single())
.doOnNext(r -> LOG.debug("search event: {}", r))
.switchMap(text -> prepareSearch(text)
.subscribeOn(Schedulers.single())
.toList()
.toFlowable(), 1)
.observeOn(SwingSchedulers.edt())
.subscribe(this::processSearchResults);
}
private Flowable<JNode> prepareSearch(String text) {
if (text == null || text.isEmpty() || options.isEmpty()) {
return Flowable.empty();
}
TextSearchIndex index = cache.getTextIndex();
if (index == null) {
return Flowable.empty();
}
return index.buildSearch(text, options);
}
private void processSearchResults(java.util.List<JNode> results) {
LOG.debug("search result size: {}", results.size());
String text = searchField.getText();
highlightText = text;
highlightTextCaseInsensitive = options.contains(SearchOptions.IGNORE_CASE);
cache.setLastSearch(text);
if (textSearch) {
cache.setLastSearchOptions(options);
}
resultsModel.clear();
resultsModel.addAll(results);
super.performSearch();
}
private static Flowable<String> onTextFieldChanges(final JTextField textField) {
return Flowable.<String>create(emitter -> {
DocumentListener listener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
change();
}
@Override
public void removeUpdate(DocumentEvent e) {
change();
}
@Override
public void changedUpdate(DocumentEvent e) {
change();
}
public void change() {
emitter.onNext(textField.getText());
}
};
textField.getDocument().addDocumentListener(listener);
emitter.setDisposable(new Disposable() {
private boolean disposed = false;
@Override
public void dispose() {
textField.getDocument().removeDocumentListener(listener);
disposed = true;
}
@Override
public boolean isDisposed() {
return disposed;
}
});
}, BackpressureStrategy.LATEST)
.debounce(300, TimeUnit.MILLISECONDS)
.distinctUntilChanged();
}
@Override
public void dispose() {
if (searchDisposable != null && !searchDisposable.isDisposed()) {
searchDisposable.dispose();
}
super.dispose();
}
private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) { private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
final JCheckBox chBox = new JCheckBox(name); final JCheckBox chBox = new JCheckBox(name);
chBox.setAlignmentX(LEFT_ALIGNMENT); chBox.setAlignmentX(LEFT_ALIGNMENT);
...@@ -194,7 +271,7 @@ public class SearchDialog extends CommonSearchDialog { ...@@ -194,7 +271,7 @@ public class SearchDialog extends CommonSearchDialog {
} else { } else {
options.remove(opt); options.remove(opt);
} }
performSearch(); searchEmitter.emitSearch();
}); });
return chBox; return chBox;
} }
......
package jadx.gui.utils; package jadx.gui.utils;
import java.util.EnumSet;
import java.util.Set;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob; import jadx.gui.jobs.IndexJob;
import jadx.gui.ui.SearchDialog;
import jadx.gui.utils.search.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
public class CacheObject { public class CacheObject {
...@@ -14,13 +18,21 @@ public class CacheObject { ...@@ -14,13 +18,21 @@ public class CacheObject {
private TextSearchIndex textIndex; private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo; private CodeUsageInfo usageInfo;
private String lastSearch; private String lastSearch;
private JNodeCache jNodeCache = new JNodeCache(); private JNodeCache jNodeCache;
private Set<SearchDialog.SearchOptions> lastSearchOptions;
public CacheObject() {
reset();
}
public void reset() { public void reset() {
decompileJob = null;
indexJob = null;
textIndex = null; textIndex = null;
lastSearch = null; lastSearch = null;
jNodeCache = new JNodeCache(); jNodeCache = new JNodeCache();
usageInfo = null; usageInfo = null;
lastSearchOptions = EnumSet.noneOf(SearchDialog.SearchOptions.class);
} }
public DecompileJob getDecompileJob() { public DecompileJob getDecompileJob() {
...@@ -69,4 +81,12 @@ public class CacheObject { ...@@ -69,4 +81,12 @@ public class CacheObject {
public JNodeCache getNodeCache() { public JNodeCache getNodeCache() {
return jNodeCache; return jNodeCache;
} }
public void setLastSearchOptions(Set<SearchDialog.SearchOptions> lastSearchOptions) {
this.lastSearchOptions = lastSearchOptions;
}
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
return lastSearchOptions;
}
} }
package jadx.gui.utils.search; package jadx.gui.utils.search;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.utils.Utils;
public class CodeIndex<T> implements SearchIndex<T> { public class CodeIndex<T> implements SearchIndex<T> {
private static final Logger LOG = LoggerFactory.getLogger(CodeIndex.class);
private final List<StringRef> keys = new ArrayList<>(); private final List<StringRef> keys = new ArrayList<>();
private final List<T> values = new ArrayList<>(); private final List<T> values = new ArrayList<>();
...@@ -28,23 +36,27 @@ public class CodeIndex<T> implements SearchIndex<T> { ...@@ -28,23 +36,27 @@ public class CodeIndex<T> implements SearchIndex<T> {
return true; return true;
} }
private boolean isMatched(StringRef key, String str, boolean caseInsensitive) {
return key.indexOf(str, caseInsensitive) != -1;
}
@Override @Override
public List<T> getValuesForKeysContaining(String str, boolean caseInsensitive) { public Flowable<T> search(final String searchStr, final boolean caseInsensitive) {
int size = size(); return Flowable.create(emitter -> {
if (size == 0) { int size = size();
return Collections.emptyList(); LOG.debug("Code search started: {} ...", searchStr);
} for (int i = 0; i < size; i++) {
if (caseInsensitive) { if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
str = str.toLowerCase(); emitter.onNext(values.get(i));
} }
List<T> results = new ArrayList<>(); if (emitter.isCancelled()) {
for (int i = 0; i < size; i++) { LOG.debug("Code search canceled: {}", searchStr);
StringRef key = keys.get(i); return;
if (key.indexOf(str, caseInsensitive) != -1) { }
results.add(values.get(i));
} }
} LOG.debug("Code search complete: {}, memory usage: {}", searchStr, Utils.memoryInfo());
return results; emitter.onComplete();
}, BackpressureStrategy.LATEST);
} }
@Override @Override
......
package jadx.gui.utils.search; package jadx.gui.utils.search;
import java.util.List; import io.reactivex.Flowable;
public interface SearchIndex<V> { public interface SearchIndex<V> {
...@@ -10,7 +10,7 @@ public interface SearchIndex<V> { ...@@ -10,7 +10,7 @@ public interface SearchIndex<V> {
boolean isStringRefSupported(); boolean isStringRefSupported();
List<V> getValuesForKeysContaining(String str, boolean caseInsensitive); Flowable<V> search(String searchStr, boolean caseInsensitive);
int size(); int size();
} }
package jadx.gui.utils.search; package jadx.gui.utils.search;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import org.apache.commons.lang3.StringUtils;
public class SimpleIndex<T> implements SearchIndex<T> { public class SimpleIndex<T> implements SearchIndex<T> {
private final List<String> keys = new ArrayList<>(); private final List<String> keys = new ArrayList<>();
...@@ -25,26 +28,28 @@ public class SimpleIndex<T> implements SearchIndex<T> { ...@@ -25,26 +28,28 @@ public class SimpleIndex<T> implements SearchIndex<T> {
return false; return false;
} }
@Override private boolean isMatched(String str, String searchStr, boolean caseInsensitive) {
public List<T> getValuesForKeysContaining(String str, boolean caseInsensitive) {
int size = size();
if (size == 0) {
return Collections.emptyList();
}
if (caseInsensitive) { if (caseInsensitive) {
str = str.toLowerCase(); return StringUtils.containsIgnoreCase(str, searchStr);
} else {
return str.contains(searchStr);
} }
List<T> results = new ArrayList<>(); }
for (int i = 0; i < size; i++) {
String key = keys.get(i); @Override
if (caseInsensitive) { public Flowable<T> search(final String searchStr, final boolean caseInsensitive) {
key = key.toLowerCase(); return Flowable.create(emitter -> {
} int size = size();
if (key.contains(str)) { for (int i = 0; i < size; i++) {
results.add(values.get(i)); if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
emitter.onNext(values.get(i));
}
if (emitter.isCancelled()) {
return;
}
} }
} emitter.onComplete();
return results; }, BackpressureStrategy.LATEST);
} }
@Override @Override
......
...@@ -2,7 +2,12 @@ package jadx.gui.utils.search; ...@@ -2,7 +2,12 @@ package jadx.gui.utils.search;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -13,9 +18,16 @@ import jadx.api.JavaNode; ...@@ -13,9 +18,16 @@ import jadx.api.JavaNode;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
import jadx.gui.treemodel.CodeNode; import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.ui.CommonSearchDialog; import jadx.gui.ui.SearchDialog;
import jadx.gui.utils.CodeLinesInfo; import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.JNodeCache; import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.Utils;
import static jadx.gui.ui.SearchDialog.SearchOptions.CLASS;
import static jadx.gui.ui.SearchDialog.SearchOptions.CODE;
import static jadx.gui.ui.SearchDialog.SearchOptions.FIELD;
import static jadx.gui.ui.SearchDialog.SearchOptions.IGNORE_CASE;
import static jadx.gui.ui.SearchDialog.SearchOptions.METHOD;
public class TextSearchIndex { public class TextSearchIndex {
...@@ -41,7 +53,7 @@ public class TextSearchIndex { ...@@ -41,7 +53,7 @@ public class TextSearchIndex {
public void indexNames(JavaClass cls) { public void indexNames(JavaClass cls) {
clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls)); clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls));
for (JavaMethod mth : cls.getMethods()) { for (JavaMethod mth : cls.getMethods()) {
mthNamesIndex.put(mth.getFullName(), this.nodeCache.makeFrom(mth)); mthNamesIndex.put(mth.getFullName(), nodeCache.makeFrom(mth));
} }
for (JavaField fld : cls.getFields()) { for (JavaField fld : cls.getFields()) {
fldNamesIndex.put(fld.getFullName(), nodeCache.makeFrom(fld)); fldNamesIndex.put(fld.getFullName(), nodeCache.makeFrom(fld));
...@@ -73,54 +85,70 @@ public class TextSearchIndex { ...@@ -73,54 +85,70 @@ public class TextSearchIndex {
} }
} }
public List<JNode> searchClsName(String text, boolean caseInsensitive) { public Flowable<JNode> buildSearch(String text, Set<SearchDialog.SearchOptions> options) {
return clsNamesIndex.getValuesForKeysContaining(text, caseInsensitive); boolean ignoreCase = options.contains(IGNORE_CASE);
} LOG.debug("Building search, ignoreCase: {}", ignoreCase);
public List<JNode> searchMthName(String text, boolean caseInsensitive) {
return mthNamesIndex.getValuesForKeysContaining(text, caseInsensitive);
}
public List<JNode> searchFldName(String text, boolean caseInsensitive) {
return fldNamesIndex.getValuesForKeysContaining(text, caseInsensitive);
}
public List<CodeNode> searchCode(String text, boolean caseInsensitive) { Flowable<JNode> result = Flowable.empty();
List<CodeNode> items; if (options.contains(CLASS)) {
if (codeIndex.size() > 0) { result = Flowable.concat(result, clsNamesIndex.search(text, ignoreCase));
items = codeIndex.getValuesForKeysContaining(text, caseInsensitive); }
if (skippedClasses.isEmpty()) { if (options.contains(METHOD)) {
return items; result = Flowable.concat(result, mthNamesIndex.search(text, ignoreCase));
}
if (options.contains(FIELD)) {
result = Flowable.concat(result, fldNamesIndex.search(text, ignoreCase));
}
if (options.contains(CODE)) {
if (codeIndex.size() > 0) {
result = Flowable.concat(result, codeIndex.search(text, ignoreCase));
}
if (!skippedClasses.isEmpty()) {
result = Flowable.concat(result, searchInSkippedClasses(text, ignoreCase));
} }
} else {
items = new ArrayList<>();
} }
addSkippedClasses(items, text); return result;
return items;
} }
private void addSkippedClasses(List<CodeNode> list, String text) { public Flowable<CodeNode> searchInSkippedClasses(final String searchStr, final boolean caseInsensitive) {
for (JavaClass javaClass : skippedClasses) { return Flowable.create(emitter -> {
String code = javaClass.getCode(); LOG.debug("Skipped code search started: {} ...", searchStr);
int pos = 0; for (JavaClass javaClass : skippedClasses) {
while (pos != -1) { String code = javaClass.getCode();
pos = searchNext(list, text, javaClass, code, pos); int pos = 0;
} while (pos != -1) {
if (list.size() > CommonSearchDialog.RESULTS_PER_PAGE) { pos = searchNext(emitter, searchStr, javaClass, code, pos, caseInsensitive);
return; if (emitter.isCancelled()) {
LOG.debug("Skipped Code search canceled: {}", searchStr);
return;
}
}
if (!Utils.isFreeMemoryAvailable()) {
LOG.warn("Skipped code search stopped due to memory limit: {}", Utils.memoryInfo());
emitter.onComplete();
return;
}
} }
} LOG.debug("Skipped code search complete: {}, memory usage: {}", searchStr, Utils.memoryInfo());
emitter.onComplete();
}, BackpressureStrategy.LATEST);
} }
private int searchNext(List<CodeNode> list, String text, JavaNode javaClass, String code, int startPos) { private int searchNext(FlowableEmitter<CodeNode> emitter, String text, JavaNode javaClass, String code,
int pos = code.indexOf(text, startPos); int startPos, boolean ignoreCase) {
int pos;
if (ignoreCase) {
pos = StringUtils.indexOfIgnoreCase(code, text, startPos);
} else {
pos = code.indexOf(text, startPos);
}
if (pos == -1) { if (pos == -1) {
return -1; return -1;
} }
int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos); int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length()); int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length());
StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd); StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd);
list.add(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim())); emitter.onNext(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()));
return lineEnd; return lineEnd;
} }
...@@ -128,10 +156,6 @@ public class TextSearchIndex { ...@@ -128,10 +156,6 @@ public class TextSearchIndex {
this.skippedClasses.add(cls); this.skippedClasses.add(cls);
} }
public List<JavaClass> getSkippedClasses() {
return skippedClasses;
}
public int getSkippedCount() { public int getSkippedCount() {
return skippedClasses.size(); return skippedClasses.size();
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册