fix(gui): improve search dialog performance
This commit is contained in:
@@ -145,8 +145,8 @@ public class BackgroundExecutor {
|
||||
task.onDone(this);
|
||||
// treat UI task operations as part of the task to not mix with others
|
||||
UiUtils.uiRunAndWait(() -> {
|
||||
task.onFinish(this);
|
||||
progressPane.setVisible(false);
|
||||
task.onFinish(this);
|
||||
});
|
||||
} finally {
|
||||
taskComplete(id);
|
||||
@@ -190,13 +190,21 @@ public class BackgroundExecutor {
|
||||
performCancel(executor);
|
||||
return cancelStatus;
|
||||
}
|
||||
updateProgress(executor);
|
||||
k++;
|
||||
Thread.sleep(k < 10 ? 200 : 1000); // faster update for short tasks
|
||||
if (jobsCount == 1 && k == 3) {
|
||||
if (k < 10) {
|
||||
// faster update for short tasks
|
||||
Thread.sleep(200);
|
||||
if (k == 5) {
|
||||
updateProgress(executor);
|
||||
}
|
||||
} else {
|
||||
updateProgress(executor);
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (jobsCount == 1 && k == 5) {
|
||||
// small delay before show progress to reduce blinking on short tasks
|
||||
progressPane.changeVisibility(this, true);
|
||||
}
|
||||
k++;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.debug("Task wait interrupted");
|
||||
@@ -210,7 +218,7 @@ public class BackgroundExecutor {
|
||||
}
|
||||
|
||||
private void updateProgress(ThreadPoolExecutor executor) {
|
||||
Consumer<ITaskProgress> onProgressListener = task.getOnProgressListener();
|
||||
Consumer<ITaskProgress> onProgressListener = task.getProgressListener();
|
||||
ITaskProgress taskProgress = task.getTaskProgress();
|
||||
if (taskProgress == null) {
|
||||
setProgress(calcProgress(executor.getCompletedTaskCount(), jobsCount));
|
||||
@@ -231,13 +239,16 @@ public class BackgroundExecutor {
|
||||
// force termination
|
||||
task.cancel();
|
||||
executor.shutdown();
|
||||
if (executor.awaitTermination(2, TimeUnit.SECONDS)) {
|
||||
LOG.debug("Task cancel complete");
|
||||
return;
|
||||
int cancelTimeout = task.getCancelTimeoutMS();
|
||||
if (cancelTimeout != 0) {
|
||||
if (executor.awaitTermination(cancelTimeout, TimeUnit.MILLISECONDS)) {
|
||||
LOG.debug("Task cancel complete");
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.debug("Forcing tasks cancel");
|
||||
executor.shutdownNow();
|
||||
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||
boolean complete = executor.awaitTermination(task.getShutdownTimeoutMS(), TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Forced task cancel status: {}",
|
||||
complete ? "success" : "fail, still active: " + executor.getActiveCount());
|
||||
}
|
||||
@@ -301,5 +312,13 @@ public class BackgroundExecutor {
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TaskWorker{status=" + status
|
||||
+ ", jobsCount=" + jobsCount
|
||||
+ ", jobsComplete=" + jobsComplete
|
||||
+ ", time=" + time + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,12 @@ public interface Cancelable {
|
||||
boolean isCanceled();
|
||||
|
||||
void cancel();
|
||||
|
||||
default int getCancelTimeoutMS() {
|
||||
return 2000;
|
||||
}
|
||||
|
||||
default int getShutdownTimeoutMS() {
|
||||
return 5000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public interface IBackgroundTask extends Cancelable {
|
||||
/**
|
||||
* Return progress notifications listener (use executor tick rate and thread) (Optional)
|
||||
*/
|
||||
default @Nullable Consumer<ITaskProgress> getOnProgressListener() {
|
||||
default @Nullable Consumer<ITaskProgress> getProgressListener() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -27,8 +29,8 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SearchTask.class);
|
||||
|
||||
private final BackgroundExecutor backgroundExecutor;
|
||||
private final Consumer<ITaskInfo> onFinish;
|
||||
private final Consumer<JNode> results;
|
||||
private final Consumer<JNode> resultsListener;
|
||||
private final BiConsumer<ITaskInfo, Boolean> onFinish;
|
||||
private final List<SearchJob> jobs = new ArrayList<>();
|
||||
private final TaskProgress taskProgress = new TaskProgress();
|
||||
|
||||
@@ -39,9 +41,9 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
|
||||
private Consumer<ITaskProgress> progressListener;
|
||||
|
||||
public SearchTask(MainWindow mainWindow, Consumer<JNode> results, Consumer<ITaskInfo> onFinish) {
|
||||
public SearchTask(MainWindow mainWindow, Consumer<JNode> results, BiConsumer<ITaskInfo, Boolean> onFinish) {
|
||||
this.backgroundExecutor = mainWindow.getBackgroundExecutor();
|
||||
this.results = results;
|
||||
this.resultsListener = results;
|
||||
this.onFinish = onFinish;
|
||||
}
|
||||
|
||||
@@ -55,8 +57,7 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
|
||||
public synchronized void fetchResults() {
|
||||
if (future != null) {
|
||||
cancel();
|
||||
waitTask();
|
||||
throw new IllegalStateException("Previous task not yet finished");
|
||||
}
|
||||
resetCancel();
|
||||
complete.set(false);
|
||||
@@ -70,9 +71,10 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
// ignore new results after cancel
|
||||
return true;
|
||||
}
|
||||
this.results.accept(resultNode);
|
||||
this.resultsListener.accept(resultNode);
|
||||
if (resultsLimit != 0 && resultsCount.incrementAndGet() >= resultsLimit) {
|
||||
cancel();
|
||||
complete.set(false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -81,14 +83,17 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
public synchronized void waitTask() {
|
||||
if (future != null) {
|
||||
try {
|
||||
future.get(2, TimeUnit.SECONDS);
|
||||
future.get(200, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
LOG.debug("Search task wait timeout");
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Wait search task failed", e);
|
||||
LOG.warn("Search task wait error", e);
|
||||
} finally {
|
||||
future.cancel(true);
|
||||
future = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,18 +106,12 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
return jobs;
|
||||
}
|
||||
|
||||
public boolean isSearchComplete() {
|
||||
return complete.get() && !isCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDone(ITaskInfo taskInfo) {
|
||||
this.complete.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(ITaskInfo status) {
|
||||
this.onFinish.accept(status);
|
||||
public void onFinish(ITaskInfo task) {
|
||||
boolean complete = !isCanceled()
|
||||
&& task.getStatus() == TaskStatus.COMPLETE
|
||||
&& task.getJobsComplete() == task.getJobsCount();
|
||||
this.onFinish.accept(task, complete);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,7 +130,17 @@ public class SearchTask extends CancelableBackgroundTask {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Consumer<ITaskProgress> getOnProgressListener() {
|
||||
public @Nullable Consumer<ITaskProgress> getProgressListener() {
|
||||
return this.progressListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCancelTimeoutMS() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShutdownTimeoutMS() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,8 @@ import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -77,9 +74,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
protected JLabel warnLabel;
|
||||
protected ProgressPanel progressPane;
|
||||
|
||||
private String highlightText;
|
||||
protected boolean highlightTextCaseInsensitive = false;
|
||||
protected boolean highlightTextUseRegex = false;
|
||||
private SearchContext highlightContext;
|
||||
|
||||
public CommonSearchDialog(MainWindow mainWindow, String title) {
|
||||
this.mainWindow = mainWindow;
|
||||
@@ -88,7 +83,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
this.codeFont = mainWindow.getSettings().getFont();
|
||||
this.windowTitle = title;
|
||||
UiUtils.setWindowIcons(this);
|
||||
updateTitle();
|
||||
updateTitle("");
|
||||
}
|
||||
|
||||
protected abstract void openInit();
|
||||
@@ -103,17 +98,24 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
if (highlightText == null || highlightText.trim().isEmpty()) {
|
||||
private void updateTitle(String searchText) {
|
||||
if (searchText == null || searchText.isEmpty() || searchText.trim().isEmpty()) {
|
||||
setTitle(windowTitle);
|
||||
} else {
|
||||
setTitle(windowTitle + ": " + highlightText);
|
||||
setTitle(windowTitle + ": " + searchText);
|
||||
}
|
||||
}
|
||||
|
||||
public void setHighlightText(String highlightText) {
|
||||
this.highlightText = highlightText;
|
||||
updateTitle();
|
||||
public void updateHighlightContext(String text, boolean caseSensitive, boolean regexp) {
|
||||
updateTitle(text);
|
||||
highlightContext = new SearchContext(text);
|
||||
highlightContext.setMatchCase(caseSensitive);
|
||||
highlightContext.setRegularExpression(regexp);
|
||||
highlightContext.setMarkAll(true);
|
||||
}
|
||||
|
||||
public void disableHighlight() {
|
||||
highlightContext = null;
|
||||
}
|
||||
|
||||
protected void registerInitOnOpen() {
|
||||
@@ -174,16 +176,16 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
openBtn.addActionListener(event -> openSelectedItem());
|
||||
getRootPane().setDefaultButton(openBtn);
|
||||
|
||||
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();
|
||||
});
|
||||
cbKeepOpen.setAlignmentY(Component.CENTER_ALIGNMENT);
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.add(cbKeepOpen);
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(15, 0)));
|
||||
buttonPane.add(progressPane);
|
||||
@@ -197,7 +199,7 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
protected JPanel initResultsTable() {
|
||||
ResultsTableCellRenderer renderer = new ResultsTableCellRenderer();
|
||||
resultsModel = new ResultsModel(renderer);
|
||||
resultsModel = new ResultsModel();
|
||||
resultsModel.addTableModelListener(e -> updateProgressLabel(false));
|
||||
|
||||
resultsTable = new ResultsTable(resultsModel, renderer);
|
||||
@@ -247,7 +249,6 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
warnLabel.setVisible(false);
|
||||
|
||||
JScrollPane scroll = new JScrollPane(resultsTable, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
// scroll.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
|
||||
|
||||
JPanel resultsActionsPanel = new JPanel();
|
||||
resultsActionsPanel.setLayout(new BoxLayout(resultsActionsPanel, BoxLayout.LINE_AXIS));
|
||||
@@ -261,7 +262,6 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
JPanel resultsPanel = new JPanel();
|
||||
resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.PAGE_AXIS));
|
||||
resultsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
resultsPanel.add(warnLabel, BorderLayout.PAGE_START);
|
||||
resultsPanel.add(scroll, BorderLayout.CENTER);
|
||||
resultsPanel.add(resultsActionsPanel, BorderLayout.PAGE_END);
|
||||
@@ -288,13 +288,12 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
protected static final class ResultsTable extends JTable {
|
||||
private static final long serialVersionUID = 3901184054736618969L;
|
||||
private final transient ResultsTableCellRenderer renderer;
|
||||
private final transient ResultsModel model;
|
||||
|
||||
public ResultsTable(ResultsModel resultsModel, ResultsTableCellRenderer renderer) {
|
||||
super(resultsModel);
|
||||
this.model = resultsModel;
|
||||
this.renderer = renderer;
|
||||
setRowHeight(renderer.getMaxRowHeight());
|
||||
}
|
||||
|
||||
public void initColumnWidth() {
|
||||
@@ -309,6 +308,11 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
public void updateTable() {
|
||||
UiUtils.uiThreadGuard();
|
||||
int rowCount = getRowCount();
|
||||
if (rowCount == 0) {
|
||||
updateUI();
|
||||
return;
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
int width = getParent().getWidth();
|
||||
TableColumn firstColumn = columnModel.getColumn(0);
|
||||
@@ -325,30 +329,6 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
} else {
|
||||
firstColumn.setPreferredWidth(width);
|
||||
}
|
||||
int rowCount = getRowCount();
|
||||
int columnCount = getColumnCount();
|
||||
Map<Class<?>, Integer> heightByType = new HashMap<>();
|
||||
for (int row = 0; row < rowCount; row++) {
|
||||
Object value = model.getValueAt(row, 0);
|
||||
Class<?> valueType = value.getClass();
|
||||
Integer cachedHeight = heightByType.get(valueType);
|
||||
if (cachedHeight != null) {
|
||||
setRowHeight(row, cachedHeight);
|
||||
} else {
|
||||
int height = 0;
|
||||
for (int col = 0; col < columnCount; col++) {
|
||||
Component comp = prepareRenderer(renderer, row, col);
|
||||
if (comp == null) {
|
||||
continue;
|
||||
}
|
||||
Dimension preferredSize = comp.getPreferredSize();
|
||||
int h = Math.max(comp.getHeight(), preferredSize.height);
|
||||
height = Math.max(height, h);
|
||||
}
|
||||
heightByType.put(valueType, height);
|
||||
setRowHeight(row, height);
|
||||
}
|
||||
}
|
||||
updateUI();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Update results table in {}ms, count: {}", System.currentTimeMillis() - start, rowCount);
|
||||
@@ -365,14 +345,9 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
private static final long serialVersionUID = -7821286846923903208L;
|
||||
private static final String[] COLUMN_NAMES = { NLS.str("search_dialog.col_node"), NLS.str("search_dialog.col_code") };
|
||||
|
||||
private final transient List<JNode> rows = Collections.synchronizedList(new ArrayList<>());
|
||||
private final transient ResultsTableCellRenderer renderer;
|
||||
private final transient List<JNode> rows = new ArrayList<>();
|
||||
private transient boolean addDescColumn;
|
||||
|
||||
public ResultsModel(ResultsTableCellRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
public void addAll(Collection<? extends JNode> nodes) {
|
||||
rows.addAll(nodes);
|
||||
if (!addDescColumn) {
|
||||
@@ -388,7 +363,6 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
public void clear() {
|
||||
addDescColumn = false;
|
||||
rows.clear();
|
||||
renderer.clear();
|
||||
}
|
||||
|
||||
public boolean isAddDescColumn() {
|
||||
@@ -417,38 +391,36 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
}
|
||||
|
||||
protected final class ResultsTableCellRenderer implements TableCellRenderer {
|
||||
private final JLabel emptyLabel = new JLabel();
|
||||
private final Font font;
|
||||
private final JLabel label;
|
||||
private final RSyntaxTextArea codeArea;
|
||||
private final JLabel emptyLabel;
|
||||
private final Color codeSelectedColor;
|
||||
private final Color codeBackground;
|
||||
private final Map<Integer, Component> componentCache = new HashMap<>();
|
||||
|
||||
public ResultsTableCellRenderer() {
|
||||
RSyntaxTextArea area = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
this.font = area.getFont();
|
||||
this.codeSelectedColor = area.getSelectionColor();
|
||||
this.codeBackground = area.getBackground();
|
||||
codeArea = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
codeArea.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
codeArea.setRows(1);
|
||||
codeBackground = codeArea.getBackground();
|
||||
codeSelectedColor = codeArea.getSelectionColor();
|
||||
label = new JLabel();
|
||||
label.setOpaque(true);
|
||||
label.setFont(codeArea.getFont());
|
||||
label.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
emptyLabel = new JLabel();
|
||||
emptyLabel.setOpaque(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object obj,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
Component comp = componentCache.computeIfAbsent(makeID(row, column), id -> {
|
||||
if (obj instanceof JNode) {
|
||||
return makeCell((JNode) obj, column);
|
||||
}
|
||||
return emptyLabel;
|
||||
});
|
||||
updateSelection(table, comp, isSelected);
|
||||
Component comp = makeCell((JNode) obj, column);
|
||||
updateSelection(table, comp, column, isSelected);
|
||||
return comp;
|
||||
}
|
||||
|
||||
private int makeID(int row, int col) {
|
||||
return row << 2 | (col & 0b11);
|
||||
}
|
||||
|
||||
private void updateSelection(JTable table, Component comp, boolean isSelected) {
|
||||
if (comp instanceof RSyntaxTextArea) {
|
||||
private void updateSelection(JTable table, Component comp, int column, boolean isSelected) {
|
||||
if (column == 1) {
|
||||
if (isSelected) {
|
||||
comp.setBackground(codeSelectedColor);
|
||||
} else {
|
||||
@@ -467,39 +439,32 @@ public abstract class CommonSearchDialog extends JFrame {
|
||||
|
||||
private Component makeCell(JNode node, int column) {
|
||||
if (column == 0) {
|
||||
JLabel label = new JLabel(node.makeLongStringHtml(), node.getIcon(), SwingConstants.LEFT);
|
||||
label.setFont(font);
|
||||
label.setOpaque(true);
|
||||
label.setText(node.makeLongStringHtml());
|
||||
label.setToolTipText(label.getText());
|
||||
label.setIcon(node.getIcon());
|
||||
return label;
|
||||
}
|
||||
if (!node.hasDescString()) {
|
||||
return emptyLabel;
|
||||
}
|
||||
|
||||
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
textArea.setSyntaxEditingStyle(node.getSyntaxName());
|
||||
codeArea.setSyntaxEditingStyle(node.getSyntaxName());
|
||||
String descStr = node.makeDescString();
|
||||
textArea.setText(descStr);
|
||||
if (descStr.contains("\n")) {
|
||||
textArea.setRows(textArea.getLineCount());
|
||||
} else {
|
||||
textArea.setRows(1);
|
||||
textArea.setColumns(descStr.length() + 1);
|
||||
codeArea.setText(descStr);
|
||||
codeArea.setColumns(descStr.length() + 1);
|
||||
if (highlightContext != null) {
|
||||
SearchEngine.markAll(codeArea, highlightContext);
|
||||
}
|
||||
if (highlightText != null) {
|
||||
SearchContext searchContext = new SearchContext(highlightText);
|
||||
searchContext.setMatchCase(!highlightTextCaseInsensitive);
|
||||
searchContext.setRegularExpression(highlightTextUseRegex);
|
||||
searchContext.setMarkAll(true);
|
||||
SearchEngine.markAll(textArea, searchContext);
|
||||
}
|
||||
textArea.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
return textArea;
|
||||
return codeArea;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
componentCache.clear();
|
||||
public int getMaxRowHeight() {
|
||||
label.setText("Text");
|
||||
codeArea.setText("Text");
|
||||
return Math.max(getCompHeight(label), getCompHeight(codeArea));
|
||||
}
|
||||
|
||||
private int getCompHeight(Component comp) {
|
||||
return Math.max(comp.getHeight(), comp.getPreferredSize().height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.gui.ui.dialog;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.KeyAdapter;
|
||||
@@ -13,6 +12,8 @@ import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -25,21 +26,20 @@ import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import hu.akarnokd.rxjava2.swing.SwingSchedulers;
|
||||
import io.reactivex.BackpressureStrategy;
|
||||
import io.reactivex.Emitter;
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.gui.jobs.ITaskInfo;
|
||||
import jadx.gui.jobs.ITaskProgress;
|
||||
import jadx.gui.search.SearchSettings;
|
||||
import jadx.gui.search.SearchTask;
|
||||
@@ -58,6 +58,7 @@ import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||
|
||||
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.ACTIVE_TAB;
|
||||
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.CLASS;
|
||||
@@ -128,6 +129,11 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
// temporal list for pending results
|
||||
private final List<JNode> pendingResults = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Use single thread to do all background work, so additional synchronisation not needed
|
||||
*/
|
||||
private final Executor searchBackgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptions> additionalOptions) {
|
||||
super(mainWindow, NLS.str("menu.text_search"));
|
||||
this.searchPreset = preset;
|
||||
@@ -148,13 +154,10 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
resultsModel.clear();
|
||||
removeActiveTabListener();
|
||||
if (searchTask != null) {
|
||||
searchTask.cancel();
|
||||
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"), () -> {
|
||||
stopSearchTask();
|
||||
unloadTempData();
|
||||
});
|
||||
}
|
||||
searchBackgroundExecutor.execute(() -> {
|
||||
stopSearchTask();
|
||||
unloadTempData();
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -167,7 +170,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
case TEXT:
|
||||
if (searchOptions.isEmpty()) {
|
||||
searchOptions.add(SearchOptions.CODE);
|
||||
searchOptions.add(SearchOptions.IGNORE_CASE);
|
||||
searchOptions.add(IGNORE_CASE);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -205,16 +208,20 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
searchField.setAlignmentX(LEFT_ALIGNMENT);
|
||||
TextStandardActions.attach(searchField);
|
||||
|
||||
JPanel searchLinePanel = new JPanel();
|
||||
searchLinePanel.setLayout(new BoxLayout(searchLinePanel, BoxLayout.LINE_AXIS));
|
||||
searchLinePanel.add(searchField);
|
||||
searchLinePanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
|
||||
findLabel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JPanel searchFieldPanel = new JPanel();
|
||||
searchFieldPanel.setLayout(new BoxLayout(searchFieldPanel, BoxLayout.PAGE_AXIS));
|
||||
searchFieldPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
|
||||
searchFieldPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
searchFieldPanel.add(findLabel);
|
||||
searchFieldPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchFieldPanel.add(searchField);
|
||||
searchFieldPanel.add(searchLinePanel);
|
||||
|
||||
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
|
||||
@@ -227,18 +234,17 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), IGNORE_CASE));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), USE_REGEX));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.active_tab"), SearchOptions.ACTIVE_TAB));
|
||||
|
||||
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT));
|
||||
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT, 0, 0));
|
||||
optionsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
optionsPanel.add(searchInPanel);
|
||||
optionsPanel.add(searchOptions);
|
||||
|
||||
JPanel searchPane = new JPanel();
|
||||
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
|
||||
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
searchPane.add(searchFieldPanel);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(optionsPanel);
|
||||
@@ -247,10 +253,13 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
JPanel resultsPanel = initResultsTable();
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPane.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setLayout(new BorderLayout(5, 5));
|
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
contentPanel.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPanel.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
|
||||
getContentPane().add(contentPanel);
|
||||
|
||||
searchField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
@@ -269,11 +278,11 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
protected void addCustomResultsActions(JPanel resultsActionsPanel) {
|
||||
loadAllButton = new JButton(NLS.str("search_dialog.load_all"));
|
||||
loadAllButton.addActionListener(e -> loadAll());
|
||||
loadAllButton.addActionListener(e -> loadMoreResults(true));
|
||||
loadAllButton.setEnabled(false);
|
||||
|
||||
loadMoreButton = new JButton(NLS.str("search_dialog.load_more"));
|
||||
loadMoreButton.addActionListener(e -> loadMore());
|
||||
loadMoreButton.addActionListener(e -> loadMoreResults(false));
|
||||
loadMoreButton.setEnabled(false);
|
||||
|
||||
resultsActionsPanel.add(loadAllButton);
|
||||
@@ -307,21 +316,36 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
Flowable<String> textChanges = onTextFieldChanges(searchField);
|
||||
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
|
||||
searchDisposable = searchEvents
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(SwingSchedulers.edt())
|
||||
.debounce(50, TimeUnit.MILLISECONDS)
|
||||
.observeOn(Schedulers.from(searchBackgroundExecutor))
|
||||
.subscribe(this::search);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private synchronized void search(String text) {
|
||||
UiUtils.uiThreadGuard();
|
||||
resetSearch();
|
||||
if (text == null || options.isEmpty()) {
|
||||
private void search(String text) {
|
||||
UiUtils.notUiThreadGuard();
|
||||
stopSearchTask();
|
||||
UiUtils.uiRun(this::resetSearch);
|
||||
searchTask = prepareSearch(text);
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
UiUtils.uiRunAndWait(() -> {
|
||||
updateTableHighlight();
|
||||
prepareForSearch();
|
||||
});
|
||||
this.searchTask.setResultsLimit(50);
|
||||
this.searchTask.setProgressListener(this::updateProgress);
|
||||
this.searchTask.fetchResults();
|
||||
LOG.debug("Total search items count estimation: {}", this.searchTask.getTaskProgress().total());
|
||||
}
|
||||
|
||||
private SearchTask prepareSearch(String text) {
|
||||
if (text == null || options.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && !options.contains(SearchOptions.COMMENT)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
LOG.debug("Building search for '{}', options: {}", text, options);
|
||||
boolean ignoreCase = options.contains(IGNORE_CASE);
|
||||
@@ -335,24 +359,16 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
} else {
|
||||
searchField.setBackground(SEARCH_FIELD_ERROR_COLOR);
|
||||
resultsInfoLabel.setText(error);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
searchTask = new SearchTask(mainWindow, this::addSearchResult, s -> searchComplete());
|
||||
if (!buildSearch(text, searchSettings)) {
|
||||
return;
|
||||
SearchTask newSearchTask = new SearchTask(mainWindow, this::addSearchResult, this::searchFinished);
|
||||
if (!buildSearch(newSearchTask, text, searchSettings)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
updateTableHighlight();
|
||||
startSearch();
|
||||
searchTask.setResultsLimit(100);
|
||||
searchTask.setProgressListener(this::updateProgress);
|
||||
searchTask.fetchResults();
|
||||
LOG.debug("Total search items count estimation: {}", searchTask.getTaskProgress().total());
|
||||
return newSearchTask;
|
||||
}
|
||||
|
||||
private boolean buildSearch(String text, SearchSettings searchSettings) {
|
||||
Objects.requireNonNull(searchTask);
|
||||
|
||||
private boolean buildSearch(SearchTask newSearchTask, String text, SearchSettings searchSettings) {
|
||||
List<JavaClass> allClasses;
|
||||
if (options.contains(ACTIVE_TAB)) {
|
||||
JumpPosition currentPos = mainWindow.getTabbedPane().getCurrentPosition();
|
||||
@@ -368,7 +384,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && options.contains(SearchOptions.COMMENT)) {
|
||||
searchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
return true;
|
||||
}
|
||||
// using ordered execution for fast tasks
|
||||
@@ -384,84 +400,92 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
if (options.contains(CODE)) {
|
||||
if (allClasses.size() == 1) {
|
||||
searchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
} else {
|
||||
List<JavaClass> topClasses = ListUtils.filter(allClasses, c -> !c.isInner());
|
||||
for (List<JavaClass> batch : mainWindow.getWrapper().buildDecompileBatches(topClasses)) {
|
||||
searchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, batch));
|
||||
List<List<JavaClass>> batches = mainWindow.getCacheObject().getDecompileBatches();
|
||||
if (batches == null) {
|
||||
List<JavaClass> topClasses = ListUtils.filter(allClasses, c -> !c.isInner());
|
||||
batches = mainWindow.getWrapper().buildDecompileBatches(topClasses);
|
||||
mainWindow.getCacheObject().setDecompileBatches(batches);
|
||||
}
|
||||
for (List<JavaClass> batch : batches) {
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, batch));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.contains(RESOURCE)) {
|
||||
searchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings));
|
||||
newSearchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings));
|
||||
}
|
||||
if (options.contains(COMMENT)) {
|
||||
searchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
}
|
||||
merged.prepare();
|
||||
searchTask.addProviderJob(merged);
|
||||
newSearchTask.addProviderJob(merged);
|
||||
return true;
|
||||
}
|
||||
|
||||
private synchronized void stopSearchTask() {
|
||||
private void stopSearchTask() {
|
||||
UiUtils.notUiThreadGuard();
|
||||
if (searchTask != null) {
|
||||
searchTask.cancel();
|
||||
searchTask.waitTask();
|
||||
searchTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadMore() {
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
startSearch();
|
||||
searchTask.fetchResults();
|
||||
private void loadMoreResults(boolean all) {
|
||||
searchBackgroundExecutor.execute(() -> {
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
searchTask.cancel();
|
||||
searchTask.waitTask();
|
||||
UiUtils.uiRunAndWait(this::prepareForSearch);
|
||||
if (all) {
|
||||
searchTask.setResultsLimit(0);
|
||||
}
|
||||
searchTask.fetchResults();
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void loadAll() {
|
||||
if (searchTask == null) {
|
||||
return;
|
||||
}
|
||||
startSearch();
|
||||
searchTask.setResultsLimit(0);
|
||||
searchTask.fetchResults();
|
||||
}
|
||||
|
||||
private synchronized void resetSearch() {
|
||||
private void resetSearch() {
|
||||
UiUtils.uiThreadGuard();
|
||||
resultsModel.clear();
|
||||
updateTable();
|
||||
resultsTable.updateTable();
|
||||
synchronized (pendingResults) {
|
||||
pendingResults.clear();
|
||||
}
|
||||
progressPane.setVisible(false);
|
||||
warnLabel.setVisible(false);
|
||||
loadAllButton.setEnabled(false);
|
||||
loadMoreButton.setEnabled(false);
|
||||
stopSearchTask();
|
||||
}
|
||||
|
||||
private void startSearch() {
|
||||
private void prepareForSearch() {
|
||||
showSearchState();
|
||||
progressStartCommon();
|
||||
}
|
||||
|
||||
private void addSearchResult(JNode node) {
|
||||
synchronized (pendingResults) {
|
||||
UiUtils.notUiThreadGuard();
|
||||
pendingResults.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTable() {
|
||||
synchronized (pendingResults) {
|
||||
UiUtils.uiThreadGuard();
|
||||
Collections.sort(pendingResults);
|
||||
resultsModel.addAll(pendingResults);
|
||||
pendingResults.clear();
|
||||
resultsTable.updateTable();
|
||||
}
|
||||
resultsTable.updateTable();
|
||||
}
|
||||
|
||||
private void updateTableHighlight() {
|
||||
String text = searchField.getText();
|
||||
setHighlightText(text);
|
||||
highlightTextCaseInsensitive = options.contains(SearchOptions.IGNORE_CASE);
|
||||
highlightTextUseRegex = options.contains(SearchOptions.USE_REGEX);
|
||||
updateHighlightContext(text, !options.contains(IGNORE_CASE), options.contains(USE_REGEX));
|
||||
cache.setLastSearch(text);
|
||||
cache.getLastSearchOptions().put(searchPreset, options);
|
||||
}
|
||||
@@ -473,17 +497,14 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void searchComplete() {
|
||||
private void searchFinished(ITaskInfo status, Boolean complete) {
|
||||
UiUtils.uiThreadGuard();
|
||||
LOG.debug("Search complete");
|
||||
updateTable();
|
||||
|
||||
boolean complete = searchTask == null || searchTask.isSearchComplete();
|
||||
LOG.debug("Search complete: {}, complete: {}", status, complete);
|
||||
loadAllButton.setEnabled(!complete);
|
||||
loadMoreButton.setEnabled(!complete);
|
||||
updateProgressLabel(complete);
|
||||
unloadTempData();
|
||||
progressFinishedCommon();
|
||||
updateTable();
|
||||
updateProgressLabel(complete);
|
||||
}
|
||||
|
||||
private void unloadTempData() {
|
||||
@@ -493,26 +514,8 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
private static Flowable<String> onTextFieldChanges(final JTextField textField) {
|
||||
return Flowable.<String>create(emitter -> {
|
||||
DocumentListener listener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
change();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
change();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
change();
|
||||
}
|
||||
|
||||
public void change() {
|
||||
emitter.onNext(textField.getText());
|
||||
}
|
||||
};
|
||||
DocumentUpdateListener listener = new DocumentUpdateListener(
|
||||
ev -> emitter.onNext(textField.getText()));
|
||||
|
||||
textField.getDocument().addDocumentListener(listener);
|
||||
emitter.setDisposable(new Disposable() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.gui.ui.dialog;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.Font;
|
||||
import java.util.ArrayList;
|
||||
@@ -132,8 +131,7 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
|
||||
Collections.sort(usageList);
|
||||
resultsModel.addAll(usageList);
|
||||
// TODO: highlight only needed node usage
|
||||
setHighlightText(null);
|
||||
updateHighlightContext(node.getName(), true, false);
|
||||
resultsTable.initColumnWidth();
|
||||
resultsTable.updateTable();
|
||||
updateProgressLabel(true);
|
||||
@@ -163,10 +161,13 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
JPanel resultsPanel = initResultsTable();
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPane.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
JPanel contentPanel = new JPanel();
|
||||
contentPanel.setLayout(new BorderLayout(5, 5));
|
||||
contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
contentPanel.add(searchPane, BorderLayout.PAGE_START);
|
||||
contentPanel.add(resultsPanel, BorderLayout.CENTER);
|
||||
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
|
||||
getContentPane().add(contentPanel);
|
||||
|
||||
pack();
|
||||
setSize(800, 500);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
@@ -19,6 +21,8 @@ public class CacheObject {
|
||||
private JRoot jRoot;
|
||||
private JadxSettings settings;
|
||||
|
||||
private List<List<JavaClass>> decompileBatches;
|
||||
|
||||
public CacheObject() {
|
||||
reset();
|
||||
}
|
||||
@@ -29,6 +33,7 @@ public class CacheObject {
|
||||
lastSearch = null;
|
||||
jNodeCache = new JNodeCache();
|
||||
lastSearchOptions = new HashMap<>();
|
||||
decompileBatches = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -63,4 +68,12 @@ public class CacheObject {
|
||||
public void setJRoot(JRoot jRoot) {
|
||||
this.jRoot = jRoot;
|
||||
}
|
||||
|
||||
public @Nullable List<List<JavaClass>> getDecompileBatches() {
|
||||
return decompileBatches;
|
||||
}
|
||||
|
||||
public void setDecompileBatches(List<List<JavaClass>> decompileBatches) {
|
||||
this.decompileBatches = decompileBatches;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,6 +374,8 @@ public class UiUtils {
|
||||
}
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(runnable);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warn("UI thread interrupted", e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -385,6 +387,12 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void notUiThreadGuard() {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
LOG.warn("Expect background thread, got: {}", Thread.currentThread(), new JadxRuntimeException());
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static void debugTimer(int periodInSeconds, Runnable action) {
|
||||
if (!LOG.isDebugEnabled()) {
|
||||
|
||||
Reference in New Issue
Block a user