feat(gui): add issues panel and summary report (#986)
This commit is contained in:
@@ -83,7 +83,7 @@ public class Deobfuscator {
|
||||
public void savePresets() {
|
||||
Path deobfMapFile = deobfPresets.getDeobfMapFile();
|
||||
if (Files.exists(deobfMapFile) && !args.isDeobfuscationForceSave()) {
|
||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
LOG.info("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
deobfMapFile.toAbsolutePath());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaNode;
|
||||
@@ -114,9 +116,11 @@ import jadx.gui.ui.dialog.LogViewerDialog;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
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.JPackagePopupMenu;
|
||||
import jadx.gui.ui.treenodes.SummaryNode;
|
||||
import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.update.JadxUpdate.IUpdateCallback;
|
||||
import jadx.gui.update.data.Release;
|
||||
@@ -129,6 +133,7 @@ import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.logs.LogCollector;
|
||||
import jadx.gui.utils.search.CommentsIndex;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
@@ -402,6 +407,7 @@ public class MainWindow extends JFrame {
|
||||
project.setFilePath(paths);
|
||||
clearTree();
|
||||
BreakpointManager.saveAndExit();
|
||||
LogCollector.getInstance().reset();
|
||||
if (paths.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -413,11 +419,31 @@ public class MainWindow extends JFrame {
|
||||
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
|
||||
return;
|
||||
}
|
||||
checkLoadedStatus();
|
||||
onOpen(paths);
|
||||
onFinish.run();
|
||||
});
|
||||
}
|
||||
|
||||
private void checkLoadedStatus() {
|
||||
if (!wrapper.getClasses().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int errors = LogCollector.getInstance().getErrors();
|
||||
if (errors > 0) {
|
||||
int result = JOptionPane.showConfirmDialog(this,
|
||||
NLS.str("message.load_errors", errors),
|
||||
NLS.str("message.errorTitle"),
|
||||
JOptionPane.OK_CANCEL_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
if (result == JOptionPane.OK_OPTION) {
|
||||
LogViewerDialog.openWithLevel(this, Level.ERROR);
|
||||
}
|
||||
} else {
|
||||
UiUtils.showMessageBox(this, NLS.str("message.no_classes"));
|
||||
}
|
||||
}
|
||||
|
||||
private void onOpen(List<Path> paths) {
|
||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||
initTree();
|
||||
@@ -428,6 +454,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
private void addTreeCustomNodes() {
|
||||
treeRoot.replaceCustomNode(ApkSignature.getApkSignature(wrapper));
|
||||
treeRoot.replaceCustomNode(new SummaryNode(this));
|
||||
}
|
||||
|
||||
private boolean ensureProjectIsSaved() {
|
||||
@@ -723,7 +750,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
private void treeRightClickAction(MouseEvent e) {
|
||||
JNode obj = getJNodeUnderMouse(e);
|
||||
JNode obj = getJNodeUnderMouse(e, false);
|
||||
if (obj instanceof JPackage) {
|
||||
JPackagePopupMenu menu = new JPackagePopupMenu(this, (JPackage) obj);
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
@@ -737,8 +764,12 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JNode getJNodeUnderMouse(MouseEvent mouseEvent) {
|
||||
private JNode getJNodeUnderMouse(MouseEvent mouseEvent, boolean trySelection) {
|
||||
TreePath path = tree.getPathForLocation(mouseEvent.getX(), mouseEvent.getY());
|
||||
if (path == null && trySelection) {
|
||||
// maybe click on node row (mouse pressed event should select this node in tree)
|
||||
path = tree.getSelectionPath();
|
||||
}
|
||||
if (path != null) {
|
||||
Object obj = path.getLastPathComponent();
|
||||
if (obj instanceof JNode) {
|
||||
@@ -939,7 +970,7 @@ public class MainWindow extends JFrame {
|
||||
Action logAction = new AbstractAction(NLS.str("menu.log"), ICON_LOG) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new LogViewerDialog(MainWindow.this).setVisible(true);
|
||||
LogViewerDialog.open(MainWindow.this);
|
||||
}
|
||||
};
|
||||
logAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.log"));
|
||||
@@ -1096,10 +1127,16 @@ public class MainWindow extends JFrame {
|
||||
ToolTipManager.sharedInstance().registerComponent(tree);
|
||||
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
tree.addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
super.mousePressed(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (SwingUtilities.isLeftMouseButton(e)) {
|
||||
nodeClickAction(getJNodeUnderMouse(e));
|
||||
nodeClickAction(getJNodeUnderMouse(e, true));
|
||||
} else if (SwingUtilities.isRightMouseButton(e)) {
|
||||
treeRightClickAction(e);
|
||||
}
|
||||
@@ -1157,13 +1194,18 @@ public class MainWindow extends JFrame {
|
||||
});
|
||||
|
||||
progressPane = new ProgressPanel(this, true);
|
||||
IssuesPanel issuesPanel = new IssuesPanel(this);
|
||||
|
||||
JPanel leftPane = new JPanel(new BorderLayout());
|
||||
JScrollPane treeScrollPane = new JScrollPane(tree);
|
||||
treeScrollPane.setMinimumSize(new Dimension(100, 150));
|
||||
|
||||
JPanel bottomPane = new JPanel(new BorderLayout());
|
||||
bottomPane.add(issuesPanel, BorderLayout.PAGE_START);
|
||||
bottomPane.add(progressPane, BorderLayout.PAGE_END);
|
||||
|
||||
leftPane.add(treeScrollPane, BorderLayout.CENTER);
|
||||
leftPane.add(progressPane, BorderLayout.PAGE_END);
|
||||
leftPane.add(bottomPane, BorderLayout.PAGE_END);
|
||||
splitPane.setLeftComponent(leftPane);
|
||||
|
||||
tabbedPane = new TabbedPane(this);
|
||||
|
||||
@@ -32,7 +32,16 @@ public class LogViewerDialog extends JDialog {
|
||||
private final transient JadxSettings settings;
|
||||
private transient RSyntaxTextArea textPane;
|
||||
|
||||
public LogViewerDialog(MainWindow mainWindow) {
|
||||
public static void open(MainWindow mainWindow) {
|
||||
openWithLevel(mainWindow, level);
|
||||
}
|
||||
|
||||
public static void openWithLevel(MainWindow mainWindow, Level newLevel) {
|
||||
level = newLevel;
|
||||
new LogViewerDialog(mainWindow).setVisible(true);
|
||||
}
|
||||
|
||||
private LogViewerDialog(MainWindow mainWindow) {
|
||||
this.settings = mainWindow.getSettings();
|
||||
initUI(mainWindow);
|
||||
registerLogListener();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package jadx.gui.ui.panel;
|
||||
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
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.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;
|
||||
|
||||
private static final ImageIcon ERROR_ICON = UiUtils.openSvgIcon("ui/error");
|
||||
private static final ImageIcon WARN_ICON = UiUtils.openSvgIcon("ui/warning");
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private JLabel errorLabel;
|
||||
private JLabel warnLabel;
|
||||
|
||||
public IssuesPanel(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
initUI();
|
||||
LogCollector.getInstance().registerIssueListener((error, warnings) -> {
|
||||
SwingUtilities.invokeLater(() -> onUpdate(error, warnings));
|
||||
});
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
JLabel label = new JLabel(NLS.str("issues_panel.label"));
|
||||
errorLabel = new JLabel(ERROR_ICON);
|
||||
warnLabel = new JLabel(WARN_ICON);
|
||||
|
||||
String toolTipText = NLS.str("issues_panel.tooltip");
|
||||
errorLabel.setToolTipText(toolTipText);
|
||||
warnLabel.setToolTipText(toolTipText);
|
||||
|
||||
errorLabel.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
LogViewerDialog.openWithLevel(mainWindow, Level.ERROR);
|
||||
}
|
||||
});
|
||||
warnLabel.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
LogViewerDialog.openWithLevel(mainWindow, Level.WARN);
|
||||
}
|
||||
});
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||
setVisible(false);
|
||||
add(label);
|
||||
add(Box.createHorizontalGlue());
|
||||
add(errorLabel);
|
||||
add(Box.createHorizontalGlue());
|
||||
add(warnLabel);
|
||||
}
|
||||
|
||||
private void onUpdate(int error, int warnings) {
|
||||
if (error == 0 && warnings == 0) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
setVisible(true);
|
||||
errorLabel.setText(NLS.str("issues_panel.errors", error));
|
||||
errorLabel.setVisible(error != 0);
|
||||
warnLabel.setText(NLS.str("issues_panel.warnings", warnings));
|
||||
warnLabel.setVisible(warnings != 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package jadx.gui.ui.treenodes;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.ProcessState;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.ui.panel.HtmlPanel;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class SummaryNode extends JNode {
|
||||
private static final long serialVersionUID = 4295299814582784805L;
|
||||
|
||||
private static final ImageIcon ICON = UiUtils.openSvgIcon("nodes/detailView");
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
|
||||
public SummaryNode(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContent() {
|
||||
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
|
||||
try {
|
||||
builder.append("<html>");
|
||||
builder.append("<body>");
|
||||
writeInputSummary(builder);
|
||||
writeDecompilationSummary(builder);
|
||||
builder.append("</body>");
|
||||
} catch (Exception e) {
|
||||
builder.append("Error build summary: ");
|
||||
builder.append("<pre>");
|
||||
builder.append(Utils.getStackTrace(e));
|
||||
builder.append("</pre>");
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void writeInputSummary(StringEscapeUtils.Builder builder) throws IOException {
|
||||
builder.append("<h2>Input</h2>");
|
||||
JadxDecompiler jadx = mainWindow.getWrapper().getDecompiler();
|
||||
builder.append("<h3>Files</h3>");
|
||||
builder.append("<ul>");
|
||||
for (File inputFile : jadx.getArgs().getInputFiles()) {
|
||||
builder.append("<li>");
|
||||
builder.escape(inputFile.getCanonicalFile().getAbsolutePath());
|
||||
builder.append("</li>");
|
||||
}
|
||||
builder.append("</ul>");
|
||||
|
||||
List<ClassNode> classes = jadx.getRoot().getClasses(true);
|
||||
List<String> codeSources = classes.stream()
|
||||
.map(ClassNode::getInputFileName)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
codeSources.remove("synthetic");
|
||||
int codeSourcesCount = codeSources.size();
|
||||
builder.append("<h3>Code sources</h3>");
|
||||
builder.append("<ul>");
|
||||
if (codeSourcesCount != 1) {
|
||||
builder.append("<li>Count: " + codeSourcesCount + "</li>");
|
||||
}
|
||||
// dex files list
|
||||
codeSources.removeIf(f -> !f.endsWith(".dex"));
|
||||
if (!codeSources.isEmpty()) {
|
||||
for (String input : codeSources) {
|
||||
builder.append("<li>");
|
||||
builder.escape(input);
|
||||
builder.append("</li>");
|
||||
}
|
||||
}
|
||||
builder.append("</ul>");
|
||||
|
||||
int methodsCount = classes.stream().mapToInt(cls -> cls.getMethods().size()).sum();
|
||||
int fieldsCount = classes.stream().mapToInt(cls -> cls.getFields().size()).sum();
|
||||
int insnCount = classes.stream().flatMap(cls -> cls.getMethods().stream()).mapToInt(MethodNode::getInsnsCount).sum();
|
||||
builder.append("<h3>Counts</h3>");
|
||||
builder.append("<ul>");
|
||||
builder.append("<li>Classes: " + classes.size() + "</li>");
|
||||
builder.append("<li>Methods: " + methodsCount + "</li>");
|
||||
builder.append("<li>Fields: " + fieldsCount + "</li>");
|
||||
builder.append("<li>Instructions: " + insnCount + " (units)</li>");
|
||||
builder.append("</ul>");
|
||||
}
|
||||
|
||||
private void writeDecompilationSummary(StringEscapeUtils.Builder builder) {
|
||||
builder.append("<h2>Decompilation</h2>");
|
||||
JadxDecompiler jadx = mainWindow.getWrapper().getDecompiler();
|
||||
List<ClassNode> classes = jadx.getRoot().getClasses(false);
|
||||
int classesCount = classes.size();
|
||||
long processedClasses = classes.stream().filter(c -> c.getState() == ProcessState.PROCESS_COMPLETE).count();
|
||||
long generatedClasses = classes.stream().filter(c -> c.getState() == ProcessState.GENERATED_AND_UNLOADED).count();
|
||||
builder.append("<ul>");
|
||||
builder.append("<li>Top level classes: " + classesCount + "</li>");
|
||||
builder.append("<li>At process stage: " + valueAndPercent(processedClasses, classesCount) + "</li>");
|
||||
builder.append("<li>Code generated: " + valueAndPercent(generatedClasses, classesCount) + "</li>");
|
||||
builder.append("</ul>");
|
||||
|
||||
ErrorsCounter counter = jadx.getRoot().getErrorsCounter();
|
||||
Set<IAttributeNode> problemNodes = new HashSet<>();
|
||||
problemNodes.addAll(counter.getErrorNodes());
|
||||
problemNodes.addAll(counter.getWarnNodes());
|
||||
long problemMethods = problemNodes.stream().filter(MethodNode.class::isInstance).count();
|
||||
int methodsCount = classes.stream().mapToInt(cls -> cls.getMethods().size()).sum();
|
||||
double methodSuccessRate = (methodsCount - problemMethods) * 100.0 / (double) methodsCount;
|
||||
|
||||
builder.append("<h3>Issues</h3>");
|
||||
builder.append("<ul>");
|
||||
builder.append("<li>Errors: " + counter.getErrorCount() + "</li>");
|
||||
builder.append("<li>Warnings: " + counter.getWarnsCount() + "</li>");
|
||||
builder.append("<li>Nodes with errors: " + counter.getErrorNodes().size() + "</li>");
|
||||
builder.append("<li>Nodes with warnings: " + counter.getWarnNodes().size() + "</li>");
|
||||
builder.append("<li>Total nodes with issues: " + problemNodes.size() + "</li>");
|
||||
builder.append("<li>Methods with issues: " + problemMethods + "</li>");
|
||||
builder.append("<li>Methods success rate: " + String.format("%.2f", methodSuccessRate) + "%</li>");
|
||||
builder.append("</ul>");
|
||||
}
|
||||
|
||||
private String valueAndPercent(long value, int total) {
|
||||
return String.format("%d (%.2f%%)", value, value * 100 / ((double) total));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContentPanel getContentPanel(TabbedPane tabbedPane) {
|
||||
return new HtmlPanel(tabbedPane, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return "Summary";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JClass getJParent() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -253,10 +253,6 @@ public class UiUtils {
|
||||
return CTRL_BNT_KEY;
|
||||
}
|
||||
|
||||
public static void showMessageBox(Component parent, String msg) {
|
||||
JOptionPane.showMessageDialog(parent, msg);
|
||||
}
|
||||
|
||||
public static void addEscapeShortCutToDispose(JDialog dialog) {
|
||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
dialog.getRootPane().registerKeyboardAction(e -> dialog.dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
@@ -292,6 +288,10 @@ public class UiUtils {
|
||||
return envVal;
|
||||
}
|
||||
|
||||
public static void showMessageBox(Component parent, String msg) {
|
||||
JOptionPane.showMessageDialog(parent, msg);
|
||||
}
|
||||
|
||||
public static void errorMessage(Component parent, String message) {
|
||||
JOptionPane.showMessageDialog(parent, message,
|
||||
NLS.str("message.errorTitle"), JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.gui.utils.logs;
|
||||
|
||||
public interface ILogIssuesListener {
|
||||
void onChange(int error, int warnings);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.gui.utils.logs;
|
||||
|
||||
import java.util.AbstractQueue;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class LimitedQueue<T> extends AbstractQueue<T> {
|
||||
|
||||
private final Deque<T> deque = new ArrayDeque<>();
|
||||
private final int limit;
|
||||
|
||||
public LimitedQueue(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return deque.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return deque.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(T t) {
|
||||
deque.addLast(t);
|
||||
if (deque.size() > limit) {
|
||||
deque.removeFirst();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T poll() {
|
||||
return deque.poll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T peek() {
|
||||
return deque.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
deque.clear();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.gui.utils.logs;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -44,8 +43,12 @@ public class LogCollector extends AppenderBase<ILoggingEvent> {
|
||||
|
||||
@Nullable
|
||||
private ILogListener listener;
|
||||
@Nullable
|
||||
private ILogIssuesListener issuesListener;
|
||||
private int errors = 0;
|
||||
private int warnings = 0;
|
||||
|
||||
private final Deque<LogEvent> buffer = new LinkedList<>();
|
||||
private final Queue<LogEvent> buffer = new LimitedQueue<>(BUFFER_SIZE);
|
||||
|
||||
public LogCollector() {
|
||||
setName("LogCollector");
|
||||
@@ -60,14 +63,24 @@ public class LogCollector extends AppenderBase<ILoggingEvent> {
|
||||
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.addLast(new LogEvent(level, msg));
|
||||
if (buffer.size() > BUFFER_SIZE) {
|
||||
buffer.pollFirst();
|
||||
}
|
||||
buffer.offer(new LogEvent(level, msg));
|
||||
}
|
||||
|
||||
public void setLayout(Layout<ILoggingEvent> layout) {
|
||||
@@ -81,10 +94,32 @@ public class LogCollector extends AppenderBase<ILoggingEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -64,6 +64,8 @@ message.userCancelTask=Aufgabe wurde vom Benutzer abgebrochen.
|
||||
message.memoryLow=Jadx hat zu wenig Speicherplatz. Bitte mit erhöhter maximaler Heap-Größe erneut starten.
|
||||
message.taskError=Die Aufgabe ist durch Fehler fehlgeschlagen (siehe Protokoll für Details).
|
||||
message.errorTitle=Fehler
|
||||
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
#message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
message.saveIncomplete=<html>Speichern unvollständig.<br> %s<br> %d Klassen oder Quellen wurden nicht gespeichert!</html>
|
||||
message.indexIncomplete=<html>Index einiger Klassen übersprungen.<br> %s<br> %d Klassen wurden nicht indiziert und werden nicht in den Suchergebnissen erscheinen!</html>
|
||||
@@ -241,6 +243,11 @@ apkSignature.warnings=Warnhinweise
|
||||
apkSignature.exception=APK-Verifizierung fehlgeschlagen
|
||||
apkSignature.unprotectedEntry=Dateien, die nicht durch eine Signatur geschützt sind. Unbefugte Änderungen an diesem JAR-Eintrag werden nicht erkannt.
|
||||
|
||||
#issues_panel.label=Issues:
|
||||
#issues_panel.errors=%d errors
|
||||
#issues_panel.warnings=%d warnings
|
||||
#issues_panel.tooltip=Open in log viewer
|
||||
|
||||
debugger.process_selector=Zu debuggenden Prozess auswählen
|
||||
debugger.step_into=Schritt Into (F7)
|
||||
debugger.step_over=Schritt Over (F8)
|
||||
|
||||
@@ -64,6 +64,8 @@ message.userCancelTask=Task was canceled by user.
|
||||
message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
message.taskError=Task failed with error (check log for details).
|
||||
message.errorTitle=Error
|
||||
message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
@@ -241,6 +243,11 @@ apkSignature.warnings=Warnings
|
||||
apkSignature.exception=APK verification failed
|
||||
apkSignature.unprotectedEntry=Files that are not protected by signature. Unauthorized modifications to this JAR entry will not be detected.
|
||||
|
||||
issues_panel.label=Issues:
|
||||
issues_panel.errors=%d errors
|
||||
issues_panel.warnings=%d warnings
|
||||
issues_panel.tooltip=Open in log viewer
|
||||
|
||||
debugger.process_selector=Select a process to debug
|
||||
debugger.step_into=Step Into (F7)
|
||||
debugger.step_over=Step Over (F8)
|
||||
|
||||
@@ -64,6 +64,8 @@ nav.forward=Adelante
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
#message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
@@ -241,6 +243,11 @@ certificate.serialPubKeyY=Y
|
||||
#apkSignature.exception=
|
||||
#apkSignature.unprotectedEntry=
|
||||
|
||||
#issues_panel.label=Issues:
|
||||
#issues_panel.errors=%d errors
|
||||
#issues_panel.warnings=%d warnings
|
||||
#issues_panel.tooltip=Open in log viewer
|
||||
|
||||
#debugger.process_selector=Select a process to debug
|
||||
#debugger.step_into=Step Into (F7)
|
||||
#debugger.step_over=Step Over (F8)
|
||||
|
||||
@@ -64,6 +64,8 @@ nav.forward=앞으로
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
#message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
@@ -241,6 +243,11 @@ apkSignature.warnings=경고
|
||||
apkSignature.exception=APK 검증 실패
|
||||
apkSignature.unprotectedEntry=서명으로 보호되지 않는 파일. 이 JAR 항목에 대한 승인되지 않은 수정은 감지되지 않습니다.
|
||||
|
||||
#issues_panel.label=Issues:
|
||||
#issues_panel.errors=%d errors
|
||||
#issues_panel.warnings=%d warnings
|
||||
#issues_panel.tooltip=Open in log viewer
|
||||
|
||||
debugger.process_selector=디버깅 할 프로세스 선택
|
||||
debugger.step_into=한 단계씩 코드 실행 (F7)
|
||||
debugger.step_over=프로시저 단위 실행 (F8)
|
||||
|
||||
@@ -64,6 +64,8 @@ nav.forward=前进
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
#message.load_errors=Load failed.\nErrors count: %d\nClick OK to open log viewer"
|
||||
#message.no_classes=No classes loaded, nothing to decompile!
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
@@ -241,6 +243,11 @@ apkSignature.warnings=警告
|
||||
apkSignature.exception=APK 验证失败
|
||||
apkSignature.unprotectedEntry=不受签名保护的文件。不会检测对此 JAR 条目的未经授权的修改。
|
||||
|
||||
#issues_panel.label=Issues:
|
||||
#issues_panel.errors=%d errors
|
||||
#issues_panel.warnings=%d warnings
|
||||
#issues_panel.tooltip=Open in log viewer
|
||||
|
||||
#debugger.process_selector=Select a process to debug
|
||||
#debugger.step_into=Step Into (F7)
|
||||
#debugger.step_over=Step Over (F8)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 5H8V8H10V6H12V11H10H8H6H4V9H6V5Z" fill="#6E6E6E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H1V15H15V1ZM13 3H3V13H13V3Z" fill="#6E6E6E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 306 B |
@@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#E05555" fill-opacity=".7" fill-rule="evenodd" d="M8,14 C4.6862915,14 2,11.3137085 2,8 C2,4.6862915 4.6862915,2 8,2 C11.3137085,2 14,4.6862915 14,8 C14,11.3137085 11.3137085,14 8,14 Z M7,4 L7,8 L9,8 L9,4 L7,4 Z M7,10 L7,12 L9,12 L9,10 L7,10 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 498 B |
@@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#F4AF3D" fill-rule="evenodd" d="M8,2 L15,14 L1,14 L8,2 Z M9,13 L9,11 L7,11 L7,13 L9,13 Z M9,10 L9,6 L7,6 L7,10 L9,10 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 374 B |
Reference in New Issue
Block a user