提交 9c88f707 编写于 作者: S Skylot

fix(gui): load file in background thread and show progress indicator

上级 9ab003df
package jadx.gui.jobs;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingWorker;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.ProgressPanel;
/**
* Class for run tasks in background with progress bar indication.
* Use instance created in {@link MainWindow}.
*/
public class BackgroundExecutor {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundExecutor.class);
private final MainWindow mainWindow;
private final ProgressPanel progressPane;
private ThreadPoolExecutor taskQueueExecutor;
public BackgroundExecutor(MainWindow mainWindow) {
this.mainWindow = mainWindow;
this.progressPane = mainWindow.getProgressPane();
this.taskQueueExecutor = makeTaskQueueExecutor();
}
public Future<Boolean> execute(IBackgroundTask task) {
TaskWorker taskWorker = new TaskWorker(task);
taskWorker.init();
taskQueueExecutor.execute(taskWorker);
return taskWorker;
}
public void cancelAll() {
try {
taskQueueExecutor.shutdownNow();
taskQueueExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (Exception e) {
LOG.error("Error terminating task executor", e);
} finally {
taskQueueExecutor = makeTaskQueueExecutor();
}
}
public void execute(String title, Runnable backgroundRunnable, Runnable onFinishUiRunnable) {
execute(new SimpleTask(title, backgroundRunnable, onFinishUiRunnable));
}
public void execute(String title, Runnable backgroundRunnable) {
execute(new SimpleTask(title, backgroundRunnable, null));
}
private ThreadPoolExecutor makeTaskQueueExecutor() {
return (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
}
private final class TaskWorker extends SwingWorker<Boolean, Void> {
private final IBackgroundTask task;
private long jobsCount;
public TaskWorker(IBackgroundTask task) {
this.task = task;
}
public void init() {
addPropertyChangeListener(progressPane);
progressPane.reset();
}
@Override
protected Boolean doInBackground() throws Exception {
progressPane.changeLabel(this, task.getTitle() + ':');
progressPane.changeCancelBtnVisible(this, task.canBeCanceled());
progressPane.changeVisibility(this, true);
List<Runnable> jobs = task.scheduleJobs();
jobsCount = jobs.size();
if (jobsCount == 1) {
jobs.get(0).run();
return true;
}
int threadsCount = mainWindow.getSettings().getThreadsCount();
if (threadsCount == 1) {
return runInCurrentThread(jobs);
}
return runInExecutor(jobs, threadsCount);
}
private boolean runInCurrentThread(List<Runnable> jobs) {
int k = 0;
for (Runnable job : jobs) {
job.run();
k++;
setProgress(calcProgress(k));
if (isCancelled()) {
return false;
}
}
return true;
}
private boolean runInExecutor(List<Runnable> jobs, int threadsCount) throws InterruptedException {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (Runnable job : jobs) {
executor.execute(job);
}
executor.shutdown();
return waitTermination(executor);
}
private boolean waitTermination(ThreadPoolExecutor executor) throws InterruptedException {
while (true) {
if (executor.isTerminated()) {
return true;
}
if (isCancelled()) {
executor.shutdownNow();
progressPane.changeLabel(this, task.getTitle() + " (Canceling):");
// force termination
executor.awaitTermination(5, TimeUnit.SECONDS);
return false;
}
setProgress(calcProgress(executor.getCompletedTaskCount()));
Thread.sleep(500);
}
}
private int calcProgress(long done) {
return Math.round(done * 100 / (float) jobsCount);
}
@Override
protected void done() {
progressPane.setVisible(false);
task.onFinish();
}
}
private static final class SimpleTask implements IBackgroundTask {
private final String title;
private final Runnable runnable;
private final Runnable onFinish;
public SimpleTask(String title, Runnable runnable, @Nullable Runnable onFinish) {
this.title = title;
this.runnable = runnable;
this.onFinish = onFinish;
}
@Override
public String getTitle() {
return title;
}
@Override
public List<Runnable> scheduleJobs() {
return Collections.singletonList(runnable);
}
@Override
public boolean canBeCanceled() {
return false;
}
@Override
public void onFinish() {
if (onFinish != null) {
onFinish.run();
}
}
}
}
...@@ -2,7 +2,9 @@ package jadx.gui.jobs; ...@@ -2,7 +2,9 @@ package jadx.gui.jobs;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import javax.swing.*; import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -13,6 +15,9 @@ import jadx.gui.utils.NLS; ...@@ -13,6 +15,9 @@ import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
/**
* Deprecated. Use {@link BackgroundExecutor} instead.
*/
public class BackgroundWorker extends SwingWorker<Void, Void> { public class BackgroundWorker extends SwingWorker<Void, Void> {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundWorker.class); private static final Logger LOG = LoggerFactory.getLogger(BackgroundWorker.class);
......
...@@ -2,6 +2,7 @@ package jadx.gui.jobs; ...@@ -2,6 +2,7 @@ package jadx.gui.jobs;
import jadx.api.JavaClass; import jadx.api.JavaClass;
import jadx.gui.JadxWrapper; import jadx.gui.JadxWrapper;
import jadx.gui.utils.NLS;
public class DecompileJob extends BackgroundJob { public class DecompileJob extends BackgroundJob {
...@@ -9,6 +10,7 @@ public class DecompileJob extends BackgroundJob { ...@@ -9,6 +10,7 @@ public class DecompileJob extends BackgroundJob {
super(wrapper, threadsCount); super(wrapper, threadsCount);
} }
@Override
protected void runJob() { protected void runJob() {
for (final JavaClass cls : wrapper.getIncludedClasses()) { for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(cls::decompile); addTask(cls::decompile);
...@@ -17,6 +19,6 @@ public class DecompileJob extends BackgroundJob { ...@@ -17,6 +19,6 @@ public class DecompileJob extends BackgroundJob {
@Override @Override
public String getInfoString() { public String getInfoString() {
return "Decompiling: "; return NLS.str("progress.decompile");
} }
} }
package jadx.gui.jobs;
import java.util.List;
public interface IBackgroundTask {
String getTitle();
List<Runnable> scheduleJobs();
void onFinish();
boolean canBeCanceled();
}
...@@ -13,6 +13,7 @@ import jadx.gui.utils.CacheObject; ...@@ -13,6 +13,7 @@ import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo; import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo; import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.JNodeCache; import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.StringRef; import jadx.gui.utils.search.StringRef;
import jadx.gui.utils.search.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
...@@ -27,6 +28,7 @@ public class IndexJob extends BackgroundJob { ...@@ -27,6 +28,7 @@ public class IndexJob extends BackgroundJob {
this.cache = cache; this.cache = cache;
} }
@Override
protected void runJob() { protected void runJob() {
JNodeCache nodeCache = cache.getNodeCache(); JNodeCache nodeCache = cache.getNodeCache();
final TextSearchIndex index = new TextSearchIndex(nodeCache); final TextSearchIndex index = new TextSearchIndex(nodeCache);
...@@ -66,6 +68,6 @@ public class IndexJob extends BackgroundJob { ...@@ -66,6 +68,6 @@ public class IndexJob extends BackgroundJob {
@Override @Override
public String getInfoString() { public String getInfoString() {
return "Indexing: "; return NLS.str("progress.index");
} }
} }
...@@ -78,6 +78,7 @@ import jadx.api.JavaClass; ...@@ -78,6 +78,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaNode; import jadx.api.JavaNode;
import jadx.api.ResourceFile; import jadx.api.ResourceFile;
import jadx.gui.JadxWrapper; import jadx.gui.JadxWrapper;
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.BackgroundWorker; import jadx.gui.jobs.BackgroundWorker;
import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob; import jadx.gui.jobs.IndexJob;
...@@ -156,6 +157,7 @@ public class MainWindow extends JFrame { ...@@ -156,6 +157,7 @@ public class MainWindow extends JFrame {
private transient Link updateLink; private transient Link updateLink;
private transient ProgressPanel progressPane; private transient ProgressPanel progressPane;
private transient BackgroundWorker backgroundWorker; private transient BackgroundWorker backgroundWorker;
private transient BackgroundExecutor backgroundExecutor;
private transient Theme editorTheme; private transient Theme editorTheme;
public MainWindow(JadxSettings settings) { public MainWindow(JadxSettings settings) {
...@@ -172,6 +174,8 @@ public class MainWindow extends JFrame { ...@@ -172,6 +174,8 @@ public class MainWindow extends JFrame {
loadSettings(); loadSettings();
checkForUpdate(); checkForUpdate();
newProject(); newProject();
this.backgroundExecutor = new BackgroundExecutor(this);
} }
public void init() { public void init() {
...@@ -252,14 +256,6 @@ public class MainWindow extends JFrame { ...@@ -252,14 +256,6 @@ public class MainWindow extends JFrame {
clearTree(); clearTree();
} }
private void clearTree() {
tabbedPane.closeAllTabs();
resetCache();
treeRoot = null;
treeModel.setRoot(treeRoot);
treeModel.reload();
}
private void saveProject() { private void saveProject() {
if (project.getProjectPath() == null) { if (project.getProjectPath() == null) {
saveProjectAs(); saveProjectAs();
...@@ -310,13 +306,15 @@ public class MainWindow extends JFrame { ...@@ -310,13 +306,15 @@ public class MainWindow extends JFrame {
openProject(path); openProject(path);
} else { } else {
project.setFilePath(path); project.setFilePath(path);
tabbedPane.closeAllTabs(); clearTree();
resetCache(); backgroundExecutor.execute(NLS.str("progress.load"),
wrapper.openFile(path.toFile()); () -> wrapper.openFile(path.toFile()),
deobfToggleBtn.setSelected(settings.isDeobfuscationOn()); () -> {
initTree(); deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
update(); initTree();
runBackgroundJobs(); update();
runBackgroundJobs();
});
} }
} }
...@@ -398,6 +396,7 @@ public class MainWindow extends JFrame { ...@@ -398,6 +396,7 @@ public class MainWindow extends JFrame {
} }
public synchronized void cancelBackgroundJobs() { public synchronized void cancelBackgroundJobs() {
backgroundExecutor.cancelAll();
if (backgroundWorker != null) { if (backgroundWorker != null) {
backgroundWorker.stop(); backgroundWorker.stop();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane); backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
...@@ -492,6 +491,14 @@ public class MainWindow extends JFrame { ...@@ -492,6 +491,14 @@ public class MainWindow extends JFrame {
reloadTree(); reloadTree();
} }
private void clearTree() {
tabbedPane.closeAllTabs();
resetCache();
treeRoot = null;
treeModel.setRoot(treeRoot);
treeModel.reload();
}
private void reloadTree() { private void reloadTree() {
treeReloading = true; treeReloading = true;
...@@ -1101,6 +1108,10 @@ public class MainWindow extends JFrame { ...@@ -1101,6 +1108,10 @@ public class MainWindow extends JFrame {
return backgroundWorker; return backgroundWorker;
} }
public ProgressPanel getProgressPane() {
return progressPane;
}
private class RecentProjectsMenuListener implements MenuListener { private class RecentProjectsMenuListener implements MenuListener {
private final JMenu recentProjects; private final JMenu recentProjects;
......
package jadx.gui.ui; package jadx.gui.ui;
import java.awt.*; import java.awt.Dimension;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import javax.swing.*; import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import jadx.gui.utils.UiUtils; import jadx.gui.utils.UiUtils;
...@@ -16,8 +23,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener { ...@@ -16,8 +23,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
private final JProgressBar progressBar; private final JProgressBar progressBar;
private final JLabel progressLabel; private final JLabel progressLabel;
private final JButton cancelButton;
private final boolean showCancelButton;
public ProgressPanel(final MainWindow mainWindow, boolean showCancelButton) { public ProgressPanel(final MainWindow mainWindow, boolean showCancelButton) {
this.showCancelButton = showCancelButton;
progressLabel = new JLabel(); progressLabel = new JLabel();
progressBar = new JProgressBar(0, 100); progressBar = new JProgressBar(0, 100);
progressBar.setIndeterminate(true); progressBar.setIndeterminate(true);
...@@ -30,28 +41,47 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener { ...@@ -30,28 +41,47 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
add(progressLabel); add(progressLabel);
add(progressBar); add(progressBar);
if (showCancelButton) { cancelButton = new JButton(ICON_CANCEL);
JButton cancelButton = new JButton(ICON_CANCEL); cancelButton.setPreferredSize(new Dimension(ICON_CANCEL.getIconWidth(), ICON_CANCEL.getIconHeight()));
cancelButton.setPreferredSize(new Dimension(ICON_CANCEL.getIconWidth(), ICON_CANCEL.getIconHeight())); cancelButton.setToolTipText("Cancel background jobs");
cancelButton.setToolTipText("Cancel background jobs"); cancelButton.setBorderPainted(false);
cancelButton.setBorderPainted(false); cancelButton.setFocusPainted(false);
cancelButton.setFocusPainted(false); cancelButton.setContentAreaFilled(false);
cancelButton.setContentAreaFilled(false); cancelButton.addActionListener(e -> mainWindow.cancelBackgroundJobs());
cancelButton.addActionListener(e -> mainWindow.cancelBackgroundJobs()); cancelButton.setVisible(showCancelButton);
add(cancelButton); add(cancelButton);
} }
public void reset() {
cancelButton.setVisible(showCancelButton);
progressBar.setIndeterminate(true);
progressBar.setValue(0);
progressBar.setString("");
progressBar.setStringPainted(true);
} }
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) { switch (evt.getPropertyName()) {
int progress = (Integer) evt.getNewValue(); case "progress":
progressBar.setIndeterminate(false); int progress = (Integer) evt.getNewValue();
progressBar.setValue(progress); progressBar.setIndeterminate(false);
progressBar.setString(progress + "%"); progressBar.setValue(progress);
progressBar.setStringPainted(true); progressBar.setString(progress + "%");
} else if ("label".equals(evt.getPropertyName())) { progressBar.setStringPainted(true);
setLabel((String) evt.getNewValue()); break;
case "label":
setLabel((String) evt.getNewValue());
break;
case "visible":
setVisible(((Boolean) evt.getNewValue()));
break;
case "cancel-visible":
cancelButton.setVisible(((Boolean) evt.getNewValue()));
break;
} }
} }
...@@ -66,4 +96,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener { ...@@ -66,4 +96,12 @@ public class ProgressPanel extends JPanel implements PropertyChangeListener {
public void changeLabel(SwingWorker<?, ?> task, String label) { public void changeLabel(SwingWorker<?, ?> task, String label) {
task.firePropertyChange("label", null, label); task.firePropertyChange("label", null, label);
} }
public void changeVisibility(SwingWorker<?, ?> task, boolean visible) {
task.firePropertyChange("visible", null, visible);
}
public void changeCancelBtnVisible(SwingWorker<?, ?> task, boolean visible) {
task.firePropertyChange("cancel-visible", null, visible);
}
} }
...@@ -62,6 +62,14 @@ public class NLS { ...@@ -62,6 +62,14 @@ public class NLS {
LANG_LOCALES_MAP.put(locale, bundle); LANG_LOCALES_MAP.put(locale, bundle);
} }
public static String str(String key) {
try {
return localizedMessagesMap.getString(key);
} catch (Exception e) {
return FALLBACK_MESSAGES_MAP.getString(key);
}
}
public static String str(String key, Object... parameters) { public static String str(String key, Object... parameters) {
String value; String value;
try { try {
......
...@@ -32,6 +32,10 @@ tree.sources_title=Quellcode ...@@ -32,6 +32,10 @@ tree.sources_title=Quellcode
tree.resources_title=Ressourcen tree.resources_title=Ressourcen
tree.loading=Laden… tree.loading=Laden…
progress.load=Laden
progress.decompile=Decompiling
progress.index=Indexing
error_dialog.title=Fehler error_dialog.title=Fehler
search.previous=Zurück search.previous=Zurück
...@@ -133,7 +137,7 @@ msg.project_error_title=Fehler ...@@ -133,7 +137,7 @@ msg.project_error_title=Fehler
msg.project_error=Projekt konnte nicht geladen werden msg.project_error=Projekt konnte nicht geladen werden
msg.rename_disabled_title=Umbenennen deaktiviert msg.rename_disabled_title=Umbenennen deaktiviert
msg.rename_disabled=Einige der Umbenennungseinstellungen sind deaktiviert, bitte beachten Sie dies. msg.rename_disabled=Einige der Umbenennungseinstellungen sind deaktiviert, bitte beachten Sie dies.
msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht. msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht.
popup.undo=Rückgängig popup.undo=Rückgängig
popup.redo=Wiederholen popup.redo=Wiederholen
......
...@@ -32,6 +32,10 @@ tree.sources_title=Source code ...@@ -32,6 +32,10 @@ tree.sources_title=Source code
tree.resources_title=Resources tree.resources_title=Resources
tree.loading=Loading... tree.loading=Loading...
progress.load=Loading
progress.decompile=Decompiling
progress.index=Indexing
error_dialog.title=Error error_dialog.title=Error
search.previous=Previous search.previous=Previous
...@@ -133,7 +137,7 @@ msg.project_error_title=Error ...@@ -133,7 +137,7 @@ msg.project_error_title=Error
msg.project_error=Project could not be loaded msg.project_error=Project could not be loaded
msg.rename_disabled_title=Rename disabled msg.rename_disabled_title=Rename disabled
msg.rename_disabled=Some of rename settings are disabled, please take this into consideration msg.rename_disabled=Some of rename settings are disabled, please take this into consideration
msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist.
popup.undo=Undo popup.undo=Undo
popup.redo=Redo popup.redo=Redo
......
...@@ -32,6 +32,10 @@ tree.sources_title=Código fuente ...@@ -32,6 +32,10 @@ tree.sources_title=Código fuente
tree.resources_title=Recursos tree.resources_title=Recursos
tree.loading=Cargando... tree.loading=Cargando...
progress.load=Cargando
progress.decompile=Decompiling
progress.index=Indexing
#error_dialog.title= #error_dialog.title=
search.previous=Anterior search.previous=Anterior
......
...@@ -32,6 +32,10 @@ tree.sources_title=源代码 ...@@ -32,6 +32,10 @@ tree.sources_title=源代码
tree.resources_title=资源文件 tree.resources_title=资源文件
tree.loading=稍等... tree.loading=稍等...
progress.load=稍等
progress.decompile=Decompiling
progress.index=Indexing
error_dialog.title=错误 error_dialog.title=错误
search.previous=上一个 search.previous=上一个
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册