feat(gui): allow to dock log viewer, new filter modes

This commit is contained in:
Skylot
2022-11-10 20:31:17 +00:00
parent acbe94df27
commit 926f4e497a
33 changed files with 791 additions and 366 deletions
+2 -1
View File
@@ -14,10 +14,11 @@ dependencies {
// jadx-script autocomplete support
implementation(project(":jadx-plugins::jadx-script:jadx-script-ide"))
implementation(project(":jadx-plugins::jadx-script:jadx-script-runtime"))
implementation 'org.jetbrains.kotlin:kotlin-scripting-common:1.7.20'
implementation 'com.fifesoft:autocomplete:3.3.0'
// use ktlint for lint and format jadx scripts
// use KtLint for format and check jadx scripts
implementation 'com.pinterest.ktlint:ktlint-core:0.47.1'
implementation 'com.pinterest.ktlint:ktlint-ruleset-standard:0.47.1'
+1 -1
View File
@@ -6,6 +6,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.cli.LogHelper;
import jadx.gui.logs.LogCollector;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.ui.ExceptionDialog;
@@ -13,7 +14,6 @@ import jadx.gui.ui.MainWindow;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.NLS;
import jadx.gui.utils.SystemInfo;
import jadx.gui.utils.logs.LogCollector;
public class JadxGUI {
private static final Logger LOG = LoggerFactory.getLogger(JadxGUI.class);
@@ -0,0 +1,7 @@
package jadx.gui.logs;
public interface ILogListener {
void onAppend(LogEvent logEvent);
void onReload();
}
@@ -0,0 +1,55 @@
package jadx.gui.logs;
import javax.swing.SwingUtilities;
import ch.qos.logback.classic.Level;
import jadx.gui.ui.panel.IssuesPanel;
import jadx.gui.utils.rx.DebounceUpdate;
public class IssuesListener implements ILogListener {
private final IssuesPanel issuesPanel;
private final DebounceUpdate updater;
private int errors = 0;
private int warnings = 0;
public IssuesListener(IssuesPanel issuesPanel) {
this.issuesPanel = issuesPanel;
this.updater = new DebounceUpdate(500, this::onUpdate);
}
private void onUpdate() {
SwingUtilities.invokeLater(() -> issuesPanel.onUpdate(errors, warnings));
}
@Override
public void onAppend(LogEvent logEvent) {
switch (logEvent.getLevel().toInt()) {
case Level.ERROR_INT:
errors++;
updater.requestUpdate();
break;
case Level.WARN_INT:
warnings++;
updater.requestUpdate();
break;
}
}
@Override
public void onReload() {
errors = 0;
warnings = 0;
updater.requestUpdate();
}
public int getErrors() {
return errors;
}
public int getWarnings() {
return warnings;
}
}
@@ -1,4 +1,4 @@
package jadx.gui.utils.logs;
package jadx.gui.logs;
import java.util.AbstractQueue;
import java.util.ArrayDeque;
@@ -0,0 +1,51 @@
package jadx.gui.logs;
import org.apache.commons.lang3.StringUtils;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.utils.UiUtils;
import static jadx.plugins.script.runtime.JadxScriptTemplateKt.JADX_SCRIPT_LOG_PREFIX;
class LogAppender implements ILogListener {
private final LogOptions options;
private final RSyntaxTextArea textArea;
public LogAppender(LogOptions options, RSyntaxTextArea textArea) {
this.options = options;
this.textArea = textArea;
}
@Override
public void onAppend(LogEvent logEvent) {
if (accept(logEvent)) {
UiUtils.uiRun(() -> textArea.append(logEvent.getMsg()));
}
}
@Override
public void onReload() {
UiUtils.uiRunAndWait(() -> textArea.append(StringUtils.repeat('=', 100) + '\n'));
}
private boolean accept(LogEvent logEvent) {
boolean byLevel = logEvent.getLevel().isGreaterOrEqual(options.getLogLevel());
if (!byLevel) {
return false;
}
switch (options.getMode()) {
case ALL:
return true;
case ALL_SCRIPTS:
return logEvent.getLoggerName().startsWith(JADX_SCRIPT_LOG_PREFIX);
case CURRENT_SCRIPT:
return logEvent.getLoggerName().equals(options.getFilter());
default:
throw new JadxRuntimeException("Unexpected log mode: " + options.getMode());
}
}
}
@@ -0,0 +1,83 @@
package jadx.gui.logs;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Layout;
public class LogCollector extends AppenderBase<ILoggingEvent> {
public static final int BUFFER_SIZE = 5000;
private static final LogCollector INSTANCE = new LogCollector();
public static LogCollector getInstance() {
return INSTANCE;
}
public static void register() {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
LoggerContext loggerContext = rootLogger.getLoggerContext();
PatternLayout layout = new PatternLayout();
layout.setContext(loggerContext);
layout.setPattern("%-5level: %msg%n");
layout.start();
INSTANCE.setContext(loggerContext);
INSTANCE.setLayout(layout);
INSTANCE.start();
rootLogger.addAppender(INSTANCE);
}
private final List<ILogListener> listeners = new ArrayList<>();
private final Queue<LogEvent> buffer = new LimitedQueue<>(BUFFER_SIZE);
private Layout<ILoggingEvent> layout;
public LogCollector() {
setName("LogCollector");
}
@Override
protected synchronized void append(ILoggingEvent event) {
String msg = layout.doLayout(event);
LogEvent logEvent = new LogEvent(event.getLevel(), event.getLoggerName(), msg);
buffer.offer(logEvent);
listeners.forEach(l -> l.onAppend(logEvent));
}
private void setLayout(Layout<ILoggingEvent> layout) {
this.layout = layout;
}
public synchronized void registerListener(ILogListener listener) {
listeners.add(listener);
buffer.forEach(listener::onAppend);
}
public synchronized boolean removeListener(@Nullable ILogListener listener) {
if (listener == null) {
return false;
}
return this.listeners.removeIf(l -> l == listener);
}
public synchronized boolean removeListenerByClass(Class<?> listenerCls) {
return this.listeners.removeIf(l -> l.getClass().equals(listenerCls));
}
public synchronized void reset() {
buffer.clear();
listeners.forEach(ILogListener::onReload);
}
}
@@ -0,0 +1,32 @@
package jadx.gui.logs;
import ch.qos.logback.classic.Level;
public final class LogEvent {
private final Level level;
private final String loggerName;
private final String msg;
LogEvent(Level level, String loggerName, String msg) {
this.level = level;
this.loggerName = loggerName;
this.msg = msg;
}
public Level getLevel() {
return level;
}
public String getLoggerName() {
return loggerName;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return level + ": " + loggerName + " - " + msg;
}
}
@@ -0,0 +1,22 @@
package jadx.gui.logs;
import org.apache.commons.lang3.StringUtils;
import jadx.gui.utils.NLS;
public enum LogMode {
ALL,
ALL_SCRIPTS,
CURRENT_SCRIPT;
private static final String[] NLS_STRINGS = StringUtils.split(NLS.str("log_viewer.modes"), '|');
public String getLocalizedName() {
return NLS_STRINGS[this.ordinal()];
}
@Override
public String toString() {
return getLocalizedName();
}
}
@@ -0,0 +1,72 @@
package jadx.gui.logs;
import org.jetbrains.annotations.Nullable;
import ch.qos.logback.classic.Level;
import jadx.core.utils.Utils;
import static jadx.plugins.script.runtime.JadxScriptTemplateKt.JADX_SCRIPT_LOG_PREFIX;
public class LogOptions {
/**
* Store latest requested log options
*/
private static LogOptions current = new LogOptions(LogMode.ALL, Level.INFO, null);
public static LogOptions allWithLevel(@Nullable Level logLevel) {
Level level = Utils.getOrElse(logLevel, current.getLogLevel());
return store(new LogOptions(LogMode.ALL, level, null));
}
public static LogOptions forLevel(@Nullable Level logLevel) {
Level level = Utils.getOrElse(logLevel, current.getLogLevel());
return store(new LogOptions(current.getMode(), level, current.getFilter()));
}
public static LogOptions forMode(LogMode mode) {
return store(new LogOptions(mode, current.getLogLevel(), current.getFilter()));
}
public static LogOptions forScript(String scriptName) {
String filter = JADX_SCRIPT_LOG_PREFIX + scriptName;
return store(new LogOptions(LogMode.CURRENT_SCRIPT, current.getLogLevel(), filter));
}
public static LogOptions current() {
return current;
}
private static LogOptions store(LogOptions logOptions) {
current = logOptions;
return logOptions;
}
private final LogMode mode;
private final Level logLevel;
private final @Nullable String filter;
private LogOptions(LogMode mode, Level logLevel, @Nullable String filter) {
this.mode = mode;
this.logLevel = logLevel;
this.filter = filter;
}
public LogMode getMode() {
return mode;
}
public Level getLogLevel() {
return logLevel;
}
public @Nullable String getFilter() {
return filter;
}
@Override
public String toString() {
return "LogOptions{mode=" + mode + ", logLevel=" + logLevel + ", filter='" + filter + '\'' + '}';
}
}
@@ -0,0 +1,176 @@
package jadx.gui.logs;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.event.ChangeListener;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.jetbrains.annotations.Nullable;
import ch.qos.logback.classic.Level;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JInputScript;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.NLS;
public class LogPanel extends JPanel {
private static final long serialVersionUID = -8077649118322056081L;
private static final Level[] LEVEL_ITEMS = { Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR };
private final MainWindow mainWindow;
private final Runnable dockAction;
private final Runnable hideAction;
private RSyntaxTextArea textPane;
private JComboBox<LogMode> modeCb;
private JComboBox<Level> levelCb;
private ChangeListener activeTabListener;
public LogPanel(MainWindow mainWindow, LogOptions logOptions, Runnable dockAction, Runnable hideAction) {
this.mainWindow = mainWindow;
this.dockAction = dockAction;
this.hideAction = hideAction;
initUI(logOptions);
applyLogOptions(logOptions);
}
public void applyLogOptions(LogOptions logOptions) {
if (logOptions.getMode() == LogMode.CURRENT_SCRIPT) {
String scriptName = getCurrentScriptName();
if (scriptName != null) {
logOptions = LogOptions.forScript(scriptName);
}
registerActiveTabListener();
} else {
removeActiveTabListener();
}
if (modeCb.getSelectedItem() != logOptions.getMode()) {
modeCb.setSelectedItem(logOptions.getMode());
}
if (levelCb.getSelectedItem() != logOptions.getLogLevel()) {
levelCb.setSelectedItem(logOptions.getLogLevel());
}
registerLogListener(logOptions);
}
public void loadSettings() {
AbstractCodeArea.loadCommonSettings(mainWindow, textPane);
}
private void initUI(LogOptions logOptions) {
JadxSettings settings = mainWindow.getSettings();
textPane = AbstractCodeArea.getDefaultArea(mainWindow);
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
modeCb = new JComboBox<>(LogMode.values());
modeCb.setSelectedItem(logOptions.getMode());
modeCb.addActionListener(e -> applyLogOptions(LogOptions.forMode((LogMode) modeCb.getSelectedItem())));
JLabel modeLabel = new JLabel(NLS.str("log_viewer.mode"));
modeLabel.setLabelFor(modeCb);
levelCb = new JComboBox<>(LEVEL_ITEMS);
levelCb.setSelectedItem(logOptions.getLogLevel());
levelCb.addActionListener(e -> applyLogOptions(LogOptions.forLevel((Level) levelCb.getSelectedItem())));
JLabel levelLabel = new JLabel(NLS.str("log_viewer.log_level"));
levelLabel.setLabelFor(levelCb);
JButton clearBtn = new JButton(NLS.str("log_viewer.clear"));
clearBtn.addActionListener(ev -> {
LogCollector.getInstance().reset();
textPane.setText("");
});
JButton dockBtn = new JButton(NLS.str(settings.isDockLogViewer() ? "log_viewer.undock" : "log_viewer.dock"));
dockBtn.addActionListener(ev -> dockAction.run());
JButton hideBtn = new JButton(NLS.str("log_viewer.hide"));
hideBtn.addActionListener(ev -> hideAction.run());
JPanel start = new JPanel();
start.setLayout(new BoxLayout(start, BoxLayout.LINE_AXIS));
start.add(modeLabel);
start.add(Box.createRigidArea(new Dimension(5, 0)));
start.add(modeCb);
start.add(Box.createRigidArea(new Dimension(15, 0)));
start.add(levelLabel);
start.add(Box.createRigidArea(new Dimension(5, 0)));
start.add(levelCb);
start.add(Box.createRigidArea(new Dimension(5, 0)));
JPanel end = new JPanel();
end.setLayout(new BoxLayout(end, BoxLayout.LINE_AXIS));
end.add(clearBtn);
end.add(Box.createRigidArea(new Dimension(15, 0)));
end.add(dockBtn);
end.add(Box.createRigidArea(new Dimension(15, 0)));
end.add(hideBtn);
JPanel controlPane = new JPanel();
controlPane.setLayout(new BorderLayout());
controlPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
controlPane.add(start, BorderLayout.LINE_START);
controlPane.add(end, BorderLayout.LINE_END);
JScrollPane scrollPane = new JScrollPane(textPane);
setLayout(new BorderLayout(5, 5));
add(controlPane, BorderLayout.PAGE_START);
add(scrollPane, BorderLayout.CENTER);
}
private void registerLogListener(LogOptions logOptions) {
LogCollector logCollector = LogCollector.getInstance();
logCollector.removeListenerByClass(LogAppender.class);
textPane.setText("");
logCollector.registerListener(new LogAppender(logOptions, textPane));
}
private @Nullable String getCurrentScriptName() {
ContentPanel selectedCodePanel = mainWindow.getTabbedPane().getSelectedCodePanel();
if (selectedCodePanel != null) {
JNode node = selectedCodePanel.getNode();
if (node instanceof JInputScript) {
return node.getName();
}
}
return null;
}
private synchronized void registerActiveTabListener() {
removeActiveTabListener();
activeTabListener = e -> {
String scriptName = getCurrentScriptName();
if (scriptName != null) {
applyLogOptions(LogOptions.forScript(scriptName));
}
};
mainWindow.getTabbedPane().addChangeListener(activeTabListener);
}
private synchronized void removeActiveTabListener() {
if (activeTabListener != null) {
mainWindow.getTabbedPane().removeChangeListener(activeTabListener);
activeTabListener = null;
}
}
public void dispose() {
LogCollector.getInstance().removeListenerByClass(LogAppender.class);
removeActiveTabListener();
}
}
@@ -22,8 +22,10 @@ import org.slf4j.LoggerFactory;
import com.pinterest.ktlint.core.LintError;
import kotlin.script.experimental.api.ScriptDiagnostic;
import kotlin.script.experimental.api.ScriptDiagnostic.Severity;
import jadx.gui.JadxWrapper;
import jadx.gui.logs.LogOptions;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.LineNumbersMode;
import jadx.gui.treemodel.JInputScript;
@@ -40,17 +42,18 @@ import jadx.gui.utils.ui.NodeLabel;
import jadx.plugins.script.ide.ScriptAnalyzeResult;
import jadx.plugins.script.ide.ScriptCompiler;
import static jadx.plugins.script.runtime.JadxScriptTemplateKt.JADX_SCRIPT_LOG_PREFIX;
public class ScriptContentPanel extends AbstractCodeContentPanel {
private static final long serialVersionUID = 6575696321112417513L;
private static final Logger LOG = LoggerFactory.getLogger(ScriptContentPanel.class);
private final ScriptCodeArea scriptArea;
private final SearchBar searchBar;
private final RTextScrollPane codeScrollPane;
private final JPanel actionPanel;
private final JLabel resultLabel;
private final ScriptErrorService errorService;
private final Logger scriptLog;
public ScriptContentPanel(TabbedPane panel, JInputScript scriptNode) {
super(panel, scriptNode);
@@ -60,6 +63,7 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
actionPanel = buildScriptActionsPanel();
searchBar = new SearchBar(scriptArea);
codeScrollPane = new RTextScrollPane(scriptArea);
scriptLog = LoggerFactory.getLogger(JADX_SCRIPT_LOG_PREFIX + scriptNode.getName());
initUI();
applySettings();
@@ -136,7 +140,7 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
wrapper.resetGuiPluginsContext();
wrapper.getDecompiler().reloadPasses();
} catch (Exception e) {
LOG.error("Passes reload failed", e);
scriptLog.error("Passes reload failed", e);
}
}, taskStatus -> {
tabbedPane.reloadInactiveTabs();
@@ -153,12 +157,16 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
ScriptCompiler scriptCompiler = new ScriptCompiler(fileName);
ScriptAnalyzeResult result = scriptCompiler.analyze(code, scriptArea.getCaretPosition());
List<ScriptDiagnostic> issues = result.getIssues();
boolean success = true;
for (ScriptDiagnostic issue : issues) {
LOG.warn("Compiler issue: {}", issue);
Severity severity = issue.getSeverity();
if (severity == Severity.ERROR || severity == Severity.FATAL) {
scriptLog.error("{}", issue.render(false, true, true, true));
success = false;
} else {
scriptLog.warn("Compiler issue: {}", issue);
}
}
boolean success = issues.stream().map(ScriptDiagnostic::getSeverity)
.noneMatch(s -> s == ScriptDiagnostic.Severity.ERROR || s == ScriptDiagnostic.Severity.FATAL);
List<LintError> lintErrs = Collections.emptyList();
if (success) {
lintErrs = getLintIssues(code, fileName);
@@ -170,12 +178,13 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
errorService.apply();
if (!success) {
resultLabel.setText("Compiler issues: " + issues.size());
getTabbedPane().getMainWindow().showLogViewer(LogOptions.forScript(getNode().getName()));
} else if (!lintErrs.isEmpty()) {
resultLabel.setText("Lint issues: " + lintErrs.size());
}
return success;
} catch (Throwable e) {
LOG.error("Failed to check code", e);
scriptLog.error("Failed to check code", e);
return true;
}
}
@@ -184,11 +193,12 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
try {
List<LintError> lintErrs = KtLintUtils.INSTANCE.lint(code, fileName);
for (LintError error : lintErrs) {
LOG.warn("Lint issue: {}", error);
scriptLog.warn("Lint issue: {} ({}:{})(ruleId={})",
error.getDetail(), error.getLine(), error.getCol(), error.getRuleId());
}
return lintErrs;
} catch (Throwable e) { // can throw initialization error
LOG.warn("KtLint failed", e);
scriptLog.warn("KtLint failed", e);
return Collections.emptyList();
}
}
@@ -205,7 +215,7 @@ public class ScriptContentPanel extends AbstractCodeContentPanel {
errorService.clearErrors();
}
} catch (Throwable e) { // can throw initialization error
LOG.error("Failed to reformat code", e);
scriptLog.error("Failed to reformat code", e);
}
}
@@ -105,6 +105,8 @@ public class JadxSettings extends JadxCLIArgs {
*/
private int treeWidth = 130;
private boolean dockLogViewer = true;
private int settingsVersion = 0;
@JadxSettingsAdapter.GsonExclude
@@ -686,6 +688,15 @@ public class JadxSettings extends JadxCLIArgs {
this.jumpOnDoubleClick = jumpOnDoubleClick;
}
public boolean isDockLogViewer() {
return dockLogViewer;
}
public void setDockLogViewer(boolean dockLogViewer) {
this.dockLogViewer = dockLogViewer;
partialSync(settings -> this.dockLogViewer = dockLogViewer);
}
private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
@@ -33,15 +33,12 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.AbstractAction;
@@ -53,7 +50,6 @@ import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
@@ -65,8 +61,6 @@ import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.WindowConstants;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
@@ -105,6 +99,9 @@ import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.DecompileTask;
import jadx.gui.jobs.ExportTask;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.logs.LogCollector;
import jadx.gui.logs.LogOptions;
import jadx.gui.logs.LogPanel;
import jadx.gui.plugins.quark.QuarkDialog;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
@@ -130,6 +127,7 @@ import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.ui.panel.IssuesPanel;
import jadx.gui.ui.panel.JDebuggerPanel;
import jadx.gui.ui.panel.ProgressPanel;
import jadx.gui.ui.popupmenu.RecentProjectsMenuListener;
import jadx.gui.ui.treenodes.StartPageNode;
import jadx.gui.ui.treenodes.SummaryNode;
import jadx.gui.update.JadxUpdate;
@@ -145,7 +143,6 @@ import jadx.gui.utils.NLS;
import jadx.gui.utils.SystemInfo;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
import jadx.gui.utils.logs.LogCollector;
import jadx.gui.utils.ui.ActionHandler;
import jadx.gui.utils.ui.NodeLabel;
import jadx.plugins.mappings.save.MappingExporter;
@@ -197,8 +194,10 @@ public class MainWindow extends JFrame {
private MappingFormat currentMappingFormat;
private boolean renamesChanged = false;
private JPanel mainPanel;
private JSplitPane splitPane;
private transient JPanel mainPanel;
private transient JSplitPane treeSplitPane;
private transient JSplitPane rightSplitPane;
private transient JSplitPane bottomSplitPane;
private JTree tree;
private DefaultTreeModel treeModel;
@@ -221,8 +220,9 @@ public class MainWindow extends JFrame {
private transient ProgressPanel progressPane;
private transient Theme editorTheme;
private JDebuggerPanel debuggerPanel;
private JSplitPane verticalSplitter;
private transient IssuesPanel issuesPanel;
private transient @Nullable LogPanel logPanel;
private transient @Nullable JDebuggerPanel debuggerPanel;
private final List<ILoadListener> loadListeners = new ArrayList<>();
private boolean loaded;
@@ -238,6 +238,7 @@ public class MainWindow extends JFrame {
resetCache();
FontUtils.registerBundledFonts();
setEditorTheme(settings.getEditorThemePath());
initUI();
this.backgroundExecutor = new BackgroundExecutor(settings, progressPane);
initMenuAndToolbar();
@@ -252,7 +253,7 @@ public class MainWindow extends JFrame {
public void init() {
pack();
setLocationAndPosition();
splitPane.setDividerLocation(settings.getTreeWidth());
treeSplitPane.setDividerLocation(settings.getTreeWidth());
heapUsageBar.setVisible(settings.isShowHeapUsageBar());
setVisible(true);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
@@ -634,7 +635,7 @@ public class MainWindow extends JFrame {
if (!wrapper.getClasses().isEmpty()) {
return;
}
int errors = LogCollector.getInstance().getErrors();
int errors = issuesPanel.getErrorsCount();
if (errors > 0) {
int result = JOptionPane.showConfirmDialog(this,
NLS.str("message.load_errors", errors),
@@ -642,7 +643,7 @@ public class MainWindow extends JFrame {
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.ERROR_MESSAGE);
if (result == JOptionPane.OK_OPTION) {
LogViewerDialog.openWithLevel(this, Level.ERROR);
showLogViewer(LogOptions.allWithLevel(Level.ERROR));
}
} else {
UiUtils.showMessageBox(this, NLS.str("message.no_classes"));
@@ -1101,7 +1102,7 @@ public class MainWindow extends JFrame {
exportAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_E, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
JMenu recentProjects = new JMenu(NLS.str("menu.recent_projects"));
recentProjects.addMenuListener(new RecentProjectsMenuListener(recentProjects));
recentProjects.addMenuListener(new RecentProjectsMenuListener(this, recentProjects));
Action prefsAction = new AbstractAction(NLS.str("menu.preferences"), ICON_PREF) {
@Override
@@ -1140,6 +1141,10 @@ public class MainWindow extends JFrame {
}
});
JCheckBoxMenuItem dockLog = new JCheckBoxMenuItem(NLS.str("menu.dock_log"));
dockLog.setState(settings.isDockLogViewer());
dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer()));
Action syncAction = new AbstractAction(NLS.str("menu.sync"), ICON_SYNC) {
@Override
public void actionPerformed(ActionEvent e) {
@@ -1211,15 +1216,10 @@ public class MainWindow extends JFrame {
deobfMenuItem = new JCheckBoxMenuItem(deobfAction);
deobfMenuItem.setState(settings.isDeobfuscationOn());
Action logAction = new AbstractAction(NLS.str("menu.log"), ICON_LOG) {
@Override
public void actionPerformed(ActionEvent e) {
LogViewerDialog.open(MainWindow.this);
}
};
logAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.log"));
logAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_L,
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
ActionHandler showLog = new ActionHandler(ev -> showLogViewer(LogOptions.current()));
showLog.setNameAndDesc(NLS.str("menu.log"));
showLog.setIcon(ICON_LOG);
showLog.setKeyBinding(getKeyStroke(KeyEvent.VK_L, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
Action aboutAction = new AbstractAction(NLS.str("menu.about"), ICON_INFO) {
@Override
@@ -1296,6 +1296,7 @@ public class MainWindow extends JFrame {
view.add(syncAction);
view.add(heapUsageBarMenuItem);
view.add(alwaysSelectOpened);
view.add(dockLog);
JMenu nav = new JMenu(NLS.str("menu.navigation"));
nav.setMnemonic(KeyEvent.VK_N);
@@ -1319,7 +1320,7 @@ public class MainWindow extends JFrame {
JMenu help = new JMenu(NLS.str("menu.help"));
help.setMnemonic(KeyEvent.VK_H);
help.add(logAction);
help.add(showLog);
if (Jadx.isDevVersion()) {
help.add(new AbstractAction("Show sample error report") {
@Override
@@ -1373,7 +1374,7 @@ public class MainWindow extends JFrame {
toolbar.add(quarkAction);
toolbar.add(openDeviceAction);
toolbar.addSeparator();
toolbar.add(logAction);
toolbar.add(showLog);
toolbar.addSeparator();
toolbar.add(prefsAction);
toolbar.addSeparator();
@@ -1403,9 +1404,9 @@ public class MainWindow extends JFrame {
private void initUI() {
setMinimumSize(new Dimension(200, 150));
mainPanel = new JPanel(new BorderLayout());
splitPane = new JSplitPane();
splitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
mainPanel.add(splitPane);
treeSplitPane = new JSplitPane();
treeSplitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
mainPanel.add(treeSplitPane);
DefaultMutableTreeNode treeRootNode = new DefaultMutableTreeNode(NLS.str("msg.open_file"));
treeModel = new DefaultTreeModel(treeRootNode);
@@ -1486,7 +1487,7 @@ public class MainWindow extends JFrame {
});
progressPane = new ProgressPanel(this, true);
IssuesPanel issuesPanel = new IssuesPanel(this);
issuesPanel = new IssuesPanel(this);
JPanel leftPane = new JPanel(new BorderLayout());
JScrollPane treeScrollPane = new JScrollPane(tree);
@@ -1498,22 +1499,27 @@ public class MainWindow extends JFrame {
leftPane.add(treeScrollPane, BorderLayout.CENTER);
leftPane.add(bottomPane, BorderLayout.PAGE_END);
splitPane.setLeftComponent(leftPane);
treeSplitPane.setLeftComponent(leftPane);
tabbedPane = new TabbedPane(this);
tabbedPane.setMinimumSize(new Dimension(150, 150));
splitPane.setRightComponent(tabbedPane);
rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
rightSplitPane.setTopComponent(tabbedPane);
rightSplitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
treeSplitPane.setRightComponent(rightSplitPane);
new DropTarget(this, DnDConstants.ACTION_COPY, new MainDropTarget(this));
heapUsageBar = new HeapUsageBar();
mainPanel.add(heapUsageBar, BorderLayout.SOUTH);
verticalSplitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
verticalSplitter.setTopComponent(splitPane);
verticalSplitter.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
bottomSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
bottomSplitPane.setTopComponent(treeSplitPane);
bottomSplitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
mainPanel.add(verticalSplitter, BorderLayout.CENTER);
mainPanel.add(bottomSplitPane, BorderLayout.CENTER);
setContentPane(mainPanel);
setTitle(DEFAULT_TITLE);
}
@@ -1643,6 +1649,9 @@ public class MainWindow extends JFrame {
tree.setRowHeight(-1);
tabbedPane.loadSettings();
if (logPanel != null) {
logPanel.loadSettings();
}
}
private void closeWindow() {
@@ -1650,7 +1659,7 @@ public class MainWindow extends JFrame {
if (!ensureProjectIsSaved()) {
return;
}
settings.setTreeWidth(splitPane.getDividerLocation());
settings.setTreeWidth(treeSplitPane.getDividerLocation());
settings.saveWindowPos(this);
settings.setMainWindowExtendedState(getExtendedState());
if (debuggerPanel != null) {
@@ -1696,7 +1705,7 @@ public class MainWindow extends JFrame {
}
private void saveSplittersInfo() {
settings.setMainWindowVerticalSplitterLoc(verticalSplitter.getDividerLocation());
settings.setMainWindowVerticalSplitterLoc(bottomSplitPane.getDividerLocation());
settings.setDebuggerStackFrameSplitterLoc(debuggerPanel.getLeftSplitterLocation());
settings.setDebuggerVarTreeSplitterLoc(debuggerPanel.getRightSplitterLocation());
}
@@ -1764,49 +1773,44 @@ public class MainWindow extends JFrame {
if (debuggerPanel == null) {
debuggerPanel = new JDebuggerPanel(this);
debuggerPanel.loadSettings();
verticalSplitter.setBottomComponent(debuggerPanel);
bottomSplitPane.setBottomComponent(debuggerPanel);
int loc = settings.getMainWindowVerticalSplitterLoc();
if (loc == 0) {
loc = 300;
}
verticalSplitter.setDividerLocation(loc);
bottomSplitPane.setDividerLocation(loc);
}
}
private class RecentProjectsMenuListener implements MenuListener {
private final JMenu menu;
public RecentProjectsMenuListener(JMenu menu) {
this.menu = menu;
public void showLogViewer(LogOptions logOptions) {
if (settings.isDockLogViewer()) {
showDockedLog(logOptions);
} else {
LogViewerDialog.open(this, logOptions);
}
}
@Override
public void menuSelected(MenuEvent menuEvent) {
Set<Path> current = new HashSet<>(project.getFilePaths());
List<JMenuItem> items = settings.getRecentProjects()
.stream()
.filter(path -> !current.contains(path))
.map(path -> {
JMenuItem menuItem = new JMenuItem(path.toAbsolutePath().toString());
menuItem.addActionListener(e -> open(Collections.singletonList(path)));
return menuItem;
}).collect(Collectors.toList());
menu.removeAll();
if (items.isEmpty()) {
menu.add(new JMenuItem(NLS.str("menu.no_recent_projects")));
} else {
items.forEach(menu::add);
}
private void showDockedLog(LogOptions logOptions) {
if (logPanel != null) {
logPanel.applyLogOptions(logOptions);
return;
}
Runnable undock = () -> {
hideDockedLog();
settings.setDockLogViewer(false);
LogViewerDialog.open(this, logOptions);
};
logPanel = new LogPanel(this, logOptions, undock, this::hideDockedLog);
rightSplitPane.setBottomComponent(logPanel);
}
@Override
public void menuDeselected(MenuEvent e) {
}
@Override
public void menuCanceled(MenuEvent e) {
private void hideDockedLog() {
if (logPanel == null) {
return;
}
logPanel.dispose();
logPanel = null;
rightSplitPane.setBottomComponent(null);
}
public JMenu getPluginsMenu() {
@@ -50,6 +50,7 @@ import ch.qos.logback.classic.Level;
import jadx.api.JavaClass;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.gui.logs.LogOptions;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResSearchNode;
@@ -305,7 +306,7 @@ public abstract class CommonSearchDialog extends JFrame {
progressInfoLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
LogViewerDialog.openWithLevel(mainWindow, Level.INFO);
mainWindow.showLogViewer(LogOptions.allWithLevel(Level.INFO));
}
});
@@ -5,61 +5,53 @@ import java.awt.Container;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import ch.qos.logback.classic.Level;
import jadx.gui.logs.LogOptions;
import jadx.gui.logs.LogPanel;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.logs.ILogListener;
import jadx.gui.utils.logs.LogCollector;
public class LogViewerDialog extends JFrame {
private static final long serialVersionUID = -2188700277429054641L;
private static final Level[] LEVEL_ITEMS = { Level.DEBUG, Level.INFO, Level.WARN, Level.ERROR };
private static Level level = Level.WARN;
private final transient JadxSettings settings;
private transient RSyntaxTextArea textPane;
private JComboBox<Level> levelCb;
private static LogViewerDialog openLogDialog;
public static void open(MainWindow mainWindow) {
openWithLevel(mainWindow, level);
}
private final transient JadxSettings settings;
private final transient LogPanel logPanel;
public static void openWithLevel(MainWindow mainWindow, Level newLevel) {
level = newLevel;
if (openLogDialog == null) {
LogViewerDialog newLogDialog = new LogViewerDialog(mainWindow);
newLogDialog.setVisible(true);
openLogDialog = newLogDialog;
public static void open(MainWindow mainWindow, LogOptions logOptions) {
LogViewerDialog logDialog;
if (openLogDialog != null) {
logDialog = openLogDialog;
} else {
LogViewerDialog logDialog = openLogDialog;
logDialog.levelCb.setSelectedItem(level);
logDialog.setVisible(true);
logDialog.toFront();
logDialog = new LogViewerDialog(mainWindow, logOptions);
openLogDialog = logDialog;
}
logDialog.setVisible(true);
logDialog.toFront();
}
private LogViewerDialog(MainWindow mainWindow) {
this.settings = mainWindow.getSettings();
initUI(mainWindow);
registerLogListener();
private LogViewerDialog(MainWindow mainWindow, LogOptions logOptions) {
settings = mainWindow.getSettings();
UiUtils.setWindowIcons(this);
Runnable dock = () -> {
mainWindow.getSettings().setDockLogViewer(true);
dispose();
mainWindow.showLogViewer(LogOptions.current());
};
logPanel = new LogPanel(mainWindow, logOptions, dock, this::dispose);
Container contentPane = getContentPane();
contentPane.add(logPanel, BorderLayout.CENTER);
setTitle(NLS.str("log_viewer.title"));
pack();
setSize(800, 600);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
settings.loadWindowPos(this);
addWindowListener(new WindowAdapter() {
@Override
@@ -69,68 +61,9 @@ public class LogViewerDialog extends JFrame {
});
}
public final void initUI(MainWindow mainWindow) {
UiUtils.setWindowIcons(this);
textPane = AbstractCodeArea.getDefaultArea(mainWindow);
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
JPanel controlPane = new JPanel();
controlPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
levelCb = new JComboBox<>(LEVEL_ITEMS);
levelCb.setSelectedItem(level);
levelCb.addActionListener(e -> {
int i = levelCb.getSelectedIndex();
level = LEVEL_ITEMS[i];
registerLogListener();
});
JLabel levelLabel = new JLabel(NLS.str("log_viewer.log_level"));
levelLabel.setLabelFor(levelCb);
controlPane.add(levelLabel);
controlPane.add(levelCb);
JScrollPane scrollPane = new JScrollPane(textPane);
JButton close = new JButton(NLS.str("tabs.close"));
close.addActionListener(event -> close());
close.setAlignmentX(0.5f);
Container contentPane = getContentPane();
contentPane.add(controlPane, BorderLayout.PAGE_START);
contentPane.add(scrollPane, BorderLayout.CENTER);
contentPane.add(close, BorderLayout.PAGE_END);
setTitle(NLS.str("log_viewer.title"));
pack();
setSize(800, 600);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
}
private void registerLogListener() {
LogCollector logCollector = LogCollector.getInstance();
logCollector.resetListener();
textPane.setText("");
logCollector.registerListener(new ILogListener() {
@Override
public Level getFilterLevel() {
return level;
}
@Override
public void onAppend(final String logStr) {
SwingUtilities.invokeLater(() -> textPane.append(logStr));
}
});
}
private void close() {
dispose();
}
@Override
public void dispose() {
LogCollector.getInstance().resetListener();
logPanel.dispose();
settings.saveWindowPos(this);
super.dispose();
}
@@ -9,15 +9,15 @@ import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import ch.qos.logback.classic.Level;
import jadx.gui.logs.IssuesListener;
import jadx.gui.logs.LogCollector;
import jadx.gui.logs.LogOptions;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.dialog.LogViewerDialog;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.logs.LogCollector;
public class IssuesPanel extends JPanel {
private static final long serialVersionUID = -7720576036668459218L;
@@ -26,15 +26,19 @@ public class IssuesPanel extends JPanel {
private static final ImageIcon WARN_ICON = UiUtils.openSvgIcon("ui/warning");
private final MainWindow mainWindow;
private final IssuesListener issuesListener;
private JLabel errorLabel;
private JLabel warnLabel;
public IssuesPanel(MainWindow mainWindow) {
this.mainWindow = mainWindow;
initUI();
LogCollector.getInstance().registerIssueListener((error, warnings) -> {
SwingUtilities.invokeLater(() -> onUpdate(error, warnings));
});
this.issuesListener = new IssuesListener(this);
LogCollector.getInstance().registerListener(issuesListener);
}
public int getErrorsCount() {
return issuesListener.getErrors();
}
private void initUI() {
@@ -49,13 +53,13 @@ public class IssuesPanel extends JPanel {
errorLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
LogViewerDialog.openWithLevel(mainWindow, Level.ERROR);
mainWindow.showLogViewer(LogOptions.allWithLevel(Level.ERROR));
}
});
warnLabel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
LogViewerDialog.openWithLevel(mainWindow, Level.WARN);
mainWindow.showLogViewer(LogOptions.allWithLevel(Level.WARN));
}
});
@@ -69,7 +73,7 @@ public class IssuesPanel extends JPanel {
add(warnLabel);
}
private void onUpdate(int error, int warnings) {
public void onUpdate(int error, int warnings) {
if (error == 0 && warnings == 0) {
setVisible(false);
return;
@@ -0,0 +1,54 @@
package jadx.gui.ui.popupmenu;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
public class RecentProjectsMenuListener implements MenuListener {
private final MainWindow mainWindow;
private final JMenu menu;
public RecentProjectsMenuListener(MainWindow mainWindow, JMenu menu) {
this.mainWindow = mainWindow;
this.menu = menu;
}
@Override
public void menuSelected(MenuEvent menuEvent) {
Set<Path> current = new HashSet<>(mainWindow.getProject().getFilePaths());
List<JMenuItem> items = mainWindow.getSettings().getRecentProjects()
.stream()
.filter(path -> !current.contains(path))
.map(path -> {
JMenuItem menuItem = new JMenuItem(path.toAbsolutePath().toString());
menuItem.addActionListener(e -> mainWindow.open(Collections.singletonList(path)));
return menuItem;
}).collect(Collectors.toList());
menu.removeAll();
if (items.isEmpty()) {
menu.add(new JMenuItem(NLS.str("menu.no_recent_projects")));
} else {
items.forEach(menu::add);
}
}
@Override
public void menuDeselected(MenuEvent e) {
}
@Override
public void menuCanceled(MenuEvent e) {
}
}
@@ -1,5 +0,0 @@
package jadx.gui.utils.logs;
public interface ILogIssuesListener {
void onChange(int error, int warnings);
}
@@ -1,9 +0,0 @@
package jadx.gui.utils.logs;
import ch.qos.logback.classic.Level;
public interface ILogListener {
Level getFilterLevel();
void onAppend(String logStr);
}
@@ -1,132 +0,0 @@
package jadx.gui.utils.logs;
import java.util.Queue;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Layout;
public class LogCollector extends AppenderBase<ILoggingEvent> {
public static final int BUFFER_SIZE = 5000;
private static final LogCollector INSTANCE = new LogCollector();
public static LogCollector getInstance() {
return INSTANCE;
}
public static void register() {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
LoggerContext loggerContext = rootLogger.getLoggerContext();
PatternLayout layout = new PatternLayout();
layout.setContext(loggerContext);
layout.setPattern("%-5level: %msg%n");
layout.start();
INSTANCE.setContext(loggerContext);
INSTANCE.setLayout(layout);
INSTANCE.start();
rootLogger.addAppender(INSTANCE);
}
private Layout<ILoggingEvent> layout;
@Nullable
private ILogListener listener;
@Nullable
private ILogIssuesListener issuesListener;
private int errors = 0;
private int warnings = 0;
private final Queue<LogEvent> buffer = new LimitedQueue<>(BUFFER_SIZE);
public LogCollector() {
setName("LogCollector");
}
@Override
protected void append(ILoggingEvent event) {
synchronized (this) {
Level level = event.getLevel();
String msg = layout.doLayout(event);
store(level, msg);
if (listener != null && level.isGreaterOrEqual(listener.getFilterLevel())) {
listener.onAppend(msg);
}
if (level == Level.ERROR) {
errors++;
issuesUpdated();
} else if (level == Level.WARN) {
warnings++;
issuesUpdated();
}
}
}
private void issuesUpdated() {
if (issuesListener != null) {
issuesListener.onChange(errors, warnings);
}
}
private void store(Level level, String msg) {
buffer.offer(new LogEvent(level, msg));
}
public void setLayout(Layout<ILoggingEvent> layout) {
this.layout = layout;
}
public void registerListener(@NotNull ILogListener listener) {
this.listener = listener;
synchronized (this) {
listener.onAppend(init(listener.getFilterLevel()));
}
}
public void registerIssueListener(@NotNull ILogIssuesListener listener) {
this.issuesListener = listener;
synchronized (this) {
listener.onChange(errors, warnings);
}
}
public void resetListener() {
this.listener = null;
}
public void reset() {
buffer.clear();
errors = 0;
warnings = 0;
issuesUpdated();
}
public int getErrors() {
return errors;
}
public int getWarnings() {
return warnings;
}
private String init(Level filterLevel) {
StringBuilder sb = new StringBuilder();
for (LogEvent event : buffer) {
if (event.getLevel().isGreaterOrEqual(filterLevel)) {
sb.append(event.getMsg());
}
}
return sb.toString();
}
}
@@ -1,26 +0,0 @@
package jadx.gui.utils.logs;
import ch.qos.logback.classic.Level;
final class LogEvent {
private final Level level;
private final String msg;
LogEvent(Level level, String msg) {
this.level = level;
this.msg = msg;
}
public Level getLevel() {
return level;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return level + ": " + msg;
}
}
@@ -0,0 +1,30 @@
package jadx.gui.utils.rx;
import java.util.concurrent.TimeUnit;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.disposables.Disposable;
public class DebounceUpdate {
private FlowableEmitter<Boolean> emitter;
private final Disposable disposable;
public DebounceUpdate(int timeMs, Runnable action) {
FlowableOnSubscribe<Boolean> source = emitter -> this.emitter = emitter;
disposable = Flowable.create(source, BackpressureStrategy.LATEST)
.debounce(timeMs, TimeUnit.MILLISECONDS)
.subscribe(v -> action.run());
}
public void requestUpdate() {
emitter.onNext(Boolean.TRUE);
}
public void dispose() {
disposable.dispose();
}
}
@@ -9,6 +9,7 @@ menu.sync=Mit Editor synchronisieren
menu.flatten=Codepaket erweitern
menu.heapUsageBar=Speicherverbrauchsleiste anzeigen
menu.alwaysSelectOpened=Immer geöffnete Datei/Klasse auswählen
#menu.dock_log=Dock log viewer
menu.navigation=Navigation
menu.text_search=Textsuche
menu.class_search=Klassen-Suche
@@ -143,6 +144,12 @@ comment_dialog.usage=Shift + Enter verwenden, um eine neue Zeile zu beginnen
log_viewer.title=Log-Anzeige
log_viewer.log_level=Log-Level:
#log_viewer.mode=Mode:
#log_viewer.modes=All|All scripts|Current script
#log_viewer.hide=Hide
#log_viewer.dock=Dock
#log_viewer.undock=Undock
#log_viewer.clear=Clear
about_dialog.title=Über JADX
@@ -9,6 +9,7 @@ menu.sync=Sync with editor
menu.flatten=Show flatten packages
menu.heapUsageBar=Show memory usage bar
menu.alwaysSelectOpened=Always Select Opened File/Class
menu.dock_log=Dock log viewer
menu.navigation=Navigation
menu.text_search=Text search
menu.class_search=Class search
@@ -143,6 +144,12 @@ comment_dialog.usage=Use Shift + Enter for start a new line
log_viewer.title=Log Viewer
log_viewer.log_level=Log level:
log_viewer.mode=Mode:
log_viewer.modes=All|All scripts|Current script
log_viewer.hide=Hide
log_viewer.dock=Dock
log_viewer.undock=Undock
log_viewer.clear=Clear
about_dialog.title=About JADX
@@ -9,6 +9,7 @@ menu.sync=Sincronizar con el editor
menu.flatten=Mostrar paquetes en vista plana
#menu.heapUsageBar=
#menu.alwaysSelectOpened=Always Select Opened File/Class
#menu.dock_log=Dock log viewer
menu.navigation=Navegación
menu.text_search=Buscar texto
menu.class_search=Buscar clase
@@ -143,6 +144,12 @@ usage_dialog.label=Usage for:
log_viewer.title=Visor log
log_viewer.log_level=Nivel log:
#log_viewer.mode=Mode:
#log_viewer.modes=All|All scripts|Current script
#log_viewer.hide=Hide
#log_viewer.dock=Dock
#log_viewer.undock=Undock
#log_viewer.clear=Clear
about_dialog.title=Sobre JADX
@@ -9,6 +9,7 @@ menu.sync=에디터와 동기화
menu.flatten=플랫 패키지 표시
menu.heapUsageBar=메모리 사용량 표시
menu.alwaysSelectOpened=항상 열린 파일/클래스 선택
#menu.dock_log=Dock log viewer
menu.navigation=네비게이션
menu.text_search=텍스트 검색
menu.class_search=클래스 검색
@@ -143,6 +144,12 @@ comment_dialog.usage=Shift + Enter 를 입력해 새 라인에 입력
log_viewer.title=로그 뷰어
log_viewer.log_level=로그 레벨:
#log_viewer.mode=Mode:
#log_viewer.modes=All|All scripts|Current script
#log_viewer.hide=Hide
#log_viewer.dock=Dock
#log_viewer.undock=Undock
#log_viewer.clear=Clear
about_dialog.title=JADX 정보
@@ -9,6 +9,7 @@ menu.sync=Sincronizar com editor
menu.flatten=Mostrar pacotes achatados
menu.heapUsageBar=Mostrar uso de memória
menu.alwaysSelectOpened=Sempre selecionar arquivo/classe aberta
#menu.dock_log=Dock log viewer
menu.navigation=Navegação
menu.text_search=Buscar por texto
menu.class_search=Buscar por classe
@@ -143,6 +144,12 @@ comment_dialog.usage=Use Shift + Enter para pular uma linha
log_viewer.title=Visualizador de log
log_viewer.log_level=Nível do log:
#log_viewer.mode=Mode:
#log_viewer.modes=All|All scripts|Current script
#log_viewer.hide=Hide
#log_viewer.dock=Dock
#log_viewer.undock=Undock
#log_viewer.clear=Clear
about_dialog.title=Sobre o JADX
@@ -9,6 +9,7 @@ menu.sync=与编辑器同步
menu.flatten=展开显示代码包
menu.heapUsageBar=显示内存使用栏
menu.alwaysSelectOpened=始终选中打开的文件/类
#menu.dock_log=Dock log viewer
menu.navigation=导航
menu.text_search=文本搜索
menu.class_search=类名搜索
@@ -143,6 +144,12 @@ comment_dialog.usage=使用 Shift + Enter 换行
log_viewer.title=日志查看器
log_viewer.log_level=日志等级:
#log_viewer.mode=Mode:
#log_viewer.modes=All|All scripts|Current script
#log_viewer.hide=Hide
#log_viewer.dock=Dock
#log_viewer.undock=Undock
#log_viewer.clear=Clear
about_dialog.title=关于 JADX
@@ -9,6 +9,7 @@ menu.sync=與編輯器同步
menu.flatten=展開顯示套件
menu.heapUsageBar=顯示記憶體使用率條
menu.alwaysSelectOpened=總是選擇已開啟的檔案/類別
#menu.dock_log=Dock log viewer
menu.navigation=瀏覽
menu.text_search=文字搜尋
menu.class_search=類別搜尋
@@ -143,6 +144,12 @@ comment_dialog.usage=按下 Shift + Enter 來換行
log_viewer.title=日誌檢視器
log_viewer.log_level=紀錄層級:
#log_viewer.mode=Mode:
#log_viewer.modes=All|All scripts|Current script
#log_viewer.hide=Hide
#log_viewer.dock=Dock
#log_viewer.undock=Undock
#log_viewer.clear=Clear
about_dialog.title=關於 JADX
@@ -101,7 +101,7 @@ fun generateFieldSnippet(fld: FieldNode): String {
return """
${generateClassSnippet(fld.parentClass)}
${fld.name} = ${fld.parentClass.name}.$rawFieldName.value;
""".trimIndent()
""".trimIndent()
}
fun isOverloaded(methodNode: MethodNode): Boolean {
@@ -28,6 +28,8 @@ import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm
const val JADX_SCRIPT_LOG_PREFIX = "JadxScript:"
@KotlinScript(
fileExtension = "jadx.kts",
compilationConfiguration = JadxScriptConfiguration::class
@@ -36,7 +38,7 @@ abstract class JadxScriptTemplate(
private val scriptData: JadxScriptData
) {
val scriptName = scriptData.scriptName
val log = KotlinLogging.logger("JadxScript:$scriptName")
val log = KotlinLogging.logger("$JADX_SCRIPT_LOG_PREFIX$scriptName")
fun getJadxInstance() = JadxScriptInstance(scriptData, log)
@@ -27,7 +27,7 @@ class JadxScriptData(
) {
val afterLoad: MutableList<() -> Unit> = ArrayList()
val scriptName get() = scriptFile.name.removeSuffix(".jadx.kts")
val scriptName = scriptFile.name.removeSuffix(".jadx.kts")
}
class JadxScriptInstance(