提交 c8df26f2 编写于 作者: S Skylot

feat(gui): add class links for AndroidManifest.xml and other minor fixes

上级 3bc96719
package jadx.api;
import java.util.Map;
public interface ICodeInfo {
String getCodeStr();
Map<Integer, Integer> getLineMapping();
Map<CodePosition, Object> getAnnotations();
}
......@@ -15,6 +15,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.DexFileFactory;
......@@ -28,6 +29,7 @@ import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
......@@ -390,6 +392,47 @@ public final class JadxDecompiler {
return null;
}
@Nullable
JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
if (obj instanceof ClassNode) {
return getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getJavaFieldByNode((FieldNode) obj);
}
return null;
}
@Nullable
public JavaNode getJavaNodeAtPosition(ICodeInfo codeInfo, int line, int offset) {
Map<CodePosition, Object> map = codeInfo.getAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public JadxArgs getArgs() {
return args;
}
......
......@@ -152,7 +152,7 @@ public final class JavaClass implements JavaNode {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (obj instanceof LineAttrNode) {
JavaNode node = convertNode(obj);
JavaNode node = getRootDecompiler().convertNode(obj);
if (node != null) {
resultMap.put(codePosition, node);
}
......@@ -161,45 +161,15 @@ public final class JavaClass implements JavaNode {
return resultMap;
}
@Nullable
private JavaNode convertNode(Object obj) {
if (!(obj instanceof LineAttrNode)) {
return null;
}
if (obj instanceof ClassNode) {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
}
return null;
}
@Nullable
public JavaNode getJavaNodeAtPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty()) {
return null;
}
Object obj = map.get(new CodePosition(line, offset));
if (obj == null) {
return null;
}
return convertNode(obj);
decompile();
return getRootDecompiler().getJavaNodeAtPosition(cls.getCode(), line, offset);
}
@Nullable
public CodePosition getDefinitionPosition(JavaNode javaNode) {
JavaClass jCls = javaNode.getTopParentClass();
jCls.decompile();
int defLine = javaNode.getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
public CodePosition getDefinitionPosition() {
return getRootDecompiler().getDefinitionPosition(this);
}
public Integer getSourceLine(int decompiledLine) {
......@@ -250,6 +220,7 @@ public final class JavaClass implements JavaNode {
return methods;
}
@Override
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
......
......@@ -164,10 +164,8 @@ public final class ResourcesLoader {
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
CodeWriter cw = new CodeWriter();
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
copyStream(is, baos);
cw.add(baos.toString("UTF-8"));
return cw;
return new CodeWriter(baos.toString("UTF-8"));
}
}
......@@ -12,12 +12,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.ICodeInfo;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
public class CodeWriter {
public class CodeWriter implements ICodeInfo {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
......@@ -242,6 +243,7 @@ public class CodeWriter {
return annotations.put(pos, obj);
}
@Override
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
......@@ -260,6 +262,7 @@ public class CodeWriter {
lineMap.put(decompiledLine, sourceLine);
}
@Override
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
......@@ -293,7 +296,11 @@ public class CodeWriter {
return buf.length();
}
@Override
public String getCodeStr() {
if (code == null) {
throw new NullPointerException("Code not set");
}
return code;
}
......
......@@ -164,6 +164,20 @@ public class RootNode {
return resolveClass(clsInfo);
}
@Nullable
public ClassNode searchClassByFullAlias(String fullName) {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.getFullName().equals(fullName)
|| classInfo.getAliasFullName().equals(fullName)) {
return cls;
}
}
}
return null;
}
public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
......
......@@ -15,6 +15,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
......@@ -32,7 +33,6 @@ import jadx.core.xmlgen.entry.ValuesParser;
* Check Element chunk size
*/
@SuppressWarnings("unused")
public class BinaryXMLParser extends CommonBinaryParser {
private static final Logger LOG = LoggerFactory.getLogger(BinaryXMLParser.class);
......@@ -306,6 +306,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
if (isDeobfCandidateAttr(shortNsName, attrName)) {
decodedAttr = deobfClassName(decodedAttr);
}
attachClassNode(writer, attrName, decodedAttr);
writer.add(StringUtils.escapeXML(decodedAttr));
} else {
decodeAttribute(attributeNS, attrValDataType, attrValData,
......@@ -401,6 +402,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
if (isDeobfCandidateAttr(shortNsName, attrName)) {
str = deobfClassName(str);
}
attachClassNode(writer, attrName, str);
writer.add(str != null ? StringUtils.escapeXML(str) : "null");
}
}
......@@ -459,9 +461,24 @@ public class BinaryXMLParser extends CommonBinaryParser {
return sb.toString();
}
private void attachClassNode(CodeWriter writer, String attrName, String clsName) {
if (clsName == null || !attrName.equals("name")) {
return;
}
String clsFullName;
if (clsName.startsWith(".")) {
clsFullName = appPackageName + clsName;
} else {
clsFullName = clsName;
}
ClassNode classNode = rootNode.searchClassByFullAlias(clsFullName);
if (classNode != null) {
writer.attachAnnotation(classNode);
}
}
private String deobfClassName(String className) {
String newName = XmlDeobf.deobfClassName(rootNode, className,
appPackageName);
String newName = XmlDeobf.deobfClassName(rootNode, className, appPackageName);
if (newName != null) {
return newName;
}
......
......@@ -3,6 +3,8 @@ package jadx.core.xmlgen;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
......@@ -17,9 +19,8 @@ public class XmlDeobf {
private XmlDeobf() {
}
public static String deobfClassName(RootNode rootNode, String potencialClassName,
String packageName) {
@Nullable
public static String deobfClassName(RootNode rootNode, String potencialClassName, String packageName) {
if (packageName != null && potencialClassName.startsWith(".")) {
potencialClassName = packageName + potencialClassName;
}
......
......@@ -131,6 +131,10 @@ public class JadxWrapper {
return openFile;
}
public JadxDecompiler getDecompiler() {
return decompiler;
}
public JadxArgs getArgs() {
return decompiler.getArgs();
}
......
......@@ -3,7 +3,9 @@ package jadx.gui.treemodel;
import javax.swing.*;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
......@@ -76,6 +78,12 @@ public class JClass extends JLoadableNode {
}
}
@Override
public @Nullable ICodeInfo getCodeInfo() {
load();
return cls.getClassNode().getCode();
}
@Override
public String getContent() {
return cls.getCode();
......@@ -147,11 +155,6 @@ public class JClass extends JLoadableNode {
return cls.getDecompiledLine();
}
@Override
public Integer getSourceLine(int line) {
return cls.getSourceLine(line);
}
@Override
public int hashCode() {
return cls.hashCode();
......
......@@ -4,7 +4,10 @@ import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.JadxDecompiler;
import jadx.api.JavaNode;
public abstract class JNode extends DefaultMutableTreeNode {
......@@ -40,10 +43,28 @@ public abstract class JNode extends DefaultMutableTreeNode {
return 0;
}
public Integer getSourceLine(int line) {
@Nullable
public ICodeInfo getCodeInfo() {
return null;
}
public final Integer getSourceLine(int line) {
ICodeInfo codeInfo = getCodeInfo();
if (codeInfo == null) {
return null;
}
return codeInfo.getLineMapping().get(line);
}
@Nullable
public JavaNode getJavaNodeAtPosition(JadxDecompiler decompiler, int line, int offset) {
ICodeInfo codeInfo = getCodeInfo();
if (codeInfo == null) {
return null;
}
return decompiler.getJavaNodeAtPosition(codeInfo, line, offset);
}
public abstract Icon getIcon();
public String getName() {
......
package jadx.gui.treemodel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
......@@ -10,7 +9,9 @@ import javax.swing.*;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.ResourceFile;
import jadx.api.ResourceFileContent;
import jadx.api.ResourceType;
......@@ -44,8 +45,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
private final transient ResourceFile resFile;
private transient boolean loaded;
private transient String content;
private transient Map<Integer, Integer> lineMapping = Collections.emptyMap();
private transient ICodeInfo content;
public JResource(ResourceFile resFile, String name, JResType type) {
this(resFile, name, name, type);
......@@ -98,10 +98,19 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
return files;
}
@Override
public @Nullable ICodeInfo getCodeInfo() {
getContent();
return content;
}
@Override
public synchronized String getContent() {
if (loaded) {
return content;
if (content == null) {
return null;
}
return content.getCodeStr();
}
if (resFile == null || type != JResType.FILE) {
return null;
......@@ -111,6 +120,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
ResContainer rc = resFile.loadContent();
if (rc == null) {
loaded = true;
return null;
}
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
......@@ -118,36 +128,35 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
for (ResContainer subFile : rc.getSubFiles()) {
loadSubNodes(this, subFile, 1);
}
loaded = true;
return content;
} else {
// single node
content = loadCurrentSingleRes(rc);
}
// single node
return loadCurrentSingleRes(rc);
loaded = true;
return content.getCodeStr();
}
private String loadCurrentSingleRes(ResContainer rc) {
private ICodeInfo loadCurrentSingleRes(ResContainer rc) {
switch (rc.getDataType()) {
case TEXT:
case RES_TABLE:
CodeWriter cw = rc.getText();
lineMapping = cw.getLineMapping();
return cw.toString();
return rc.getText();
case RES_LINK:
try {
return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> {
if (size > 10 * 1024 * 1024L) {
return "File too large for view";
return new CodeWriter("File too large for view");
}
return ResourcesLoader.loadToCodeWriter(is).toString();
return ResourcesLoader.loadToCodeWriter(is);
});
} catch (Exception e) {
return "Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e);
return new CodeWriter("Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e));
}
case DECODED_DATA:
default:
return "Unexpected resource type: " + rc;
return new CodeWriter("Unexpected resource type: " + rc);
}
}
......@@ -192,14 +201,6 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
return resDir;
}
@Override
public Integer getSourceLine(int line) {
if (lineMapping == null) {
return null;
}
return lineMapping.get(line);
}
@Override
public String getSyntaxName() {
if (resFile == null) {
......@@ -291,10 +292,6 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
return resFile;
}
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
}
@Override
public JClass getJParent() {
return null;
......
......@@ -32,7 +32,7 @@ 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.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
......@@ -403,7 +403,7 @@ public abstract class CommonSearchDialog extends JDialog {
private final Map<Integer, Component> componentCache = new HashMap<>();
public ResultsTableCellRenderer() {
RSyntaxTextArea area = CodeArea.getDefaultArea(mainWindow);
RSyntaxTextArea area = AbstractCodeArea.getDefaultArea(mainWindow);
this.font = area.getFont();
this.codeSelectedColor = area.getSelectionColor();
this.codeBackground = area.getBackground();
......@@ -455,7 +455,7 @@ public abstract class CommonSearchDialog extends JDialog {
if (!node.hasDescString()) {
return emptyLabel;
}
RSyntaxTextArea textArea = CodeArea.getDefaultArea(mainWindow);
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(mainWindow);
textArea.setLayout(new GridLayout(1, 1));
textArea.setEditable(false);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
......
......@@ -16,7 +16,7 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.codearea.AbstractCodeArea;
public class ImagePanel extends ContentPanel {
private static final long serialVersionUID = 4071356367073142688L;
......@@ -29,7 +29,7 @@ public class ImagePanel extends ContentPanel {
ImageViewer imageViewer = new ImageViewer(img);
add(imageViewer.getComponent());
} catch (Exception e) {
RSyntaxTextArea textArea = CodeArea.getDefaultArea(panel.getMainWindow());
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(panel.getMainWindow());
textArea.setText("Image load error: \n" + Utils.getStackTrace(e));
add(textArea);
}
......
......@@ -9,7 +9,7 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import ch.qos.logback.classic.Level;
import jadx.gui.settings.JadxSettings;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.utils.NLS;
import jadx.gui.utils.logs.ILogListener;
import jadx.gui.utils.logs.LogCollector;
......@@ -31,7 +31,7 @@ class LogViewer extends JDialog {
}
public final void initUI(MainWindow mainWindow) {
textPane = CodeArea.getDefaultArea(mainWindow);
textPane = AbstractCodeArea.getDefaultArea(mainWindow);
textPane.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
JPanel controlPane = new JPanel();
......
package jadx.gui.ui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.*;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
......@@ -29,27 +23,7 @@ import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
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;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.ProgressMonitor;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.TreeExpansionEvent;
......@@ -518,7 +492,7 @@ public class MainWindow extends JFrame {
if (resFile != null && JResource.isSupportedForView(resFile.getType())) {
tabbedPane.showResource(res);
}
} else if ((obj instanceof JCertificate) || (obj instanceof ApkSignature)) {
} else if (obj instanceof JCertificate || obj instanceof ApkSignature) {
tabbedPane.showSimpleNode((JNode) obj);
} else if (obj instanceof JNode) {
JNode node = (JNode) obj;
......@@ -533,10 +507,13 @@ public class MainWindow extends JFrame {
}
private void treeRightClickAction(MouseEvent e) {
Object obj = tree.getLastSelectedPathComponent();
if (obj instanceof JPackage) {
JPackagePopUp menu = new JPackagePopUp((JPackage) obj);
menu.show(e.getComponent(), e.getX(), e.getY());
TreePath path = tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
Object obj = path.getLastPathComponent();
if (obj instanceof JPackage) {
JPackagePopUp menu = new JPackagePopUp((JPackage) obj);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
}
......
......@@ -42,7 +42,7 @@ public class TabbedPane extends JTabbedPane {
private final transient MainWindow mainWindow;
private final transient Map<JNode, ContentPanel> openTabs = new LinkedHashMap<>();
private transient JumpManager jumps = new JumpManager();
private final transient JumpManager jumps = new JumpManager();
TabbedPane(MainWindow window) {
this.mainWindow = window;
......@@ -216,7 +216,7 @@ public class TabbedPane extends JTabbedPane {
button.setBorderPainted(false);
button.addActionListener(e -> closeCodePanel(contentPanel));
panel.addMouseListener(new MouseAdapter() {
MouseAdapter clickAdapter = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isMiddleMouseButton(e)) {
......@@ -228,7 +228,9 @@ public class TabbedPane extends JTabbedPane {
setSelectedComponent(contentPanel);
}
}
});
};
panel.addMouseListener(clickAdapter);
label.addMouseListener(clickAdapter);
panel.add(label);
panel.add(button);
......
package jadx.gui.ui.codearea;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultCaret;
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;
......@@ -30,6 +33,19 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
public AbstractCodeArea(ContentPanel contentPanel) {
this.contentPanel = contentPanel;
this.node = contentPanel.getNode();
setMarkOccurrences(true);
setEditable(false);
setCodeFoldingEnabled(false);
loadSettings();
Caret caret = getCaret();
if (caret instanceof DefaultCaret) {
((DefaultCaret) caret).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
}
caret.setVisible(true);
registerWordHighlighter();
}
/**
......@@ -37,6 +53,22 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
*/
public abstract void load();
public static RSyntaxTextArea getDefaultArea(MainWindow mainWindow) {
RSyntaxTextArea area = new RSyntaxTextArea();
area.setEditable(false);
area.setCodeFoldingEnabled(false);
loadCommonSettings(mainWindow, area);
return area;
}
public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) {
area.setAntiAliasingEnabled(true);
mainWindow.getEditorTheme().apply(area);
JadxSettings settings = mainWindow.getSettings();
area.setFont(settings.getFont());
}
public void loadSettings() {
loadCommonSettings(contentPanel.getTabbedPane().getMainWindow(), this);
}
......@@ -85,6 +117,34 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
}
}
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);
}
public JumpPosition getCurrentPosition() {
return new JumpPosition(node, getCaretLineNumber() + 1);
}
......@@ -101,12 +161,4 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
public JNode getNode() {
return node;
}
public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) {
area.setAntiAliasingEnabled(true);
mainWindow.getEditorTheme().apply(area);
JadxSettings settings = mainWindow.getSettings();
area.setFont(settings.getFont());
}
}
package jadx.gui.ui.codearea;
import java.awt.BorderLayout;
import java.awt.*;
import javax.swing.JTabbedPane;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.TabbedPane;
......@@ -19,18 +20,22 @@ import jadx.gui.utils.NLS;
public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
private static final long serialVersionUID = -7229931102504634591L;
private final CodePanel javaCodePanel;
private final CodePanel smaliCodePanel;
private JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
private final transient CodePanel javaCodePanel;
private final transient CodePanel smaliCodePanel;
// private final transient JTabbedPane areaTabbedPane;
public ClassCodeContentPanel(TabbedPane panel, JNode jnode) {
super(panel, jnode);
javaCodePanel = new CodePanel(this, new CodeArea(this));
smaliCodePanel = new CodePanel(this, new SmaliArea(this));
javaCodePanel = new CodePanel(new CodeArea(this));
smaliCodePanel = new CodePanel(new SmaliArea(this));
setLayout(new BorderLayout());
setBorder(new EmptyBorder(0, 0, 0, 0));
JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
areaTabbedPane.setBorder(new EmptyBorder(0, 0, 0, 0));
areaTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
areaTabbedPane.add(javaCodePanel, NLS.str("tabs.code"));
areaTabbedPane.add(smaliCodePanel, NLS.str("tabs.smali"));
add(areaTabbedPane);
......
package jadx.gui.ui.codearea;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JPopupMenu;
import javax.swing.text.Caret;
import javax.swing.text.DefaultCaret;
import javax.swing.*;
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.JadxDecompiler;
import jadx.api.JavaNode;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.JumpPosition;
/**
......@@ -34,29 +25,18 @@ public final class CodeArea extends AbstractCodeArea {
CodeArea(ContentPanel contentPanel) {
super(contentPanel);
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);
((RSyntaxDocument) getDocument()).setSyntaxStyle(new JadxTokenMaker(this));
addMenuItems(jClsNode);
}
registerWordHighlighter();
setHyperlinksEnabled(true);
CodeLinkGenerator codeLinkProcessor = new CodeLinkGenerator(this);
setLinkGenerator(codeLinkProcessor);
addHyperlinkListener(codeLinkProcessor);
}
@Override
......@@ -67,36 +47,8 @@ public final class CodeArea extends AbstractCodeArea {
}
}
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) {
FindUsageAction findUsage = new FindUsageAction(contentPanel, this, jCls);
FindUsageAction findUsage = new FindUsageAction(contentPanel, this);
GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(contentPanel, this, jCls);
JPopupMenu popup = getPopupMenu();
......@@ -107,22 +59,16 @@ public final class CodeArea extends AbstractCodeArea {
popup.addPopupMenuListener(goToDeclaration);
}
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);
public JumpPosition getDefPosForNodeAtOffset(int offset) {
JavaNode foundNode = getJavaNodeAtOffset(offset);
if (foundNode == null) {
return null;
}
CodePosition pos = jCls.getCls().getDefinitionPosition(foundNode);
CodePosition pos = getDecompiler().getDefinitionPosition(foundNode);
if (pos == null) {
return null;
}
......@@ -133,16 +79,19 @@ public final class CodeArea extends AbstractCodeArea {
/**
* Search referenced java node by offset in {@code jCls} code
*/
public JavaNode getJavaNodeAtOffset(JClass jCls, int offset) {
public JavaNode getJavaNodeAtOffset(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);
return node.getJavaNodeAtPosition(getDecompiler(), line + 1, lineOffset + 1);
} catch (Exception e) {
LOG.error("Can't get java node by offset: {}", offset, e);
}
return null;
}
private JadxDecompiler getDecompiler() {
return contentPanel.getTabbedPane().getMainWindow().getWrapper().getDecompiler();
}
}
package jadx.gui.ui.codearea;
import java.awt.BorderLayout;
import java.awt.*;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.TabbedPane;
......@@ -13,7 +13,7 @@ public final class CodeContentPanel extends AbstractCodeContentPanel {
public CodeContentPanel(TabbedPane panel, JNode jnode) {
super(panel, jnode);
setLayout(new BorderLayout());
codePanel = new CodePanel(this, new CodeArea(this));
codePanel = new CodePanel(new CodeArea(this));
add(codePanel, BorderLayout.CENTER);
codePanel.load();
}
......
......@@ -14,6 +14,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.ContentPanel;
import jadx.gui.utils.JumpPosition;
......@@ -22,29 +23,40 @@ public class CodeLinkGenerator implements LinkGenerator, HyperlinkListener {
private final ContentPanel contentPanel;
private final CodeArea codeArea;
private final JClass jCls;
private final JNode jNode;
public CodeLinkGenerator(ContentPanel contentPanel, CodeArea codeArea, JClass cls) {
this.contentPanel = contentPanel;
public CodeLinkGenerator(CodeArea codeArea) {
this.contentPanel = codeArea.getContentPanel();
this.codeArea = codeArea;
this.jCls = cls;
this.jNode = codeArea.getNode();
}
@Override
public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offset) {
try {
if (jNode.getCodeInfo() == null) {
return null;
}
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;
if (jNode instanceof JClass) {
if (type == TokenTypes.IDENTIFIER) {
sourceOffset = token.getOffset();
} else if (type == TokenTypes.ANNOTATION && token.length() > 1) {
sourceOffset = token.getOffset() + 1;
} else {
return null;
}
} else {
return null;
if (type == TokenTypes.MARKUP_TAG_ATTRIBUTE_VALUE) {
sourceOffset = token.getOffset() + 1; // skip quote at start (")
} else {
return null;
}
}
// fast skip
if (token.length() == 1) {
......@@ -53,11 +65,11 @@ public class CodeLinkGenerator implements LinkGenerator, HyperlinkListener {
return null;
}
}
final JumpPosition defPos = codeArea.getDefPosForNodeAtOffset(jCls, sourceOffset);
final JumpPosition defPos = codeArea.getDefPosForNodeAtOffset(sourceOffset);
if (defPos == null) {
return null;
}
if (Objects.equals(defPos.getNode().getRootClass(), jCls)
if (Objects.equals(defPos.getNode().getRootClass(), jNode)
&& defPos.getLine() == textArea.getLineOfOffset(sourceOffset) + 1) {
// ignore self jump
return null;
......
......@@ -3,14 +3,14 @@ package jadx.gui.ui.codearea;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import org.fife.ui.rtextarea.RTextScrollPane;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.ContentPanel;
import jadx.api.ICodeInfo;
import jadx.gui.utils.UiUtils;
/**
......@@ -21,14 +21,15 @@ public class CodePanel extends JPanel {
private final SearchBar searchBar;
private final AbstractCodeArea codeArea;
private final RTextScrollPane codeScrollPane;
private final JScrollPane codeScrollPane;
public CodePanel(ContentPanel contentPanel, AbstractCodeArea codeArea) {
public CodePanel(AbstractCodeArea codeArea) {
this.codeArea = codeArea;
searchBar = new SearchBar(codeArea);
codeScrollPane = new RTextScrollPane(codeArea, false);
codeScrollPane = new JScrollPane(codeArea);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(0, 0, 0, 0));
add(searchBar, BorderLayout.NORTH);
add(codeScrollPane, BorderLayout.CENTER);
......@@ -60,12 +61,19 @@ public class CodePanel extends JPanel {
}
private boolean isUseSourceLines() {
JNode node = codeArea.getNode();
if (node instanceof JResource) {
JResource resNode = (JResource) node;
return !resNode.getLineMapping().isEmpty();
if (codeArea instanceof SmaliArea) {
return false;
}
ICodeInfo codeInfo = codeArea.getNode().getCodeInfo();
if (codeInfo == null) {
return false;
}
Map<Integer, Integer> lineMapping = codeInfo.getLineMapping();
if (lineMapping.isEmpty()) {
return false;
}
return false;
Set<Integer> uniqueSourceLines = new HashSet<>(lineMapping.values());
return uniqueSourceLines.size() > 3;
}
public SearchBar getSearchBar() {
......
......@@ -10,7 +10,6 @@ import javax.swing.event.PopupMenuListener;
import org.fife.ui.rsyntaxtextarea.Token;
import jadx.api.JavaNode;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.MainWindow;
......@@ -22,15 +21,13 @@ public final class FindUsageAction extends AbstractAction implements PopupMenuLi
private final transient ContentPanel contentPanel;
private final transient CodeArea codeArea;
private final transient JClass jCls;
private transient JavaNode node;
public FindUsageAction(ContentPanel contentPanel, CodeArea codeArea, JClass jCls) {
public FindUsageAction(ContentPanel contentPanel, CodeArea codeArea) {
super(NLS.str("popup.find_usage"));
this.contentPanel = contentPanel;
this.codeArea = codeArea;
this.jCls = jCls;
}
@Override
......@@ -51,7 +48,7 @@ public final class FindUsageAction extends AbstractAction implements PopupMenuLi
if (pos != null) {
Token token = codeArea.viewToToken(pos);
if (token != null) {
node = codeArea.getJavaNodeAtOffset(jCls, token.getOffset());
node = codeArea.getJavaNodeAtOffset(token.getOffset());
}
}
setEnabled(node != null);
......
......@@ -49,7 +49,7 @@ public final class GoToDeclarationAction extends AbstractAction implements Popup
if (pos != null) {
Token token = codeArea.viewToToken(pos);
if (token != null) {
node = codeArea.getJavaNodeAtOffset(jCls, token.getOffset());
node = codeArea.getJavaNodeAtOffset(token.getOffset());
}
}
setEnabled(node != null);
......
......@@ -13,17 +13,14 @@ 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) {
public JadxTokenMaker(CodeArea codeArea) {
this.codeArea = codeArea;
this.jCls = jCls;
}
@Override
......@@ -62,7 +59,7 @@ public final class JadxTokenMaker extends JavaTokenMaker {
if (annotation) {
offset++;
}
JavaNode javaNode = codeArea.getJavaNodeAtOffset(jCls, offset);
JavaNode javaNode = codeArea.getJavaNodeAtOffset(offset);
if (javaNode instanceof JavaClass) {
String name = javaNode.getName();
String lexeme = current.getLexeme();
......
......@@ -20,6 +20,8 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeInfo;
public class LineNumbers extends JPanel implements CaretListener {
private static final Logger LOG = LoggerFactory.getLogger(LineNumbers.class);
......@@ -41,6 +43,7 @@ public class LineNumbers extends JPanel implements CaretListener {
private transient Insets textAreaInsets;
private transient Rectangle visibleRect = new Rectangle();
private transient ICodeInfo codeInfo;
public LineNumbers(AbstractCodeArea codeArea) {
this.codeArea = codeArea;
......@@ -60,10 +63,8 @@ public class LineNumbers extends JPanel implements CaretListener {
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
useSourceLines = !useSourceLines;
repaint();
}
useSourceLines = !useSourceLines;
repaint();
}
});
}
......@@ -96,6 +97,8 @@ public class LineNumbers extends JPanel implements CaretListener {
@Override
public void paintComponent(Graphics g) {
codeInfo = codeArea.getNode().getCodeInfo();
visibleRect = g.getClipBounds(visibleRect);
if (visibleRect == null) {
visibleRect = getVisibleRect();
......@@ -131,8 +134,7 @@ public class LineNumbers extends JPanel implements CaretListener {
int y = actualTopY + ascent;
int endY = visibleRect.y + visibleRect.height + ascent;
Element rootElement = codeArea.getDocument().getDefaultRootElement();
int currentLine = 1 + rootElement.getElementIndex(codeArea.getCaretPosition());
int currentLine = 1 + codeArea.getCaretLineNumber();
int lineNum = topLine + 1;
int linesCount = codeArea.getLineCount();
boolean isCurLine = updateColor(g, false, true);
......@@ -186,7 +188,7 @@ public class LineNumbers extends JPanel implements CaretListener {
if (!useSourceLines) {
return String.valueOf(lineNumber);
}
Integer sourceLine = codeArea.getSourceLine(lineNumber);
Integer sourceLine = codeInfo.getLineMapping().get(lineNumber);
if (sourceLine == null) {
return null;
}
......
package jadx.gui.ui.codearea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.TextNode;
import jadx.gui.ui.ContentPanel;
public final class SmaliArea extends AbstractCodeArea {
private static final long serialVersionUID = 1334485631870306494L;
private final JNode textNode;
SmaliArea(ContentPanel contentPanel) {
super(contentPanel);
setEditable(false);
this.textNode = new TextNode(node.getName());
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
}
@Override
......@@ -17,4 +24,10 @@ public final class SmaliArea extends AbstractCodeArea {
setCaretPosition(0);
}
}
@Override
public JNode getNode() {
// this area contains only smali without other node attributes
return textNode;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册