feat(gui): limit search to a package (PR #2284)

* Add isDescendantOf and getJavaPackage helper functions

* Add i18n strings for search package

* Added search package to options in SearchSettings

* Add package limiting to each search provider

* Add package search to dialog and logic to get package by string.

* Added search option to package context menu

* Fix spotlessJavaCheck complaints

* Revert changes to individual search providers and add filter to base provider
This commit is contained in:
Andy Smith
2024-09-20 05:31:20 -04:00
committed by GitHub
parent 699ceb197e
commit efa2f5d172
17 changed files with 145 additions and 11 deletions
@@ -252,6 +252,10 @@ public final class JavaClass implements JavaNode {
return cls.getPackage();
}
public JavaPackage getJavaPackage() {
return cls.getPackageNode().getJavaNode();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
@@ -76,6 +76,22 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return !Objects.equals(parent, aliasParent);
}
public boolean isDescendantOf(JavaPackage ancestor) {
JavaPackage current = this;
while (current != null) {
if (ancestor.equals(current)) {
return true;
}
if (current.getPkgNode().getParentPkg() == null) {
current = null;
} else {
current = current.getPkgNode().getParentPkg().getJavaNode();
}
}
return false;
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return pkgNode;
@@ -4,6 +4,7 @@ import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;
import jadx.api.JavaPackage;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JResource;
@@ -12,16 +13,18 @@ public class SearchSettings {
private final String searchString;
private final boolean useRegex;
private final boolean ignoreCase;
private final JavaPackage searchPackage;
private JClass activeCls;
private JResource activeResource;
private Pattern regexPattern;
private ISearchMethod searchMethod;
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex) {
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex, JavaPackage searchPackage) {
this.searchString = searchString;
this.useRegex = useRegex;
this.ignoreCase = ignoreCase;
this.searchPackage = searchPackage;
}
@Nullable
@@ -50,6 +53,10 @@ public class SearchSettings {
return this.ignoreCase;
}
public JavaPackage getSearchPackage() {
return this.searchPackage;
}
public String getSearchString() {
return this.searchString;
}
@@ -2,6 +2,7 @@ package jadx.gui.search.providers;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
@@ -22,13 +23,22 @@ public abstract class BaseSearchProvider implements ISearchProvider {
protected final ISearchMethod searchMth;
protected final String searchStr;
protected final List<JavaClass> classes;
protected final SearchSettings searchSettings;
public BaseSearchProvider(MainWindow mw, SearchSettings searchSettings, List<JavaClass> classes) {
this.nodeCache = mw.getCacheObject().getNodeCache();
this.decompiler = mw.getWrapper().getDecompiler();
this.searchMth = searchSettings.getSearchMethod();
this.searchStr = searchSettings.getSearchString();
this.classes = classes;
if (searchSettings.getSearchPackage() != null) {
this.classes = classes
.stream()
.filter(c -> c.getJavaPackage().isDescendantOf(searchSettings.getSearchPackage()))
.collect(Collectors.toList());
} else {
this.classes = classes;
}
this.searchSettings = searchSettings;
}
protected boolean isMatch(String str) {
@@ -51,10 +51,7 @@ public class CommentSearchProvider implements ISearchProvider {
@Override
public @Nullable JNode next(Cancelable cancelable) {
while (true) {
if (cancelable.isCanceled()) {
return null;
}
while (!cancelable.isCanceled()) {
List<ICodeComment> comments = project.getCodeData().getComments();
if (progress >= comments.size()) {
return null;
@@ -65,6 +62,7 @@ public class CommentSearchProvider implements ISearchProvider {
return result;
}
}
return null;
}
@Nullable
@@ -73,6 +71,10 @@ public class CommentSearchProvider implements ISearchProvider {
if (all || searchSettings.isMatch(comment.getComment())) {
JNode refNode = getRefNode(comment);
if (refNode != null) {
if (searchSettings.getSearchPackage() != null
&& !refNode.getRootClass().getCls().getJavaPackage().isDescendantOf(searchSettings.getSearchPackage())) {
return null;
}
JClass activeCls = searchSettings.getActiveCls();
if (activeCls == null || Objects.equals(activeCls, refNode.getRootClass())) {
return getCommentNode(comment, refNode);
@@ -41,6 +41,7 @@ import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import jadx.api.JavaClass;
import jadx.api.JavaPackage;
import jadx.core.utils.ListUtils;
import jadx.gui.jobs.ITaskInfo;
import jadx.gui.jobs.ITaskProgress;
@@ -96,6 +97,12 @@ public class SearchDialog extends CommonSearchDialog {
show(searchDialog, window);
}
public static void searchPackage(MainWindow window, String packageName) {
SearchDialog searchDialog = new SearchDialog(window, SearchPreset.TEXT, Collections.emptySet());
searchDialog.initSearchPackage = packageName;
show(searchDialog, window);
}
private static void show(SearchDialog searchDialog, MainWindow mw) {
mw.addLoadListener(loaded -> {
if (!loaded) {
@@ -130,6 +137,7 @@ public class SearchDialog extends CommonSearchDialog {
private transient Color searchFieldDefaultBgColor;
private transient JTextField searchField;
private transient JTextField packageField;
private transient @Nullable SearchTask searchTask;
private transient JButton loadAllButton;
@@ -142,6 +150,7 @@ public class SearchDialog extends CommonSearchDialog {
private transient ChangeListener activeTabListener;
private transient String initSearchText = null;
private transient String initSearchPackage = null;
// temporal list for pending results
private final List<JNode> pendingResults = new ArrayList<>();
@@ -210,6 +219,10 @@ public class SearchDialog extends CommonSearchDialog {
searchField.setText(searchText);
searchField.selectAll();
}
String searchPackage = initSearchPackage != null ? initSearchPackage : cache.getLastSearchPackage();
if (searchPackage != null) {
packageField.setText(searchPackage);
}
searchField.requestFocus();
resultsTable.initColumnWidth();
@@ -278,10 +291,21 @@ public class SearchDialog extends CommonSearchDialog {
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), USE_REGEX));
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.active_tab"), SearchOptions.ACTIVE_TAB));
packageField = new JTextField();
packageField.setAlignmentX(LEFT_ALIGNMENT);
packageField.setPreferredSize(new Dimension(300, packageField.getPreferredSize().height));
TextStandardActions.attach(packageField);
packageField.putClientProperty(FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true);
JPanel searchPackageOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchPackageOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.limit_package")));
searchPackageOptions.add(packageField);
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT, 0, 0));
optionsPanel.setAlignmentX(LEFT_ALIGNMENT);
optionsPanel.add(searchInPanel);
optionsPanel.add(searchOptions);
optionsPanel.add(searchPackageOptions);
JPanel searchPane = new JPanel();
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
@@ -387,15 +411,22 @@ public class SearchDialog extends CommonSearchDialog {
searchEmitter = new SearchEventEmitter();
Flowable<String> searchEvents;
if (mainWindow.getSettings().isUseAutoSearch()) {
searchEvents = Flowable.merge(RxUtils.textFieldChanges(searchField),
RxUtils.textFieldEnterPress(searchField), searchEmitter.getFlowable());
searchEvents = Flowable.merge(List.of(
RxUtils.textFieldChanges(searchField),
RxUtils.textFieldEnterPress(searchField),
RxUtils.textFieldChanges(packageField),
RxUtils.textFieldEnterPress(packageField),
searchEmitter.getFlowable()));
} else {
searchEvents = Flowable.merge(RxUtils.textFieldEnterPress(searchField), searchEmitter.getFlowable());
searchEvents = Flowable.merge(
RxUtils.textFieldEnterPress(searchField),
RxUtils.textFieldEnterPress(packageField),
searchEmitter.getFlowable());
}
searchDisposable = searchEvents
.debounce(50, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.from(searchBackgroundExecutor))
.subscribe(this::search);
.subscribe(t -> this.search(searchField.getText()));
}
private void search(String text) {
@@ -427,7 +458,29 @@ public class SearchDialog extends CommonSearchDialog {
LOG.debug("Building search for '{}', options: {}", text, options);
boolean ignoreCase = options.contains(IGNORE_CASE);
boolean useRegex = options.contains(USE_REGEX);
SearchSettings searchSettings = new SearchSettings(text, ignoreCase, useRegex);
// Find the JavaPackage for the searched package string
String packageText = packageField.getText();
JavaPackage searchPackage = null;
if (!packageText.isBlank()) {
searchPackage = mainWindow
.getWrapper()
.getPackages()
.stream()
.filter(p -> p.getFullName().equals(packageText))
.findFirst()
.orElse(null);
if (searchPackage == null) {
resultsInfoLabel.setText(NLS.str("search_dialog.package_not_found"));
packageField.setBackground(SEARCH_FIELD_ERROR_COLOR);
return null;
}
}
if (Objects.equals(packageField.getBackground(), SEARCH_FIELD_ERROR_COLOR)) {
packageField.setBackground(searchFieldDefaultBgColor);
}
SearchSettings searchSettings = new SearchSettings(text, ignoreCase, useRegex, searchPackage);
String error = searchSettings.prepare();
if (error == null) {
if (Objects.equals(searchField.getBackground(), SEARCH_FIELD_ERROR_COLOR)) {
@@ -597,6 +650,7 @@ public class SearchDialog extends CommonSearchDialog {
String text = searchField.getText();
updateHighlightContext(text, !options.contains(IGNORE_CASE), options.contains(USE_REGEX), false);
cache.setLastSearch(text);
cache.setLastSearchPackage(packageField.getText());
cache.getLastSearchOptions().put(searchPreset, options);
if (!mainWindow.getSettings().isUseAutoSearch()) {
mainWindow.getProject().addToSearchHistory(text);
@@ -21,6 +21,7 @@ import jadx.gui.treemodel.JPackage;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.dialog.ExcludePkgDialog;
import jadx.gui.ui.dialog.RenameDialog;
import jadx.gui.ui.dialog.SearchDialog;
import jadx.gui.ui.filedialog.FileDialogWrapper;
import jadx.gui.ui.filedialog.FileOpenMode;
import jadx.gui.utils.NLS;
@@ -41,6 +42,7 @@ public class JPackagePopupMenu extends JPopupMenu {
add(makeExcludeItem());
add(makeRenameMenuItem(pkg));
add(makeExportSubMenu(pkg));
add(makeSearchItem(pkg));
}
private JMenuItem makeRenameMenuItem(JPackage pkg) {
@@ -132,4 +134,14 @@ public class JPackagePopupMenu extends JPopupMenu {
}
});
}
private JMenuItem makeSearchItem(JPackage pkg) {
JMenuItem searchItem = new JMenuItem(NLS.str("menu.text_search"));
searchItem.addActionListener(e -> {
String fullName = pkg.getPkg().getFullName();
LOG.debug("Searching package: {}", fullName);
SearchDialog.searchPackage(mainWindow, fullName);
});
return searchItem;
}
}
@@ -16,6 +16,7 @@ public class CacheObject {
private String lastSearch;
private JNodeCache jNodeCache;
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
private String lastSearchPackage;
private List<List<JavaClass>> decompileBatches;
private PackageHelper packageHelper;
@@ -30,6 +31,7 @@ public class CacheObject {
lastSearch = null;
jNodeCache = new JNodeCache();
lastSearchOptions = new HashMap<>();
lastSearchPackage = null;
decompileBatches = null;
packageHelper = null;
fullDecompilationFinished = false;
@@ -40,10 +42,19 @@ public class CacheObject {
return lastSearch;
}
@Nullable
public String getLastSearchPackage() {
return lastSearchPackage;
}
public void setLastSearch(String lastSearch) {
this.lastSearch = lastSearch;
}
public void setLastSearchPackage(String lastSearchPackage) {
this.lastSearchPackage = lastSearchPackage;
}
public JNodeCache getNodeCache() {
return jNodeCache;
}
@@ -152,6 +152,8 @@ search_dialog.comments=Kommentare
search_dialog.resource=Ressourcen
search_dialog.keep_open=Offen halten
search_dialog.tip_searching=Suchen…
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=Verwendungssuche
usage_dialog.label=Verwendungen von:
@@ -152,6 +152,8 @@ search_dialog.comments=Comments
search_dialog.resource=Resource
search_dialog.keep_open=Keep open
search_dialog.tip_searching=Searching
search_dialog.limit_package=Limit to package:
search_dialog.package_not_found=No matching package found
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
@@ -152,6 +152,8 @@ search_dialog.regex=Regex
#search_dialog.resource=
#search_dialog.keep_open=
#search_dialog.tip_searching=
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=Usage search
usage_dialog.label=Usage for:
@@ -152,6 +152,8 @@ search_dialog.comments=Komentar
search_dialog.resource=Sumber daya
search_dialog.keep_open=Tetap terbuka
search_dialog.tip_searching=Mencari
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=Pencarian penggunaan
usage_dialog.label=Penggunaan untuk:
@@ -152,6 +152,8 @@ search_dialog.comments=주석
search_dialog.resource=리소스
search_dialog.keep_open=열어 두기
search_dialog.tip_searching=검색 중...
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=사용 검색
usage_dialog.label=다음의 사용 검색 결과:
@@ -152,6 +152,8 @@ search_dialog.comments=Comentários
search_dialog.resource=Recursos
search_dialog.keep_open=Manter aberto
search_dialog.tip_searching=Buscando
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=Busca por utilização
usage_dialog.label=Usado por:
@@ -152,6 +152,8 @@ search_dialog.comments=Комментарии
search_dialog.resource=Ресурсы
search_dialog.keep_open=Оставлять поиск открытым
search_dialog.tip_searching=Поиск...
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=Поиск использований
usage_dialog.label=Использования:
@@ -152,6 +152,8 @@ search_dialog.comments=注释
search_dialog.resource=资源
search_dialog.keep_open=保持窗口
search_dialog.tip_searching=搜索中…
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=查找
usage_dialog.label=查找用例:
@@ -152,6 +152,8 @@ search_dialog.comments=註解
search_dialog.resource=資源
search_dialog.keep_open=保持開啟
search_dialog.tip_searching=正在搜尋
#search_dialog.limit_package=
#search_dialog.package_not_found=
usage_dialog.title=使用情況搜尋
usage_dialog.label=使用情況: