提交 ae265126 编写于 作者: S Skylot

fix: use internal usage info for rename, fix index refresh (#791)

上级 498c2f52
......@@ -6,6 +6,8 @@ public interface ICodeCache {
void add(String clsFullName, ICodeInfo codeInfo);
void remove(String clsFullName);
@Nullable
ICodeInfo get(String clsFullName);
}
......@@ -4,16 +4,19 @@ import java.io.Closeable;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
......@@ -422,6 +425,13 @@ public final class JadxDecompiler implements Closeable {
throw new JadxRuntimeException("Unexpected node type: " + obj);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
return nodesList.stream()
.map(this::convertNode)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
......
......@@ -59,6 +59,8 @@ public final class JavaClass implements JavaNode {
public synchronized void refresh() {
listsLoaded = false;
cls.unload();
cls.deepUnload();
cls.reRunDecompile();
}
......@@ -124,7 +126,7 @@ public final class JavaClass implements JavaNode {
}
}
private JadxDecompiler getRootDecompiler() {
protected JadxDecompiler getRootDecompiler() {
if (parent != null) {
return parent.getRootDecompiler();
}
......@@ -156,6 +158,11 @@ public final class JavaClass implements JavaNode {
return resultMap;
}
@Override
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
}
@Nullable
@Deprecated
public JavaNode getJavaNodeAtPosition(int line, int offset) {
......
package jadx.api;
import java.util.List;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
......@@ -47,6 +49,11 @@ public final class JavaField implements JavaNode {
return field.getDecompiledLine();
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
}
/**
* Internal API. Not Stable!
*/
......
......@@ -56,6 +56,11 @@ public final class JavaMethod implements JavaNode {
return ArgType.tryToResolveClassAlias(mth.root(), retType);
}
@Override
public List<JavaNode> getUseIn() {
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUseIn());
}
public boolean isConstructor() {
return mth.getMethodInfo().isConstructor();
}
......
package jadx.api;
import java.util.List;
public interface JavaNode {
String getName();
......@@ -11,4 +13,6 @@ public interface JavaNode {
JavaClass getTopParentClass();
int getDecompiledLine();
List<JavaNode> getUseIn();
}
package jadx.api;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
......@@ -43,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return 0;
}
@Override
public List<JavaNode> getUseIn() {
return Collections.emptyList();
}
@Override
public int compareTo(@NotNull JavaPackage o) {
return name.compareTo(o.name);
......
......@@ -17,6 +17,11 @@ public class InMemoryCodeCache implements ICodeCache {
storage.put(clsFullName, codeInfo);
}
@Override
public void remove(String clsFullName) {
storage.remove(clsFullName);
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return storage.get(clsFullName);
......
......@@ -12,6 +12,11 @@ public class NoOpCodeCache implements ICodeCache {
// do nothing
}
@Override
public void remove(String clsFullName) {
// do nothing
}
@Override
public @Nullable ICodeInfo get(String clsFullName) {
return null;
......
......@@ -699,12 +699,12 @@ public class ClassGen {
List<ClassNode> deps = cls.getDependencies();
code.startLine("// deps - ").add(Integer.toString(deps.size()));
for (ClassNode depCls : deps) {
code.startLine("// ").add(depCls.getFullName());
code.startLine("// ").add(depCls.getClassInfo().getFullName());
}
List<ClassNode> useIn = cls.getUseIn();
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
for (ClassNode useCls : useIn) {
code.startLine("// ").add(useCls.getFullName());
code.startLine("// ").add(useCls.getClassInfo().getFullName());
}
List<MethodNode> useInMths = cls.getUseInMth();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
......
......@@ -345,6 +345,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
@Override
public void unload() {
if (state == NOT_LOADED) {
return;
}
methods.forEach(MethodNode::unload);
innerClasses.forEach(ClassNode::unload);
fields.forEach(FieldNode::unloadAttributes);
......
......@@ -3,7 +3,7 @@ package jadx.core.dex.nodes;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.AccessInfo;
public interface ICodeNode extends IDexNode, IAttributeNode {
public interface ICodeNode extends IDexNode, IAttributeNode, IUsageInfoNode {
AccessInfo getAccessFlags();
void setAccessFlags(AccessInfo newAccessFlags);
......
package jadx.core.dex.nodes;
import java.util.List;
public interface IUsageInfoNode {
List<? extends ICodeNode> getUseIn();
}
......@@ -6,7 +6,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.Opcode;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
......@@ -49,9 +48,6 @@ public class UsageInfoVisitor extends AbstractVisitor {
}
// TODO: process annotations and generics
for (MethodNode methodNode : cls.getMethods()) {
if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) {
continue;
}
processMethod(methodNode, usageInfo);
}
}
......@@ -70,6 +66,9 @@ public class UsageInfoVisitor extends AbstractVisitor {
}
private static void processInstructions(MethodNode mth, UsageInfo usageInfo) {
if (mth.isNoCode()) {
return;
}
ICodeReader codeReader = mth.getCodeReader();
if (codeReader == null) {
return;
......
......@@ -7,7 +7,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingWorker;
import javax.swing.*;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
......@@ -36,8 +36,10 @@ public class BackgroundExecutor {
public Future<Boolean> execute(IBackgroundTask task) {
TaskWorker taskWorker = new TaskWorker(task);
taskWorker.init();
taskQueueExecutor.execute(taskWorker);
taskQueueExecutor.execute(() -> {
taskWorker.init();
taskWorker.run();
});
return taskWorker;
}
......@@ -52,6 +54,14 @@ public class BackgroundExecutor {
}
}
public void execute(String title, List<Runnable> backgroundJobs, Runnable onFinishUiRunnable) {
execute(new SimpleTask(title, backgroundJobs, onFinishUiRunnable));
}
public void execute(String title, List<Runnable> backgroundJobs) {
execute(new SimpleTask(title, backgroundJobs, null));
}
public void execute(String title, Runnable backgroundRunnable, Runnable onFinishUiRunnable) {
execute(new SimpleTask(title, backgroundRunnable, onFinishUiRunnable));
}
......@@ -85,6 +95,7 @@ public class BackgroundExecutor {
List<Runnable> jobs = task.scheduleJobs();
jobsCount = jobs.size();
LOG.debug("Starting background task '{}', jobs count: {}", task.getTitle(), jobsCount);
if (jobsCount == 1) {
jobs.get(0).run();
return true;
......@@ -149,15 +160,19 @@ public class BackgroundExecutor {
private static final class SimpleTask implements IBackgroundTask {
private final String title;
private final Runnable runnable;
private final List<Runnable> jobs;
private final Runnable onFinish;
public SimpleTask(String title, Runnable runnable, @Nullable Runnable onFinish) {
public SimpleTask(String title, List<Runnable> jobs, @Nullable Runnable onFinish) {
this.title = title;
this.runnable = runnable;
this.jobs = jobs;
this.onFinish = onFinish;
}
public SimpleTask(String title, Runnable job, @Nullable Runnable onFinish) {
this(title, Collections.singletonList(job), onFinish);
}
@Override
public String getTitle() {
return title;
......@@ -165,7 +180,7 @@ public class BackgroundExecutor {
@Override
public List<Runnable> scheduleJobs() {
return Collections.singletonList(runnable);
return jobs;
}
@Override
......
......@@ -54,23 +54,6 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
runJob(cache.getDecompileJob());
LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo());
if (cache.getUnloadJob() != null) {
LOG.info("Memory usage: Before unload: {}", UiUtils.memoryInfo());
long start = System.nanoTime();
runJob(cache.getUnloadJob());
cache.setUnloadJob(null);
LOG.info("Memory usage: After unload: {}, unload took {} ms", UiUtils.memoryInfo(), (System.nanoTime() - start) / 1000000);
}
if (cache.getRefreshJob() != null) {
LOG.info("Memory usage: Before refresh: {}", UiUtils.memoryInfo());
long start = System.nanoTime();
runJob(cache.getRefreshJob());
cache.setRefreshJob(null);
LOG.info("Memory usage: After refresh: {}, refresh took {} ms", UiUtils.memoryInfo(),
(System.nanoTime() - start) / 1000000);
}
LOG.debug("Memory usage: Before index: {}", UiUtils.memoryInfo());
runJob(cache.getIndexJob());
LOG.debug("Memory usage: After index: {}", UiUtils.memoryInfo());
......
......@@ -31,33 +31,53 @@ public class IndexJob extends BackgroundJob {
@Override
protected void runJob() {
JNodeCache nodeCache = cache.getNodeCache();
final TextSearchIndex index = new TextSearchIndex(nodeCache);
final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
TextSearchIndex index = new TextSearchIndex(nodeCache);
CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
cache.setTextIndex(index);
cache.setUsageInfo(usageInfo);
for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(() -> {
try {
index.indexNames(cls);
addTask(() -> indexCls(cache, cls));
}
}
public static void indexCls(CacheObject cache, JavaClass cls) {
try {
TextSearchIndex index = cache.getTextIndex();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (index == null || usageInfo == null) {
return;
}
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
List<StringRef> lines = splitLines(cls);
index.indexNames(cls);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
List<StringRef> lines = splitLines(cls);
usageInfo.processClass(cls, linesInfo, lines);
if (UiUtils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines);
} else {
index.classCodeIndexSkipped(cls);
}
} catch (Exception e) {
LOG.error("Index error in class: {}", cls.getFullName(), e);
}
}
usageInfo.processClass(cls, linesInfo, lines);
if (UiUtils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines);
} else {
index.classCodeIndexSkipped(cls);
}
} catch (Exception e) {
LOG.error("Index error in class: {}", cls.getFullName(), e);
}
});
public static void refreshIndex(CacheObject cache, JavaClass cls) {
TextSearchIndex index = cache.getTextIndex();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (index == null || usageInfo == null) {
return;
}
index.remove(cls);
usageInfo.remove(cls);
indexCls(cache, cls);
}
@NotNull
protected List<StringRef> splitLines(JavaClass cls) {
protected static 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++) {
......
package jadx.gui.jobs;
import java.util.Set;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
public class RefreshJob extends BackgroundJob {
private Set<JavaClass> refreshClasses;
public RefreshJob(JadxWrapper jadxWrapper, int threadsCount, Set<JavaClass> refreshClasses) {
super(jadxWrapper, threadsCount);
this.refreshClasses = refreshClasses;
}
protected void runJob() {
for (final JavaClass cls : refreshClasses) {
addTask(cls::refresh);
}
}
@Override
public String getInfoString() {
return "Refreshing: ";
}
}
package jadx.gui.jobs;
import java.util.Set;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
public class UnloadJob extends BackgroundJob {
private Set<JavaClass> refreshClasses;
public UnloadJob(JadxWrapper jadxWrapper, int threadsCount, Set<JavaClass> refreshClasses) {
super(jadxWrapper, threadsCount);
this.refreshClasses = refreshClasses;
}
protected void runJob() {
for (final JavaClass cls : refreshClasses) {
addTask(() -> {
cls.unload();
cls.getClassNode().deepUnload();
});
}
}
@Override
public String getInfoString() {
return "Refreshing: ";
}
}
......@@ -48,6 +48,10 @@ public class CodeNode extends JNode {
return null;
}
public StringRef getLineStr() {
return line;
}
@Override
public int getLine() {
return lineNum;
......@@ -72,4 +76,21 @@ public class CodeNode extends JNode {
public String makeLongString() {
return makeString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CodeNode)) {
return false;
}
CodeNode codeNode = (CodeNode) o;
return jNode.equals(codeNode.jNode);
}
@Override
public int hashCode() {
return jNode.hashCode();
}
}
......@@ -415,12 +415,6 @@ public class MainWindow extends JFrame {
}
}
synchronized void runBackgroundUnloadRefreshAndIndexJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
backgroundWorker.exec();
}
public synchronized void cancelBackgroundJobs() {
backgroundExecutor.cancelAll();
if (backgroundWorker != null) {
......@@ -520,7 +514,7 @@ public class MainWindow extends JFrame {
tabbedPane.closeAllTabs();
resetCache();
treeRoot = null;
treeModel.setRoot(treeRoot);
treeModel.setRoot(null);
treeModel.reload();
}
......@@ -1136,6 +1130,10 @@ public class MainWindow extends JFrame {
return backgroundWorker;
}
public BackgroundExecutor getBackgroundExecutor() {
return backgroundExecutor;
}
public ProgressPanel getProgressPane() {
return progressPane;
}
......
......@@ -8,10 +8,10 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.*;
......@@ -23,11 +23,11 @@ import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.utils.Utils;
import jadx.gui.jobs.IndexJob;
import jadx.gui.jobs.RefreshJob;
import jadx.gui.jobs.UnloadJob;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
......@@ -37,77 +37,59 @@ import jadx.gui.treemodel.JPackage;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.codearea.CodePanel;
import jadx.gui.utils.*;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions;
public class RenameDialog extends CommonSearchDialog {
public class RenameDialog extends JDialog {
private static final long serialVersionUID = -3269715644416902410L;
private static final Logger LOG = LoggerFactory.getLogger(RenameDialog.class);
protected final transient MainWindow mainWindow;
private final transient MainWindow mainWindow;
private final transient CacheObject cache;
private final transient JNode node;
private JTextField renameField;
private CodeArea codeArea;
private JButton renameBtn;
private transient JTextField renameField;
public RenameDialog(CodeArea codeArea, JNode node) {
super(codeArea.getMainWindow());
mainWindow = codeArea.getMainWindow();
this.codeArea = codeArea;
this.mainWindow = codeArea.getMainWindow();
this.cache = mainWindow.getCacheObject();
this.node = node;
if (isDeobfuscationSettingsValid()) {
if (checkSettings()) {
initUI();
registerInitOnOpen();
loadWindowPos();
} else {
LOG.error("Deobfuscation settings are invalid - please enable deobfuscation and disable force rewrite deobfuscation map");
}
}
private boolean isDeobfuscationSettingsValid() {
boolean valid = true;
String errorMessage = null;
private boolean checkSettings() {
StringBuilder errorMessage = new StringBuilder();
errorMessage.append(NLS.str("msg.rename_disabled")).append(CodeWriter.NL);
JadxSettings settings = mainWindow.getSettings();
final LangLocale langLocale = settings.getLangLocale();
if (settings.isDeobfuscationForceSave()) {
boolean valid = true;
if (!settings.isDeobfuscationOn()) {
errorMessage.append(" - ").append(NLS.str("msg.rename_disabled_deobfuscation_disabled")).append(CodeWriter.NL);
valid = false;
errorMessage = NLS.str("msg.rename_disabled_force_rewrite_enabled", langLocale);
}
if (!settings.isDeobfuscationOn()) {
if (settings.isDeobfuscationForceSave()) {
errorMessage.append(" - ").append(NLS.str("msg.rename_disabled_force_rewrite_enabled")).append(CodeWriter.NL);
valid = false;
errorMessage = NLS.str("msg.rename_disabled_deobfuscation_disabled", langLocale);
}
if (errorMessage != null) {
showRenameDisabledErrorMessage(langLocale, errorMessage);
if (valid) {
return true;
}
return valid;
}
private void showRenameDisabledErrorMessage(LangLocale langLocale, String message) {
JOptionPane.showMessageDialog(
mainWindow,
message,
NLS.str("msg.rename_disabled_title", langLocale),
JOptionPane.ERROR_MESSAGE);
}
@Override
protected void openInit() {
prepare();
}
@Override
protected void loadStart() {
renameBtn.setEnabled(false);
}
int result = JOptionPane.showConfirmDialog(mainWindow, errorMessage.toString(),
NLS.str("msg.rename_disabled_title"), JOptionPane.OK_CANCEL_OPTION);
if (result != JOptionPane.OK_OPTION) {
return false;
}
settings.setDeobfuscationOn(true);
settings.setDeobfuscationForceSave(false);
settings.sync();
@Override
protected void loadFinished() {
renameBtn.setEnabled(true);
mainWindow.reOpenFile();
return false; // TODO: can't open dialog, 'node' is replaced with new one after reopen
}
private Path getDeobfMapPath(RootNode root) {
......@@ -195,29 +177,26 @@ public class RenameDialog extends CommonSearchDialog {
}
private void rename() {
long start = System.nanoTime();
String renameText = renameField.getText();
if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) {
return;
}
RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
if (node == null) {
LOG.error("rename(): rootNode is null!");
dispose();
return;
}
if (!refreshDeobfMapFile(renameText, root)) {
LOG.error("rename(): refreshDeobfMapFile() failed!");
dispose();
return;
try {
String renameText = renameField.getText();
if (renameText == null || renameText.length() == 0) {
return;
}
RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
if (node == null) {
LOG.error("rename(): rootNode is null!");
dispose();
return;
}
if (!refreshDeobfMapFile(renameText, root)) {
LOG.error("rename(): refreshDeobfMapFile() failed!");
dispose();
return;
}
refreshState(root);
} catch (Exception e) {
LOG.error("Rename failed", e);
}
long refreshStart = System.nanoTime();
int classes = refreshState(root);
long refreshTime = (System.nanoTime() - refreshStart) / 1000000;
long totalTime = (System.nanoTime() - start) / 1000000;
LOG.info("refreshState() took {} ms to update state, {} classes will be refreshed in background ({} ms total)", refreshTime,
classes,
totalTime);
dispose();
}
......@@ -240,92 +219,60 @@ public class RenameDialog extends CommonSearchDialog {
return true;
}
private int refreshState(RootNode rootNode) {
private void refreshState(RootNode rootNode) {
RenameVisitor renameVisitor = new RenameVisitor();
renameVisitor.init(rootNode);
cache.getNodeCache().refresh(node);
Set<JavaClass> updatedClasses = getUpdatedClasses();
JNodeCache nodeCache = cache.getNodeCache();
Set<JClass> updatedClasses = node.getJavaNode().getUseIn()
.stream()
.map(nodeCache::makeFrom)
.map(JNode::getRootClass)
.collect(Collectors.toSet());
updatedClasses.add(node.getRootClass());
mainWindow.reloadTree();
refreshTabs(mainWindow.getTabbedPane(), updatedClasses);
if (updatedClasses.size() > 0) {
setRefreshTask(updatedClasses);
if (!updatedClasses.isEmpty()) {
mainWindow.getBackgroundExecutor().execute("Refreshing",
Utils.collectionMap(updatedClasses, cls -> () -> refreshJClass(cls)),
mainWindow::reloadTree);
}
}
return updatedClasses.size();
private void refreshJClass(JClass cls) {
cls.refresh();
IndexJob.refreshIndex(cache, cls.getCls());
}
private void refreshTabs(TabbedPane tabbedPane, Set<JavaClass> updatedClasses) {
for (Map.Entry<JNode, ContentPanel> panel : tabbedPane.getOpenTabs().entrySet()) {
ContentPanel contentPanel = panel.getValue();
private void refreshTabs(TabbedPane tabbedPane, Set<JClass> updatedClasses) {
for (Map.Entry<JNode, ContentPanel> entry : tabbedPane.getOpenTabs().entrySet()) {
ContentPanel contentPanel = entry.getValue();
if (contentPanel instanceof ClassCodeContentPanel) {
JNode node = panel.getKey();
JNode node = entry.getKey();
JClass rootClass = node.getRootClass();
JavaClass javaClass = rootClass.getCls();
if (updatedClasses.contains(javaClass) || node.getRootClass().getCls() == javaClass) {
LOG.info("Refreshing rootClass " + javaClass.getRawName());
javaClass.unload();
javaClass.getClassNode().deepUnload();
rootClass.refresh(); // Update code cache
if (updatedClasses.contains(rootClass)) {
refreshJClass(rootClass);
ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel;
CodePanel javaPanel = codePanel.getJavaCodePanel();
javaPanel.refresh();
tabbedPane.refresh(node);
updatedClasses.remove(javaClass);
tabbedPane.refresh(rootClass);
}
}
}
}
private Set<JavaClass> getUpdatedClasses() {
Set<JavaClass> usageClasses = new HashSet<>();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (usageInfo != null) {
usageInfo.getUsageList(node).forEach((node) -> {
JavaClass rootClass = node.getRootClass().getCls();
// LOG.info("updateUsages(): Going to update class {}", rootClass.getRealFullName());
usageClasses.add(rootClass);
});
// usageClasses.parallelStream().forEach(JavaClass::refresh);
}
return usageClasses;
}
private void setRefreshTask(Set<JavaClass> refreshClasses) {
UnloadJob unloadJob = new UnloadJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses);
RefreshJob refreshJob = new RefreshJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses);
LOG.info("Waiting for old unloadJob and refreshJob");
while (cache.getUnloadJob() != null || cache.getRefreshJob() != null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return;
}
}
LOG.info("Old unloadJob and refreshJob finished");
cache.setUnloadJob(unloadJob);
cache.setRefreshJob(refreshJob);
cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), cache, mainWindow.getSettings().getThreadsCount()));
mainWindow.runBackgroundUnloadRefreshAndIndexJobs();
}
@NotNull
protected JPanel initButtonsPanel() {
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
cancelButton.addActionListener(event -> dispose());
renameBtn = new JButton(NLS.str("popup.rename"));
JButton renameBtn = new JButton(NLS.str("popup.rename"));
renameBtn.addActionListener(event -> rename());
getRootPane().setDefaultButton(renameBtn);
progressPane = new ProgressPanel(mainWindow, false);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(progressPane);
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(renameBtn);
......@@ -351,18 +298,12 @@ public class RenameDialog extends CommonSearchDialog {
renamePane.add(nodeLabel);
renamePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
warnLabel = new JLabel();
warnLabel.setForeground(Color.RED);
warnLabel.setVisible(false);
JPanel textPane = new JPanel();
textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS));
textPane.add(warnLabel);
textPane.add(Box.createRigidArea(new Dimension(0, 5)));
textPane.add(renameField);
textPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
initCommon();
JPanel buttonPane = initButtonsPanel();
Container contentPane = getContentPane();
......@@ -376,5 +317,7 @@ public class RenameDialog extends CommonSearchDialog {
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.MODELESS);
mainWindow.getSettings().loadWindowPos(this);
}
}
......@@ -7,8 +7,6 @@ import org.jetbrains.annotations.Nullable;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.jobs.RefreshJob;
import jadx.gui.jobs.UnloadJob;
import jadx.gui.ui.SearchDialog;
import jadx.gui.utils.search.TextSearchIndex;
......@@ -16,8 +14,6 @@ public class CacheObject {
private DecompileJob decompileJob;
private IndexJob indexJob;
private UnloadJob unloadJob;
private RefreshJob refreshJob;
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
......@@ -93,20 +89,4 @@ public class CacheObject {
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
return lastSearchOptions;
}
public RefreshJob getRefreshJob() {
return refreshJob;
}
public void setRefreshJob(RefreshJob refreshJob) {
this.refreshJob = refreshJob;
}
public UnloadJob getUnloadJob() {
return unloadJob;
}
public void setUnloadJob(UnloadJob unloadJob) {
this.unloadJob = unloadJob;
}
}
......@@ -70,4 +70,14 @@ public class CodeUsageInfo {
}
return usageInfo.getUsageList();
}
public void remove(JavaClass cls) {
usageMap.entrySet().removeIf(e -> {
if (e.getKey().getJavaNode().getTopParentClass().equals(cls)) {
return true;
}
e.getValue().getUsageList().removeIf(node -> node.getJavaNode().getTopParentClass().equals(cls));
return false;
});
}
}
......@@ -29,12 +29,6 @@ public class JNodeCache {
return jNode;
}
public void refresh(JNode node) {
JavaNode javaNode = node.getJavaNode();
cache.remove(javaNode);
makeFrom(javaNode);
}
private JNode convert(JavaNode node) {
if (node == null) {
return null;
......
......@@ -9,46 +9,34 @@ import org.slf4j.LoggerFactory;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import jadx.api.JavaClass;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.utils.UiUtils;
public class CodeIndex<T> implements SearchIndex<T> {
public class CodeIndex {
private static final Logger LOG = LoggerFactory.getLogger(CodeIndex.class);
private final List<StringRef> keys = new ArrayList<>();
private final List<T> values = new ArrayList<>();
private final List<CodeNode> values = new ArrayList<>();
@Override
public void put(String str, T value) {
throw new UnsupportedOperationException("CodeIndex.put for string not supported");
}
@Override
public synchronized void put(StringRef str, T value) {
if (str == null || str.length() == 0) {
return;
}
keys.add(str);
public synchronized void put(CodeNode value) {
values.add(value);
}
@Override
public boolean isStringRefSupported() {
return true;
public void removeForCls(JavaClass cls) {
values.removeIf(v -> v.getJavaNode().getTopParentClass().equals(cls));
}
private boolean isMatched(StringRef key, String str, boolean caseInsensitive) {
return key.indexOf(str, caseInsensitive) != -1;
}
@Override
public Flowable<T> search(final String searchStr, final boolean caseInsensitive) {
public Flowable<CodeNode> search(final String searchStr, final boolean caseInsensitive) {
return Flowable.create(emitter -> {
int size = size();
LOG.debug("Code search started: {} ...", searchStr);
for (int i = 0; i < size; i++) {
if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
emitter.onNext(values.get(i));
for (CodeNode node : values) {
if (isMatched(node.getLineStr(), searchStr, caseInsensitive)) {
emitter.onNext(node);
}
if (emitter.isCancelled()) {
LOG.debug("Code search canceled: {}", searchStr);
......@@ -60,8 +48,7 @@ public class CodeIndex<T> implements SearchIndex<T> {
}, BackpressureStrategy.LATEST);
}
@Override
public int size() {
return keys.size();
return values.size();
}
}
......@@ -6,10 +6,6 @@ public interface SearchIndex<V> {
void put(String str, V value);
void put(StringRef str, V value);
boolean isStringRefSupported();
Flowable<V> search(String searchStr, boolean caseInsensitive);
int size();
......
package jadx.gui.utils.search;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
public class SimpleIndex<T> implements SearchIndex<T> {
import jadx.api.JavaClass;
import jadx.gui.treemodel.JNode;
private final List<String> keys = new ArrayList<>();
private final List<T> values = new ArrayList<>();
public class SimpleIndex {
private final Map<JNode, String> data = new ConcurrentHashMap<>();
private final Object syncData = new Object();
@Override
public void put(String str, T value) {
synchronized (syncData) {
keys.add(str);
values.add(value);
}
}
@Override
public void put(StringRef str, T value) {
throw new UnsupportedOperationException("StringRef not supported");
public void put(String str, JNode value) {
data.put(value, str);
}
@Override
public boolean isStringRefSupported() {
return false;
public void removeForCls(JavaClass cls) {
data.entrySet().removeIf(e -> e.getKey().getJavaNode().getTopParentClass().equals(cls));
}
private boolean isMatched(String str, String searchStr, boolean caseInsensitive) {
......@@ -41,28 +30,22 @@ public class SimpleIndex<T> implements SearchIndex<T> {
}
}
@Override
public Flowable<T> search(final String searchStr, final boolean caseInsensitive) {
public Flowable<JNode> search(final String searchStr, final boolean caseInsensitive) {
return Flowable.create(emitter -> {
synchronized (syncData) {
int size = keys.size();
for (int i = 0; i < size; i++) {
if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
emitter.onNext(values.get(i));
}
if (emitter.isCancelled()) {
return;
}
for (Map.Entry<JNode, String> entry : data.entrySet()) {
if (isMatched(entry.getValue(), searchStr, caseInsensitive)) {
emitter.onNext(entry.getKey());
}
if (emitter.isCancelled()) {
return;
}
}
emitter.onComplete();
}, BackpressureStrategy.LATEST);
}
@Override
public int size() {
synchronized (syncData) {
return keys.size();
}
return data.size();
}
}
......@@ -36,19 +36,19 @@ public class TextSearchIndex {
private final JNodeCache nodeCache;
private SearchIndex<JNode> clsNamesIndex;
private SearchIndex<JNode> mthSignaturesIndex;
private SearchIndex<JNode> fldSignaturesIndex;
private SearchIndex<CodeNode> codeIndex;
private final SimpleIndex clsNamesIndex;
private final SimpleIndex mthSignaturesIndex;
private final SimpleIndex fldSignaturesIndex;
private final CodeIndex codeIndex;
private List<JavaClass> skippedClasses = new ArrayList<>();
private final List<JavaClass> skippedClasses = new ArrayList<>();
public TextSearchIndex(JNodeCache nodeCache) {
this.nodeCache = nodeCache;
this.clsNamesIndex = new SimpleIndex<>();
this.mthSignaturesIndex = new SimpleIndex<>();
this.fldSignaturesIndex = new SimpleIndex<>();
this.codeIndex = new CodeIndex<>();
this.clsNamesIndex = new SimpleIndex();
this.mthSignaturesIndex = new SimpleIndex();
this.fldSignaturesIndex = new SimpleIndex();
this.codeIndex = new CodeIndex();
}
public void indexNames(JavaClass cls) {
......@@ -68,7 +68,6 @@ public class TextSearchIndex {
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);
......@@ -78,18 +77,21 @@ public class TextSearchIndex {
}
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);
}
JNode nodeAtLine = nodeCache.makeFrom(node == null ? cls : node);
codeIndex.put(new CodeNode(nodeAtLine, lineNum, line));
}
} catch (Exception e) {
LOG.warn("Failed to index class: {}", cls, e);
}
}
public void remove(JavaClass cls) {
this.clsNamesIndex.removeForCls(cls);
this.mthSignaturesIndex.removeForCls(cls);
this.fldSignaturesIndex.removeForCls(cls);
this.codeIndex.removeForCls(cls);
}
public Flowable<JNode> buildSearch(String text, Set<SearchDialog.SearchOptions> options) {
boolean ignoreCase = options.contains(IGNORE_CASE);
LOG.debug("Building search, ignoreCase: {}", ignoreCase);
......
......@@ -137,9 +137,9 @@ msg.index_not_initialized=Index not initialized, search will be disabled!
msg.project_error_title=Error
msg.project_error=Project could not be loaded
msg.rename_disabled_title=Rename disabled
msg.rename_disabled=Some of rename settings are disabled, please take this into consideration
msg.rename_disabled_force_rewrite_enabled=Please disable "Force rewrite deobfuscation map file" option to rename.
msg.rename_disabled_deobfuscation_disabled=Please enable deobfuscation to rename.
msg.rename_disabled=Some of rename settings are disabled, next options will be changed:
msg.rename_disabled_force_rewrite_enabled=Disable "Force rewrite deobfuscation map file" option.
msg.rename_disabled_deobfuscation_disabled=Enable deobfuscation.
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
popup.undo=Undo
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册