* feat(gui): add code comments (#359) * refactor: replace instanceof search with method dispatch in RegionGen * fix: various bug fixes and improvements for code comments * fix(gui): support multiline code comments * fix: resolve code differences after class reload * fix(gui): add search for comments, allow search in active tab only * fix: correct search for inner classes * fix(gui): run full index on search dialog open
This commit is contained in:
+190
@@ -0,0 +1,190 @@
|
||||
import java.awt.*;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* FlowLayout subclass that fully supports wrapping of components.
|
||||
*/
|
||||
public class WrapLayout extends FlowLayout
|
||||
{
|
||||
private Dimension preferredLayoutSize;
|
||||
|
||||
/**
|
||||
* Constructs a new <code>WrapLayout</code> with a left
|
||||
* alignment and a default 5-unit horizontal and vertical gap.
|
||||
*/
|
||||
public WrapLayout()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>FlowLayout</code> with the specified
|
||||
* alignment and a default 5-unit horizontal and vertical gap.
|
||||
* The value of the alignment argument must be one of
|
||||
* <code>WrapLayout</code>, <code>WrapLayout</code>,
|
||||
* or <code>WrapLayout</code>.
|
||||
* @param align the alignment value
|
||||
*/
|
||||
public WrapLayout(int align)
|
||||
{
|
||||
super(align);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new flow layout manager with the indicated alignment
|
||||
* and the indicated horizontal and vertical gaps.
|
||||
* <p>
|
||||
* The value of the alignment argument must be one of
|
||||
* <code>WrapLayout</code>, <code>WrapLayout</code>,
|
||||
* or <code>WrapLayout</code>.
|
||||
* @param align the alignment value
|
||||
* @param hgap the horizontal gap between components
|
||||
* @param vgap the vertical gap between components
|
||||
*/
|
||||
public WrapLayout(int align, int hgap, int vgap)
|
||||
{
|
||||
super(align, hgap, vgap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred dimensions for this layout given the
|
||||
* <i>visible</i> components in the specified target container.
|
||||
* @param target the component which needs to be laid out
|
||||
* @return the preferred dimensions to lay out the
|
||||
* subcomponents of the specified container
|
||||
*/
|
||||
@Override
|
||||
public Dimension preferredLayoutSize(Container target)
|
||||
{
|
||||
return layoutSize(target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum dimensions needed to layout the <i>visible</i>
|
||||
* components contained in the specified target container.
|
||||
* @param target the component which needs to be laid out
|
||||
* @return the minimum dimensions to lay out the
|
||||
* subcomponents of the specified container
|
||||
*/
|
||||
@Override
|
||||
public Dimension minimumLayoutSize(Container target)
|
||||
{
|
||||
Dimension minimum = layoutSize(target, false);
|
||||
minimum.width -= (getHgap() + 1);
|
||||
return minimum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum or preferred dimension needed to layout the target
|
||||
* container.
|
||||
*
|
||||
* @param target target to get layout size for
|
||||
* @param preferred should preferred size be calculated
|
||||
* @return the dimension to layout the target container
|
||||
*/
|
||||
private Dimension layoutSize(Container target, boolean preferred)
|
||||
{
|
||||
synchronized (target.getTreeLock())
|
||||
{
|
||||
// Each row must fit with the width allocated to the containter.
|
||||
// When the container width = 0, the preferred width of the container
|
||||
// has not yet been calculated so lets ask for the maximum.
|
||||
|
||||
int targetWidth = target.getSize().width;
|
||||
Container container = target;
|
||||
|
||||
while (container.getSize().width == 0 && container.getParent() != null)
|
||||
{
|
||||
container = container.getParent();
|
||||
}
|
||||
|
||||
targetWidth = container.getSize().width;
|
||||
|
||||
if (targetWidth == 0)
|
||||
targetWidth = Integer.MAX_VALUE;
|
||||
|
||||
int hgap = getHgap();
|
||||
int vgap = getVgap();
|
||||
Insets insets = target.getInsets();
|
||||
int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
|
||||
int maxWidth = targetWidth - horizontalInsetsAndGap;
|
||||
|
||||
// Fit components into the allowed width
|
||||
|
||||
Dimension dim = new Dimension(0, 0);
|
||||
int rowWidth = 0;
|
||||
int rowHeight = 0;
|
||||
|
||||
int nmembers = target.getComponentCount();
|
||||
|
||||
for (int i = 0; i < nmembers; i++)
|
||||
{
|
||||
Component m = target.getComponent(i);
|
||||
|
||||
if (m.isVisible())
|
||||
{
|
||||
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
|
||||
|
||||
// Can't add the component to current row. Start a new row.
|
||||
|
||||
if (rowWidth + d.width > maxWidth)
|
||||
{
|
||||
addRow(dim, rowWidth, rowHeight);
|
||||
rowWidth = 0;
|
||||
rowHeight = 0;
|
||||
}
|
||||
|
||||
// Add a horizontal gap for all components after the first
|
||||
|
||||
if (rowWidth != 0)
|
||||
{
|
||||
rowWidth += hgap;
|
||||
}
|
||||
|
||||
rowWidth += d.width;
|
||||
rowHeight = Math.max(rowHeight, d.height);
|
||||
}
|
||||
}
|
||||
|
||||
addRow(dim, rowWidth, rowHeight);
|
||||
|
||||
dim.width += horizontalInsetsAndGap;
|
||||
dim.height += insets.top + insets.bottom + vgap * 2;
|
||||
|
||||
// When using a scroll pane or the DecoratedLookAndFeel we need to
|
||||
// make sure the preferred size is less than the size of the
|
||||
// target containter so shrinking the container size works
|
||||
// correctly. Removing the horizontal gap is an easy way to do this.
|
||||
|
||||
Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
|
||||
|
||||
if (scrollPane != null && target.isValid())
|
||||
{
|
||||
dim.width -= (hgap + 1);
|
||||
}
|
||||
|
||||
return dim;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A new row has been completed. Use the dimensions of this row
|
||||
* to update the preferred size for the container.
|
||||
*
|
||||
* @param dim update the width and height when appropriate
|
||||
* @param rowWidth the width of the row to add
|
||||
* @param rowHeight the height of the row to add
|
||||
*/
|
||||
private void addRow(Dimension dim, int rowWidth, int rowHeight)
|
||||
{
|
||||
dim.width = Math.max(dim.width, rowWidth);
|
||||
|
||||
if (dim.height > 0)
|
||||
{
|
||||
dim.height += getVgap();
|
||||
}
|
||||
|
||||
dim.height += rowHeight;
|
||||
}
|
||||
}
|
||||
@@ -2,42 +2,27 @@ package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
private int usagePosition = -1;
|
||||
private final int pos;
|
||||
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
public CodePosition(int line, int offset, int pos) {
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public CodePosition(int line) {
|
||||
this(line, 0, -1);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public CodePosition(int line, int offset) {
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
this(line, offset, -1);
|
||||
}
|
||||
|
||||
public int getUsagePosition() {
|
||||
return usagePosition;
|
||||
}
|
||||
|
||||
public CodePosition setUsagePosition(int usagePosition) {
|
||||
this.usagePosition = usagePosition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
@@ -72,8 +57,8 @@ public final class CodePosition {
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (node != null) {
|
||||
sb.append(' ').append(node);
|
||||
if (pos > 0) {
|
||||
sb.append('@').append(pos);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
|
||||
public interface ICodeWriter {
|
||||
@@ -51,4 +53,8 @@ public interface ICodeWriter {
|
||||
String getCodeStr();
|
||||
|
||||
int getLength();
|
||||
|
||||
StringBuilder getRawBuf();
|
||||
|
||||
Map<CodePosition, Object> getRawAnnotations();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
|
||||
@@ -78,6 +79,8 @@ public class JadxArgs {
|
||||
|
||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||
|
||||
private ICodeData codeData;
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
@@ -384,6 +387,14 @@ public class JadxArgs {
|
||||
this.codeWriterProvider = codeWriterProvider;
|
||||
}
|
||||
|
||||
public ICodeData getCodeData() {
|
||||
return codeData;
|
||||
}
|
||||
|
||||
public void setCodeData(ICodeData codeData) {
|
||||
this.codeData = codeData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
|
||||
@@ -30,7 +30,11 @@ import jadx.api.plugins.input.data.ILoadResult;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.nodes.*;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -428,6 +432,15 @@ public final class JadxDecompiler implements Closeable {
|
||||
throw new JadxRuntimeException("JavaField not found by FieldNode: " + fld);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||
return getRoot().getClasses().stream()
|
||||
.filter(cls -> cls.getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.map(this::getJavaClassByNode)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JavaNode convertNode(Object obj) {
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
@@ -481,7 +494,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
return new CodePosition(defLine, 0, javaNode.getDefPos());
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
|
||||
@@ -131,7 +131,7 @@ public final class JavaClass implements JavaNode {
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
public Map<CodePosition, Object> getCodeAnnotations() {
|
||||
ICodeInfo code = getCodeInfo();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
@@ -139,6 +139,10 @@ public final class JavaClass implements JavaNode {
|
||||
return code.getAnnotations();
|
||||
}
|
||||
|
||||
public Object getAnnotationAt(CodePosition pos) {
|
||||
return getCodeAnnotations().get(pos);
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
@@ -229,6 +233,11 @@ public final class JavaClass implements JavaNode {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return cls.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
||||
|
||||
@@ -49,6 +49,11 @@ public final class JavaField implements JavaNode {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return field.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(field.getUseIn());
|
||||
|
||||
@@ -85,6 +85,11 @@ public final class JavaMethod implements JavaNode {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return mth.getDefPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal API. Not Stable!
|
||||
*/
|
||||
|
||||
@@ -14,5 +14,7 @@ public interface JavaNode {
|
||||
|
||||
int getDecompiledLine();
|
||||
|
||||
int getDefPos();
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -43,6 +43,11 @@ public class JavaVariable implements JavaNode {
|
||||
return node.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefPos() {
|
||||
return node.getDefPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.api.data;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ICodeComment extends Comparable<ICodeComment> {
|
||||
|
||||
IJavaNodeRef getNodeRef();
|
||||
|
||||
String getComment();
|
||||
|
||||
/**
|
||||
* Instruction offset inside method
|
||||
*/
|
||||
int getOffset();
|
||||
|
||||
enum AttachType {
|
||||
VAR_DECLARE
|
||||
}
|
||||
|
||||
@Nullable
|
||||
AttachType getAttachType();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package jadx.api.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ICodeData {
|
||||
|
||||
long getUpdateId();
|
||||
|
||||
List<ICodeComment> getComments();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package jadx.api.data;
|
||||
|
||||
public interface IJavaNodeRef extends Comparable<IJavaNodeRef> {
|
||||
|
||||
enum RefType {
|
||||
CLASS, FIELD, METHOD
|
||||
}
|
||||
|
||||
RefType getType();
|
||||
|
||||
String getDeclaringClass();
|
||||
|
||||
String getShortId();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
|
||||
public class CustomOffsetRef implements ICodeRawOffset {
|
||||
private final int offset;
|
||||
private final ICodeComment.AttachType attachType;
|
||||
|
||||
public CustomOffsetRef(int offset, ICodeComment.AttachType attachType) {
|
||||
this.offset = offset;
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public ICodeComment.AttachType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CustomOffsetRef{offset=" + offset + ", attachType=" + attachType + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
public interface ICodeRawOffset {
|
||||
int getOffset();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package jadx.api.data.annotations;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class InsnCodeOffset implements ICodeRawOffset {
|
||||
|
||||
public static void attach(ICodeWriter code, InsnNode insn) {
|
||||
if (insn == null) {
|
||||
return;
|
||||
}
|
||||
if (code.isMetadataSupported()) {
|
||||
InsnCodeOffset ann = from(insn);
|
||||
if (ann != null) {
|
||||
code.attachLineAnnotation(ann);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void attach(ICodeWriter code, int offset) {
|
||||
if (offset >= 0 && code.isMetadataSupported()) {
|
||||
code.attachLineAnnotation(new InsnCodeOffset(offset));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static InsnCodeOffset from(InsnNode insn) {
|
||||
int offset = insn.getOffset();
|
||||
if (offset < 0) {
|
||||
return null;
|
||||
}
|
||||
return new InsnCodeOffset(offset);
|
||||
}
|
||||
|
||||
private final int offset;
|
||||
|
||||
public InsnCodeOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "offset=" + offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxCodeComment implements ICodeComment {
|
||||
|
||||
private IJavaNodeRef nodeRef;
|
||||
private String comment;
|
||||
private int offset;
|
||||
private AttachType attachType;
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
|
||||
this(nodeRef, comment, -1, null);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, int offset) {
|
||||
this(nodeRef, comment, offset, null);
|
||||
}
|
||||
|
||||
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, int offset, AttachType attachType) {
|
||||
this.nodeRef = nodeRef;
|
||||
this.comment = comment;
|
||||
this.offset = offset;
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
public JadxCodeComment() {
|
||||
// for json deserialization
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJavaNodeRef getNodeRef() {
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
public void setNodeRef(IJavaNodeRef nodeRef) {
|
||||
this.nodeRef = nodeRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachType getAttachType() {
|
||||
return attachType;
|
||||
}
|
||||
|
||||
public void setAttachType(AttachType attachType) {
|
||||
this.attachType = attachType;
|
||||
}
|
||||
|
||||
private static final Comparator<ICodeComment> COMPARATOR = Comparator
|
||||
.comparing(ICodeComment::getNodeRef)
|
||||
.thenComparing(ICodeComment::getOffset);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ICodeComment other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxCodeComment{" + nodeRef + ", comment='" + comment + '\'' + ", offset=" + offset + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.ICodeData;
|
||||
|
||||
public class JadxCodeData implements ICodeData {
|
||||
|
||||
private long updateId = System.currentTimeMillis();
|
||||
private List<ICodeComment> comments = Collections.emptyList();
|
||||
|
||||
@Override
|
||||
public long getUpdateId() {
|
||||
return updateId;
|
||||
}
|
||||
|
||||
public void markUpdate() {
|
||||
updateId = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ICodeComment> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
public void setComments(List<ICodeComment> comments) {
|
||||
markUpdate();
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(updateId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxCodeData)) {
|
||||
return false;
|
||||
}
|
||||
JadxCodeData that = (JadxCodeData) o;
|
||||
return updateId == that.updateId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package jadx.api.data.impl;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxNodeRef implements IJavaNodeRef, Comparable<IJavaNodeRef> {
|
||||
|
||||
@Nullable
|
||||
public static JadxNodeRef forJavaNode(JavaNode javaNode) {
|
||||
if (javaNode instanceof JavaClass) {
|
||||
return forCls((JavaClass) javaNode);
|
||||
}
|
||||
if (javaNode instanceof JavaMethod) {
|
||||
return forMth((JavaMethod) javaNode);
|
||||
}
|
||||
if (javaNode instanceof JavaField) {
|
||||
return forFld((JavaField) javaNode);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static JadxNodeRef forCls(JavaClass cls) {
|
||||
return new JadxNodeRef(RefType.CLASS, cls.getClassNode().getClassInfo().getFullName(), null);
|
||||
}
|
||||
|
||||
public static JadxNodeRef forCls(String clsFullName) {
|
||||
return new JadxNodeRef(RefType.CLASS, clsFullName, null);
|
||||
}
|
||||
|
||||
public static JadxNodeRef forMth(JavaMethod mth) {
|
||||
return new JadxNodeRef(RefType.METHOD,
|
||||
mth.getDeclaringClass().getClassNode().getClassInfo().getFullName(),
|
||||
mth.getMethodNode().getMethodInfo().getShortId());
|
||||
}
|
||||
|
||||
public static JadxNodeRef forFld(JavaField fld) {
|
||||
return new JadxNodeRef(RefType.FIELD,
|
||||
fld.getDeclaringClass().getClassNode().getClassInfo().getFullName(),
|
||||
fld.getFieldNode().getFieldInfo().getShortId());
|
||||
}
|
||||
|
||||
private RefType refType;
|
||||
private String declClass;
|
||||
@Nullable
|
||||
private String shortId;
|
||||
|
||||
public JadxNodeRef(RefType refType, String declClass, @Nullable String shortId) {
|
||||
this.refType = refType;
|
||||
this.declClass = declClass;
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
public JadxNodeRef() {
|
||||
// for json deserialization
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefType getType() {
|
||||
return refType;
|
||||
}
|
||||
|
||||
public void setRefType(RefType refType) {
|
||||
this.refType = refType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDeclaringClass() {
|
||||
return declClass;
|
||||
}
|
||||
|
||||
public void setDeclClass(String declClass) {
|
||||
this.declClass = declClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
public void setShortId(@Nullable String shortId) {
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
private static final Comparator<IJavaNodeRef> COMPARATOR = Comparator
|
||||
.comparing(IJavaNodeRef::getType)
|
||||
.thenComparing(IJavaNodeRef::getDeclaringClass)
|
||||
.thenComparing(IJavaNodeRef::getShortId);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull IJavaNodeRef other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(refType, declClass, shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof JadxNodeRef)) {
|
||||
return false;
|
||||
}
|
||||
JadxNodeRef that = (JadxNodeRef) o;
|
||||
return refType == that.refType
|
||||
&& Objects.equals(declClass, that.declClass)
|
||||
&& Objects.equals(shortId, that.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (refType) {
|
||||
case CLASS:
|
||||
return declClass;
|
||||
case FIELD:
|
||||
case METHOD:
|
||||
return declClass + "->" + shortId;
|
||||
default:
|
||||
return "unknown node ref type";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,17 +65,13 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
}
|
||||
AnnotatedCodeWriter code = ((AnnotatedCodeWriter) cw);
|
||||
line--;
|
||||
int startLine = line;
|
||||
int startPos = getLength();
|
||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||
Object val = entry.getValue();
|
||||
if (val instanceof DefinitionWrapper) {
|
||||
LineAttrNode node = ((DefinitionWrapper) val).getNode();
|
||||
node.setDefPosition(node.getDefPosition() + this.buf.length());
|
||||
}
|
||||
CodePosition pos = entry.getKey();
|
||||
int usagePos = pos.getUsagePosition() + getLength();
|
||||
attachAnnotation(val,
|
||||
new CodePosition(line + pos.getLine(), pos.getOffset())
|
||||
.setUsagePosition(usagePos));
|
||||
CodePosition codePos = entry.getKey();
|
||||
int newLine = startLine + codePos.getLine();
|
||||
int newPos = startPos + codePos.getPos();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(newLine, codePos.getOffset(), newPos));
|
||||
}
|
||||
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
|
||||
attachSourceLine(line + entry.getKey(), entry.getValue());
|
||||
@@ -119,19 +115,21 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
|
||||
@Override
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
obj.setDefPosition(buf.length());
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset, getLength()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1).setUsagePosition(getLength()));
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1, getLength()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, 0));
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
attachAnnotation(obj, new CodePosition(line, 0, getLength() - offset));
|
||||
}
|
||||
|
||||
private void attachAnnotation(Object obj, CodePosition pos) {
|
||||
@@ -165,13 +163,20 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
|
||||
return new AnnotatedCodeInfo(code, lineMap, annotations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
private void processDefinitionAnnotations() {
|
||||
if (!annotations.isEmpty()) {
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
CodePosition codePos = entry.getKey();
|
||||
l.setDecompiledLine(codePos.getLine());
|
||||
l.setDefPosition(codePos.getPos());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package jadx.api.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
@@ -228,6 +232,16 @@ public class SimpleCodeWriter implements ICodeWriter {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringBuilder getRawBuf() {
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<CodePosition, Object> getRawAnnotations() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCodeStr() {
|
||||
removeFirstEmptyLine();
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.AttachCommentsVisitor;
|
||||
import jadx.core.dex.visitors.AttachMethodDetails;
|
||||
import jadx.core.dex.visitors.AttachTryCatchVisitor;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
@@ -71,8 +72,9 @@ public class Jadx {
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getFallbackPassesList() {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>(3);
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new AttachCommentsVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
passes.add(new FallbackModeVisitor());
|
||||
return passes;
|
||||
@@ -82,6 +84,7 @@ public class Jadx {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
passes.add(new SignatureProcessor());
|
||||
passes.add(new OverrideMethodVisitor());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new RenameVisitor());
|
||||
passes.add(new UsageInfoVisitor());
|
||||
return passes;
|
||||
@@ -97,6 +100,7 @@ public class Jadx {
|
||||
passes.add(new DebugInfoAttachVisitor());
|
||||
}
|
||||
passes.add(new AttachTryCatchVisitor());
|
||||
passes.add(new AttachCommentsVisitor());
|
||||
passes.add(new ProcessInstructionsVisitor());
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
@@ -144,7 +148,6 @@ public class Jadx {
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ public final class ProcessClass {
|
||||
try {
|
||||
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
|
||||
cls.remove(AFlag.CLASS_DEEP_RELOAD);
|
||||
cls.unload();
|
||||
cls.deepUnload();
|
||||
cls.root().runPreDecompileStageForClass(cls);
|
||||
}
|
||||
|
||||
@@ -121,8 +121,9 @@ public class ClassGen {
|
||||
if (Consts.DEBUG_USAGE) {
|
||||
addClassUsageInfo(code, cls);
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
CodeGenUtils.addSourceFileInfo(code, cls);
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
}
|
||||
@@ -145,7 +146,6 @@ public class ClassGen {
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
insertRenameInfo(clsCode, cls);
|
||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString());
|
||||
if (af.isInterface()) {
|
||||
if (af.isAnnotation()) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.plugins.input.data.MethodHandleType;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -69,7 +70,6 @@ public class InsnGen {
|
||||
protected final MethodNode mth;
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
protected final boolean attachInsns;
|
||||
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
@@ -82,7 +82,6 @@ public class InsnGen {
|
||||
this.mth = mgen.getMethodNode();
|
||||
this.root = mth.root();
|
||||
this.fallback = fallback;
|
||||
this.attachInsns = root.getArgs().isJsonOutput();
|
||||
}
|
||||
|
||||
private boolean isFallback() {
|
||||
@@ -260,9 +259,7 @@ public class InsnGen {
|
||||
} else {
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
if (insn.contains(AFlag.COMMENT_OUT)) {
|
||||
code.add("// ");
|
||||
}
|
||||
@@ -278,6 +275,7 @@ public class InsnGen {
|
||||
makeInsnBody(code, insn, EMPTY_FLAGS);
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.plugins.input.data.AccessFlags;
|
||||
import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.core.Consts;
|
||||
@@ -44,7 +45,7 @@ import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.BLOCK_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.COMMENTED_DUMP;
|
||||
import static jadx.core.codegen.MethodGen.FallbackOption.FALLBACK_MODE;
|
||||
import static jadx.core.dex.nodes.VariableNode.*;
|
||||
import static jadx.core.dex.nodes.VariableNode.VarKind;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
@@ -289,17 +290,19 @@ public class MethodGen {
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(ICodeWriter code, FallbackOption fallbackOption) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
if (fallbackOption != FALLBACK_MODE) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
if (insnArr == null) {
|
||||
@@ -333,7 +336,6 @@ public class MethodGen {
|
||||
public static void addFallbackInsns(ICodeWriter code, MethodNode mth, InsnNode[] insnArr, FallbackOption option) {
|
||||
int startIndent = code.getIndent();
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null) {
|
||||
@@ -360,11 +362,9 @@ public class MethodGen {
|
||||
code.startLine("*/");
|
||||
code.startLine("// ");
|
||||
} else {
|
||||
code.startLine();
|
||||
}
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
}
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg != null) {
|
||||
ArgType varType = resArg.getInitType();
|
||||
@@ -382,6 +382,7 @@ public class MethodGen {
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.setIndent(startIndent);
|
||||
|
||||
@@ -135,9 +135,6 @@ public class NameGen {
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
name = getFallbackName(var);
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
name += '_' + getFallbackName(var);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.annotations.CustomOffsetRef;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.fldinit.FieldInitAttr;
|
||||
@@ -26,7 +29,6 @@ import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
@@ -44,6 +46,7 @@ import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -57,28 +60,8 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
public void makeRegion(ICodeWriter code, IContainer cont) throws CodegenException {
|
||||
if (cont instanceof IBlock) {
|
||||
makeSimpleBlock((IBlock) cont, code);
|
||||
} else if (cont instanceof IRegion) {
|
||||
if (cont instanceof Region) {
|
||||
makeSimpleRegion(code, (Region) cont);
|
||||
} else {
|
||||
declareVars(code, cont);
|
||||
if (cont instanceof IfRegion) {
|
||||
makeIf((IfRegion) cont, code, true);
|
||||
} else if (cont instanceof SwitchRegion) {
|
||||
makeSwitch((SwitchRegion) cont, code);
|
||||
} else if (cont instanceof LoopRegion) {
|
||||
makeLoop((LoopRegion) cont, code);
|
||||
} else if (cont instanceof TryCatchRegion) {
|
||||
makeTryCatch((TryCatchRegion) cont, code);
|
||||
} else if (cont instanceof SynchronizedRegion) {
|
||||
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Not processed container: " + cont);
|
||||
}
|
||||
declareVars(code, cont);
|
||||
cont.generate(this, code);
|
||||
}
|
||||
|
||||
private void declareVars(ICodeWriter code, IContainer cont) {
|
||||
@@ -88,24 +71,30 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
attachVariableCommentsData(code, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSimpleRegion(ICodeWriter code, Region region) throws CodegenException {
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
private void attachVariableCommentsData(ICodeWriter code, CodeVar v) {
|
||||
RegisterArg assignReg = v.getSsaVars().get(0).getAssign();
|
||||
if (code.isMetadataSupported()) {
|
||||
InsnNode parentInsn = assignReg.getParentInsn();
|
||||
if (parentInsn != null) {
|
||||
int offset = parentInsn.getOffset();
|
||||
code.attachLineAnnotation(new CustomOffsetRef(offset, ICodeComment.AttachType.VAR_DECLARE));
|
||||
}
|
||||
}
|
||||
CodeGenUtils.addCodeComments(code, assignReg);
|
||||
}
|
||||
|
||||
public void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
|
||||
private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
|
||||
code.incIndent();
|
||||
makeRegion(code, region);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
private void makeSimpleBlock(IBlock block, ICodeWriter code) throws CodegenException {
|
||||
public void makeSimpleBlock(IBlock block, ICodeWriter code) throws CodegenException {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
@@ -121,22 +110,12 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeIf(IfRegion region, ICodeWriter code, boolean newLine) throws CodegenException {
|
||||
public void makeIf(IfRegion region, ICodeWriter code, boolean newLine) throws CodegenException {
|
||||
if (newLine) {
|
||||
code.startLineWithNum(region.getSourceLine());
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
if (attachInsns) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null) {
|
||||
code.attachLineAnnotation(lastInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean comment = region.contains(AFlag.COMMENT_OUT);
|
||||
if (comment) {
|
||||
code.add("// ");
|
||||
@@ -145,6 +124,15 @@ public class RegionGen extends InsnGen {
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
if (code.isMetadataSupported()) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
InsnCodeOffset.attach(code, lastInsn);
|
||||
CodeGenUtils.addCodeComments(code, lastInsn);
|
||||
}
|
||||
}
|
||||
makeRegionIndent(code, region.getThenRegion());
|
||||
if (comment) {
|
||||
code.startLine("// }");
|
||||
@@ -186,43 +174,49 @@ public class RegionGen extends InsnGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
||||
public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException {
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
}
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
|
||||
IfCondition condition = region.getCondition();
|
||||
if (condition == null) {
|
||||
// infinite loop
|
||||
code.startLine("while (true) {");
|
||||
code.add("while (true) {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return;
|
||||
}
|
||||
InsnNode condInsn = condition.getFirstInsn();
|
||||
InsnCodeOffset.attach(code, condInsn);
|
||||
|
||||
ConditionGen conditionGen = new ConditionGen(this);
|
||||
LoopType type = region.getType();
|
||||
if (type != null) {
|
||||
if (type instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) type;
|
||||
code.startLine("for (");
|
||||
code.add("for (");
|
||||
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
||||
code.add("; ");
|
||||
conditionGen.add(code, condition);
|
||||
code.add("; ");
|
||||
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
||||
code.add(") {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return;
|
||||
}
|
||||
if (type instanceof ForEachLoop) {
|
||||
ForEachLoop forEachLoop = (ForEachLoop) type;
|
||||
code.startLine("for (");
|
||||
code.add("for (");
|
||||
declareVar(code, forEachLoop.getVarArg());
|
||||
code.add(" : ");
|
||||
addArg(code, forEachLoop.getIterableArg(), false);
|
||||
code.add(") {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return;
|
||||
@@ -230,37 +224,45 @@ public class RegionGen extends InsnGen {
|
||||
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
||||
}
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
code.add("do {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("} while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(");");
|
||||
} else {
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(") {");
|
||||
CodeGenUtils.addCodeComments(code, condInsn);
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSynchronizedRegion(SynchronizedRegion cont, ICodeWriter code) throws CodegenException {
|
||||
public void makeSynchronizedRegion(SynchronizedRegion cont, ICodeWriter code) throws CodegenException {
|
||||
code.startLine("synchronized (");
|
||||
addArg(code, cont.getEnterInsn().getArg(0));
|
||||
InsnNode monitorEnterInsn = cont.getEnterInsn();
|
||||
addArg(code, monitorEnterInsn.getArg(0));
|
||||
code.add(") {");
|
||||
|
||||
InsnCodeOffset.attach(code, monitorEnterInsn);
|
||||
CodeGenUtils.addCodeComments(code, monitorEnterInsn);
|
||||
|
||||
makeRegionIndent(code, cont.getRegion());
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenException {
|
||||
public void makeSwitch(SwitchRegion sw, ICodeWriter code) throws CodegenException {
|
||||
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
|
||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg, false);
|
||||
code.add(") {");
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
code.incIndent();
|
||||
|
||||
for (CaseInfo caseInfo : sw.getCases()) {
|
||||
@@ -304,8 +306,13 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
|
||||
code.startLine("try {");
|
||||
|
||||
InsnNode insn = Utils.first(region.getTryCatchBlock().getInsns());
|
||||
InsnCodeOffset.attach(code, insn);
|
||||
CodeGenUtils.addCodeComments(code, insn);
|
||||
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||
ExceptionHandler allHandler = null;
|
||||
@@ -381,6 +388,10 @@ public class RegionGen extends InsnGen {
|
||||
throw new JadxRuntimeException("Unexpected arg type in catch block: " + arg + ", class: " + arg.getClass().getSimpleName());
|
||||
}
|
||||
code.add(") {");
|
||||
|
||||
InsnCodeOffset.attach(code, handler.getHandleOffset());
|
||||
CodeGenUtils.addCodeComments(code, handler.getHandlerBlock());
|
||||
|
||||
makeRegionIndent(code, region);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
@@ -29,7 +30,6 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
@@ -193,9 +193,9 @@ public class JsonCodeGen {
|
||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||
jsonCodeLine.setCode(codeLine);
|
||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||
Object obj = annotations.get(new CodePosition(line, 0));
|
||||
if (obj instanceof InsnNode) {
|
||||
long offset = ((InsnNode) obj).getOffset();
|
||||
Object obj = annotations.get(new CodePosition(line));
|
||||
if (obj instanceof InsnCodeOffset) {
|
||||
long offset = ((InsnCodeOffset) obj).getOffset();
|
||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||
}
|
||||
codeLines.add(jsonCodeLine);
|
||||
|
||||
@@ -42,6 +42,9 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
// class, method, field, insn
|
||||
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
|
||||
|
||||
// class, method, field
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
|
||||
@@ -33,6 +33,23 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType) {
|
||||
IAttribute attr = attrNode.get(attrType);
|
||||
if (attr != null) {
|
||||
this.addAttr(attr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove attribute in this node, add copy from other if exists
|
||||
*/
|
||||
@Override
|
||||
public <T extends IAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType) {
|
||||
remove(attrType);
|
||||
copyAttributeFrom(attrNode, attrType);
|
||||
}
|
||||
|
||||
private AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
|
||||
@@ -14,6 +14,10 @@ public interface IAttributeNode {
|
||||
|
||||
void copyAttributesFrom(AttrNode attrNode);
|
||||
|
||||
<T extends IAttribute> void copyAttributeFrom(AttrNode attrNode, AType<T> attrType);
|
||||
|
||||
<T extends IAttribute> void rewriteAttributeFrom(AttrNode attrNode, AType<T> attrType);
|
||||
|
||||
boolean contains(AFlag flag);
|
||||
|
||||
<T extends IAttribute> boolean contains(AType<T> type);
|
||||
|
||||
@@ -35,11 +35,9 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
|
||||
|
||||
public void addComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
LOG.info("{} in {}", commentStr, this);
|
||||
}
|
||||
|
||||
public void addDebugComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr);
|
||||
LOG.debug("{} in {}", commentStr, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,10 @@ public final class FieldInfo {
|
||||
return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
public String getRawFullId() {
|
||||
return declClass.makeRawFullName() + '.' + name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -191,9 +190,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
if (fileName.endsWith(".java")) {
|
||||
fileName = fileName.substring(0, fileName.length() - 5);
|
||||
}
|
||||
if (fileName.isEmpty()
|
||||
|| fileName.equals("SourceFile")
|
||||
|| fileName.equals("\"")) {
|
||||
if (fileName.isEmpty() || fileName.equals("SourceFile")) {
|
||||
return;
|
||||
}
|
||||
if (clsInfo != null) {
|
||||
@@ -201,12 +198,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
if (fileName.equals(name)) {
|
||||
return;
|
||||
}
|
||||
if (fileName.contains("$")
|
||||
&& fileName.endsWith('$' + name)) {
|
||||
return;
|
||||
}
|
||||
ClassInfo parentCls = clsInfo.getTopParentClass();
|
||||
if (parentCls != null && fileName.equals(parentCls.getShortName())) {
|
||||
if (fileName.contains("$") && fileName.endsWith('$' + name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -240,14 +232,12 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
// manually added class
|
||||
return;
|
||||
}
|
||||
unload();
|
||||
clearAttributes();
|
||||
root().getConstValues().removeForClass(this);
|
||||
initialLoad(clsData);
|
||||
ProcessAnonymous.runForClass(this);
|
||||
|
||||
for (ClassNode innerClass : innerClasses) {
|
||||
innerClass.deepUnload();
|
||||
}
|
||||
innerClasses.forEach(ClassNode::deepUnload);
|
||||
}
|
||||
|
||||
private synchronized ICodeInfo decompile(boolean searchInCache) {
|
||||
@@ -375,6 +365,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldNode searchFieldByShortId(String shortId) {
|
||||
for (FieldNode f : fields) {
|
||||
if (f.getFieldInfo().getShortId().equals(shortId)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodNode searchMethod(MethodInfo mth) {
|
||||
return mthInfoMap.get(mth);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,16 @@ package jadx.core.dex.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public interface IBlock extends IContainer {
|
||||
|
||||
List<InsnNode> getInstructions();
|
||||
|
||||
@Override
|
||||
default void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeSimpleBlock(this, code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public interface IContainer extends IAttributeNode {
|
||||
|
||||
@@ -8,4 +11,11 @@ public interface IContainer extends IAttributeNode {
|
||||
* Unique id for use in 'toString()' method
|
||||
*/
|
||||
String baseString();
|
||||
|
||||
/**
|
||||
* Dispatch to needed generate method in RegionGen
|
||||
*/
|
||||
default void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
throw new CodegenException("Code generate not implemented for container: " + getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public class RootNode {
|
||||
// sort classes by name, expect top classes before inner
|
||||
classes.sort(Comparator.comparing(ClassNode::getFullName));
|
||||
initInnerClasses();
|
||||
LOG.debug("Classes loaded: {}", classes.size());
|
||||
LOG.info("Classes loaded: {}", classes.size());
|
||||
}
|
||||
|
||||
private void addDummyClass(IClassData classData, Exception exc) {
|
||||
@@ -239,6 +239,7 @@ public class RootNode {
|
||||
|
||||
public void runPreDecompileStage() {
|
||||
for (IDexTreeVisitor pass : preDecompilePasses) {
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
pass.init(this);
|
||||
} catch (Exception e) {
|
||||
@@ -247,6 +248,9 @@ public class RootNode {
|
||||
for (ClassNode cls : classes) {
|
||||
DepthTraversal.visit(pass, cls);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("{} time: {}ms", pass.getClass().getSimpleName(), System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ package jadx.core.dex.regions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class Region extends AbstractRegion {
|
||||
|
||||
@@ -26,6 +29,13 @@ public final class Region extends AbstractRegion {
|
||||
blocks.add(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
for (IContainer c : blocks) {
|
||||
regionGen.makeRegion(code, c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
|
||||
int i = blocks.indexOf(oldBlock);
|
||||
|
||||
@@ -5,11 +5,13 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@@ -72,6 +74,11 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
return Collections.unmodifiableList(getCaseContainers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeSwitch(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return header.baseString();
|
||||
|
||||
@@ -3,9 +3,12 @@ package jadx.core.dex.regions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class SynchronizedRegion extends AbstractRegion {
|
||||
|
||||
@@ -36,6 +39,11 @@ public final class SynchronizedRegion extends AbstractRegion {
|
||||
return region.getSubBlocks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeSynchronizedRegion(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return Integer.toHexString(enterInsn.getOffset());
|
||||
|
||||
@@ -6,12 +6,15 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class TryCatchRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@@ -77,6 +80,11 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio
|
||||
return getSubBlocks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeTryCatch(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return tryRegion.baseString();
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
@@ -262,6 +264,14 @@ public final class IfCondition extends AttrNode {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InsnNode getFirstInsn() {
|
||||
if (mode == Mode.COMPARE) {
|
||||
return compare.getInsn();
|
||||
}
|
||||
return args.get(0).getFirstInsn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (mode) {
|
||||
@@ -313,5 +323,4 @@ public final class IfCondition extends AttrNode {
|
||||
result = 31 * result + (compare != null ? compare.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
@@ -12,6 +14,7 @@ import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@@ -129,6 +132,11 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeIf(this, code, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.codegen.RegionGen;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -16,6 +18,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public final class LoopRegion extends AbstractRegion {
|
||||
|
||||
@@ -165,6 +168,11 @@ public final class LoopRegion extends AbstractRegion {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generate(RegionGen regionGen, ICodeWriter code) throws CodegenException {
|
||||
regionGen.makeLoop(this, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return body == null ? "-" : body.baseString();
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.ICodeData;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Attach comments",
|
||||
desc = "Attach comments",
|
||||
runBefore = {
|
||||
ProcessInstructionsVisitor.class
|
||||
}
|
||||
)
|
||||
public class AttachCommentsVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AttachCommentsVisitor.class);
|
||||
|
||||
private final CommentsData cachedCommentsData = new CommentsData();
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) {
|
||||
List<ICodeComment> clsComments = getCommentsData(cls);
|
||||
if (!clsComments.isEmpty()) {
|
||||
applyComments(cls, clsComments);
|
||||
}
|
||||
cls.getInnerClasses().forEach(this::visit);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void applyComments(ClassNode cls, List<ICodeComment> clsComments) {
|
||||
for (ICodeComment comment : clsComments) {
|
||||
IJavaNodeRef nodeRef = comment.getNodeRef();
|
||||
switch (nodeRef.getType()) {
|
||||
case CLASS:
|
||||
addComment(cls, comment.getComment());
|
||||
break;
|
||||
|
||||
case FIELD:
|
||||
FieldNode fieldNode = cls.searchFieldByShortId(nodeRef.getShortId());
|
||||
if (fieldNode == null) {
|
||||
LOG.warn("Field reference not found: {}", nodeRef);
|
||||
} else {
|
||||
addComment(fieldNode, comment.getComment());
|
||||
}
|
||||
break;
|
||||
|
||||
case METHOD:
|
||||
MethodNode methodNode = cls.searchMethodByShortId(nodeRef.getShortId());
|
||||
if (methodNode == null) {
|
||||
LOG.warn("Method reference not found: {}", nodeRef);
|
||||
} else {
|
||||
int offset = comment.getOffset();
|
||||
if (offset < 0) {
|
||||
addComment(methodNode, comment.getComment());
|
||||
} else if (comment.getAttachType() != null) {
|
||||
processCustomAttach(methodNode, comment);
|
||||
} else {
|
||||
InsnNode insn = getInsnByOffset(methodNode, offset);
|
||||
addComment(insn, comment.getComment());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static InsnNode getInsnByOffset(MethodNode mth, int offset) {
|
||||
try {
|
||||
return mth.getInstructions()[offset];
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Insn reference not found in: {} with offset: {}", mth, offset);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void processCustomAttach(MethodNode mth, ICodeComment comment) {
|
||||
ICodeComment.AttachType attachType = comment.getAttachType();
|
||||
if (attachType == null) {
|
||||
return;
|
||||
}
|
||||
switch (attachType) {
|
||||
case VAR_DECLARE:
|
||||
InsnNode insn = getInsnByOffset(mth, comment.getOffset());
|
||||
if (insn != null) {
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null) {
|
||||
result.addAttr(AType.CODE_COMMENTS, comment.getComment());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unexpected attach type: " + attachType);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addComment(@Nullable IAttributeNode node, String comment) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
node.remove(AType.CODE_COMMENTS);
|
||||
node.addAttr(AType.CODE_COMMENTS, comment);
|
||||
}
|
||||
|
||||
private static final class CommentsData {
|
||||
long updateId;
|
||||
Map<String, List<ICodeComment>> clsCommentsMap;
|
||||
}
|
||||
|
||||
private List<ICodeComment> getCommentsData(ClassNode cls) {
|
||||
ICodeData additionalData = cls.root().getArgs().getCodeData();
|
||||
if (additionalData == null || additionalData.getComments().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
synchronized (cachedCommentsData) {
|
||||
CommentsData commentsData = this.cachedCommentsData;
|
||||
if (commentsData.updateId != additionalData.getUpdateId()) {
|
||||
updateCommentsData(additionalData, commentsData);
|
||||
}
|
||||
List<ICodeComment> clsComments = commentsData.clsCommentsMap.get(cls.getClassInfo().getFullName());
|
||||
if (clsComments == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return clsComments;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateCommentsData(ICodeData data, CommentsData commentsData) {
|
||||
Map<String, List<ICodeComment>> map = new HashMap<>();
|
||||
for (ICodeComment comment : data.getComments()) {
|
||||
String declClsId = comment.getNodeRef().getDeclaringClass();
|
||||
List<ICodeComment> comments = map.computeIfAbsent(declClsId, s -> new ArrayList<>());
|
||||
comments.add(comment);
|
||||
}
|
||||
commentsData.clsCommentsMap = map;
|
||||
commentsData.updateId = data.getUpdateId();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.BaseInvokeNode;
|
||||
import jadx.core.dex.instructions.ConstStringNode;
|
||||
@@ -225,6 +226,12 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
if (insnType == InsnType.RETURN) {
|
||||
useInsn.setSourceLine(constInsn.getSourceLine());
|
||||
if (useInsn.contains(AFlag.SYNTHETIC)) {
|
||||
useInsn.setOffset(constInsn.getOffset());
|
||||
useInsn.rewriteAttributeFrom(constInsn, AType.CODE_COMMENTS);
|
||||
} else {
|
||||
useInsn.copyAttributeFrom(constInsn, AType.CODE_COMMENTS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -608,5 +608,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
excHandler.setArg(namedArg);
|
||||
replaceInsn(mth, block, 0, moveInsn);
|
||||
}
|
||||
block.copyAttributeFrom(insn, AType.CODE_COMMENTS); // save comment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.clsp.ClspMethod;
|
||||
@@ -41,27 +39,20 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
}
|
||||
)
|
||||
public class OverrideMethodVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OverrideMethodVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) throws JadxException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processCls(cls);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("OverrideMethod pass time: {}ms", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
processCls(cls);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean processCls(ClassNode cls) {
|
||||
private void processCls(ClassNode cls) {
|
||||
List<ArgType> superTypes = collectSuperTypes(cls);
|
||||
if (!superTypes.isEmpty()) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
processMth(cls, superTypes, mth);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void processMth(ClassNode cls, List<ArgType> superTypes, MethodNode mth) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ProcessAnonymous",
|
||||
@@ -12,19 +13,20 @@ import jadx.core.dex.nodes.RootNode;
|
||||
)
|
||||
public class ProcessAnonymous extends AbstractVisitor {
|
||||
|
||||
private boolean inlineAnonymous;
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
if (root.getArgs().isInlineAnonymousClasses()) {
|
||||
for (ClassNode cls : root.getClasses(true)) {
|
||||
markAnonymousClass(cls);
|
||||
}
|
||||
}
|
||||
inlineAnonymous = root.getArgs().isInlineAnonymousClasses();
|
||||
}
|
||||
|
||||
public static void runForClass(ClassNode cls) {
|
||||
if (cls.root().getArgs().isInlineAnonymousClasses()) {
|
||||
markAnonymousClass(cls);
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (!inlineAnonymous) {
|
||||
return false;
|
||||
}
|
||||
markAnonymousClass(cls);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void markAnonymousClass(ClassNode cls) {
|
||||
|
||||
@@ -7,8 +7,6 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.Consts;
|
||||
@@ -26,7 +24,6 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RenameVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
@@ -34,11 +31,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
if (inputFiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
long startTime = System.currentTimeMillis();
|
||||
process(root);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Rename pass time: {}ms", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void process(RootNode root) {
|
||||
|
||||
@@ -749,7 +749,9 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
first = false;
|
||||
} else {
|
||||
for (InsnNode oldInsn : exitBlock.getInstructions()) {
|
||||
newRetBlock.getInstructions().add(oldInsn.copyWithoutSsa());
|
||||
InsnNode copyInsn = oldInsn.copyWithoutSsa();
|
||||
copyInsn.add(AFlag.SYNTHETIC);
|
||||
newRetBlock.getInstructions().add(copyInsn);
|
||||
}
|
||||
}
|
||||
BlockSplitter.replaceConnection(pred, exitBlock, newRetBlock);
|
||||
|
||||
@@ -6,7 +6,10 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
@@ -142,10 +145,6 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean inline(MethodNode mth, RegisterArg arg, InsnNode insn, BlockNode block) {
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn != null && parentInsn.getType() == InsnType.RETURN) {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
}
|
||||
if (insn.contains(AFlag.FORCE_ASSIGN_INLINE)) {
|
||||
return assignInline(mth, arg, insn, block);
|
||||
}
|
||||
@@ -153,11 +152,27 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
InsnArg wrappedArg = arg.wrapInstruction(mth, insn, false);
|
||||
boolean replaced = wrappedArg != null;
|
||||
if (replaced) {
|
||||
processCodeComment(insn, arg.getParentInsn());
|
||||
InsnRemover.removeWithoutUnbind(mth, block, insn);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
private static void processCodeComment(InsnNode insn, @Nullable InsnNode parentInsn) {
|
||||
if (parentInsn == null) {
|
||||
return;
|
||||
}
|
||||
if (parentInsn.getType() == InsnType.RETURN) {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
if (parentInsn.contains(AFlag.SYNTHETIC)) {
|
||||
parentInsn.setOffset(insn.getOffset());
|
||||
parentInsn.rewriteAttributeFrom(insn, AType.CODE_COMMENTS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
parentInsn.copyAttributeFrom(insn, AType.CODE_COMMENTS);
|
||||
}
|
||||
|
||||
private static boolean canMoveBetweenBlocks(MethodNode mth, InsnNode assignInsn, BlockNode assignBlock,
|
||||
BlockNode useBlock, InsnNode useInsn) {
|
||||
if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package jadx.core.dex.visitors.usage;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
@@ -27,17 +24,14 @@ import jadx.core.dex.visitors.RenameVisitor;
|
||||
}
|
||||
)
|
||||
public class UsageInfoVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsageInfoVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
UsageInfo usageInfo = new UsageInfo(root);
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processClass(cls, usageInfo);
|
||||
}
|
||||
usageInfo.apply();
|
||||
LOG.debug("Dependency collection done in {}ms", System.currentTimeMillis() - startTime);
|
||||
}
|
||||
|
||||
private static void processClass(ClassNode cls, UsageInfo usageInfo) {
|
||||
|
||||
@@ -2,24 +2,79 @@ package jadx.core.utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.ICodeNode;
|
||||
|
||||
public class CodeGenUtils {
|
||||
|
||||
public static void addComments(ICodeWriter code, AttrNode node) {
|
||||
public static void addComments(ICodeWriter code, IAttributeNode node) {
|
||||
List<String> comments = node.getAll(AType.COMMENTS);
|
||||
if (!comments.isEmpty()) {
|
||||
comments.stream().distinct()
|
||||
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
|
||||
}
|
||||
addCodeComments(code, node);
|
||||
}
|
||||
|
||||
public static void addCodeComments(ICodeWriter code, @Nullable IAttributeNode node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
List<String> comments = node.getAll(AType.CODE_COMMENTS);
|
||||
if (comments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (node instanceof ICodeNode) {
|
||||
// for classes, fields and methods add on line before node declaration
|
||||
code.startLine();
|
||||
} else {
|
||||
code.add(' ');
|
||||
}
|
||||
if (comments.size() == 1) {
|
||||
String comment = comments.get(0);
|
||||
if (!comment.contains("\n")) {
|
||||
code.add("// ").add(comment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addMultiLineComment(code, comments);
|
||||
}
|
||||
|
||||
private static void addMultiLineComment(ICodeWriter code, List<String> comments) {
|
||||
boolean first = true;
|
||||
String indent = "";
|
||||
Object lineAnn = null;
|
||||
for (String comment : comments) {
|
||||
for (String line : comment.split("\n")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
StringBuilder buf = code.getRawBuf();
|
||||
int startLinePos = buf.lastIndexOf(ICodeWriter.NL) + 1;
|
||||
indent = Utils.strRepeat(" ", buf.length() - startLinePos);
|
||||
if (code.isMetadataSupported()) {
|
||||
lineAnn = code.getRawAnnotations().get(new CodePosition(code.getLine()));
|
||||
}
|
||||
} else {
|
||||
code.newLine().add(indent);
|
||||
if (lineAnn != null) {
|
||||
code.attachLineAnnotation(lineAnn);
|
||||
}
|
||||
}
|
||||
code.add("// ").add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addRenamedComment(ICodeWriter code, AttrNode node, String origName) {
|
||||
@@ -35,7 +90,13 @@ public class CodeGenUtils {
|
||||
public static void addSourceFileInfo(ICodeWriter code, ClassNode node) {
|
||||
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
|
||||
if (sourceFileAttr != null) {
|
||||
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
|
||||
String fileName = sourceFileAttr.getFileName();
|
||||
String topClsName = node.getTopParentClass().getClassInfo().getShortName();
|
||||
if (topClsName.contains(fileName)) {
|
||||
// ignore similar name
|
||||
return;
|
||||
}
|
||||
code.startLine("/* compiled from: ").add(fileName).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
public class GsonUtils {
|
||||
|
||||
public static <T> InterfaceReplace<T> interfaceReplace(Class<T> replaceCls) {
|
||||
return new InterfaceReplace<>(replaceCls);
|
||||
}
|
||||
|
||||
private static final class InterfaceReplace<T> implements JsonSerializer<T>, JsonDeserializer<T> {
|
||||
private final Class<T> replaceCls;
|
||||
|
||||
private InterfaceReplace(Class<T> replaceCls) {
|
||||
this.replaceCls = replaceCls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return context.deserialize(json, this.replaceCls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return context.serialize(src, this.replaceCls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,28 @@ public class RegionUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static InsnNode getFirstInsn(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
List<InsnNode> insnList = block.getInstructions();
|
||||
if (insnList.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return insnList.get(0);
|
||||
} else if (container instanceof IBranchRegion) {
|
||||
return null;
|
||||
} else if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
List<IContainer> blocks = region.getSubBlocks();
|
||||
if (blocks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return getFirstInsn(blocks.get(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException(unknownContainerType(container));
|
||||
}
|
||||
}
|
||||
|
||||
public static InsnNode getLastInsn(IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
IBlock block = (IBlock) container;
|
||||
|
||||
@@ -308,6 +308,23 @@ public class Utils {
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T first(List<T> list) {
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T first(Iterable<T> list) {
|
||||
Iterator<T> it = list.iterator();
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T last(List<T> list) {
|
||||
if (list.isEmpty()) {
|
||||
@@ -316,6 +333,20 @@ public class Utils {
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T last(Iterable<T> list) {
|
||||
Iterator<T> it = list.iterator();
|
||||
if (!it.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
while (true) {
|
||||
T next = it.next();
|
||||
if (!it.hasNext()) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T getOrElse(@Nullable T obj, T defaultObj) {
|
||||
if (obj == null) {
|
||||
return defaultObj;
|
||||
|
||||
@@ -7,13 +7,18 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public class JadxAssertions extends Assertions {
|
||||
|
||||
public static JadxClassNodeAssertions assertThat(ClassNode actual) {
|
||||
Assertions.assertThat(actual).isNotNull();
|
||||
return new JadxClassNodeAssertions(actual);
|
||||
public static JadxClassNodeAssertions assertThat(ClassNode cls) {
|
||||
Assertions.assertThat(cls).isNotNull();
|
||||
return new JadxClassNodeAssertions(cls);
|
||||
}
|
||||
|
||||
public static JadxCodeAssertions assertThat(ICodeInfo actual) {
|
||||
Assertions.assertThat(actual).isNotNull();
|
||||
return new JadxCodeAssertions(actual.getCodeStr());
|
||||
public static JadxCodeInfoAssertions assertThat(ICodeInfo codeInfo) {
|
||||
Assertions.assertThat(codeInfo).isNotNull();
|
||||
return new JadxCodeInfoAssertions(codeInfo);
|
||||
}
|
||||
|
||||
public static JadxCodeAssertions assertThat(String code) {
|
||||
Assertions.assertThat(code).isNotNull();
|
||||
return new JadxCodeAssertions(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.tests.api.utils.assertj;
|
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -13,10 +14,17 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
||||
super(cls, JadxClassNodeAssertions.class);
|
||||
}
|
||||
|
||||
public JadxCodeInfoAssertions decompile() {
|
||||
isNotNull();
|
||||
ICodeInfo codeInfo = actual.getCode();
|
||||
Assertions.assertThat(codeInfo).isNotNull();
|
||||
return new JadxCodeInfoAssertions(codeInfo);
|
||||
}
|
||||
|
||||
public JadxCodeAssertions code() {
|
||||
isNotNull();
|
||||
ICodeInfo code = actual.getCode();
|
||||
assertThat(code).isNotNull();
|
||||
Assertions.assertThat(code).isNotNull();
|
||||
String codeStr = code.getCodeStr();
|
||||
assertThat(codeStr).isNotBlank();
|
||||
return new JadxCodeAssertions(codeStr);
|
||||
@@ -25,9 +33,9 @@ public class JadxClassNodeAssertions extends AbstractObjectAssert<JadxClassNodeA
|
||||
public JadxCodeAssertions reloadCode(IntegrationTest testInstance) {
|
||||
isNotNull();
|
||||
ICodeInfo code = actual.reloadCode();
|
||||
assertThat(code).isNotNull();
|
||||
Assertions.assertThat(code).isNotNull();
|
||||
String codeStr = code.getCodeStr();
|
||||
assertThat(codeStr).isNotBlank();
|
||||
Assertions.assertThat(codeStr).isNotBlank();
|
||||
|
||||
JadxCodeAssertions codeAssertions = new JadxCodeAssertions(codeStr);
|
||||
codeAssertions.print();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package jadx.tests.api.utils.assertj;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.data.annotations.ICodeRawOffset;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class JadxCodeInfoAssertions extends AbstractObjectAssert<JadxCodeInfoAssertions, ICodeInfo> {
|
||||
public JadxCodeInfoAssertions(ICodeInfo cls) {
|
||||
super(cls, JadxCodeInfoAssertions.class);
|
||||
}
|
||||
|
||||
public JadxCodeAssertions code() {
|
||||
isNotNull();
|
||||
String codeStr = actual.getCodeStr();
|
||||
assertThat(codeStr).isNotBlank();
|
||||
return new JadxCodeAssertions(codeStr);
|
||||
}
|
||||
|
||||
public JadxCodeInfoAssertions checkCodeOffsets() {
|
||||
long dupOffsetCount = actual.getAnnotations().values().stream()
|
||||
.filter(o -> o instanceof ICodeRawOffset)
|
||||
.collect(Collectors.groupingBy(o -> ((ICodeRawOffset) o).getOffset(), Collectors.toList()))
|
||||
.values().stream()
|
||||
.filter(list -> list.size() > 1)
|
||||
.count();
|
||||
assertThat(dupOffsetCount)
|
||||
.describedAs("Found duplicated code offsets")
|
||||
.isEqualTo(0);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,8 @@ public class TestClassReGen extends IntegrationTest {
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls.getCode())
|
||||
assertThat(cls)
|
||||
.code()
|
||||
.containsOnlyOnce("private int intField = 5;")
|
||||
.containsOnlyOnce("public static class A {")
|
||||
.containsOnlyOnce("public int test() {");
|
||||
@@ -32,8 +33,8 @@ public class TestClassReGen extends IntegrationTest {
|
||||
cls.searchMethodByShortName("test").getMethodInfo().setAlias("testRenamed");
|
||||
cls.searchFieldByName("intField").getFieldInfo().setAlias("intFieldRenamed");
|
||||
|
||||
assertThat(cls.reloadCode())
|
||||
.print()
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOnlyOnce("private int intFieldRenamed = 5;")
|
||||
.containsOnlyOnce("public static class ARenamed {")
|
||||
.containsOnlyOnce("public int testRenamed() {");
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
private int intField = 5;
|
||||
|
||||
public static class A {
|
||||
}
|
||||
|
||||
public int test() {
|
||||
System.out.println("Hello");
|
||||
System.out.println("comment");
|
||||
return intField;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
ICodeComment clsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId), "class comment");
|
||||
ICodeComment innerClsComment = new JadxCodeComment(JadxNodeRef.forCls(baseClsId + ".A"), "inner class comment");
|
||||
ICodeComment fldComment = new JadxCodeComment(new JadxNodeRef(RefType.FIELD, baseClsId, "intField:I"), "field comment");
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test()I");
|
||||
ICodeComment mthComment = new JadxCodeComment(mthRef, "method comment");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "insn comment", 11);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
getArgs().setCodeData(codeData);
|
||||
codeData.setComments(Arrays.asList(clsComment, innerClsComment, fldComment, mthComment, insnComment));
|
||||
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls)
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("// class comment")
|
||||
.containsOne("// inner class comment")
|
||||
.containsOne("// field comment")
|
||||
.containsOne("// method comment")
|
||||
.containsOne("System.out.println(\"comment\"); // insn comment");
|
||||
|
||||
String code = cls.getCode().getCodeStr();
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.isEqualTo(code);
|
||||
|
||||
ICodeComment updInsnComment = new JadxCodeComment(mthRef, "updated insn comment", 11);
|
||||
codeData.setComments(Collections.singletonList(updInsnComment));
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOne("System.out.println(\"comment\"); // updated insn comment")
|
||||
.doesNotContain("class comment")
|
||||
.containsOne(" comment");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments2 extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public int test(boolean z) {
|
||||
if (z) {
|
||||
System.out.println("z");
|
||||
return 1;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", 10);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", 11);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(insnComment, insnComment2));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("// " + insnComment.getComment())
|
||||
.containsOne("// " + insnComment2.getComment());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeComments2a extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
private int f;
|
||||
|
||||
public int test(boolean z) {
|
||||
if (z) {
|
||||
System.out.println("z");
|
||||
return new Random().nextInt();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "return comment", 18);
|
||||
ICodeComment insnComment2 = new JadxCodeComment(mthRef, "another return comment", 19);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(insnComment, insnComment2));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("// " + insnComment.getComment())
|
||||
.containsOne("// " + insnComment2.getComment());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeCommentsMultiline extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public int test(boolean z) {
|
||||
if (z) {
|
||||
System.out.println("z");
|
||||
return 1;
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId, "test(Z)I");
|
||||
ICodeComment insnComment = new JadxCodeComment(mthRef, "multi\nline\ncomment", 11);
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Collections.singletonList(insnComment));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("// multi")
|
||||
.containsOne("// line")
|
||||
.containsOne("// comment");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package jadx.tests.integration.others;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef.RefType;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestCodeCommentsOverride extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public interface I {
|
||||
void mth();
|
||||
}
|
||||
|
||||
public static class A implements I {
|
||||
@Override
|
||||
public void mth() {
|
||||
System.out.println("mth");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String baseClsId = TestCls.class.getName();
|
||||
JadxNodeRef iMthRef = new JadxNodeRef(RefType.METHOD, baseClsId + ".I", "mth()V");
|
||||
ICodeComment iMthComment = new JadxCodeComment(iMthRef, "interface mth comment");
|
||||
|
||||
JadxNodeRef mthRef = new JadxNodeRef(RefType.METHOD, baseClsId + ".A", "mth()V");
|
||||
ICodeComment mthComment = new JadxCodeComment(mthRef, "mth comment");
|
||||
|
||||
JadxCodeData codeData = new JadxCodeData();
|
||||
codeData.setComments(Arrays.asList(iMthComment, mthComment));
|
||||
getArgs().setCodeData(codeData);
|
||||
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
assertThat(cls)
|
||||
.decompile()
|
||||
.checkCodeOffsets()
|
||||
.code()
|
||||
.containsOne("@Override")
|
||||
.containsOne("// " + iMthComment.getComment())
|
||||
.containsOne("// " + mthComment.getComment());
|
||||
|
||||
assertThat(cls)
|
||||
.reloadCode(this)
|
||||
.containsOne("@Override")
|
||||
.containsOne("// " + iMthComment.getComment())
|
||||
.containsOne("// " + mthComment.getComment());
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.api.ResourceFile;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
|
||||
import static jadx.gui.utils.FileUtils.toFiles;
|
||||
@@ -29,6 +30,7 @@ public class JadxWrapper {
|
||||
|
||||
private final JadxSettings settings;
|
||||
private JadxDecompiler decompiler;
|
||||
private JadxProject project;
|
||||
private List<Path> openPaths = Collections.emptyList();
|
||||
|
||||
public JadxWrapper(JadxSettings settings) {
|
||||
@@ -41,6 +43,7 @@ public class JadxWrapper {
|
||||
try {
|
||||
JadxArgs jadxArgs = settings.toJadxArgs();
|
||||
jadxArgs.setInputFiles(toFiles(paths));
|
||||
jadxArgs.setCodeData(project.getCodeData());
|
||||
|
||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||
this.decompiler.load();
|
||||
@@ -155,10 +158,14 @@ public class JadxWrapper {
|
||||
return decompiler.getArgs();
|
||||
}
|
||||
|
||||
public void setProject(JadxProject project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fullName Full name of an outer class. Inner classes are not supported.
|
||||
*/
|
||||
public @Nullable JavaClass searchJavaClassByClassName(String fullName) {
|
||||
public @Nullable JavaClass searchJavaClassByFullAlias(String fullName) {
|
||||
return decompiler.getClasses().stream()
|
||||
.filter(cls -> cls.getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
@@ -166,10 +173,7 @@ public class JadxWrapper {
|
||||
}
|
||||
|
||||
public @Nullable JavaClass searchJavaClassByOrigClassName(String fullName) {
|
||||
return decompiler.getClasses().stream()
|
||||
.filter(cls -> cls.getClassNode().getClassInfo().getFullName().equals(fullName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
return decompiler.searchJavaClassByOrigFullName(fullName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ import jadx.gui.utils.search.TextSearchIndex;
|
||||
public class IndexJob extends BackgroundJob {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(IndexJob.class);
|
||||
|
||||
private final CacheObject cache;
|
||||
|
||||
public IndexJob(JadxWrapper wrapper, CacheObject cache, int threadsCount) {
|
||||
@@ -29,18 +30,14 @@ public class IndexJob extends BackgroundJob {
|
||||
|
||||
@Override
|
||||
protected void runJob() {
|
||||
TextSearchIndex index = new TextSearchIndex(cache);
|
||||
CodeUsageInfo usageInfo = new CodeUsageInfo(cache.getNodeCache());
|
||||
|
||||
cache.setTextIndex(index);
|
||||
cache.setUsageInfo(usageInfo);
|
||||
TextSearchIndex index = cache.getTextIndex();
|
||||
addTask(index::indexResource);
|
||||
for (final JavaClass cls : wrapper.getIncludedClasses()) {
|
||||
addTask(() -> indexCls(cache, cls));
|
||||
}
|
||||
}
|
||||
|
||||
public static void indexCls(CacheObject cache, JavaClass cls) {
|
||||
private static void indexCls(CacheObject cache, JavaClass cls) {
|
||||
try {
|
||||
TextSearchIndex index = cache.getTextIndex();
|
||||
CodeUsageInfo usageInfo = cache.getUsageInfo();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package jadx.gui.settings;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -14,63 +15,76 @@ import org.slf4j.LoggerFactory;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.utils.GsonUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.PathTypeAdapter;
|
||||
|
||||
public class JadxProject {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxProject.class);
|
||||
private static final int CURRENT_SETTINGS_VERSION = 0;
|
||||
|
||||
private static final int CURRENT_PROJECT_VERSION = 1;
|
||||
public static final String PROJECT_EXTENSION = "jadx";
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Path.class, PathTypeAdapter.singleton())
|
||||
.registerTypeAdapter(ICodeComment.class, GsonUtils.interfaceReplace(JadxCodeComment.class))
|
||||
.registerTypeAdapter(IJavaNodeRef.class, GsonUtils.interfaceReplace(JadxNodeRef.class))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
private transient MainWindow mainWindow;
|
||||
private transient JadxSettings settings;
|
||||
|
||||
private transient String name = "New Project";
|
||||
private transient Path projectPath;
|
||||
private List<Path> filesPath;
|
||||
private List<String[]> treeExpansions = new ArrayList<>();
|
||||
|
||||
private transient boolean saved;
|
||||
private transient boolean initial = true;
|
||||
private transient boolean saved;
|
||||
|
||||
private int projectVersion = 0;
|
||||
private List<Path> files;
|
||||
private List<String[]> treeExpansions = new ArrayList<>();
|
||||
private JadxCodeData codeData = new JadxCodeData();
|
||||
|
||||
private int projectVersion;
|
||||
|
||||
// Don't remove. Used in json serialization
|
||||
public JadxProject() {
|
||||
}
|
||||
|
||||
public JadxProject(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void setSettings(JadxSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void setMainWindow(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
public Path getProjectPath() {
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
private void setProjectPath(Path projectPath) {
|
||||
this.projectPath = projectPath;
|
||||
if (projectVersion != CURRENT_SETTINGS_VERSION) {
|
||||
upgradeSettings(projectVersion);
|
||||
}
|
||||
name = projectPath.getFileName().toString();
|
||||
name = name.substring(0, name.lastIndexOf('.'));
|
||||
int dotPos = name.lastIndexOf('.');
|
||||
if (dotPos != -1) {
|
||||
name = name.substring(0, dotPos);
|
||||
}
|
||||
changed();
|
||||
}
|
||||
|
||||
public List<Path> getFilePaths() {
|
||||
return filesPath;
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setFilePath(List<Path> files) {
|
||||
if (!files.equals(getFilePaths())) {
|
||||
this.filesPath = files;
|
||||
this.files = files;
|
||||
changed();
|
||||
}
|
||||
}
|
||||
@@ -85,11 +99,7 @@ public class JadxProject {
|
||||
}
|
||||
|
||||
public void removeTreeExpansion(String[] expansion) {
|
||||
for (Iterator<String[]> it = treeExpansions.iterator(); it.hasNext();) {
|
||||
if (isParentOfExpansion(expansion, it.next())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
treeExpansions.removeIf(strings -> isParentOfExpansion(expansion, strings));
|
||||
changed();
|
||||
}
|
||||
|
||||
@@ -106,13 +116,25 @@ public class JadxProject {
|
||||
return false;
|
||||
}
|
||||
|
||||
public JadxCodeData getCodeData() {
|
||||
return codeData;
|
||||
}
|
||||
|
||||
public void setCodeData(JadxCodeData codeData) {
|
||||
this.codeData = codeData;
|
||||
changed();
|
||||
}
|
||||
|
||||
private void changed() {
|
||||
if (settings.isAutoSaveProject()) {
|
||||
if (settings != null && settings.isAutoSaveProject()) {
|
||||
save();
|
||||
} else {
|
||||
saved = false;
|
||||
}
|
||||
initial = false;
|
||||
if (mainWindow != null) {
|
||||
mainWindow.updateProject(this);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -134,8 +156,8 @@ public class JadxProject {
|
||||
|
||||
public void save() {
|
||||
if (getProjectPath() != null) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(getProjectPath())) {
|
||||
writer.write(GSON.toJson(this));
|
||||
try (Writer writer = Files.newBufferedWriter(getProjectPath(), StandardCharsets.UTF_8)) {
|
||||
GSON.toJson(this, writer);
|
||||
saved = true;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving project", e);
|
||||
@@ -143,29 +165,29 @@ public class JadxProject {
|
||||
}
|
||||
}
|
||||
|
||||
public static JadxProject from(Path path, JadxSettings settings) {
|
||||
try {
|
||||
List<String> lines = Files.readAllLines(path);
|
||||
|
||||
if (!lines.isEmpty()) {
|
||||
JadxProject project = GSON.fromJson(lines.get(0), JadxProject.class);
|
||||
project.settings = settings;
|
||||
project.setProjectPath(path);
|
||||
project.saved = true;
|
||||
return project;
|
||||
}
|
||||
public static JadxProject from(Path path) {
|
||||
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
JadxProject project = GSON.fromJson(reader, JadxProject.class);
|
||||
project.saved = true;
|
||||
project.setProjectPath(path);
|
||||
project.upgrade();
|
||||
return project;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error loading project", e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void upgradeSettings(int fromVersion) {
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
|
||||
private void upgrade() {
|
||||
int fromVersion = projectVersion;
|
||||
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_PROJECT_VERSION);
|
||||
if (fromVersion == 0) {
|
||||
fromVersion++;
|
||||
}
|
||||
projectVersion = CURRENT_SETTINGS_VERSION;
|
||||
if (fromVersion != CURRENT_PROJECT_VERSION) {
|
||||
throw new JadxRuntimeException("Project update failed");
|
||||
}
|
||||
projectVersion = CURRENT_PROJECT_VERSION;
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,10 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
return Collections.unmodifiableList(recentProjects);
|
||||
}
|
||||
|
||||
public void addRecentProject(Path projectPath) {
|
||||
public void addRecentProject(@Nullable Path projectPath) {
|
||||
if (projectPath == null) {
|
||||
return;
|
||||
}
|
||||
recentProjects.remove(projectPath);
|
||||
recentProjects.add(0, projectPath);
|
||||
int count = recentProjects.size();
|
||||
|
||||
@@ -64,9 +64,6 @@ public class JadxSettingsAdapter {
|
||||
} else {
|
||||
settings.fixOnLoad();
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded settings: {}", makeString(settings));
|
||||
}
|
||||
return settings;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error load settings. Settings will reset.\n Loaded json string: {}", jsonSettings, e);
|
||||
@@ -77,7 +74,6 @@ public class JadxSettingsAdapter {
|
||||
public static void store(JadxSettings settings) {
|
||||
try {
|
||||
String jsonSettings = makeString(settings);
|
||||
LOG.debug("Saving settings: {}", jsonSettings);
|
||||
PREFS.put(JADX_GUI_KEY, jsonSettings);
|
||||
PREFS.sync();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.utils.search.StringRef;
|
||||
@@ -13,14 +13,14 @@ public class CodeNode extends JNode {
|
||||
private final transient JClass jParent;
|
||||
private final transient StringRef line;
|
||||
private final transient int lineNum;
|
||||
private transient int pos = -1;
|
||||
private transient boolean precise;
|
||||
private transient int pos;
|
||||
|
||||
public CodeNode(JNode jNode, int lineNum, StringRef lineStr) {
|
||||
public CodeNode(JNode jNode, StringRef lineStr, int lineNum, int pos) {
|
||||
this.jNode = jNode;
|
||||
this.jParent = this.jNode.getJParent();
|
||||
this.line = lineStr;
|
||||
this.lineNum = lineNum;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,6 +79,11 @@ public class CodeNode extends JNode {
|
||||
return makeString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return jNode.getSyntaxName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -96,24 +101,8 @@ public class CodeNode extends JNode {
|
||||
return jNode.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public CodeNode setPos(int pos) {
|
||||
this.pos = pos;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeNode setPrecisePos(int pos) {
|
||||
this.pos = pos;
|
||||
if (pos > -1) {
|
||||
this.precise = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isPrecisePos() {
|
||||
return precise;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package jadx.gui.treemodel;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -71,6 +73,11 @@ public class JField extends JNode {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return UiUtils.typeFormat(field.getName(), field.getType());
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.Iterator;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -73,6 +75,11 @@ public class JMethod extends JNode {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSyntaxName() {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
|
||||
|
||||
@@ -101,6 +101,14 @@ public abstract class JNode extends DefaultMutableTreeNode {
|
||||
return makeLongString();
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
JavaNode javaNode = getJavaNode();
|
||||
if (javaNode == null) {
|
||||
return -1;
|
||||
}
|
||||
return javaNode.getDefPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return makeString();
|
||||
|
||||
@@ -6,6 +6,8 @@ import jadx.api.JavaNode;
|
||||
import jadx.api.JavaVariable;
|
||||
|
||||
public class JVariable extends JNode {
|
||||
private static final long serialVersionUID = -3002100457834453783L;
|
||||
|
||||
JClass cls;
|
||||
JavaVariable var;
|
||||
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dialog;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class CommentDialog extends JDialog {
|
||||
private static final long serialVersionUID = -1865682124935757528L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommentDialog.class);
|
||||
|
||||
public static void show(CodeArea codeArea, ICodeComment blankComment) {
|
||||
ICodeComment existComment = searchForExistComment(codeArea, blankComment);
|
||||
Dialog dialog;
|
||||
if (existComment != null) {
|
||||
dialog = new CommentDialog(codeArea, existComment, true);
|
||||
} else {
|
||||
dialog = new CommentDialog(codeArea, blankComment, false);
|
||||
}
|
||||
dialog.setVisible(true);
|
||||
}
|
||||
|
||||
private static void updateCommentsData(CodeArea codeArea, Consumer<List<ICodeComment>> updater) {
|
||||
try {
|
||||
JadxProject project = codeArea.getProject();
|
||||
JadxCodeData codeData = project.getCodeData();
|
||||
if (codeData == null) {
|
||||
codeData = new JadxCodeData();
|
||||
}
|
||||
List<ICodeComment> list = new ArrayList<>(codeData.getComments());
|
||||
updater.accept(list);
|
||||
Collections.sort(list);
|
||||
codeData.setComments(list);
|
||||
project.setCodeData(codeData);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Comment action failed", e);
|
||||
}
|
||||
try {
|
||||
// refresh code
|
||||
codeArea.refreshClass();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to reload code", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ICodeComment searchForExistComment(CodeArea codeArea, ICodeComment blankComment) {
|
||||
try {
|
||||
JadxProject project = codeArea.getProject();
|
||||
JadxCodeData codeData = project.getCodeData();
|
||||
if (codeData == null || codeData.getComments().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (ICodeComment comment : codeData.getComments()) {
|
||||
if (Objects.equals(comment.getNodeRef(), blankComment.getNodeRef())
|
||||
&& comment.getOffset() == blankComment.getOffset()
|
||||
&& comment.getAttachType() == blankComment.getAttachType()) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error searching for exists comment", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final transient CodeArea codeArea;
|
||||
private final transient ICodeComment comment;
|
||||
private final transient boolean updateComment;
|
||||
|
||||
private transient JTextArea commentArea;
|
||||
|
||||
public CommentDialog(CodeArea codeArea, ICodeComment comment, boolean updateComment) {
|
||||
super(codeArea.getMainWindow());
|
||||
this.codeArea = codeArea;
|
||||
this.comment = comment;
|
||||
this.updateComment = updateComment;
|
||||
initUI();
|
||||
}
|
||||
|
||||
private void apply() {
|
||||
String newCommentStr = commentArea.getText().trim();
|
||||
if (newCommentStr.isEmpty()) {
|
||||
if (updateComment) {
|
||||
remove();
|
||||
} else {
|
||||
cancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
ICodeComment newComment = new JadxCodeComment(comment.getNodeRef(),
|
||||
newCommentStr, comment.getOffset(), comment.getAttachType());
|
||||
if (updateComment) {
|
||||
updateCommentsData(codeArea, list -> {
|
||||
list.remove(comment);
|
||||
list.add(newComment);
|
||||
});
|
||||
} else {
|
||||
updateCommentsData(codeArea, list -> list.add(newComment));
|
||||
}
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void remove() {
|
||||
updateCommentsData(codeArea, list -> list.removeIf(c -> c == comment));
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void cancel() {
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
commentArea = new JTextArea();
|
||||
TextStandardActions.attach(commentArea);
|
||||
commentArea.setEditable(true);
|
||||
commentArea.setFont(codeArea.getMainWindow().getSettings().getFont());
|
||||
commentArea.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
commentArea.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_ENTER:
|
||||
if (e.isShiftDown() || e.isControlDown()) {
|
||||
commentArea.append("\n");
|
||||
} else {
|
||||
apply();
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.VK_ESCAPE:
|
||||
cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (updateComment) {
|
||||
commentArea.setText(comment.getComment());
|
||||
}
|
||||
|
||||
JScrollPane textAreaScrollPane = new JScrollPane(commentArea);
|
||||
textAreaScrollPane.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JLabel commentLabel = new JLabel(NLS.str("comment_dialog.label"), SwingConstants.LEFT);
|
||||
JLabel usageLabel = new JLabel(NLS.str("comment_dialog.usage"), SwingConstants.LEFT);
|
||||
|
||||
JPanel mainPanel = new JPanel();
|
||||
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
|
||||
mainPanel.add(commentLabel);
|
||||
mainPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
mainPanel.add(textAreaScrollPane);
|
||||
mainPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
mainPanel.add(usageLabel);
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
JPanel buttonPane = initButtonsPanel();
|
||||
|
||||
Container contentPane = getContentPane();
|
||||
contentPane.add(mainPanel, BorderLayout.CENTER);
|
||||
contentPane.add(buttonPane, BorderLayout.PAGE_END);
|
||||
|
||||
if (updateComment) {
|
||||
setTitle(NLS.str("comment_dialog.title.update"));
|
||||
} else {
|
||||
setTitle(NLS.str("comment_dialog.title.add"));
|
||||
}
|
||||
pack();
|
||||
if (!codeArea.getMainWindow().getSettings().loadWindowPos(this)) {
|
||||
setSize(800, 140);
|
||||
}
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setModalityType(ModalityType.APPLICATION_MODAL);
|
||||
UiUtils.addEscapeShortCutToDispose(this);
|
||||
}
|
||||
|
||||
protected JPanel initButtonsPanel() {
|
||||
JButton cancelButton = new JButton(NLS.str("common_dialog.cancel"));
|
||||
cancelButton.addActionListener(event -> cancel());
|
||||
|
||||
String applyStr = updateComment ? NLS.str("common_dialog.update") : NLS.str("common_dialog.add");
|
||||
JButton renameBtn = new JButton(applyStr);
|
||||
renameBtn.addActionListener(event -> apply());
|
||||
getRootPane().setDefaultButton(renameBtn);
|
||||
|
||||
JButton removeBtn;
|
||||
if (updateComment) {
|
||||
removeBtn = new JButton(NLS.str("common_dialog.remove"));
|
||||
removeBtn.addActionListener(event -> remove());
|
||||
} else {
|
||||
removeBtn = null;
|
||||
}
|
||||
|
||||
JPanel buttonPane = new JPanel();
|
||||
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
||||
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
|
||||
buttonPane.add(Box.createHorizontalGlue());
|
||||
buttonPane.add(renameBtn);
|
||||
if (removeBtn != null) {
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
buttonPane.add(removeBtn);
|
||||
}
|
||||
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
||||
buttonPane.add(cancelButton);
|
||||
return buttonPane;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
codeArea.getMainWindow().getSettings().saveWindowPos(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
@@ -13,14 +18,27 @@ import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.SwingWorker;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.fife.ui.rtextarea.SearchContext;
|
||||
import org.fife.ui.rtextarea.SearchEngine;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -31,7 +49,8 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.gui.jobs.BackgroundJob;
|
||||
import jadx.gui.jobs.BackgroundWorker;
|
||||
import jadx.gui.jobs.DecompileJob;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResSearchNode;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
@@ -112,16 +131,9 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
JumpPosition jmpPos;
|
||||
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
|
||||
if (node instanceof JResSearchNode) {
|
||||
jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine())
|
||||
.setPrecise(((JResSearchNode) node).getPos());
|
||||
} else if (node instanceof CodeNode) {
|
||||
CodeNode codeNode = (CodeNode) node;
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), codeNode.getPos());
|
||||
if (codeNode.isPrecisePos()) {
|
||||
jmpPos.setPrecise(codeNode.getPos());
|
||||
}
|
||||
jmpPos = new JumpPosition(((JResSearchNode) node).getResNode(), node.getLine(), node.getPos());
|
||||
} else {
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine());
|
||||
jmpPos = new JumpPosition(node.getRootClass(), node.getLine(), node.getPos());
|
||||
}
|
||||
tabbedPane.codeJump(jmpPos);
|
||||
if (!mainWindow.getSettings().getKeepCommonDialogOpen()) {
|
||||
@@ -285,11 +297,11 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
int firstColMaxWidth = (int) (width * 0.5);
|
||||
int rowCount = getRowCount();
|
||||
int columnCount = getColumnCount();
|
||||
if (!model.isAddDescColumn()) {
|
||||
boolean addDescColumn = model.isAddDescColumn();
|
||||
if (!addDescColumn) {
|
||||
firstColMaxWidth = width;
|
||||
}
|
||||
Component nodeComp = null;
|
||||
Component codeComp = null;
|
||||
setRowHeight(10); // reset all rows height
|
||||
for (int col = 0; col < columnCount; col++) {
|
||||
int colWidth = 50;
|
||||
for (int row = 0; row < rowCount; row++) {
|
||||
@@ -298,11 +310,9 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
continue;
|
||||
}
|
||||
colWidth = Math.max(comp.getPreferredSize().width, colWidth);
|
||||
if (nodeComp == null && col == 0) {
|
||||
nodeComp = comp;
|
||||
}
|
||||
if (codeComp == null && col == 1) {
|
||||
codeComp = comp;
|
||||
int h = Math.max(getRowHeight(row), getHeight(comp));
|
||||
if (h > 1) {
|
||||
setRowHeight(row, h);
|
||||
}
|
||||
}
|
||||
colWidth += 10;
|
||||
@@ -314,10 +324,7 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
TableColumn column = columnModel.getColumn(col);
|
||||
column.setPreferredWidth(colWidth);
|
||||
}
|
||||
// setRowHeight(Math.max(nodeComp.getPreferredSize().height, codeComp.getPreferredSize().height +
|
||||
// 4));
|
||||
updateUI();
|
||||
setRowHeight(Math.max(getHeight(nodeComp), getHeight(codeComp) + 4));
|
||||
}
|
||||
|
||||
private int getHeight(@Nullable Component nodeComp) {
|
||||
@@ -487,20 +494,25 @@ public abstract class CommonSearchDialog extends JDialog {
|
||||
if (!node.hasDescString()) {
|
||||
return emptyLabel;
|
||||
}
|
||||
|
||||
RSyntaxTextArea textArea = AbstractCodeArea.getDefaultArea(mainWindow);
|
||||
textArea.setLayout(new GridLayout(1, 1));
|
||||
textArea.setEditable(false);
|
||||
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
|
||||
textArea.setText(" " + node.makeDescString());
|
||||
textArea.setRows(1);
|
||||
textArea.setColumns(textArea.getText().length() + 1);
|
||||
textArea.setSyntaxEditingStyle(node.getSyntaxName());
|
||||
String descStr = node.makeDescString();
|
||||
textArea.setText(descStr);
|
||||
if (descStr.contains("\n")) {
|
||||
textArea.setRows(textArea.getLineCount());
|
||||
} else {
|
||||
textArea.setRows(1);
|
||||
textArea.setColumns(descStr.length() + 1);
|
||||
}
|
||||
if (highlightText != null) {
|
||||
SearchContext searchContext = new SearchContext(highlightText);
|
||||
searchContext.setMatchCase(!highlightTextCaseInsensitive);
|
||||
searchContext.setMarkAll(true);
|
||||
searchContext.setRegularExpression(highlightTextUseRegex);
|
||||
searchContext.setMarkAll(true);
|
||||
SearchEngine.markAll(textArea, searchContext);
|
||||
}
|
||||
textArea.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
|
||||
return textArea;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,12 +106,15 @@ import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.update.JadxUpdate.IUpdateCallback;
|
||||
import jadx.gui.update.data.Release;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.CodeUsageInfo;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.search.CommentsIndex;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
import static io.reactivex.internal.functions.Functions.EMPTY_RUNNABLE;
|
||||
import static jadx.gui.utils.FileUtils.fileNamesToPaths;
|
||||
@@ -137,6 +140,7 @@ public class MainWindow extends JFrame {
|
||||
private static final ImageIcon ICON_FLAT_PKG = UiUtils.openIcon("empty_logical_package_obj");
|
||||
private static final ImageIcon ICON_SEARCH = UiUtils.openIcon("wand");
|
||||
private static final ImageIcon ICON_FIND = UiUtils.openIcon("magnifier");
|
||||
private static final ImageIcon ICON_COMMENT_SEARCH = UiUtils.openIcon("table_edit");
|
||||
private static final ImageIcon ICON_BACK = UiUtils.openIcon("icon_back");
|
||||
private static final ImageIcon ICON_FORWARD = UiUtils.openIcon("icon_forward");
|
||||
private static final ImageIcon ICON_PREF = UiUtils.openIcon("wrench");
|
||||
@@ -219,7 +223,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
private void handleSelectClassOption() {
|
||||
if (settings.getCmdSelectClass() != null) {
|
||||
JavaNode javaNode = wrapper.searchJavaClassByClassName(settings.getCmdSelectClass());
|
||||
JavaNode javaNode = wrapper.searchJavaClassByFullAlias(settings.getCmdSelectClass());
|
||||
if (javaNode == null) {
|
||||
javaNode = wrapper.searchJavaClassByOrigClassName(settings.getCmdSelectClass());
|
||||
}
|
||||
@@ -230,8 +234,7 @@ public class MainWindow extends JFrame {
|
||||
return;
|
||||
}
|
||||
JNode node = cacheObject.getNodeCache().makeFrom(javaNode);
|
||||
tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine())
|
||||
.setPrecise(JumpPosition.getDefPos(node)));
|
||||
tabbedPane.codeJump(new JumpPosition(node.getRootClass(), node.getLine(), JumpPosition.getDefPos(node)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,9 +314,10 @@ public class MainWindow extends JFrame {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
project = new JadxProject(settings);
|
||||
update();
|
||||
cancelBackgroundJobs();
|
||||
clearTree();
|
||||
wrapper.close();
|
||||
updateProject(new JadxProject());
|
||||
}
|
||||
|
||||
private void saveProject() {
|
||||
@@ -356,6 +360,7 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
project.saveAs(path);
|
||||
settings.addRecentProject(path);
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -408,18 +413,18 @@ public class MainWindow extends JFrame {
|
||||
if (!ensureProjectIsSaved()) {
|
||||
return;
|
||||
}
|
||||
project = JadxProject.from(path, settings);
|
||||
if (project == null) {
|
||||
JadxProject jadxProject = JadxProject.from(path);
|
||||
if (jadxProject == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
NLS.str("msg.project_error"),
|
||||
NLS.str("msg.project_error_title"),
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
return;
|
||||
jadxProject = new JadxProject();
|
||||
}
|
||||
update();
|
||||
updateProject(jadxProject);
|
||||
settings.addRecentProject(path);
|
||||
List<Path> filePaths = project.getFilePaths();
|
||||
List<Path> filePaths = jadxProject.getFilePaths();
|
||||
if (filePaths == null) {
|
||||
clearTree();
|
||||
} else {
|
||||
@@ -427,6 +432,15 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProject(JadxProject jadxProject) {
|
||||
jadxProject.setSettings(settings);
|
||||
jadxProject.setMainWindow(this);
|
||||
this.project = jadxProject;
|
||||
this.wrapper.setProject(jadxProject);
|
||||
this.cacheObject.setCommentsIndex(new CommentsIndex(wrapper, cacheObject, jadxProject));
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
newProjectAction.setEnabled(!project.isInitial());
|
||||
saveProjectAction.setEnabled(!project.isSaved());
|
||||
@@ -444,17 +458,14 @@ public class MainWindow extends JFrame {
|
||||
|
||||
protected void resetCache() {
|
||||
cacheObject.reset();
|
||||
// TODO: decompilation freezes sometime with several threads
|
||||
this.cacheObject.setJRoot(treeRoot);
|
||||
this.cacheObject.setJadxSettings(settings);
|
||||
cacheObject.setJRoot(treeRoot);
|
||||
cacheObject.setJadxSettings(settings);
|
||||
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
}
|
||||
|
||||
public void resetIndex() {
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
cacheObject.setUsageInfo(new CodeUsageInfo(cacheObject.getNodeCache()));
|
||||
cacheObject.setTextIndex(new TextSearchIndex(this));
|
||||
}
|
||||
|
||||
synchronized void runBackgroundJobs() {
|
||||
@@ -471,7 +482,9 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
|
||||
public synchronized void cancelBackgroundJobs() {
|
||||
backgroundExecutor.cancelAll();
|
||||
if (backgroundExecutor != null) {
|
||||
backgroundExecutor.cancelAll();
|
||||
}
|
||||
if (backgroundWorker != null) {
|
||||
backgroundWorker.stop();
|
||||
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
|
||||
@@ -518,8 +531,7 @@ public class MainWindow extends JFrame {
|
||||
continue;
|
||||
}
|
||||
JNode newNode = cacheObject.getNodeCache().makeFrom(newClass);
|
||||
tabbedPane.codeJump(new JumpPosition(newNode, position)
|
||||
.setPrecise(JumpPosition.getDefPos(newNode)));
|
||||
tabbedPane.codeJump(new JumpPosition(newNode, position, JumpPosition.getDefPos(newNode)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,12 +663,7 @@ public class MainWindow extends JFrame {
|
||||
} else if (obj instanceof ApkSignature) {
|
||||
tabbedPane.showSimpleNode((JNode) obj);
|
||||
} else if (obj instanceof JNode) {
|
||||
JNode node = (JNode) obj;
|
||||
JClass cls = node.getRootClass();
|
||||
if (cls != null) {
|
||||
tabbedPane.codeJump(new JumpPosition(cls, node.getLine())
|
||||
.setPrecise(JumpPosition.getDefPos(node)));
|
||||
}
|
||||
tabbedPane.codeJump(new JumpPosition((JNode) obj));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Content loading error", e);
|
||||
@@ -829,7 +836,7 @@ public class MainWindow extends JFrame {
|
||||
return;
|
||||
}
|
||||
}
|
||||
new SearchDialog(MainWindow.this, true).setVisible(true);
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.TEXT);
|
||||
}
|
||||
};
|
||||
textSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.text_search"));
|
||||
@@ -839,12 +846,22 @@ public class MainWindow extends JFrame {
|
||||
Action clsSearchAction = new AbstractAction(NLS.str("menu.class_search"), ICON_FIND) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new SearchDialog(MainWindow.this, false).setVisible(true);
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.CLASS);
|
||||
}
|
||||
};
|
||||
clsSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.class_search"));
|
||||
clsSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_N, UiUtils.ctrlButton()));
|
||||
|
||||
Action commentSearchAction = new AbstractAction(NLS.str("menu.comment_search"), ICON_COMMENT_SEARCH) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
SearchDialog.search(MainWindow.this, SearchDialog.SearchPreset.COMMENT);
|
||||
}
|
||||
};
|
||||
commentSearchAction.putValue(Action.SHORT_DESCRIPTION, NLS.str("menu.comment_search"));
|
||||
commentSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_SEMICOLON,
|
||||
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
|
||||
|
||||
Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -925,6 +942,7 @@ public class MainWindow extends JFrame {
|
||||
nav.setMnemonic(KeyEvent.VK_N);
|
||||
nav.add(textSearchAction);
|
||||
nav.add(clsSearchAction);
|
||||
nav.add(commentSearchAction);
|
||||
nav.addSeparator();
|
||||
nav.add(backAction);
|
||||
nav.add(forwardAction);
|
||||
@@ -969,6 +987,7 @@ public class MainWindow extends JFrame {
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(textSearchAction);
|
||||
toolbar.add(clsSearchAction);
|
||||
toolbar.add(commentSearchAction);
|
||||
toolbar.addSeparator();
|
||||
toolbar.add(backAction);
|
||||
toolbar.add(forwardAction);
|
||||
@@ -1202,6 +1221,10 @@ public class MainWindow extends JFrame {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public JadxProject getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public TabbedPane getTabbedPane() {
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -23,7 +27,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.*;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.deobf.DeobfPresets;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
@@ -35,10 +43,19 @@ import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JVariable;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.CodePanel;
|
||||
import jadx.gui.utils.*;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class RenameDialog extends JDialog {
|
||||
private static final long serialVersionUID = -3269715644416902410L;
|
||||
@@ -235,17 +252,11 @@ public class RenameDialog extends JDialog {
|
||||
|
||||
private void refreshTabs(TabbedPane tabbedPane, Set<JClass> updatedClasses) {
|
||||
for (Map.Entry<JNode, ContentPanel> entry : tabbedPane.getOpenTabs().entrySet()) {
|
||||
ContentPanel contentPanel = entry.getValue();
|
||||
if (contentPanel instanceof ClassCodeContentPanel) {
|
||||
JNode node = entry.getKey();
|
||||
JClass rootClass = node.getRootClass();
|
||||
if (updatedClasses.contains(rootClass)) {
|
||||
refreshJClass(rootClass);
|
||||
ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel;
|
||||
CodePanel javaPanel = codePanel.getJavaCodePanel();
|
||||
javaPanel.refresh();
|
||||
tabbedPane.refresh(rootClass);
|
||||
}
|
||||
JClass rootClass = entry.getKey().getRootClass();
|
||||
if (updatedClasses.remove(rootClass)) {
|
||||
ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) entry.getValue();
|
||||
CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea();
|
||||
codeArea.refreshClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,7 +265,7 @@ public class RenameDialog extends JDialog {
|
||||
protected JPanel initButtonsPanel() {
|
||||
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
|
||||
cancelButton.addActionListener(event -> dispose());
|
||||
JButton renameBtn = new JButton(NLS.str("popup.rename"));
|
||||
JButton renameBtn = new JButton(NLS.str("common_dialog.ok"));
|
||||
renameBtn.addActionListener(event -> rename());
|
||||
getRootPane().setDefaultButton(renameBtn);
|
||||
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.FlowLayout;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
@@ -21,109 +34,195 @@ import io.reactivex.Flowable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
public class SearchDialog extends CommonSearchDialog {
|
||||
private static final long serialVersionUID = -5105405456969134105L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SearchDialog.class);
|
||||
private static final long serialVersionUID = -5105405456969134105L;
|
||||
private final boolean textSearch;
|
||||
|
||||
public static void search(MainWindow window, SearchPreset preset) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, preset, Collections.emptySet());
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
public static void searchInActiveTab(MainWindow window, SearchPreset preset) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, preset, EnumSet.of(SearchOptions.ACTIVE_TAB));
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
public static void searchText(MainWindow window, String text) {
|
||||
SearchDialog searchDialog = new SearchDialog(window, SearchPreset.TEXT, Collections.emptySet());
|
||||
searchDialog.initSearchText = text;
|
||||
searchDialog.setVisible(true);
|
||||
}
|
||||
|
||||
public enum SearchPreset {
|
||||
TEXT, CLASS, COMMENT
|
||||
}
|
||||
|
||||
public enum SearchOptions {
|
||||
CLASS,
|
||||
METHOD,
|
||||
FIELD,
|
||||
CODE,
|
||||
RESOURCE,
|
||||
COMMENT,
|
||||
|
||||
IGNORE_CASE,
|
||||
USE_REGEX,
|
||||
Resource
|
||||
ACTIVE_TAB
|
||||
}
|
||||
|
||||
private transient Set<SearchOptions> options;
|
||||
private final transient SearchPreset searchPreset;
|
||||
private final transient Set<SearchOptions> options;
|
||||
|
||||
private transient JTextField searchField;
|
||||
|
||||
private transient Disposable searchDisposable;
|
||||
private transient SearchEventEmitter searchEmitter;
|
||||
private transient String text = null;
|
||||
private transient ChangeListener activeTabListener;
|
||||
|
||||
public SearchDialog(MainWindow mainWindow, boolean textSearch) {
|
||||
private transient String initSearchText = null;
|
||||
|
||||
private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptions> additionalOptions) {
|
||||
super(mainWindow);
|
||||
this.textSearch = textSearch;
|
||||
if (textSearch) {
|
||||
Set<SearchOptions> lastSearchOptions = cache.getLastSearchOptions();
|
||||
if (!lastSearchOptions.isEmpty()) {
|
||||
this.options = lastSearchOptions;
|
||||
} else {
|
||||
this.options = EnumSet.of(SearchOptions.CODE, SearchOptions.IGNORE_CASE);
|
||||
}
|
||||
} else {
|
||||
this.options = EnumSet.of(SearchOptions.CLASS);
|
||||
}
|
||||
this.searchPreset = preset;
|
||||
this.options = buildOptions(preset);
|
||||
this.options.addAll(additionalOptions);
|
||||
|
||||
initUI();
|
||||
searchFieldSubscribe();
|
||||
registerInitOnOpen();
|
||||
loadWindowPos();
|
||||
registerActiveTabListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (searchDisposable != null && !searchDisposable.isDisposed()) {
|
||||
searchDisposable.dispose();
|
||||
}
|
||||
removeActiveTabListener();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private Set<SearchOptions> buildOptions(SearchPreset preset) {
|
||||
Set<SearchOptions> searchOptions = cache.getLastSearchOptions().get(preset);
|
||||
if (searchOptions == null) {
|
||||
searchOptions = new HashSet<>();
|
||||
}
|
||||
switch (preset) {
|
||||
case TEXT:
|
||||
if (searchOptions.isEmpty()) {
|
||||
searchOptions.add(SearchOptions.CODE);
|
||||
searchOptions.add(SearchOptions.IGNORE_CASE);
|
||||
}
|
||||
break;
|
||||
|
||||
case CLASS:
|
||||
searchOptions.add(SearchOptions.CLASS);
|
||||
break;
|
||||
|
||||
case COMMENT:
|
||||
searchOptions.add(SearchOptions.COMMENT);
|
||||
searchOptions.remove(SearchOptions.ACTIVE_TAB);
|
||||
break;
|
||||
}
|
||||
return searchOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openInit() {
|
||||
prepare();
|
||||
String lastSearch = cache.getLastSearch();
|
||||
if (lastSearch != null) {
|
||||
searchField.setText(lastSearch);
|
||||
String searchText = initSearchText != null ? initSearchText : cache.getLastSearch();
|
||||
if (searchText != null) {
|
||||
searchField.setText(searchText);
|
||||
searchField.selectAll();
|
||||
}
|
||||
searchField.requestFocus();
|
||||
|
||||
if (searchField.getText().isEmpty()) {
|
||||
checkIndex();
|
||||
}
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
|
||||
private TextSearchIndex checkIndex() {
|
||||
if (!cache.getIndexJob().isComplete()) {
|
||||
if (isFullIndexNeeded()) {
|
||||
prepare();
|
||||
}
|
||||
}
|
||||
return cache.getTextIndex();
|
||||
}
|
||||
|
||||
private boolean isFullIndexNeeded() {
|
||||
for (SearchOptions option : options) {
|
||||
switch (option) {
|
||||
case CLASS:
|
||||
case METHOD:
|
||||
case FIELD:
|
||||
// TODO: split indexes so full decompilation not needed for these
|
||||
return true;
|
||||
|
||||
case CODE:
|
||||
return true;
|
||||
|
||||
case RESOURCE:
|
||||
case COMMENT:
|
||||
// full index not needed
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initUI() {
|
||||
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
|
||||
searchField = new JTextField();
|
||||
searchField.setAlignmentX(LEFT_ALIGNMENT);
|
||||
new TextStandardActions(searchField);
|
||||
searchFieldSubscribe();
|
||||
TextStandardActions.attach(searchField);
|
||||
|
||||
JCheckBox caseChBox = makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE);
|
||||
JCheckBox regexChBox = makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX);
|
||||
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
|
||||
findLabel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
JCheckBox resChBox = makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.Resource);
|
||||
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
|
||||
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
|
||||
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
|
||||
JCheckBox codeChBox = makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE);
|
||||
JPanel searchFieldPanel = new JPanel();
|
||||
searchFieldPanel.setLayout(new BoxLayout(searchFieldPanel, BoxLayout.PAGE_AXIS));
|
||||
searchFieldPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
|
||||
searchFieldPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
searchFieldPanel.add(findLabel);
|
||||
searchFieldPanel.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchFieldPanel.add(searchField);
|
||||
|
||||
JPanel searchInPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchInPanel.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
|
||||
searchInPanel.add(resChBox);
|
||||
searchInPanel.add(clsChBox);
|
||||
searchInPanel.add(mthChBox);
|
||||
searchInPanel.add(fldChBox);
|
||||
searchInPanel.add(codeChBox);
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.resource"), SearchOptions.RESOURCE));
|
||||
searchInPanel.add(makeOptionsCheckBox(NLS.str("search_dialog.comments"), SearchOptions.COMMENT));
|
||||
|
||||
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.options")));
|
||||
searchOptions.add(caseChBox);
|
||||
searchOptions.add(regexChBox);
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.ignorecase"), SearchOptions.IGNORE_CASE));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.regex"), SearchOptions.USE_REGEX));
|
||||
searchOptions.add(makeOptionsCheckBox(NLS.str("search_dialog.active_tab"), SearchOptions.ACTIVE_TAB));
|
||||
|
||||
Box box = Box.createHorizontalBox();
|
||||
box.setAlignmentX(LEFT_ALIGNMENT);
|
||||
box.add(searchInPanel);
|
||||
box.add(searchOptions);
|
||||
JPanel optionsPanel = new JPanel(new WrapLayout(WrapLayout.LEFT));
|
||||
optionsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
optionsPanel.add(searchInPanel);
|
||||
optionsPanel.add(searchOptions);
|
||||
|
||||
JPanel searchPane = new JPanel();
|
||||
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
|
||||
findLabel.setLabelFor(searchField);
|
||||
searchPane.add(findLabel);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(searchField);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(box);
|
||||
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
searchPane.add(searchFieldPanel);
|
||||
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
|
||||
searchPane.add(optionsPanel);
|
||||
|
||||
initCommon();
|
||||
JPanel resultsPanel = initResultsTable();
|
||||
@@ -181,9 +280,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
Flowable<String> textChanges = onTextFieldChanges(searchField);
|
||||
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
|
||||
searchDisposable = searchEvents
|
||||
.filter(text -> text.length() > 0)
|
||||
.subscribeOn(Schedulers.single())
|
||||
.doOnNext(r -> LOG.debug("search event: {}", r))
|
||||
.switchMap(text -> prepareSearch(text)
|
||||
.doOnError(e -> LOG.error("Error prepare search: {}", e.getMessage(), e))
|
||||
.subscribeOn(Schedulers.single())
|
||||
@@ -195,13 +292,19 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
|
||||
private Flowable<JNode> prepareSearch(String text) {
|
||||
if (text == null || text.isEmpty() || options.isEmpty()) {
|
||||
if (text == null || options.isEmpty()) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
TextSearchIndex index = cache.getTextIndex();
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && !options.contains(SearchOptions.COMMENT)) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
|
||||
TextSearchIndex index = checkIndex();
|
||||
if (index == null) {
|
||||
return Flowable.empty();
|
||||
}
|
||||
LOG.debug("search event: {}", text);
|
||||
showSearchState();
|
||||
return index.buildSearch(text, options);
|
||||
}
|
||||
@@ -214,9 +317,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
highlightTextUseRegex = options.contains(SearchOptions.USE_REGEX);
|
||||
|
||||
cache.setLastSearch(text);
|
||||
if (textSearch) {
|
||||
cache.setLastSearchOptions(options);
|
||||
}
|
||||
cache.getLastSearchOptions().put(searchPreset, options);
|
||||
|
||||
resultsModel.clear();
|
||||
resultsModel.addAll(results);
|
||||
@@ -266,14 +367,6 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (searchDisposable != null && !searchDisposable.isDisposed()) {
|
||||
searchDisposable.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
|
||||
final JCheckBox chBox = new JCheckBox(name);
|
||||
chBox.setAlignmentX(LEFT_ALIGNMENT);
|
||||
@@ -291,23 +384,32 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
|
||||
@Override
|
||||
protected void loadFinished() {
|
||||
if (!StringUtils.isEmpty(text)) {
|
||||
searchField.setText(text);
|
||||
}
|
||||
resultsTable.setEnabled(true);
|
||||
searchField.setEnabled(true);
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadStart() {
|
||||
text = cache.getLastSearch(); // SearchDialog is opened by menu item, let loadFinished to set text
|
||||
cache.setLastSearch("");
|
||||
resultsTable.setEnabled(false);
|
||||
searchField.setEnabled(false);
|
||||
}
|
||||
|
||||
public static void searchText(MainWindow window, String text) {
|
||||
window.getCacheObject().setLastSearch(text);
|
||||
new SearchDialog(window, true).setVisible(true);
|
||||
private void registerActiveTabListener() {
|
||||
removeActiveTabListener();
|
||||
activeTabListener = e -> {
|
||||
if (options.contains(SearchOptions.ACTIVE_TAB)) {
|
||||
LOG.debug("active tab change event received");
|
||||
searchEmitter.emitSearch();
|
||||
}
|
||||
};
|
||||
mainWindow.getTabbedPane().addChangeListener(activeTabListener);
|
||||
}
|
||||
|
||||
private void removeActiveTabListener() {
|
||||
if (activeTabListener != null) {
|
||||
mainWindow.getTabbedPane().removeChangeListener(activeTabListener);
|
||||
activeTabListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.awt.Component;
|
||||
import java.awt.KeyEventDispatcher;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -19,14 +27,17 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.treemodel.ApkSignature;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.codearea.*;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
import jadx.gui.ui.codearea.CodeContentPanel;
|
||||
import jadx.gui.utils.JumpManager;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
|
||||
public class TabbedPane extends JTabbedPane {
|
||||
private static final long serialVersionUID = -8833600618794570904L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TabbedPane.class);
|
||||
private static final long serialVersionUID = -8833600618794570904L;
|
||||
|
||||
private final transient MainWindow mainWindow;
|
||||
private final transient Map<JNode, ContentPanel> openTabs = new LinkedHashMap<>();
|
||||
@@ -148,42 +159,42 @@ public class TabbedPane extends JTabbedPane {
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
private void showCode(final JumpPosition pos) {
|
||||
final AbstractCodeContentPanel contentPanel = (AbstractCodeContentPanel) getContentPanel(pos.getNode());
|
||||
private void showCode(final JumpPosition jumpPos) {
|
||||
JNode jumpNode = jumpPos.getNode();
|
||||
Objects.requireNonNull(jumpNode, "Null node in JumpPosition");
|
||||
|
||||
final AbstractCodeContentPanel contentPanel = (AbstractCodeContentPanel) getContentPanel(jumpNode);
|
||||
if (contentPanel == null) {
|
||||
return;
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setSelectedComponent(contentPanel);
|
||||
AbstractCodeArea codeArea = contentPanel.getCodeArea();
|
||||
if (pos.isPrecise()) {
|
||||
codeArea.scrollToPos(pos.getPos());
|
||||
int pos = jumpPos.getPos();
|
||||
if (pos > 0) {
|
||||
codeArea.scrollToPos(pos);
|
||||
} else {
|
||||
int line = pos.getLine();
|
||||
int line = jumpPos.getLine();
|
||||
if (line < 0) {
|
||||
try {
|
||||
line = 1 + codeArea.getLineOfOffset(-line);
|
||||
} catch (BadLocationException e) {
|
||||
LOG.error("Can't get line for: {}", pos, e);
|
||||
line = pos.getNode().getLine();
|
||||
LOG.error("Can't get line for: {}", jumpPos, e);
|
||||
line = jumpNode.getLine();
|
||||
}
|
||||
}
|
||||
if (pos.getPos() < 0) {
|
||||
codeArea.scrollToLine(line);
|
||||
} else {
|
||||
int lineNum = Math.max(0, line - 1);
|
||||
try {
|
||||
int offs = codeArea.getLineStartOffset(lineNum);
|
||||
while (StringUtils.isWhite(codeArea.getText(offs, 1).charAt(0))) {
|
||||
offs += 1;
|
||||
}
|
||||
offs += pos.getPos();
|
||||
pos.setPrecise(offs);
|
||||
codeArea.scrollToPos(offs);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
codeArea.scrollToLine(line);
|
||||
int lineNum = Math.max(0, line - 1);
|
||||
try {
|
||||
int offs = codeArea.getLineStartOffset(lineNum);
|
||||
while (StringUtils.isWhite(codeArea.getText(offs, 1).charAt(0))) {
|
||||
offs += 1;
|
||||
}
|
||||
offs += pos;
|
||||
jumpPos.setPos(offs);
|
||||
codeArea.scrollToPos(offs);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
codeArea.scrollToLine(line);
|
||||
}
|
||||
}
|
||||
codeArea.requestFocus();
|
||||
@@ -216,7 +227,7 @@ public class TabbedPane extends JTabbedPane {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
JumpPosition getCurrentPosition() {
|
||||
public JumpPosition getCurrentPosition() {
|
||||
ContentPanel selectedCodePanel = getSelectedCodePanel();
|
||||
if (selectedCodePanel instanceof AbstractCodeContentPanel) {
|
||||
return ((AbstractCodeContentPanel) selectedCodePanel).getCodeArea().getCurrentPosition();
|
||||
@@ -273,6 +284,7 @@ public class TabbedPane extends JTabbedPane {
|
||||
ContentPanel panel = openTabs.get(node);
|
||||
if (panel != null) {
|
||||
setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel));
|
||||
fireStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.FocusEvent;
|
||||
import java.awt.event.FocusListener;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
import javax.swing.text.*;
|
||||
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;
|
||||
@@ -19,9 +30,11 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.ContentPanel;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
@@ -66,21 +79,11 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
});
|
||||
popupMenu.add(wrapItem);
|
||||
popupMenu.addPopupMenuListener(new PopupMenuListener() {
|
||||
popupMenu.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
wrapItem.setState(getLineWrap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
Caret caret = getCaret();
|
||||
@@ -305,9 +308,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
}
|
||||
|
||||
public JumpPosition getCurrentPosition() {
|
||||
JumpPosition jp = new JumpPosition(node, getCaretLineNumber() + 1);
|
||||
jp.setPrecise(getCaretPosition());
|
||||
return jp;
|
||||
return new JumpPosition(node, getCaretLineNumber() + 1, getCaretPosition());
|
||||
}
|
||||
|
||||
public String getLineText(int line) throws BadLocationException {
|
||||
int lineNum = line - 1;
|
||||
int startOffset = getLineStartOffset(lineNum);
|
||||
int endOffset = getLineEndOffset(lineNum);
|
||||
return getText(startOffset, endOffset - startOffset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -322,4 +330,12 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
|
||||
public JNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JClass getJClass() {
|
||||
if (node instanceof JClass) {
|
||||
return (JClass) node;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JTabbedPane;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
@@ -86,5 +86,4 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
|
||||
public AbstractCodeArea getSmaliCodeArea() {
|
||||
return smaliCodePanel.getCodeArea();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
@@ -15,12 +18,17 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.ContentPanel;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.CaretPositionFix;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
/**
|
||||
* The {@link AbstractCodeArea} implementation used for displaying Java code and text based
|
||||
@@ -85,15 +93,33 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
FindUsageAction findUsage = new FindUsageAction(this);
|
||||
GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(this);
|
||||
RenameAction rename = new RenameAction(this);
|
||||
CommentAction comment = new CommentAction(this);
|
||||
|
||||
JPopupMenu popup = getPopupMenu();
|
||||
popup.addSeparator();
|
||||
popup.add(findUsage);
|
||||
popup.add(goToDeclaration);
|
||||
popup.add(comment);
|
||||
popup.add(new CommentSearchAction(this));
|
||||
popup.add(rename);
|
||||
popup.addPopupMenuListener(findUsage);
|
||||
popup.addPopupMenuListener(goToDeclaration);
|
||||
popup.addPopupMenuListener(comment);
|
||||
popup.addPopupMenuListener(rename);
|
||||
|
||||
// move caret on mouse right button click
|
||||
popup.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
CodeArea codeArea = CodeArea.this;
|
||||
if (codeArea.getSelectedText() == null) {
|
||||
int offset = UiUtils.getOffsetAtMousePosition(codeArea);
|
||||
if (offset >= 0) {
|
||||
codeArea.setCaretPosition(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public int adjustOffsetForToken(@Nullable Token token) {
|
||||
@@ -145,8 +171,7 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
return null;
|
||||
}
|
||||
JNode jNode = convertJavaNode(foundNode);
|
||||
return new JumpPosition(jNode.getRootClass(), pos.getLine())
|
||||
.setPrecise(JumpPosition.getDefPos(jNode));
|
||||
return new JumpPosition(jNode.getRootClass(), pos.getLine(), JumpPosition.getDefPos(jNode));
|
||||
}
|
||||
|
||||
private JNode convertJavaNode(JavaNode javaNode) {
|
||||
@@ -189,11 +214,34 @@ public final class CodeArea extends AbstractCodeArea {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void refreshClass() {
|
||||
if (node instanceof JClass) {
|
||||
JClass cls = (JClass) node;
|
||||
try {
|
||||
CaretPositionFix caretFix = new CaretPositionFix(this);
|
||||
caretFix.save();
|
||||
|
||||
cls.reload();
|
||||
IndexJob.refreshIndex(getMainWindow().getCacheObject(), cls.getCls());
|
||||
|
||||
ClassCodeContentPanel codeContentPanel = (ClassCodeContentPanel) this.contentPanel;
|
||||
codeContentPanel.getTabbedPane().refresh(cls);
|
||||
codeContentPanel.getJavaCodePanel().refresh(caretFix);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to reload class: {}", cls.getFullName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MainWindow getMainWindow() {
|
||||
return contentPanel.getTabbedPane().getMainWindow();
|
||||
}
|
||||
|
||||
private JadxDecompiler getDecompiler() {
|
||||
public JadxDecompiler getDecompiler() {
|
||||
return getMainWindow().getWrapper().getDecompiler();
|
||||
}
|
||||
|
||||
public JadxProject getProject() {
|
||||
return getMainWindow().getProject();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Point;
|
||||
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.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.JPopupMenu.Separator;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.CaretPositionFix;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
@@ -27,11 +37,13 @@ import jadx.gui.utils.UiUtils;
|
||||
* A panel combining a {@link SearchBar and a scollable {@link CodeArea}
|
||||
*/
|
||||
public class CodePanel extends JPanel {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodePanel.class);
|
||||
private static final long serialVersionUID = 1117721869391885865L;
|
||||
|
||||
private final SearchBar searchBar;
|
||||
private final AbstractCodeArea codeArea;
|
||||
private final JScrollPane codeScrollPane;
|
||||
private LineNumbers lineNumbers;
|
||||
|
||||
public CodePanel(AbstractCodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
@@ -71,7 +83,7 @@ public class CodePanel extends JPanel {
|
||||
globalSearchItem.setAction(globalSearchAction);
|
||||
Separator separator = new Separator();
|
||||
JPopupMenu popupMenu = codeArea.getPopupMenu();
|
||||
popupMenu.addPopupMenuListener(new PopupMenuListener() {
|
||||
popupMenu.addPopupMenuListener(new DefaultPopupMenuListener() {
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
String preferText = codeArea.getSelectedText();
|
||||
@@ -90,18 +102,7 @@ public class CodePanel extends JPanel {
|
||||
popupMenu.remove(searchItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCanceled(PopupMenuEvent e) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void loadSettings() {
|
||||
@@ -115,9 +116,13 @@ public class CodePanel extends JPanel {
|
||||
}
|
||||
|
||||
private void initLineNumbers() {
|
||||
LineNumbers numbers = new LineNumbers(codeArea);
|
||||
numbers.setUseSourceLines(isUseSourceLines());
|
||||
codeScrollPane.setRowHeaderView(numbers);
|
||||
initLineNumbers(isUseSourceLines());
|
||||
}
|
||||
|
||||
private void initLineNumbers(boolean useSourceLines) {
|
||||
lineNumbers = new LineNumbers(codeArea);
|
||||
lineNumbers.setUseSourceLines(useSourceLines);
|
||||
codeScrollPane.setRowHeaderView(lineNumbers);
|
||||
}
|
||||
|
||||
private boolean isUseSourceLines() {
|
||||
@@ -148,79 +153,15 @@ public class CodePanel extends JPanel {
|
||||
return codeScrollPane;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
int line = 0;
|
||||
int tokenIndex;
|
||||
int pos = codeArea.getCaretPosition();
|
||||
int lineCount = codeArea.getLineCount();
|
||||
try {
|
||||
// after rename the change of document is undetectable, so
|
||||
// use Token offset to calculate the new caret position.
|
||||
line = codeArea.getLineOfOffset(pos);
|
||||
Token token = codeArea.getTokenListForLine(line);
|
||||
tokenIndex = getTokenIndexByOffset(token, pos);
|
||||
} catch (BadLocationException e) {
|
||||
e.printStackTrace();
|
||||
tokenIndex = -1;
|
||||
}
|
||||
if (tokenIndex == -1) {
|
||||
refreshToViewport();
|
||||
return;
|
||||
}
|
||||
codeArea.refresh();
|
||||
initLineNumbers();
|
||||
int lineDiff = codeArea.getLineCount() - lineCount;
|
||||
if (lineDiff > 0) {
|
||||
lineDiff--;
|
||||
} else if (lineDiff < 0) {
|
||||
lineDiff++;
|
||||
}
|
||||
Token token = codeArea.getTokenListForLine(line + lineDiff);
|
||||
int newPos = getOffsetOfTokenByIndex(tokenIndex, token);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (newPos != -1) {
|
||||
codeArea.scrollToPos(newPos);
|
||||
} else {
|
||||
codeArea.scrollToLine(codeArea.getLineCount() - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshToViewport() {
|
||||
public void refresh(CaretPositionFix caretFix) {
|
||||
JViewport viewport = getCodeScrollPane().getViewport();
|
||||
Point viewPosition = viewport.getViewPosition();
|
||||
codeArea.refresh();
|
||||
initLineNumbers();
|
||||
initLineNumbers(lineNumbers.isUseSourceLines());
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
viewport.setViewPosition(viewPosition);
|
||||
caretFix.restore();
|
||||
});
|
||||
}
|
||||
|
||||
private int getTokenIndexByOffset(Token token, int offset) {
|
||||
if (token != null) {
|
||||
int index = 1;
|
||||
while (token.getEndOffset() < offset) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return -1;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getOffsetOfTokenByIndex(int index, Token token) {
|
||||
if (token != null && index != -1) {
|
||||
for (int i = 0; i < index; i++) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return token.getOffset();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeComment;
|
||||
import jadx.api.data.annotations.CustomOffsetRef;
|
||||
import jadx.api.data.annotations.InsnCodeOffset;
|
||||
import jadx.api.data.impl.JadxCodeComment;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.CommentDialog;
|
||||
import jadx.gui.utils.CodeLinesInfo;
|
||||
import jadx.gui.utils.DefaultPopupMenuListener;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class CommentAction extends AbstractAction implements DefaultPopupMenuListener {
|
||||
private static final long serialVersionUID = 4753838562204629112L;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommentAction.class);
|
||||
private final CodeArea codeArea;
|
||||
private final JavaClass topCls;
|
||||
|
||||
private ICodeComment actionComment;
|
||||
|
||||
public CommentAction(CodeArea codeArea) {
|
||||
super(NLS.str("popup.add_comment") + " (;)");
|
||||
this.codeArea = codeArea;
|
||||
JNode topNode = codeArea.getNode();
|
||||
if (topNode instanceof JClass) {
|
||||
this.topCls = ((JClass) topNode).getCls();
|
||||
} else {
|
||||
this.topCls = null;
|
||||
}
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, 0);
|
||||
codeArea.getInputMap().put(key, "popup.add_comment");
|
||||
codeArea.getActionMap().put("popup.add_comment", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
int line = codeArea.getCaretLineNumber() + 1;
|
||||
ICodeComment codeComment = getCommentRef(line);
|
||||
showCommentDialog(codeComment);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
ICodeComment codeComment = getCommentRef(getMouseLine());
|
||||
setEnabled(codeComment != null);
|
||||
this.actionComment = codeComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
showCommentDialog(this.actionComment);
|
||||
}
|
||||
|
||||
private void showCommentDialog(ICodeComment codeComment) {
|
||||
if (codeComment == null) {
|
||||
UiUtils.showMessageBox(codeArea.getMainWindow(), NLS.str("msg.cant_add_comment"));
|
||||
return;
|
||||
}
|
||||
CommentDialog.show(codeArea, codeComment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if possible insert comment at current line.
|
||||
*
|
||||
* @return blank code comment object (comment string empty)
|
||||
*/
|
||||
@Nullable
|
||||
private ICodeComment getCommentRef(int line) {
|
||||
if (line == -1 || this.topCls == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
CodeLinesInfo linesInfo = new CodeLinesInfo(topCls, true); // TODO: cache and update on class refresh
|
||||
// add comment if node definition at this line
|
||||
JavaNode nodeAtLine = linesInfo.getDefAtLine(line);
|
||||
if (nodeAtLine != null) {
|
||||
// at node definition -> add comment for it
|
||||
JadxNodeRef nodeRef = JadxNodeRef.forJavaNode(nodeAtLine);
|
||||
return new JadxCodeComment(nodeRef, "");
|
||||
}
|
||||
Object ann = topCls.getAnnotationAt(new CodePosition(line));
|
||||
if (ann == null) {
|
||||
// check if line with comment above node definition
|
||||
try {
|
||||
JavaNode defNode = linesInfo.getJavaNodeBelowLine(line);
|
||||
if (defNode != null) {
|
||||
String lineStr = codeArea.getLineText(line).trim();
|
||||
if (lineStr.startsWith("//")) {
|
||||
return new JadxCodeComment(JadxNodeRef.forJavaNode(defNode), "");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to check comment line: " + line, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// try to add method line comment
|
||||
JavaNode node = linesInfo.getJavaNodeByLine(line);
|
||||
if (node instanceof JavaMethod) {
|
||||
JadxNodeRef nodeRef = JadxNodeRef.forMth((JavaMethod) node);
|
||||
if (ann instanceof InsnCodeOffset) {
|
||||
int rawOffset = ((InsnCodeOffset) ann).getOffset();
|
||||
return new JadxCodeComment(nodeRef, "", rawOffset);
|
||||
}
|
||||
if (ann instanceof CustomOffsetRef) {
|
||||
CustomOffsetRef customRef = (CustomOffsetRef) ann;
|
||||
JadxCodeComment comment = new JadxCodeComment(nodeRef, "", customRef.getOffset());
|
||||
comment.setAttachType(customRef.getAttachType());
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to add comment at line: " + line, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getMouseLine() {
|
||||
int closestOffset = UiUtils.getOffsetAtMousePosition(codeArea);
|
||||
if (closestOffset == -1) {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
return codeArea.getLineOfOffset(closestOffset) + 1;
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Failed to get line by offset: {}", closestOffset);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
import static javax.swing.KeyStroke.getKeyStroke;
|
||||
|
||||
public class CommentSearchAction extends AbstractAction {
|
||||
private static final long serialVersionUID = -3646341661734961590L;
|
||||
|
||||
private final CodeArea codeArea;
|
||||
|
||||
public CommentSearchAction(CodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
|
||||
KeyStroke key = getKeyStroke(KeyEvent.VK_SEMICOLON, UiUtils.ctrlButton());
|
||||
putValue(Action.NAME, NLS.str("popup.search_comment") + " (Ctrl + ;)");
|
||||
|
||||
codeArea.getInputMap().put(key, "popup.search_comment");
|
||||
codeArea.getActionMap().put("popup.search_comment", new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
startSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
startSearch();
|
||||
}
|
||||
|
||||
private void startSearch() {
|
||||
SearchDialog.searchInActiveTab(codeArea.getMainWindow(), SearchDialog.SearchPreset.COMMENT);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public abstract class JNodeMenuAction<T> extends AbstractAction implements PopupMenuListener {
|
||||
private static final long serialVersionUID = -2600154727884853550L;
|
||||
|
||||
@@ -36,8 +38,7 @@ public abstract class JNodeMenuAction<T> extends AbstractAction implements Popup
|
||||
|
||||
@Nullable
|
||||
private T getNode() {
|
||||
Point pos = MouseInfo.getPointerInfo().getLocation();
|
||||
SwingUtilities.convertPointFromScreen(pos, codeArea);
|
||||
Point pos = UiUtils.getMousePosition(codeArea);
|
||||
Token token = codeArea.viewToToken(pos);
|
||||
int offset = codeArea.adjustOffsetForToken(token);
|
||||
return getNodeByOffset(offset);
|
||||
|
||||
@@ -107,6 +107,7 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
codeInfo = codeArea.getNode().getCodeInfo();
|
||||
@@ -265,6 +266,10 @@ public class LineNumbers extends JPanel implements CaretListener {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUseSourceLines() {
|
||||
return useSourceLines;
|
||||
}
|
||||
|
||||
public void setUseSourceLines(boolean useSourceLines) {
|
||||
this.useSourceLines = useSourceLines;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -10,6 +11,7 @@ import jadx.gui.jobs.IndexJob;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JRoot;
|
||||
import jadx.gui.ui.SearchDialog;
|
||||
import jadx.gui.utils.search.CommentsIndex;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
public class CacheObject {
|
||||
@@ -19,9 +21,11 @@ public class CacheObject {
|
||||
|
||||
private TextSearchIndex textIndex;
|
||||
private CodeUsageInfo usageInfo;
|
||||
private CommentsIndex commentsIndex;
|
||||
private String lastSearch;
|
||||
private JNodeCache jNodeCache;
|
||||
private Set<SearchDialog.SearchOptions> lastSearchOptions;
|
||||
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
|
||||
|
||||
private JRoot jRoot;
|
||||
private JadxSettings settings;
|
||||
|
||||
@@ -38,7 +42,7 @@ public class CacheObject {
|
||||
lastSearch = null;
|
||||
jNodeCache = new JNodeCache();
|
||||
usageInfo = null;
|
||||
lastSearchOptions = EnumSet.noneOf(SearchDialog.SearchOptions.class);
|
||||
lastSearchOptions = new HashMap<>();
|
||||
}
|
||||
|
||||
public DecompileJob getDecompileJob() {
|
||||
@@ -49,7 +53,6 @@ public class CacheObject {
|
||||
this.decompileJob = decompileJob;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TextSearchIndex getTextIndex() {
|
||||
return textIndex;
|
||||
}
|
||||
@@ -76,6 +79,14 @@ public class CacheObject {
|
||||
this.usageInfo = usageInfo;
|
||||
}
|
||||
|
||||
public CommentsIndex getCommentsIndex() {
|
||||
return commentsIndex;
|
||||
}
|
||||
|
||||
public void setCommentsIndex(CommentsIndex commentsIndex) {
|
||||
this.commentsIndex = commentsIndex;
|
||||
}
|
||||
|
||||
public IndexJob getIndexJob() {
|
||||
return indexJob;
|
||||
}
|
||||
@@ -88,11 +99,7 @@ public class CacheObject {
|
||||
return jNodeCache;
|
||||
}
|
||||
|
||||
public void setLastSearchOptions(Set<SearchDialog.SearchOptions> lastSearchOptions) {
|
||||
this.lastSearchOptions = lastSearchOptions;
|
||||
}
|
||||
|
||||
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
|
||||
public Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> getLastSearchOptions() {
|
||||
return lastSearchOptions;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.annotations.ICodeRawOffset;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
|
||||
/**
|
||||
* After class refresh (rename, comment, etc) the change of document is undetectable.
|
||||
* So use Token index or offset in line to calculate the new caret position.
|
||||
*/
|
||||
public class CaretPositionFix {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CaretPositionFix.class);
|
||||
|
||||
private final AbstractCodeArea codeArea;
|
||||
|
||||
private int linesCount;
|
||||
private int line;
|
||||
private int lineOffset;
|
||||
private TokenInfo tokenInfo;
|
||||
|
||||
private int javaNodeLine = -1;
|
||||
private int codeRawOffset = -1;
|
||||
|
||||
public CaretPositionFix(AbstractCodeArea codeArea) {
|
||||
this.codeArea = codeArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save caret position by anchor to token under caret
|
||||
*/
|
||||
public void save() {
|
||||
try {
|
||||
linesCount = codeArea.getLineCount();
|
||||
int pos = codeArea.getCaretPosition();
|
||||
line = codeArea.getLineOfOffset(pos);
|
||||
lineOffset = pos - codeArea.getLineStartOffset(line);
|
||||
|
||||
tokenInfo = getTokenInfoByOffset(codeArea.getTokenListForLine(line), pos);
|
||||
|
||||
JClass cls = codeArea.getJClass();
|
||||
if (cls != null) {
|
||||
JavaClass topParentClass = cls.getJavaNode().getTopParentClass();
|
||||
Object ann = topParentClass.getAnnotationAt(new CodePosition(line));
|
||||
if (ann instanceof ICodeRawOffset) {
|
||||
codeRawOffset = ((ICodeRawOffset) ann).getOffset();
|
||||
CodeLinesInfo codeLinesInfo = new CodeLinesInfo(topParentClass);
|
||||
JavaNode javaNodeAtLine = codeLinesInfo.getJavaNodeByLine(line);
|
||||
if (javaNodeAtLine != null) {
|
||||
javaNodeLine = javaNodeAtLine.getDecompiledLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.debug("Saved position data: line={}, lineOffset={}, token={}, codeRawOffset={}, javaNodeLine={}",
|
||||
line, lineOffset, tokenInfo, codeRawOffset, javaNodeLine);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to save caret position before refresh", e);
|
||||
line = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore caret position in refreshed code.
|
||||
* Expected to be called in UI thread.
|
||||
*/
|
||||
public void restore() {
|
||||
if (line == -1) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
int newLine = getNewLine();
|
||||
int lineStartOffset = codeArea.getLineStartOffset(newLine);
|
||||
int lineEndOffset = codeArea.getLineEndOffset(newLine) - 1;
|
||||
int lineLength = lineEndOffset - lineStartOffset;
|
||||
Token token = codeArea.getTokenListForLine(newLine);
|
||||
int newPos = getOffsetFromTokenInfo(tokenInfo, token);
|
||||
if (newPos == -1) {
|
||||
// can't restore using token -> just restore by line offset
|
||||
if (lineOffset < lineLength) {
|
||||
newPos = lineStartOffset + lineOffset;
|
||||
} else {
|
||||
// line truncated -> set caret at line end
|
||||
newPos = lineEndOffset;
|
||||
}
|
||||
}
|
||||
codeArea.setCaretPosition(newPos);
|
||||
LOG.debug("Restored caret position: {}, line: {}", newPos, newLine);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to restore caret position", e);
|
||||
}
|
||||
}
|
||||
|
||||
private int getNewLine() {
|
||||
int newLinesCount = codeArea.getLineCount();
|
||||
if (linesCount == newLinesCount) {
|
||||
return line;
|
||||
}
|
||||
// lines count changes, try find line by raw offset
|
||||
if (javaNodeLine != -1) {
|
||||
JClass cls = codeArea.getJClass();
|
||||
if (cls != null) {
|
||||
JavaClass topParentClass = cls.getJavaNode().getTopParentClass();
|
||||
for (Map.Entry<CodePosition, Object> entry : topParentClass.getCodeAnnotations().entrySet()) {
|
||||
CodePosition pos = entry.getKey();
|
||||
if (pos.getOffset() == 0 && pos.getLine() >= javaNodeLine) {
|
||||
Object ann = entry.getValue();
|
||||
if (ann instanceof ICodeRawOffset && ((ICodeRawOffset) ann).getOffset() == codeRawOffset) {
|
||||
return pos.getLine() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// fallback: assume lines added/removed before caret
|
||||
return line - (linesCount - newLinesCount);
|
||||
}
|
||||
|
||||
private TokenInfo getTokenInfoByOffset(Token token, int offset) {
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
int index = 1;
|
||||
while (token.getEndOffset() < offset) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return new TokenInfo(index, token.getType());
|
||||
}
|
||||
|
||||
private int getOffsetFromTokenInfo(TokenInfo tokenInfo, Token token) {
|
||||
if (tokenInfo == null || token == null) {
|
||||
return -1;
|
||||
}
|
||||
int index = tokenInfo.getIndex();
|
||||
if (index == -1) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < index; i++) {
|
||||
token = token.getNextToken();
|
||||
if (token == null) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (token.getType() != tokenInfo.getType()) {
|
||||
return -1;
|
||||
}
|
||||
return token.getOffset();
|
||||
}
|
||||
|
||||
private static final class TokenInfo {
|
||||
private final int index;
|
||||
private final int type;
|
||||
|
||||
public TokenInfo(int index, int type) {
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Token{index=" + index + ", type=" + type + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
|
||||
@@ -12,18 +13,27 @@ public class CodeLinesInfo {
|
||||
private final NavigableMap<Integer, JavaNode> map = new TreeMap<>();
|
||||
|
||||
public CodeLinesInfo(JavaClass cls) {
|
||||
addClass(cls);
|
||||
addClass(cls, false);
|
||||
}
|
||||
|
||||
public void addClass(JavaClass cls) {
|
||||
public CodeLinesInfo(JavaClass cls, boolean includeFields) {
|
||||
addClass(cls, includeFields);
|
||||
}
|
||||
|
||||
private void addClass(JavaClass cls, boolean includeFields) {
|
||||
map.put(cls.getDecompiledLine(), cls);
|
||||
for (JavaClass innerCls : cls.getInnerClasses()) {
|
||||
map.put(innerCls.getDecompiledLine(), innerCls);
|
||||
addClass(innerCls);
|
||||
addClass(innerCls, includeFields);
|
||||
}
|
||||
for (JavaMethod mth : cls.getMethods()) {
|
||||
map.put(mth.getDecompiledLine(), mth);
|
||||
}
|
||||
if (includeFields) {
|
||||
for (JavaField field : cls.getFields()) {
|
||||
map.put(field.getDecompiledLine(), field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JavaNode getJavaNodeByLine(int line) {
|
||||
@@ -33,4 +43,16 @@ public class CodeLinesInfo {
|
||||
}
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
public JavaNode getJavaNodeBelowLine(int line) {
|
||||
Map.Entry<Integer, JavaNode> entry = map.ceilingEntry(line);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
public JavaNode getDefAtLine(int line) {
|
||||
return map.get(line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class CodeUsageInfo {
|
||||
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
|
||||
StringRef codeLine = lines.get(line - 1);
|
||||
JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
|
||||
CodeNode codeNode = new CodeNode(node, line, codeLine).setPrecisePos(codePosition.getUsagePosition());
|
||||
CodeNode codeNode = new CodeNode(node, codeLine, line, codePosition.getPos());
|
||||
usageInfo.addUsage(codeNode);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
|
||||
public interface DefaultPopupMenuListener extends PopupMenuListener {
|
||||
@Override
|
||||
default void popupMenuWillBecomeVisible(PopupMenuEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default void popupMenuCanceled(PopupMenuEvent e) {
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import jadx.api.*;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.treemodel.*;
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
|
||||
public class JumpPosition {
|
||||
private final JNode node;
|
||||
private final int line;
|
||||
private int pos;
|
||||
private boolean precise;
|
||||
|
||||
public JumpPosition(JNode node, int line) {
|
||||
this(node, line, -1);
|
||||
public JumpPosition(JNode jumpNode) {
|
||||
this(jumpNode.getRootClass(), jumpNode.getLine(), jumpNode.getPos());
|
||||
}
|
||||
|
||||
public JumpPosition(JNode jumpNode, CodePosition codePos) {
|
||||
this(jumpNode.getRootClass(), codePos.getLine(), codePos.getPos());
|
||||
}
|
||||
|
||||
public JumpPosition(JNode node, int line, int pos) {
|
||||
@@ -20,20 +23,14 @@ public class JumpPosition {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public boolean isPrecise() {
|
||||
return precise;
|
||||
}
|
||||
|
||||
public JumpPosition setPrecise(int pos) {
|
||||
this.pos = pos;
|
||||
this.precise = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void setPos(int pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public JNode getNode() {
|
||||
return node;
|
||||
}
|
||||
@@ -43,35 +40,11 @@ public class JumpPosition {
|
||||
}
|
||||
|
||||
public static int getDefPos(JNode node) {
|
||||
if (node instanceof JClass) {
|
||||
return ((JClass) node).getCls().getClassNode().getDefPosition();
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
if (javaNode == null) {
|
||||
return -1;
|
||||
}
|
||||
if (node instanceof JMethod) {
|
||||
return ((JMethod) node).getJavaMethod().getMethodNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JField) {
|
||||
return ((JField) node).getJavaField().getFieldNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JVariable) {
|
||||
return ((JVariable) node).getJavaVarNode().getVariableNode().getDefPosition();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node " + node);
|
||||
}
|
||||
|
||||
public static int getDefPos(JavaNode node) {
|
||||
if (node instanceof JavaClass) {
|
||||
return ((JavaClass) node).getClassNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JavaMethod) {
|
||||
return ((JavaMethod) node).getMethodNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JavaField) {
|
||||
return ((JavaField) node).getFieldNode().getDefPosition();
|
||||
}
|
||||
if (node instanceof JavaVariable) {
|
||||
return ((JavaVariable) node).getVariableNode().getDefPosition();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected node " + node);
|
||||
return javaNode.getDefPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -11,7 +11,6 @@ import javax.swing.*;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.undo.UndoManager;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TextStandardActions {
|
||||
|
||||
private final JTextComponent textComponent;
|
||||
@@ -27,6 +26,10 @@ public class TextStandardActions {
|
||||
private Action deleteAction;
|
||||
private Action selectAllAction;
|
||||
|
||||
public static void attach(JTextComponent textComponent) {
|
||||
new TextStandardActions(textComponent);
|
||||
}
|
||||
|
||||
public TextStandardActions(JTextComponent textComponent) {
|
||||
this.textComponent = textComponent;
|
||||
this.undoManager = new UndoManager();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Component;
|
||||
import java.awt.Image;
|
||||
import java.awt.MouseInfo;
|
||||
import java.awt.Point;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.Window;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
@@ -10,7 +15,14 @@ import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.slf4j.Logger;
|
||||
@@ -19,6 +31,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
|
||||
public class UiUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UiUtils.class);
|
||||
@@ -190,7 +203,7 @@ public class UiUtils {
|
||||
@SuppressWarnings("deprecation")
|
||||
@MagicConstant(flagsFromClass = InputEvent.class)
|
||||
private static int getCtrlButton() {
|
||||
if (System.getProperty("os.name").toLowerCase().contains("mac")) {
|
||||
if (SystemInfo.IS_MAC) {
|
||||
return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
|
||||
} else {
|
||||
return InputEvent.CTRL_DOWN_MASK;
|
||||
@@ -210,4 +223,26 @@ public class UiUtils {
|
||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
dialog.getRootPane().registerKeyboardAction(e -> dialog.dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get closest offset at mouse position
|
||||
*
|
||||
* @return -1 on error
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static int getOffsetAtMousePosition(AbstractCodeArea codeArea) {
|
||||
try {
|
||||
Point mousePos = getMousePosition(codeArea);
|
||||
return codeArea.viewToModel(mousePos);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to get offset at mouse position", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static Point getMousePosition(Component comp) {
|
||||
Point pos = MouseInfo.getPointerInfo().getLocation();
|
||||
SwingUtilities.convertPointFromScreen(pos, comp);
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user