From a3464d71846439b33fee1fa2d59fbce911d6909b Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 28 Oct 2018 18:24:09 +0300 Subject: [PATCH] fix(gui): make link for full class names (#378) --- .../main/java/jadx/api/JadxDecompiler.java | 28 ++ .../src/main/java/jadx/api/JavaClass.java | 15 +- .../main/java/jadx/core/codegen/ClassGen.java | 30 +- .../java/jadx/gui/settings/JadxSettings.java | 4 +- .../jadx/gui/settings/JadxSettingsWindow.java | 5 +- .../java/jadx/gui/ui/CertificatePanel.java | 1 + .../src/main/java/jadx/gui/ui/CodeArea.java | 361 ------------------ .../java/jadx/gui/ui/CommonSearchDialog.java | 5 +- .../main/java/jadx/gui/ui/ContentPanel.java | 8 +- .../src/main/java/jadx/gui/ui/LogViewer.java | 1 + .../src/main/java/jadx/gui/ui/MainWindow.java | 4 +- .../src/main/java/jadx/gui/ui/TabbedPane.java | 20 +- .../java/jadx/gui/ui/codearea/CodeArea.java | 204 ++++++++++ .../gui/ui/codearea/CodeLinkGenerator.java | 88 +++++ .../jadx/gui/ui/{ => codearea}/CodePanel.java | 14 +- .../jadx/gui/ui/codearea/EditorTheme.java | 40 ++ .../jadx/gui/ui/codearea/FindUsageAction.java | 67 ++++ .../jadx/gui/ui/codearea/JadxTokenMaker.java | 105 +++++ .../gui/ui/{ => codearea}/LineNumbers.java | 2 +- .../jadx/gui/ui/{ => codearea}/SearchBar.java | 9 +- .../main/java/jadx/gui/utils/JumpManager.java | 12 +- .../{Position.java => JumpPosition.java} | 15 +- .../resources/i18n/Messages_en_US.properties | 1 + .../jadx/gui/tests/TestJumpManager.groovy | 26 +- 24 files changed, 602 insertions(+), 463 deletions(-) delete mode 100644 jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java rename jadx-gui/src/main/java/jadx/gui/ui/{ => codearea}/CodePanel.java (82%) create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/codearea/EditorTheme.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/codearea/FindUsageAction.java create mode 100644 jadx-gui/src/main/java/jadx/gui/ui/codearea/JadxTokenMaker.java rename jadx-gui/src/main/java/jadx/gui/ui/{ => codearea}/LineNumbers.java (96%) rename jadx-gui/src/main/java/jadx/gui/ui/{ => codearea}/SearchBar.java (97%) rename jadx-gui/src/main/java/jadx/gui/utils/{Position.java => JumpPosition.java} (64%) diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 38f15e5b..db84e02e 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -311,10 +311,38 @@ public final class JadxDecompiler { return methodsMap; } + JavaMethod getJavaMethodByNode(MethodNode mth) { + JavaMethod javaMethod = methodsMap.get(mth); + if (javaMethod != null) { + return javaMethod; + } + // parent class not loaded yet + JavaClass javaClass = classesMap.get(mth.getParentClass()); + if (javaClass != null) { + javaClass.decompile(); + return methodsMap.get(mth); + } + return null; + } + Map getFieldsMap() { return fieldsMap; } + JavaField getJavaFieldByNode(FieldNode fld) { + JavaField javaField = fieldsMap.get(fld); + if (javaField != null) { + return javaField; + } + // parent class not loaded yet + JavaClass javaClass = classesMap.get(fld.getParentClass()); + if (javaClass != null) { + javaClass.decompile(); + return fieldsMap.get(fld); + } + return null; + } + public JadxArgs getArgs() { return args; } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 372c7684..e25b5a52 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -160,10 +160,10 @@ public final class JavaClass implements JavaNode { return getRootDecompiler().getClassesMap().get(obj); } if (obj instanceof MethodNode) { - return getRootDecompiler().getMethodsMap().get(obj); + return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj)); } if (obj instanceof FieldNode) { - return getRootDecompiler().getFieldsMap().get(obj); + return getRootDecompiler().getJavaFieldByNode((FieldNode) obj); } return null; } @@ -181,15 +181,6 @@ public final class JavaClass implements JavaNode { return convertNode(obj); } - @Nullable - public CodePosition getDefinitionPosition(int line, int offset) { - JavaNode javaNode = getJavaNodeAtPosition(line, offset); - if (javaNode == null) { - return null; - } - return getDefinitionPosition(javaNode); - } - @Nullable public CodePosition getDefinitionPosition(JavaNode javaNode) { JavaClass jCls = javaNode.getTopParentClass(); @@ -265,6 +256,6 @@ public final class JavaClass implements JavaNode { @Override public String toString() { - return cls.getFullName() + "[ " + getFullName() + " ]"; + return getFullName(); } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 344c07fe..92213709 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -1,7 +1,6 @@ package jadx.core.codegen; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; @@ -84,35 +83,18 @@ public class ClassGen { } int importsCount = imports.size(); if (importsCount != 0) { - List sortImports = new ArrayList<>(importsCount); - for (ClassInfo ic : imports) { - sortImports.add(ic.getAlias().getFullName()); - } - Collections.sort(sortImports); - - for (String imp : sortImports) { - ClassInfo importClassInfo = ClassInfo.fromName(cls.dex().root(), imp); - ClassNode classNode = cls.dex().resolveClass(importClassInfo); - // Clickable element seems to be limited by the next dot, therefore - // we can't just use the complete class name including packagename - int clsDotIdx = imp.lastIndexOf('.'); - String pkg = ""; - if (clsDotIdx >= 0) { - pkg = imp.substring(0, clsDotIdx + 1); - imp = imp.substring(clsDotIdx + 1); - } + List sortedImports = new ArrayList<>(imports); + sortedImports.sort(Comparator.comparing(classInfo -> classInfo.getAlias().getFullName())); + sortedImports.forEach(classInfo -> { clsCode.startLine("import "); - clsCode.add(pkg); + ClassNode classNode = cls.root().resolveClass(classInfo); if (classNode != null) { - // attach the clickable link info to the class name clsCode.attachAnnotation(classNode); } - clsCode.add(imp); + clsCode.add(classInfo.getAlias().getFullName()); clsCode.add(';'); - } + }); clsCode.newLine(); - - sortImports.clear(); imports.clear(); } clsCode.add(clsBody); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 762e4f18..687fcf64 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; import jadx.api.JadxArgs; import jadx.cli.JadxCLIArgs; -import jadx.gui.ui.CodeArea; +import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; @@ -263,7 +263,7 @@ public class JadxSettings extends JadxCLIArgs { fromVersion++; } if (fromVersion == 1) { - setEditorThemePath(CodeArea.getAllThemes()[0].getPath()); + setEditorThemePath(EditorTheme.ALL_THEMES[0].getPath()); fromVersion++; } if (fromVersion == 2) { diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 9c1d8c2a..da2b6c76 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -12,8 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import say.swing.JFontChooser; -import jadx.gui.ui.CodeArea; -import jadx.gui.ui.CodeArea.EditorTheme; +import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.ui.MainWindow; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; @@ -191,7 +190,7 @@ public class JadxSettingsWindow extends JDialog { } }); - EditorTheme[] editorThemes = CodeArea.getAllThemes(); + EditorTheme[] editorThemes = EditorTheme.ALL_THEMES; JComboBox themesCbx = new JComboBox<>(editorThemes); for (EditorTheme theme : editorThemes) { if (theme.getPath().equals(settings.getEditorThemePath())) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java index 6e8d2f53..c1761faf 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CertificatePanel.java @@ -6,6 +6,7 @@ import java.awt.*; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import jadx.gui.treemodel.JNode; +import jadx.gui.ui.codearea.CodeArea; public final class CertificatePanel extends ContentPanel { private static final long serialVersionUID = 8566591625905036877L; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java deleted file mode 100644 index 4b56af41..00000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java +++ /dev/null @@ -1,361 +0,0 @@ -package jadx.gui.ui; - -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; -import javax.swing.event.HyperlinkListener; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; -import javax.swing.text.BadLocationException; -import javax.swing.text.Caret; -import javax.swing.text.DefaultCaret; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; - -import org.fife.ui.rsyntaxtextarea.LinkGenerator; -import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.Token; -import org.fife.ui.rsyntaxtextarea.TokenTypes; -import org.fife.ui.rtextarea.SearchContext; -import org.fife.ui.rtextarea.SearchEngine; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import jadx.api.CodePosition; -import jadx.api.JavaNode; -import jadx.gui.settings.JadxSettings; -import jadx.gui.treemodel.JClass; -import jadx.gui.treemodel.JNode; -import jadx.gui.utils.Position; - -public final class CodeArea extends RSyntaxTextArea { - private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class); - - private static final long serialVersionUID = 6312736869579635796L; - - private final CodePanel contentPanel; - private final JNode node; - - CodeArea(CodePanel panel) { - this.contentPanel = panel; - this.node = panel.getNode(); - - setMarkOccurrences(true); - setEditable(false); - loadSettings(); - - Caret caret = getCaret(); - if (caret instanceof DefaultCaret) { - ((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); - } - caret.setVisible(true); - - setSyntaxEditingStyle(node.getSyntaxName()); - if (node instanceof JClass) { - setHyperlinksEnabled(true); - CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator((JClass) node); - setLinkGenerator(codeLinkProcessor); - addHyperlinkListener(codeLinkProcessor); - addMenuItems(this, (JClass) node); - } - registerWordHighlighter(); - setText(node.getContent()); - } - - private void registerWordHighlighter() { - addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent evt) { - if (evt.getClickCount() % 2 == 0 && !evt.isConsumed()) { - evt.consume(); - String str = getSelectedText(); - if (str != null) { - highlightAllMatches(str); - } - } else { - highlightAllMatches(null); - } - } - }); - } - - /** - * @param str - if null -> reset current highlights - */ - private void highlightAllMatches(@Nullable String str) { - SearchContext context = new SearchContext(str); - context.setMarkAll(true); - context.setMatchCase(true); - context.setWholeWord(true); - SearchEngine.markAll(this, context); - } - - private void addMenuItems(CodeArea codeArea, JClass jCls) { - Action findUsage = new FindUsageAction(codeArea, jCls); - - JPopupMenu popup = getPopupMenu(); - popup.addSeparator(); - popup.add(findUsage); - popup.addPopupMenuListener((PopupMenuListener) findUsage); - } - - public void loadSettings() { - loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this); - } - - public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) { - area.setAntiAliasingEnabled(true); - mainWindow.getEditorTheme().apply(area); - - JadxSettings settings = mainWindow.getSettings(); - area.setFont(settings.getFont()); - } - - public static RSyntaxTextArea getDefaultArea(MainWindow mainWindow) { - RSyntaxTextArea area = new RSyntaxTextArea(); - loadCommonSettings(mainWindow, area); - return area; - } - - private boolean isJumpToken(Token token) { - if (token.getType() == TokenTypes.IDENTIFIER) { - // fast skip - if (token.length() == 1) { - char ch = token.getTextArray()[token.getTextOffset()]; - if (ch == '.' || ch == ',' || ch == ';') { - return false; - } - } - if (node instanceof JClass) { - Position pos = getDefPosition((JClass) node, this, token.getOffset()); - if (pos != null) { - // don't underline definition place - try { - int defLine = pos.getLine(); - int lineOfOffset = getLineOfOffset(token.getOffset()) + 1; - if (defLine == lineOfOffset) { - return false; - } - } catch (BadLocationException e) { - return false; - } - return true; - } - } - } - return false; - } - -// @Override -// public Color getForegroundForToken(Token t) { -// if (isJumpToken(t)) { -// return getHyperlinkForeground(); -// } -// return super.getForegroundForToken(t); -// } - - static Position getDefPosition(JClass jCls, RSyntaxTextArea textArea, int offset) { - JavaNode node = getJavaNodeAtOffset(jCls, textArea, offset); - if (node == null) { - return null; - } - CodePosition pos = jCls.getCls().getDefinitionPosition(node); - if (pos == null) { - return null; - } - return new Position(pos); - } - - static JavaNode getJavaNodeAtOffset(JClass jCls, RSyntaxTextArea textArea, int offset) { - try { - int line = textArea.getLineOfOffset(offset); - int lineOffset = offset - textArea.getLineStartOffset(line); - return jCls.getCls().getJavaNodeAtPosition(line + 1, lineOffset + 1); - } catch (BadLocationException e) { - LOG.error("Can't get java node by offset", e); - } - return null; - } - - public Position getCurrentPosition() { - return new Position(node, getCaretLineNumber() + 1); - } - - Integer getSourceLine(int line) { - return node.getSourceLine(line); - } - - void scrollToLine(int line) { - int lineNum = line - 1; - if (lineNum < 0) { - lineNum = 0; - } - setCaretAtLine(lineNum); - centerCurrentLine(); - forceCurrentLineHighlightRepaint(); - } - - public void centerCurrentLine() { - JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this); - if (viewport == null) { - return; - } - try { - Rectangle r = modelToView(getCaretPosition()); - if (r == null) { - return; - } - int extentHeight = viewport.getExtentSize().height; - Dimension viewSize = viewport.getViewSize(); - if (viewSize == null) { - return; - } - int viewHeight = viewSize.height; - - int y = Math.max(0, r.y - extentHeight / 2); - y = Math.min(y, viewHeight - extentHeight); - - viewport.setViewPosition(new Point(0, y)); - } catch (BadLocationException e) { - LOG.debug("Can't center current line", e); - } - } - - private void setCaretAtLine(int line) { - try { - setCaretPosition(getLineStartOffset(line)); - } catch (BadLocationException e) { - LOG.debug("Can't scroll to {}", line, e); - } - } - - private class FindUsageAction extends AbstractAction implements PopupMenuListener { - private static final long serialVersionUID = 4692546569977976384L; - - private final transient CodeArea codeArea; - private final transient JClass jCls; - - private transient JavaNode node; - - public FindUsageAction(CodeArea codeArea, JClass jCls) { - super("Find Usage"); - this.codeArea = codeArea; - this.jCls = jCls; - } - - @Override - public void actionPerformed(ActionEvent e) { - if (node == null) { - return; - } - MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow(); - JNode jNode = mainWindow.getCacheObject().getNodeCache().makeFrom(node); - UsageDialog usageDialog = new UsageDialog(mainWindow, jNode); - usageDialog.setVisible(true); - } - - @Override - public void popupMenuWillBecomeVisible(PopupMenuEvent e) { - node = null; - Point pos = codeArea.getMousePosition(); - if (pos != null) { - Token token = codeArea.viewToToken(pos); - if (token != null) { - node = getJavaNodeAtOffset(jCls, codeArea, token.getOffset()); - } - } - setEnabled(node != null); - } - - @Override - public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { - } - - @Override - public void popupMenuCanceled(PopupMenuEvent e) { - } - } - - private class CodeLinkGenerator implements LinkGenerator, HyperlinkListener { - private final JClass jCls; - - public CodeLinkGenerator(JClass cls) { - this.jCls = cls; - } - - @Override - public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offset) { - try { - Token token = textArea.modelToToken(offset); - if (token == null) { - return null; - } - final int sourceOffset = token.getOffset(); - final Position defPos = getDefPosition(jCls, textArea, sourceOffset); - if (defPos == null) { - return null; - } - return new LinkGeneratorResult() { - @Override - public HyperlinkEvent execute() { - return new HyperlinkEvent(defPos, HyperlinkEvent.EventType.ACTIVATED, null, - defPos.getNode().makeLongString()); - } - - @Override - public int getSourceOffset() { - return sourceOffset; - } - }; - } catch (Exception e) { - LOG.error("isLinkAtOffset error", e); - return null; - } - } - - @Override - public void hyperlinkUpdate(HyperlinkEvent e) { - Object obj = e.getSource(); - if (obj instanceof Position) { - contentPanel.getTabbedPane().codeJump((Position) obj); - } - } - } - - public static final class EditorTheme { - private final String name; - private final String path; - - public EditorTheme(String name, String path) { - this.name = name; - this.path = path; - } - - public String getName() { - return name; - } - - public String getPath() { - return path; - } - - @Override - public String toString() { - return name; - } - } - - public static EditorTheme[] getAllThemes() { - return new EditorTheme[]{ - new EditorTheme("default", "/org/fife/ui/rsyntaxtextarea/themes/default.xml"), - new EditorTheme("eclipse", "/org/fife/ui/rsyntaxtextarea/themes/eclipse.xml"), - new EditorTheme("idea", "/org/fife/ui/rsyntaxtextarea/themes/idea.xml"), - new EditorTheme("vs", "/org/fife/ui/rsyntaxtextarea/themes/vs.xml"), - new EditorTheme("dark", "/org/fife/ui/rsyntaxtextarea/themes/dark.xml"), - new EditorTheme("monokai", "/org/fife/ui/rsyntaxtextarea/themes/monokai.xml") - }; - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index 4d3c8647..597239bc 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -31,9 +31,10 @@ import jadx.gui.jobs.BackgroundJob; import jadx.gui.jobs.BackgroundWorker; import jadx.gui.jobs.DecompileJob; import jadx.gui.treemodel.JNode; +import jadx.gui.ui.codearea.CodeArea; import jadx.gui.utils.CacheObject; import jadx.gui.utils.NLS; -import jadx.gui.utils.Position; +import jadx.gui.utils.JumpPosition; import jadx.gui.utils.search.TextSearchIndex; public abstract class CommonSearchDialog extends JDialog { @@ -106,7 +107,7 @@ public abstract class CommonSearchDialog extends JDialog { return; } JNode node = (JNode) resultsModel.getValueAt(selectedId, 0); - tabbedPane.codeJump(new Position(node.getRootClass(), node.getLine())); + tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine())); dispose(); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java index 2822fe26..1797b5f1 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/ContentPanel.java @@ -4,25 +4,25 @@ import javax.swing.*; import jadx.gui.treemodel.JNode; -abstract class ContentPanel extends JPanel { +public abstract class ContentPanel extends JPanel { private static final long serialVersionUID = 3237031760631677822L; protected final TabbedPane tabbedPane; protected final JNode node; - ContentPanel(TabbedPane panel, JNode jnode) { + protected ContentPanel(TabbedPane panel, JNode jnode) { tabbedPane = panel; node = jnode; } public abstract void loadSettings(); - TabbedPane getTabbedPane() { + public TabbedPane getTabbedPane() { return tabbedPane; } - JNode getNode() { + public JNode getNode() { return node; } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java b/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java index 095a2785..8d117bc2 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/LogViewer.java @@ -7,6 +7,7 @@ import ch.qos.logback.classic.Level; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import jadx.gui.settings.JadxSettings; +import jadx.gui.ui.codearea.CodeArea; import jadx.gui.utils.NLS; import jadx.gui.utils.logs.ILogListener; import jadx.gui.utils.logs.LogCollector; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 4ecb6c79..9abcf031 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -51,7 +51,7 @@ import jadx.gui.update.data.Release; import jadx.gui.utils.CacheObject; import jadx.gui.utils.Link; import jadx.gui.utils.NLS; -import jadx.gui.utils.Position; +import jadx.gui.utils.JumpPosition; import jadx.gui.utils.Utils; import static javax.swing.KeyStroke.getKeyStroke; @@ -300,7 +300,7 @@ public class MainWindow extends JFrame { JNode node = (JNode) obj; JClass cls = node.getRootClass(); if (cls != null) { - tabbedPane.codeJump(new Position(cls, node.getLine())); + tabbedPane.codeJump(new JumpPosition(cls, node.getLine())); } } } catch (Exception e) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index ea2b01dd..08c6c226 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -21,12 +21,14 @@ import jadx.gui.treemodel.JCertificate; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JResource; +import jadx.gui.ui.codearea.CodeArea; +import jadx.gui.ui.codearea.CodePanel; import jadx.gui.utils.JumpManager; +import jadx.gui.utils.JumpPosition; import jadx.gui.utils.NLS; -import jadx.gui.utils.Position; import jadx.gui.utils.Utils; -class TabbedPane extends JTabbedPane { +public class TabbedPane extends JTabbedPane { private static final Logger LOG = LoggerFactory.getLogger(TabbedPane.class); private static final long serialVersionUID = -8833600618794570904L; @@ -57,11 +59,11 @@ class TabbedPane extends JTabbedPane { }); } - MainWindow getMainWindow() { + public MainWindow getMainWindow() { return mainWindow; } - private void showCode(final Position pos) { + private void showCode(final JumpPosition pos) { final CodePanel contentPanel = (CodePanel) getContentPanel(pos.getNode()); if (contentPanel == null) { return; @@ -99,8 +101,8 @@ class TabbedPane extends JTabbedPane { SwingUtilities.invokeLater(() -> setSelectedComponent(contentPanel)); } - public void codeJump(Position pos) { - Position curPos = getCurrentPosition(); + public void codeJump(JumpPosition pos) { + JumpPosition curPos = getCurrentPosition(); if (curPos != null) { jumps.addPosition(curPos); jumps.addPosition(pos); @@ -109,7 +111,7 @@ class TabbedPane extends JTabbedPane { } @Nullable - private Position getCurrentPosition() { + private JumpPosition getCurrentPosition() { ContentPanel selectedCodePanel = getSelectedCodePanel(); if (selectedCodePanel instanceof CodePanel) { return ((CodePanel) selectedCodePanel).getCodeArea().getCurrentPosition(); @@ -118,14 +120,14 @@ class TabbedPane extends JTabbedPane { } public void navBack() { - Position pos = jumps.getPrev(); + JumpPosition pos = jumps.getPrev(); if (pos != null) { showCode(pos); } } public void navForward() { - Position pos = jumps.getNext(); + JumpPosition pos = jumps.getNext(); if (pos != null) { showCode(pos); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java new file mode 100644 index 00000000..929ac2ec --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -0,0 +1,204 @@ +package jadx.gui.ui.codearea; + +import javax.swing.*; +import javax.swing.event.PopupMenuListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Caret; +import javax.swing.text.DefaultCaret; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rtextarea.SearchContext; +import org.fife.ui.rtextarea.SearchEngine; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.CodePosition; +import jadx.api.JavaNode; +import jadx.gui.settings.JadxSettings; +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.ui.MainWindow; +import jadx.gui.utils.JumpPosition; + +public final class CodeArea extends RSyntaxTextArea { + private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class); + + private static final long serialVersionUID = 6312736869579635796L; + + private final CodePanel contentPanel; + private final JNode node; + + CodeArea(CodePanel panel) { + this.contentPanel = panel; + this.node = panel.getNode(); + + setMarkOccurrences(true); + setEditable(false); + loadSettings(); + + Caret caret = getCaret(); + if (caret instanceof DefaultCaret) { + ((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + } + caret.setVisible(true); + + setSyntaxEditingStyle(node.getSyntaxName()); + if (node instanceof JClass) { + JClass jClsNode = (JClass) this.node; + ((RSyntaxDocument) getDocument()).setSyntaxStyle(new JadxTokenMaker(this, jClsNode)); + + setHyperlinksEnabled(true); + CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator(contentPanel, this, jClsNode); + setLinkGenerator(codeLinkProcessor); + addHyperlinkListener(codeLinkProcessor); + addMenuItems(jClsNode); + } + registerWordHighlighter(); + setText(node.getContent()); + } + + private void registerWordHighlighter() { + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + if (evt.getClickCount() % 2 == 0 && !evt.isConsumed()) { + evt.consume(); + String str = getSelectedText(); + if (str != null) { + highlightAllMatches(str); + } + } else { + highlightAllMatches(null); + } + } + }); + } + + /** + * @param str - if null -> reset current highlights + */ + private void highlightAllMatches(@Nullable String str) { + SearchContext context = new SearchContext(str); + context.setMarkAll(true); + context.setMatchCase(true); + context.setWholeWord(true); + SearchEngine.markAll(this, context); + } + + private void addMenuItems(JClass jCls) { + Action findUsage = new FindUsageAction(contentPanel, this, jCls); + + JPopupMenu popup = getPopupMenu(); + popup.addSeparator(); + popup.add(findUsage); + popup.addPopupMenuListener((PopupMenuListener) findUsage); + } + + public void loadSettings() { + loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this); + } + + public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) { + area.setAntiAliasingEnabled(true); + mainWindow.getEditorTheme().apply(area); + + JadxSettings settings = mainWindow.getSettings(); + area.setFont(settings.getFont()); + } + + public static RSyntaxTextArea getDefaultArea(MainWindow mainWindow) { + RSyntaxTextArea area = new RSyntaxTextArea(); + loadCommonSettings(mainWindow, area); + return area; + } + + /** + * Search node by offset in {@code jCls} code and return its definition position + * (useful for jumps from usage) + */ + public JumpPosition getDefPosForNodeAtOffset(JClass jCls, int offset) { + JavaNode foundNode = getJavaNodeAtOffset(jCls, offset); + if (foundNode == null) { + return null; + } + CodePosition pos = jCls.getCls().getDefinitionPosition(foundNode); + if (pos == null) { + return null; + } + JNode jNode = contentPanel.getTabbedPane().getMainWindow().getCacheObject().getNodeCache().makeFrom(foundNode); + return new JumpPosition(jNode.getRootClass(), pos.getLine()); + } + + /** + * Search referenced java node by offset in {@code jCls} code + */ + public JavaNode getJavaNodeAtOffset(JClass jCls, int offset) { + try { + // TODO: add direct mapping for code offset to CodeWriter (instead of line and line offset pair) + int line = this.getLineOfOffset(offset); + int lineOffset = offset - this.getLineStartOffset(line); + return jCls.getCls().getJavaNodeAtPosition(line + 1, lineOffset + 1); + } catch (Exception e) { + LOG.error("Can't get java node by offset: {}", offset, e); + } + return null; + } + + public JumpPosition getCurrentPosition() { + return new JumpPosition(node, getCaretLineNumber() + 1); + } + + @Nullable + Integer getSourceLine(int line) { + return node.getSourceLine(line); + } + + public void scrollToLine(int line) { + int lineNum = line - 1; + if (lineNum < 0) { + lineNum = 0; + } + setCaretAtLine(lineNum); + centerCurrentLine(); + forceCurrentLineHighlightRepaint(); + } + + public void centerCurrentLine() { + JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, this); + if (viewport == null) { + return; + } + try { + Rectangle r = modelToView(getCaretPosition()); + if (r == null) { + return; + } + int extentHeight = viewport.getExtentSize().height; + Dimension viewSize = viewport.getViewSize(); + if (viewSize == null) { + return; + } + int viewHeight = viewSize.height; + + int y = Math.max(0, r.y - extentHeight / 2); + y = Math.min(y, viewHeight - extentHeight); + + viewport.setViewPosition(new Point(0, y)); + } catch (BadLocationException e) { + LOG.debug("Can't center current line", e); + } + } + + private void setCaretAtLine(int line) { + try { + setCaretPosition(getLineStartOffset(line)); + } catch (BadLocationException e) { + LOG.debug("Can't scroll to {}", line, e); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java new file mode 100644 index 00000000..d44fbfa1 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeLinkGenerator.java @@ -0,0 +1,88 @@ +package jadx.gui.ui.codearea; + +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import java.util.Objects; + +import org.fife.ui.rsyntaxtextarea.LinkGenerator; +import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.gui.treemodel.JClass; +import jadx.gui.utils.JumpPosition; + +public class CodeLinkGenerator implements LinkGenerator, HyperlinkListener { + private static final Logger LOG = LoggerFactory.getLogger(CodeLinkGenerator.class); + + private final CodePanel contentPanel; + private final CodeArea codeArea; + private final JClass jCls; + + public CodeLinkGenerator(CodePanel contentPanel, CodeArea codeArea, JClass cls) { + this.contentPanel = contentPanel; + this.codeArea = codeArea; + this.jCls = cls; + } + + @Override + public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offset) { + try { + Token token = textArea.modelToToken(offset); + if (token == null) { + return null; + } + int type = token.getType(); + final int sourceOffset; + if (type == TokenTypes.IDENTIFIER) { + sourceOffset = token.getOffset(); + } else if (type == TokenTypes.ANNOTATION && token.length() > 1) { + sourceOffset = token.getOffset() + 1; + } else { + return null; + } + // fast skip + if (token.length() == 1) { + char ch = token.getTextArray()[token.getTextOffset()]; + if (ch == '.' || ch == ',' || ch == ';') { + return null; + } + } + final JumpPosition defPos = codeArea.getDefPosForNodeAtOffset(jCls, sourceOffset); + if (defPos == null) { + return null; + } + if (Objects.equals(defPos.getNode().getRootClass(), jCls) + && defPos.getLine() == textArea.getLineOfOffset(sourceOffset) + 1) { + // ignore self jump + return null; + } + return new LinkGeneratorResult() { + @Override + public HyperlinkEvent execute() { + return new HyperlinkEvent(defPos, HyperlinkEvent.EventType.ACTIVATED, null, + defPos.getNode().makeLongString()); + } + + @Override + public int getSourceOffset() { + return sourceOffset; + } + }; + } catch (Exception e) { + LOG.error("isLinkAtOffset error", e); + return null; + } + } + + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + Object obj = e.getSource(); + if (obj instanceof JumpPosition) { + contentPanel.getTabbedPane().codeJump((JumpPosition) obj); + } + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java similarity index 82% rename from jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java rename to jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java index 7594a8aa..aaabef16 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java @@ -1,4 +1,4 @@ -package jadx.gui.ui; +package jadx.gui.ui.codearea; import javax.swing.*; import java.awt.*; @@ -7,9 +7,11 @@ import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import jadx.gui.treemodel.JNode; +import jadx.gui.ui.ContentPanel; +import jadx.gui.ui.TabbedPane; import jadx.gui.utils.Utils; -class CodePanel extends ContentPanel { +public final class CodePanel extends ContentPanel { private static final long serialVersionUID = 5310536092010045565L; @@ -17,7 +19,7 @@ class CodePanel extends ContentPanel { private final CodeArea codeArea; private final JScrollPane scrollPane; - CodePanel(TabbedPane panel, JNode jnode) { + public CodePanel(TabbedPane panel, JNode jnode) { super(panel, jnode); codeArea = new CodeArea(this); @@ -55,12 +57,12 @@ class CodePanel extends ContentPanel { } @Override - TabbedPane getTabbedPane() { + public TabbedPane getTabbedPane() { return tabbedPane; } @Override - JNode getNode() { + public JNode getNode() { return node; } @@ -68,7 +70,7 @@ class CodePanel extends ContentPanel { return searchBar; } - CodeArea getCodeArea() { + public CodeArea getCodeArea() { return codeArea; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/EditorTheme.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/EditorTheme.java new file mode 100644 index 00000000..b537cf88 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/EditorTheme.java @@ -0,0 +1,40 @@ +package jadx.gui.ui.codearea; + +public final class EditorTheme { + private static final String RSTA_THEME_PATH = "/org/fife/ui/rsyntaxtextarea/themes/"; + + public static final EditorTheme[] ALL_THEMES = + new EditorTheme[]{ + new EditorTheme("default"), + new EditorTheme("eclipse"), + new EditorTheme("idea"), + new EditorTheme("vs"), + new EditorTheme("dark"), + new EditorTheme("monokai") + }; + + private final String name; + private final String path; + + public EditorTheme(String name) { + this(name, RSTA_THEME_PATH + name + ".xml"); + } + + public EditorTheme(String name, String path) { + this.name = name; + this.path = path; + } + + public String getName() { + return name; + } + + public String getPath() { + return path; + } + + @Override + public String toString() { + return name; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/FindUsageAction.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/FindUsageAction.java new file mode 100644 index 00000000..ea2afe9d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/FindUsageAction.java @@ -0,0 +1,67 @@ +package jadx.gui.ui.codearea; + +import javax.swing.*; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import java.awt.*; +import java.awt.event.ActionEvent; + +import org.fife.ui.rsyntaxtextarea.Token; + +import jadx.api.JavaNode; +import jadx.gui.treemodel.JClass; +import jadx.gui.treemodel.JNode; +import jadx.gui.ui.MainWindow; +import jadx.gui.ui.UsageDialog; +import jadx.gui.utils.NLS; + +public final class FindUsageAction extends AbstractAction implements PopupMenuListener { + private static final long serialVersionUID = 4692546569977976384L; + + private final transient CodePanel contentPanel; + private final transient CodeArea codeArea; + private final transient JClass jCls; + + private transient JavaNode node; + + public FindUsageAction(CodePanel contentPanel, CodeArea codeArea, JClass jCls) { + super(NLS.str("popup.find_usage")); + this.contentPanel = contentPanel; + this.codeArea = codeArea; + this.jCls = jCls; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (node == null) { + return; + } + MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow(); + JNode jNode = mainWindow.getCacheObject().getNodeCache().makeFrom(node); + UsageDialog usageDialog = new UsageDialog(mainWindow, jNode); + usageDialog.setVisible(true); + } + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + node = null; + Point pos = codeArea.getMousePosition(); + if (pos != null) { + Token token = codeArea.viewToToken(pos); + if (token != null) { + node = codeArea.getJavaNodeAtOffset(jCls, token.getOffset()); + } + } + setEnabled(node != null); + } + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + // do nothing + } + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + // do nothing + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/JadxTokenMaker.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JadxTokenMaker.java new file mode 100644 index 00000000..4491dc7f --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/JadxTokenMaker.java @@ -0,0 +1,105 @@ +package jadx.gui.ui.codearea; + +import javax.swing.text.Segment; + +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenImpl; +import org.fife.ui.rsyntaxtextarea.TokenTypes; +import org.fife.ui.rsyntaxtextarea.modes.JavaTokenMaker; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.JavaClass; +import jadx.api.JavaNode; +import jadx.gui.treemodel.JClass; + +public final class JadxTokenMaker extends JavaTokenMaker { + private static final Logger LOG = LoggerFactory.getLogger(JadxTokenMaker.class); + + private final CodeArea codeArea; + private final JClass jCls; + + public JadxTokenMaker(CodeArea codeArea, JClass jCls) { + this.codeArea = codeArea; + this.jCls = jCls; + } + + @Override + public Token getTokenList(Segment text, int initialTokenType, int startOffset) { + Token tokens = super.getTokenList(text, initialTokenType, startOffset); + if (startOffset > 0 && tokens.getType() != TokenTypes.NULL) { + try { + processTokens(tokens); + } catch (Exception e) { + LOG.error("Process tokens failed for text: {}", text, e); + } + } + return tokens; + } + + private void processTokens(Token tokens) { + Token prev = null; + Token current = tokens; + while (current != null) { + if (prev != null) { + int tokenType = current.getType(); + if (tokenType == TokenTypes.IDENTIFIER) { + current = mergeLongClassNames(prev, current, false); + } else if (tokenType == TokenTypes.ANNOTATION) { + current = mergeLongClassNames(prev, current, true); + } + } + prev = current; + current = current.getNextToken(); + } + } + + @NotNull + private Token mergeLongClassNames(Token prev, Token current, boolean annotation) { + int offset = current.getOffset(); + if (annotation) { + offset++; + } + JavaNode javaNode = codeArea.getJavaNodeAtOffset(jCls, offset); + if (javaNode instanceof JavaClass) { + String name = javaNode.getName(); + String lexeme = current.getLexeme(); + if (annotation && lexeme.length() > 1) { + lexeme = lexeme.substring(1); + } + if (!lexeme.equals(name) && javaNode.getFullName().startsWith(lexeme)) { + // try to replace long class name with one token + Token replace = concatTokensUntil(current, name); + if (replace != null && prev instanceof TokenImpl) { + TokenImpl impl = ((TokenImpl) prev); + impl.setNextToken(replace); + current = replace; + } + } + } + return current; + } + + @Nullable + private Token concatTokensUntil(Token start, String endText) { + StringBuilder sb = new StringBuilder(); + Token current = start; + while (current != null && current.getType() != TokenTypes.NULL) { + String text = current.getLexeme(); + if (text != null) { + sb.append(text); + if (text.equals(endText)) { + char[] line = sb.toString().toCharArray(); + TokenImpl token = new TokenImpl(line, 0, line.length - 1, start.getOffset(), + start.getType(), start.getLanguageIndex()); + token.setNextToken(current.getNextToken()); + return token; + } + } + current = current.getNextToken(); + } + return null; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java similarity index 96% rename from jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java rename to jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java index ab956656..ad2506ac 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/LineNumbers.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/LineNumbers.java @@ -1,4 +1,4 @@ -package jadx.gui.ui; +package jadx.gui.ui.codearea; import javax.swing.*; import javax.swing.border.Border; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchBar.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java similarity index 97% rename from jadx-gui/src/main/java/jadx/gui/ui/SearchBar.java rename to jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java index 10ce53c0..c0af0bff 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchBar.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SearchBar.java @@ -1,4 +1,4 @@ -package jadx.gui.ui; +package jadx.gui.ui.codearea; import javax.swing.*; import javax.swing.text.BadLocationException; @@ -112,12 +112,7 @@ class SearchBar extends JToolBar { JButton closeButton = new JButton(); closeButton.setIcon(ICON_CLOSE); - closeButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - toggle(); - } - }); + closeButton.addActionListener(l -> toggle()); closeButton.setBorderPainted(false); add(closeButton); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java b/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java index 645ccdee..aef5bc0f 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java @@ -5,10 +5,10 @@ import java.util.List; public class JumpManager { - private List list = new ArrayList<>(); + private List list = new ArrayList<>(); private int currentPos = 0; - public void addPosition(Position pos) { + public void addPosition(JumpPosition pos) { if (pos.equals(getCurrent())) { return; } @@ -25,14 +25,14 @@ public class JumpManager { } } - private Position getCurrent() { + private JumpPosition getCurrent() { if (currentPos >= 0 && currentPos < list.size()) { return list.get(currentPos); } return null; } - public Position getPrev() { + public JumpPosition getPrev() { if (currentPos == 0) { return null; } @@ -40,7 +40,7 @@ public class JumpManager { return list.get(currentPos); } - public Position getNext() { + public JumpPosition getNext() { int size = list.size(); if (size == 0) { currentPos = 0; @@ -51,7 +51,7 @@ public class JumpManager { currentPos = size - 1; return null; } - Position position = list.get(newPos); + JumpPosition position = list.get(newPos); if (position == null) { return null; } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Position.java b/jadx-gui/src/main/java/jadx/gui/utils/JumpPosition.java similarity index 64% rename from jadx-gui/src/main/java/jadx/gui/utils/Position.java rename to jadx-gui/src/main/java/jadx/gui/utils/JumpPosition.java index 8d6c99c5..58a171cb 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Position.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/JumpPosition.java @@ -1,19 +1,12 @@ package jadx.gui.utils; -import jadx.api.CodePosition; -import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; -public class Position { +public class JumpPosition { private final JNode node; private final int line; - public Position(CodePosition pos) { - this.node = new JClass(pos.getJavaClass()); - this.line = pos.getLine(); - } - - public Position(JNode node, int line) { + public JumpPosition(JNode node, int line) { this.node = node; this.line = line; } @@ -31,10 +24,10 @@ public class Position { if (this == obj) { return true; } - if (!(obj instanceof Position)) { + if (!(obj instanceof JumpPosition)) { return false; } - Position position = (Position) obj; + JumpPosition position = (JumpPosition) obj; return line == position.line && node.equals(position.node); } diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 3ab52ca1..4093935a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -114,6 +114,7 @@ popup.paste=Paste popup.delete=Delete popup.select_all=Select All +popup.find_usage=Find Usage certificate.title=Certificate certificate.cert_type=Type certificate.serialSigVer=Version diff --git a/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy b/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy index 09c8de1c..ab71c9c3 100644 --- a/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy +++ b/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy @@ -1,7 +1,7 @@ package jadx.gui.tests import jadx.gui.utils.JumpManager -import jadx.gui.utils.Position +import jadx.gui.utils.JumpPosition import spock.lang.Specification class TestJumpManager extends Specification { @@ -29,7 +29,7 @@ class TestJumpManager extends Specification { def "1 element"() { when: - jm.addPosition(Mock(Position)) + jm.addPosition(Mock(JumpPosition)) then: jm.getPrev() == null jm.getNext() == null @@ -37,9 +37,9 @@ class TestJumpManager extends Specification { def "2 elements"() { when: - def mock1 = Mock(Position) + def mock1 = Mock(JumpPosition) jm.addPosition(mock1) - def mock2 = Mock(Position) + def mock2 = Mock(JumpPosition) jm.addPosition(mock2) // 1 - 2@ then: @@ -52,15 +52,15 @@ class TestJumpManager extends Specification { def "navigation"() { expect: - def mock1 = Mock(Position) + def mock1 = Mock(JumpPosition) jm.addPosition(mock1) // 1@ - def mock2 = Mock(Position) + def mock2 = Mock(JumpPosition) jm.addPosition(mock2) // 1 - 2@ jm.getPrev() == mock1 // 1@ - 2 - def mock3 = Mock(Position) + def mock3 = Mock(JumpPosition) jm.addPosition(mock3) // 1 - 3@ jm.getNext() == null @@ -71,23 +71,23 @@ class TestJumpManager extends Specification { def "navigation2"() { expect: - def mock1 = Mock(Position) + def mock1 = Mock(JumpPosition) jm.addPosition(mock1) // 1@ - def mock2 = Mock(Position) + def mock2 = Mock(JumpPosition) jm.addPosition(mock2) // 1 - 2@ - def mock3 = Mock(Position) + def mock3 = Mock(JumpPosition) jm.addPosition(mock3) // 1 - 2 - 3@ - def mock4 = Mock(Position) + def mock4 = Mock(JumpPosition) jm.addPosition(mock4) // 1 - 2 - 3 - 4@ jm.getPrev() == mock3 // 1 - 2 - 3@ - 4 jm.getPrev() == mock2 // 1 - 2@ - 3 - 4 - def mock5 = Mock(Position) + def mock5 = Mock(JumpPosition) jm.addPosition(mock5) // 1 - 2 - 5@ jm.getNext() == null @@ -106,7 +106,7 @@ class TestJumpManager extends Specification { def "add same element"() { when: - def mock = Mock(Position) + def mock = Mock(JumpPosition) jm.addPosition(mock) jm.addPosition(mock) then: -- GitLab