Co-authored-by: tobias <tobias.hotmail.com>
This commit is contained in:
@@ -256,6 +256,56 @@ public class StringUtils {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns how many lines does it have between start to pos in content.
|
||||
*/
|
||||
public static int countLinesByPos(String content, int pos, int start) {
|
||||
if (start >= pos) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
int tempPos = start;
|
||||
do {
|
||||
tempPos = content.indexOf("\n", tempPos);
|
||||
if (tempPos == -1) {
|
||||
break;
|
||||
}
|
||||
if (tempPos >= pos) {
|
||||
break;
|
||||
}
|
||||
count += 1;
|
||||
tempPos += 1;
|
||||
} while (tempPos < content.length());
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns lines that contain pos to end if end is not -1.
|
||||
*/
|
||||
public static String getLine(String content, int pos, int end) {
|
||||
if (pos >= content.length()) {
|
||||
return "";
|
||||
}
|
||||
if (end != -1) {
|
||||
if (end > content.length()) {
|
||||
end = content.length() - 1;
|
||||
}
|
||||
} else {
|
||||
end = pos + 1;
|
||||
}
|
||||
// get to line head
|
||||
int headPos = content.lastIndexOf("\n", pos);
|
||||
if (headPos == -1) {
|
||||
headPos = 0;
|
||||
}
|
||||
// get to line end
|
||||
int endPos = content.indexOf("\n", end);
|
||||
if (endPos == -1) {
|
||||
endPos = content.length();
|
||||
}
|
||||
return content.substring(headPos, endPos);
|
||||
}
|
||||
|
||||
public static boolean isWhite(char chr) {
|
||||
return WHITES.indexOf(chr) != -1;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.CodeLinesInfo;
|
||||
import jadx.gui.utils.CodeUsageInfo;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.search.StringRef;
|
||||
@@ -30,12 +29,12 @@ public class IndexJob extends BackgroundJob {
|
||||
|
||||
@Override
|
||||
protected void runJob() {
|
||||
JNodeCache nodeCache = cache.getNodeCache();
|
||||
TextSearchIndex index = new TextSearchIndex(nodeCache);
|
||||
CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
|
||||
TextSearchIndex index = new TextSearchIndex(cache);
|
||||
CodeUsageInfo usageInfo = new CodeUsageInfo(cache.getNodeCache());
|
||||
|
||||
cache.setTextIndex(index);
|
||||
cache.setUsageInfo(usageInfo);
|
||||
|
||||
addTask(index::indexResource);
|
||||
for (final JavaClass cls : wrapper.getIncludedClasses()) {
|
||||
addTask(() -> indexCls(cache, cls));
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
private Map<String, WindowLocation> windowPos = new HashMap<>();
|
||||
private int mainWindowExtendedState = JFrame.NORMAL;
|
||||
private boolean codeAreaLineWrap = false;
|
||||
private int srhResourceSkipSize = 1000;
|
||||
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
|
||||
private boolean keepCommonDialogOpen = false;
|
||||
|
||||
/**
|
||||
* UI setting: the width of the tree showing the classes, resources, ...
|
||||
*/
|
||||
@@ -400,6 +404,30 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
return this.codeAreaLineWrap;
|
||||
}
|
||||
|
||||
public int getSrhResourceSkipSize() {
|
||||
return srhResourceSkipSize;
|
||||
}
|
||||
|
||||
public void setSrhResourceSkipSize(int size) {
|
||||
srhResourceSkipSize = size;
|
||||
}
|
||||
|
||||
public String getSrhResourceFileExt() {
|
||||
return srhResourceFileExt;
|
||||
}
|
||||
|
||||
public void setSrhResourceFileExt(String all) {
|
||||
srhResourceFileExt = all.trim();
|
||||
}
|
||||
|
||||
public void setKeepCommonDialogOpen(boolean yes) {
|
||||
keepCommonDialogOpen = yes;
|
||||
}
|
||||
|
||||
public boolean getKeepCommonDialogOpen() {
|
||||
return keepCommonDialogOpen;
|
||||
}
|
||||
|
||||
private void upgradeSettings(int fromVersion) {
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
||||
if (fromVersion == 0) {
|
||||
@@ -454,6 +482,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
showHeapUsageBar = false;
|
||||
fromVersion++;
|
||||
}
|
||||
if (fromVersion == 10) {
|
||||
srhResourceSkipSize = 3;
|
||||
srhResourceFileExt = ".xml|.html|.js|.json|.txt";
|
||||
}
|
||||
settingsVersion = CURRENT_SETTINGS_VERSION;
|
||||
sync();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -74,6 +78,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
leftPanel.add(makeProjectGroup());
|
||||
leftPanel.add(makeEditorGroup());
|
||||
leftPanel.add(makeOtherGroup());
|
||||
leftPanel.add(makeSearchResGroup());
|
||||
|
||||
rightPanel.add(makeDecompilationGroup());
|
||||
|
||||
@@ -474,6 +479,51 @@ public class JadxSettingsWindow extends JDialog {
|
||||
return group;
|
||||
}
|
||||
|
||||
private SettingsGroup makeSearchResGroup() {
|
||||
SettingsGroup group = new SettingsGroup(NLS.str("preferences.search_res_title"));
|
||||
int prevSize = settings.getSrhResourceSkipSize();
|
||||
String prevExts = settings.getSrhResourceFileExt();
|
||||
SpinnerNumberModel sizeLimitModel = new SpinnerNumberModel(prevSize,
|
||||
0, Integer.MAX_VALUE, 1);
|
||||
JSpinner spinner = new JSpinner(sizeLimitModel);
|
||||
JTextField fileExtField = new JTextField();
|
||||
group.addRow(NLS.str("preferences.res_skip_file"), spinner);
|
||||
group.addRow(NLS.str("preferences.res_file_ext"), fileExtField);
|
||||
|
||||
spinner.addChangeListener(new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
int size = (Integer) spinner.getValue();
|
||||
settings.setSrhResourceSkipSize(size);
|
||||
}
|
||||
});
|
||||
|
||||
fileExtField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
private void update() {
|
||||
String ext = fileExtField.getText();
|
||||
settings.setSrhResourceFileExt(ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
fileExtField.setText(prevExts);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private void needReload() {
|
||||
needReload = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
public class JResSearchNode extends JNode {
|
||||
private static final long serialVersionUID = -2222084945157778639L;
|
||||
private final transient JResource resNode;
|
||||
private final transient String text;
|
||||
private final transient int line;
|
||||
private final transient int pos;
|
||||
|
||||
public JResSearchNode(JResource resNode, String text, int line, int pos) {
|
||||
this.pos = pos;
|
||||
this.text = text;
|
||||
this.line = line;
|
||||
this.resNode = resNode;
|
||||
}
|
||||
|
||||
public JResource getResNode() {
|
||||
return resNode;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeDescString() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return resNode.getJParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeLongStringHtml() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return resNode.getIcon();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return resNode.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return resNode.makeString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDescString() {
|
||||
return !StringUtils.isEmpty(text);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,10 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
}
|
||||
JumpPosition jmpPos;
|
||||
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
|
||||
if (node instanceof CodeNode) {
|
||||
if (node instanceof JResSearchNode) {
|
||||
jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine())
|
||||
.setPrecise(((JResSearchNode) node).getPos());
|
||||
} else if (node instanceof CodeNode) {
|
||||
CodeNode codeNode = (CodeNode) node;
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), codeNode.getPos());
|
||||
if (codeNode.isPrecisePos()) {
|
||||
@@ -121,8 +124,9 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine());
|
||||
}
|
||||
tabbedPane.codeJump(jmpPos);
|
||||
|
||||
dispose();
|
||||
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,6 +152,15 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
|
||||
JCheckBox cbKeepOpen = new JCheckBox(NLS.str("search_dialog.keep_open"));
|
||||
cbKeepOpen.setSelected(mainWindow.getSettings().getKeepCommonDialogOpen());
|
||||
cbKeepOpen.addActionListener(e -> {
|
||||
mainWindow.getSettings().setKeepCommonDialogOpen(cbKeepOpen.isSelected());
|
||||
mainWindow.getSettings().sync();
|
||||
});
|
||||
buttonPane.add(cbKeepOpen);
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(15, 0)));
|
||||
buttonPane.add(progressPane);
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
buttonPane.add(Box.createHorizontalGlue());
|
||||
@@ -251,6 +264,10 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
resultsInfoLabel.setText(statusText);
|
||||
}
|
||||
|
||||
protected void showSearchState() {
|
||||
resultsInfoLabel.setText(NLS.str("search_dialog.tip_searching"));
|
||||
}
|
||||
|
||||
protected static class ResultsTable extends JTable {
|
||||
private static final long serialVersionUID = 3901184054736618969L;
|
||||
private final transient ResultsTableCellRenderer renderer;
|
||||
|
||||
@@ -445,6 +445,8 @@ public class MainWindow extends JFrame {
|
||||
protected void resetCache() {
|
||||
cacheObject.reset();
|
||||
// TODO: decompilation freezes sometime with several threads
|
||||
this.cacheObject.setJRoot(treeRoot);
|
||||
this.cacheObject.setJadxSettings(settings);
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
@@ -563,6 +565,8 @@ public class MainWindow extends JFrame {
|
||||
treeModel.setRoot(treeRoot);
|
||||
treeRoot.update();
|
||||
reloadTree();
|
||||
cacheObject.setJRoot(treeRoot);
|
||||
cacheObject.setJadxSettings(settings);
|
||||
}
|
||||
|
||||
private void clearTree() {
|
||||
|
||||
@@ -39,7 +39,8 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
FIELD,
|
||||
CODE,
|
||||
IGNORE_CASE,
|
||||
USE_REGEX
|
||||
USE_REGEX,
|
||||
Resource
|
||||
}
|
||||
|
||||
private transient Set<SearchOptions> options;
|
||||
@@ -76,7 +77,6 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
if (lastSearch != null) {
|
||||
searchField.setText(lastSearch);
|
||||
searchField.selectAll();
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
searchField.requestFocus();
|
||||
}
|
||||
@@ -91,6 +91,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE);
|
||||
JCheckBox regexChBox = makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX);
|
||||
|
||||
JCheckBox resChBox = makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.Resource);
|
||||
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
|
||||
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
|
||||
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
|
||||
@@ -98,6 +99,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
|
||||
searchInPanel.add(resChBox);
|
||||
searchInPanel.add(clsChBox);
|
||||
searchInPanel.add(mthChBox);
|
||||
searchInPanel.add(fldChBox);
|
||||
@@ -200,6 +202,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
if (index == null) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
showSearchState();
|
||||
return index.buildSearch(text, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -149,10 +149,10 @@ public class CodePanel extends JPanel {
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
int line;
|
||||
int lineCount;
|
||||
int line = 0;
|
||||
int tokenIndex;
|
||||
int pos = codeArea.getCaretPosition();
|
||||
int lineCount = codeArea.getLineCount();
|
||||
try {
|
||||
// after rename the change of document is undetectable, so
|
||||
// use Token offset to calculate the new caret position.
|
||||
@@ -161,10 +161,12 @@ public class CodePanel extends JPanel {
|
||||
tokenIndex = getTokenIndexByOffset(token, pos);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
tokenIndex = 0;
|
||||
line = codeArea.getLineCount() - 1;
|
||||
tokenIndex = -1;
|
||||
}
|
||||
if (tokenIndex == -1) {
|
||||
refreshToViewport();
|
||||
return;
|
||||
}
|
||||
lineCount = codeArea.getLineCount();
|
||||
codeArea.refresh();
|
||||
initLineNumbers();
|
||||
int lineDiff = codeArea.getLineCount() - lineCount;
|
||||
@@ -184,14 +186,23 @@ public class CodePanel extends JPanel {
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshToViewport() {
|
||||
JViewport viewport = getCodeScrollPane().getViewport();
|
||||
Point viewPosition = viewport.getViewPosition();
|
||||
codeArea.refresh();
|
||||
initLineNumbers();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
viewport.setViewPosition(viewPosition);
|
||||
});
|
||||
}
|
||||
|
||||
private int getTokenIndexByOffset(Token token, int offset) {
|
||||
if (token != null) {
|
||||
int index = 1;
|
||||
while (token.getEndOffset() < offset) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
index = 0;
|
||||
break;
|
||||
return -1;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -201,7 +212,7 @@ public class CodePanel extends JPanel {
|
||||
}
|
||||
|
||||
private int getOffsetOfTokenByIndex(int index, Token token) {
|
||||
if (token != null) {
|
||||
if (token != null && index != -1) {
|
||||
for (int i = 0; i < index; i++) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
|
||||
@@ -7,6 +7,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.jobs.DecompileJob;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
@@ -20,12 +22,16 @@ public class CacheObject {
|
||||
private String lastSearch;
|
||||
private JNodeCache jNodeCache;
|
||||
private Set<SearchDialog.SearchOptions> lastSearchOptions;
|
||||
private JRoot jRoot;
|
||||
private JadxSettings settings;
|
||||
|
||||
public CacheObject() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
jRoot = null;
|
||||
settings = null;
|
||||
decompileJob = null;
|
||||
indexJob = null;
|
||||
textIndex = null;
|
||||
@@ -89,4 +95,20 @@ public class CacheObject {
|
||||
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
|
||||
return lastSearchOptions;
|
||||
}
|
||||
|
||||
public void setJadxSettings(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public JadxSettings getJadxSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
public JRoot getJRoot() {
|
||||
return jRoot;
|
||||
}
|
||||
|
||||
public void setJRoot(JRoot jRoot) {
|
||||
this.jRoot = jRoot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class CodeIndex {
|
||||
}
|
||||
LOG.debug("Code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
|
||||
emitter.onComplete();
|
||||
}, BackpressureStrategy.LATEST);
|
||||
}, BackpressureStrategy.BUFFER);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package jadx.gui.utils.search;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import javax.swing.tree.TreeNode;
|
||||
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.FlowableEmitter;
|
||||
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.api.ResourceType;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.treemodel.JResSearchNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
|
||||
import static jadx.core.utils.StringUtils.*;
|
||||
|
||||
public class ResourceIndex {
|
||||
private final List<JResource> resNodes = new ArrayList<>();
|
||||
private final Set<String> extSet = new HashSet<>();
|
||||
private CacheObject cache;
|
||||
private String fileExts;
|
||||
private boolean anyExt;
|
||||
private int sizeLimit;
|
||||
|
||||
public ResourceIndex(CacheObject cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
private void search(final JResource resNode,
|
||||
FlowableEmitter<JResSearchNode> emitter,
|
||||
SearchSettings searchSettings) {
|
||||
int pos = 0;
|
||||
int line = 0;
|
||||
int lastPos = 0;
|
||||
int lastLineOccurred = -1;
|
||||
JResSearchNode lastNode = null;
|
||||
int searchStrLen = searchSettings.getSearchString().length();
|
||||
String content;
|
||||
try {
|
||||
content = resNode.getContent();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
do {
|
||||
searchSettings.setStartPos(lastPos);
|
||||
pos = searchSettings.find(content);
|
||||
if (pos > -1) {
|
||||
line += countLinesByPos(content, pos, lastPos);
|
||||
lastPos = pos + searchStrLen;
|
||||
String lineText = getLine(content, pos, lastPos);
|
||||
if (lastLineOccurred != line) {
|
||||
lastLineOccurred = line;
|
||||
if (lastNode != null) {
|
||||
emitter.onNext(lastNode);
|
||||
}
|
||||
lastNode = new JResSearchNode(resNode, lineText.trim(), line + 1, pos);
|
||||
}
|
||||
} else {
|
||||
if (lastNode != null) { // commit the final result node.
|
||||
emitter.onNext(lastNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} while (!emitter.isCancelled() && lastPos < content.length());
|
||||
}
|
||||
|
||||
public Flowable<JResSearchNode> search(SearchSettings settings) {
|
||||
refreshSettings();
|
||||
if (resNodes.size() == 0) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
return Flowable.create(emitter -> {
|
||||
for (JResource resNode : resNodes) {
|
||||
if (!emitter.isCancelled()) {
|
||||
search(resNode, emitter, settings);
|
||||
}
|
||||
}
|
||||
emitter.onComplete();
|
||||
}, BackpressureStrategy.BUFFER);
|
||||
}
|
||||
|
||||
public void index() {
|
||||
refreshSettings();
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
anyExt = false;
|
||||
sizeLimit = -1;
|
||||
fileExts = "";
|
||||
extSet.clear();
|
||||
resNodes.clear();
|
||||
}
|
||||
|
||||
private void traverseTree(TreeNode root, ZipFile zip) {
|
||||
for (int i = 0; i < root.getChildCount(); i++) {
|
||||
TreeNode node = root.getChildAt(i);
|
||||
if (node instanceof JResource) {
|
||||
JResource resNode = (JResource) node;
|
||||
try {
|
||||
resNode.loadNode();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
ResourceFile resFile = resNode.getResFile();
|
||||
if (resFile == null) {
|
||||
traverseTree(node, zip);
|
||||
} else {
|
||||
if (resFile.getType() == ResourceType.ARSC && shouldSearchXML()) {
|
||||
resFile.loadContent();
|
||||
resNode.getFiles().forEach(t -> traverseTree(t, null));
|
||||
} else {
|
||||
filter(resNode, zip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSearchXML() {
|
||||
return anyExt || fileExts.contains(".xml");
|
||||
}
|
||||
|
||||
private ZipFile getZipFile(TreeNode res) {
|
||||
for (int i = 0; i < res.getChildCount(); i++) {
|
||||
TreeNode node = res.getChildAt(i);
|
||||
if (node instanceof JResource) {
|
||||
JResource resNode = (JResource) node;
|
||||
try {
|
||||
resNode.loadNode();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
ResourceFile file = resNode.getResFile();
|
||||
if (file == null) {
|
||||
ZipFile zip = getZipFile(resNode);
|
||||
if (zip != null) {
|
||||
return zip;
|
||||
}
|
||||
} else {
|
||||
File zfile = file.getZipRef().getZipFile();
|
||||
if (FileUtils.isZipFile(zfile)) {
|
||||
try {
|
||||
return new ZipFile(zfile);
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void filter(JResource resNode, ZipFile zip) {
|
||||
ResourceFile resFile = resNode.getResFile();
|
||||
if (JResource.isSupportedForView(resFile.getType())) {
|
||||
long size = -1;
|
||||
if (zip != null) {
|
||||
ZipEntry entry = zip.getEntry(resFile.getOriginalName());
|
||||
if (entry != null) {
|
||||
size = entry.getSize();
|
||||
}
|
||||
}
|
||||
if (size == -1) { // resource from ARSC is unknown size
|
||||
try {
|
||||
size = resNode.getContent().length();
|
||||
} catch (Exception ignore) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (size <= sizeLimit) {
|
||||
if (!anyExt) {
|
||||
for (String ext : extSet) {
|
||||
if (resFile.getOriginalName().endsWith(ext)) {
|
||||
resNodes.add(resNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resNodes.add(resNode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshSettings() {
|
||||
int size = cache.getJadxSettings().getSrhResourceSkipSize() * 10240;
|
||||
if (size != sizeLimit
|
||||
|| !cache.getJadxSettings().getSrhResourceFileExt().equals(fileExts)) {
|
||||
clear();
|
||||
sizeLimit = size;
|
||||
fileExts = cache.getJadxSettings().getSrhResourceFileExt();
|
||||
String[] exts = fileExts.split("\\|");
|
||||
for (String ext : exts) {
|
||||
ext = ext.trim();
|
||||
if (!ext.isEmpty()) {
|
||||
anyExt = ext.equals("*");
|
||||
if (anyExt) {
|
||||
break;
|
||||
}
|
||||
extSet.add(ext);
|
||||
}
|
||||
}
|
||||
ZipFile zipFile = getZipFile(cache.getJRoot());
|
||||
traverseTree(cache.getJRoot(), zipFile); // reindex
|
||||
try {
|
||||
if (zipFile != null) {
|
||||
zipFile.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class SimpleIndex {
|
||||
}
|
||||
}
|
||||
emitter.onComplete();
|
||||
}, BackpressureStrategy.LATEST);
|
||||
}, BackpressureStrategy.BUFFER);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
|
||||
@@ -19,6 +19,7 @@ import jadx.core.codegen.CodeWriter;
|
||||
import jadx.gui.treemodel.CodeNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.CodeLinesInfo;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
@@ -28,6 +29,7 @@ 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;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.Resource;
|
||||
import static jadx.gui.ui.SearchDialog.SearchOptions.USE_REGEX;
|
||||
|
||||
public class TextSearchIndex {
|
||||
@@ -40,11 +42,13 @@ public class TextSearchIndex {
|
||||
private final SimpleIndex mthSignaturesIndex;
|
||||
private final SimpleIndex fldSignaturesIndex;
|
||||
private final CodeIndex codeIndex;
|
||||
private final ResourceIndex resIndex;
|
||||
|
||||
private final List<JavaClass> skippedClasses = new ArrayList<>();
|
||||
|
||||
public TextSearchIndex(JNodeCache nodeCache) {
|
||||
this.nodeCache = nodeCache;
|
||||
public TextSearchIndex(CacheObject cache) {
|
||||
this.nodeCache = cache.getNodeCache();
|
||||
this.resIndex = new ResourceIndex(cache);
|
||||
this.clsNamesIndex = new SimpleIndex();
|
||||
this.mthSignaturesIndex = new SimpleIndex();
|
||||
this.fldSignaturesIndex = new SimpleIndex();
|
||||
@@ -85,6 +89,10 @@ public class TextSearchIndex {
|
||||
}
|
||||
}
|
||||
|
||||
public void indexResource() {
|
||||
resIndex.index();
|
||||
}
|
||||
|
||||
public void remove(JavaClass cls) {
|
||||
this.clsNamesIndex.removeForCls(cls);
|
||||
this.mthSignaturesIndex.removeForCls(cls);
|
||||
@@ -121,6 +129,9 @@ public class TextSearchIndex {
|
||||
result = Flowable.concat(result, searchInSkippedClasses(searchSettings));
|
||||
}
|
||||
}
|
||||
if (options.contains(Resource)) {
|
||||
result = Flowable.concat(result, resIndex.search(searchSettings));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -146,7 +157,7 @@ public class TextSearchIndex {
|
||||
}
|
||||
LOG.debug("Skipped code search complete: {}, memory usage: {}", searchSettings.getSearchString(), UiUtils.memoryInfo());
|
||||
emitter.onComplete();
|
||||
}, BackpressureStrategy.LATEST);
|
||||
}, BackpressureStrategy.BUFFER);
|
||||
}
|
||||
|
||||
private int searchNext(FlowableEmitter<CodeNode> emitter, JavaNode javaClass, String code, final SearchSettings searchSettings) {
|
||||
|
||||
@@ -77,6 +77,9 @@ search_dialog.info_label=Zeige Ergebnisse %1$d bis %2$d von %3$d
|
||||
search_dialog.col_node=Knoten
|
||||
search_dialog.col_code=Code
|
||||
search_dialog.regex=Regex
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
|
||||
usage_dialog.title=Verwendungssuche
|
||||
usage_dialog.label=Verwendung für:
|
||||
@@ -132,6 +135,9 @@ preferences.rename=Umbenennen
|
||||
preferences.rename_case=System unterscheidet zwischen Groß/Kleinschreibung
|
||||
preferences.rename_valid=Ist eine gültige Kennung
|
||||
preferences.rename_printable=Ist druckbar
|
||||
#preferences.search_res_title=
|
||||
#preferences.res_file_ext=
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=Bitte Datei öffnen
|
||||
msg.saving_sources=Quellen speichern…
|
||||
|
||||
@@ -77,6 +77,9 @@ search_dialog.info_label=Showing results %1$d to %2$d of %3$d
|
||||
search_dialog.col_node=Node
|
||||
search_dialog.col_code=Code
|
||||
search_dialog.regex=Regex
|
||||
search_dialog.resource=Resource
|
||||
search_dialog.keep_open=Keep open
|
||||
search_dialog.tip_searching=Searching ...
|
||||
|
||||
usage_dialog.title=Usage search
|
||||
usage_dialog.label=Usage for:
|
||||
@@ -132,6 +135,9 @@ preferences.rename=Rename
|
||||
preferences.rename_case=System case sensitivity
|
||||
preferences.rename_valid=To be valid identifier
|
||||
preferences.rename_printable=To be printable
|
||||
preferences.search_res_title=Search Resource
|
||||
preferences.res_file_ext=File Extensions (e.g. .xml|.html), * means all
|
||||
preferences.res_skip_file=Skip files exceed (MB)
|
||||
|
||||
msg.open_file=Please open file
|
||||
msg.saving_sources=Saving sources...
|
||||
|
||||
@@ -77,6 +77,9 @@ search_dialog.info_label=Mostrando resultados %1$d a %2$d de %3$d
|
||||
search_dialog.col_node=Nodo
|
||||
search_dialog.col_code=Código
|
||||
search_dialog.regex=Regex
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
|
||||
usage_dialog.title=Usage search
|
||||
usage_dialog.label=Usage for:
|
||||
@@ -132,6 +135,9 @@ preferences.reset_title=Reestablecer preferencias
|
||||
#preferences.rename_case=
|
||||
#preferences.rename_valid=
|
||||
#preferences.rename_printable=
|
||||
#preferences.search_res_title=
|
||||
#preferences.res_file_ext=
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=Por favor, abra un archivo
|
||||
msg.saving_sources=Guardando fuente...
|
||||
|
||||
@@ -77,6 +77,9 @@ search_dialog.info_label=%3$d 중 %1$d-%2$d 결과 표시
|
||||
search_dialog.col_node=노드
|
||||
search_dialog.col_code=코드
|
||||
search_dialog.regex=정규식
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
|
||||
usage_dialog.title=사용 검색
|
||||
usage_dialog.label=다음의 사용 검색 결과:
|
||||
@@ -132,6 +135,9 @@ preferences.rename=이름 바꾸기
|
||||
preferences.rename_case=시스템 대소문자 구분
|
||||
preferences.rename_valid=유효한 식별자로 바꾸기
|
||||
preferences.rename_printable=출력 가능하게 바꾸기
|
||||
#preferences.search_res_title=
|
||||
#preferences.res_file_ext=
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=파일을 여십시오
|
||||
msg.saving_sources=소스 저장 중 ...
|
||||
|
||||
@@ -77,6 +77,9 @@ search_dialog.info_label=显示了 %3$d 个结果中的第 %1$d 至第 %2$d 个
|
||||
search_dialog.col_node=节点
|
||||
search_dialog.col_code=代码
|
||||
search_dialog.regex=正则表达式
|
||||
#search_dialog.resource=
|
||||
#search_dialog.keep_open=
|
||||
#search_dialog.tip_searching=
|
||||
|
||||
usage_dialog.title=查找
|
||||
usage_dialog.label=查找用例:
|
||||
@@ -132,6 +135,9 @@ preferences.rename=重命名
|
||||
preferences.rename_case=系统区分大小写
|
||||
preferences.rename_valid=是有效的标识符
|
||||
preferences.rename_printable=是可打印
|
||||
#preferences.search_res_title=
|
||||
#preferences.res_file_ext=
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=请打开文件
|
||||
msg.saving_sources=正在导出源代码...
|
||||
|
||||
Reference in New Issue
Block a user