gui: add hyperlinks for classes and fields
This commit is contained in:
@@ -30,6 +30,10 @@ public final class CodePosition {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return line != 0 || offset != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
|
||||
@@ -73,7 +73,7 @@ public class AnnotationGen {
|
||||
|
||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||
code.add('@');
|
||||
code.add(classGen.useClass(a.getType()));
|
||||
classGen.useType(code, a.getType());
|
||||
Map<String, Object> vl = a.getValues();
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
@@ -102,7 +102,7 @@ public class AnnotationGen {
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
|
||||
ArgType ex = it.next();
|
||||
code.add(TypeGen.translate(classGen, ex));
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
@@ -144,11 +144,12 @@ public class AnnotationGen {
|
||||
} else if (val instanceof Byte) {
|
||||
code.add(TypeGen.formatByte((Byte) val));
|
||||
} else if (val instanceof ArgType) {
|
||||
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
|
||||
classGen.useType(code, (ArgType) val);
|
||||
code.add(".class");
|
||||
} else if (val instanceof FieldInfo) {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof List) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((List) val).iterator();
|
||||
|
||||
@@ -11,6 +11,7 @@ import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -130,7 +131,9 @@ public class ClassGen {
|
||||
if (sup != null
|
||||
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
|
||||
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
|
||||
clsCode.add("extends ").add(useClass(sup)).add(' ');
|
||||
clsCode.add("extends ");
|
||||
useClass(clsCode, sup);
|
||||
clsCode.add(' ');
|
||||
}
|
||||
|
||||
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
|
||||
@@ -141,7 +144,7 @@ public class ClassGen {
|
||||
}
|
||||
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
ClassInfo interf = it.next();
|
||||
clsCode.add(useClass(interf));
|
||||
useClass(clsCode, interf);
|
||||
if (it.hasNext()) {
|
||||
clsCode.add(", ");
|
||||
}
|
||||
@@ -165,12 +168,20 @@ public class ClassGen {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
code.add(useClass(type));
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(type));
|
||||
}
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
|
||||
ArgType g = it.next();
|
||||
code.add(useClass(g));
|
||||
if (g.isGenericType()) {
|
||||
code.add(g.getObject());
|
||||
} else {
|
||||
useClass(code, ClassInfo.fromType(g));
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(" & ");
|
||||
}
|
||||
@@ -259,7 +270,7 @@ public class ClassGen {
|
||||
}
|
||||
annotationGen.addForField(code, f);
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
code.add(TypeGen.translate(this, f.getType()));
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.add(f.getName());
|
||||
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||
@@ -278,80 +289,91 @@ public class ClassGen {
|
||||
|
||||
private void addEnumFields(CodeWriter code) throws CodegenException {
|
||||
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
|
||||
if (enumFields != null) {
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getName());
|
||||
if (f.getArgs().size() != 0) {
|
||||
code.add('(');
|
||||
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
|
||||
InsnArg arg = aIt.next();
|
||||
if (igen == null) {
|
||||
// don't init mth gen if this is simple enum
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
}
|
||||
igen.addArg(code, arg);
|
||||
if (aIt.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
if (enumFields == null) {
|
||||
return;
|
||||
}
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getName());
|
||||
if (f.getArgs().size() != 0) {
|
||||
code.add('(');
|
||||
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
|
||||
InsnArg arg = aIt.next();
|
||||
if (igen == null) {
|
||||
// don't init mth gen if this is simple enum
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
}
|
||||
igen.addArg(code, arg);
|
||||
if (aIt.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(',');
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
if (enumFields.getFields().isEmpty()) {
|
||||
code.startLine();
|
||||
if (f.getCls() != null) {
|
||||
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(',');
|
||||
}
|
||||
code.add(';');
|
||||
code.newLine();
|
||||
}
|
||||
if (enumFields.getFields().isEmpty()) {
|
||||
code.startLine();
|
||||
}
|
||||
code.add(';');
|
||||
code.newLine();
|
||||
}
|
||||
|
||||
public String useClass(ArgType clsType) {
|
||||
if (clsType.isGenericType()) {
|
||||
return clsType.getObject();
|
||||
}
|
||||
return useClass(ClassInfo.fromType(clsType));
|
||||
}
|
||||
|
||||
public String useClass(ClassInfo classInfo) {
|
||||
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
ArgType type = classInfo.getType();
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
if (generics == null) {
|
||||
return baseClass;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(baseClass);
|
||||
sb.append('<');
|
||||
int len = generics.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
ArgType wt = gt.getWildcardType();
|
||||
if (wt != null) {
|
||||
sb.append('?');
|
||||
int bounds = gt.getWildcardBounds();
|
||||
if (bounds != 0) {
|
||||
sb.append(bounds == -1 ? " super " : " extends ");
|
||||
sb.append(TypeGen.translate(this, wt));
|
||||
}
|
||||
public void useType(CodeWriter code, ArgType type) {
|
||||
final PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null) {
|
||||
code.add(type.toString());
|
||||
} else if (stype == PrimitiveType.OBJECT) {
|
||||
if (type.isGenericType()) {
|
||||
code.add(type.getObject());
|
||||
} else {
|
||||
sb.append(TypeGen.translate(this, gt));
|
||||
useClass(code, ClassInfo.fromType(type));
|
||||
}
|
||||
} else if (stype == PrimitiveType.ARRAY) {
|
||||
useType(code, type.getArrayElement());
|
||||
code.add("[]");
|
||||
} else {
|
||||
code.add(stype.getLongName());
|
||||
}
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
ArgType[] generics = classInfo.getType().getGenericTypes();
|
||||
code.add(baseClass);
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
int len = generics.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
code.add(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
ArgType wt = gt.getWildcardType();
|
||||
if (wt != null) {
|
||||
code.add('?');
|
||||
int bounds = gt.getWildcardBounds();
|
||||
if (bounds != 0) {
|
||||
code.add(bounds == -1 ? " super " : " extends ");
|
||||
useType(code, wt);
|
||||
}
|
||||
} else {
|
||||
useType(code, gt);
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
sb.append('>');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
|
||||
|
||||
@@ -99,7 +99,7 @@ public class InsnGen {
|
||||
} else if (arg.isField()) {
|
||||
FieldArg f = (FieldArg) arg;
|
||||
if (f.isStatic()) {
|
||||
code.add(staticField(f.getField()));
|
||||
staticField(code, f.getField());
|
||||
} else {
|
||||
instanceField(code, f.getField(), f.getRegisterArg());
|
||||
}
|
||||
@@ -118,7 +118,7 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
code.add(useType(arg.getType()));
|
||||
useType(code, arg.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.assignArg(arg));
|
||||
}
|
||||
@@ -134,38 +134,52 @@ public class InsnGen {
|
||||
if (replace != null) {
|
||||
FieldInfo info = replace.getFieldInfo();
|
||||
if (replace.isOuterClass()) {
|
||||
code.add(useClass(info.getDeclClass())).add(".this");
|
||||
useClass(code, info.getDeclClass());
|
||||
code.add(".this");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
fieldNode = mth.dex().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getName());
|
||||
}
|
||||
|
||||
public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
|
||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
|
||||
return field.getName();
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getFullName().startsWith(declClass.getFullName());
|
||||
if (!fieldFromThisClass) {
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
clsGen.useClass(code, parentClass);
|
||||
code.add('.');
|
||||
code.add(declClass.getShortName());
|
||||
} else {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
|
||||
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
return clsGen.useClass(declClass) + '.' + field.getName();
|
||||
code.add(field.getName());
|
||||
}
|
||||
|
||||
protected String staticField(FieldInfo field) {
|
||||
return makeStaticFieldAccess(field, mgen.getClassGen());
|
||||
protected void staticField(CodeWriter code, FieldInfo field) {
|
||||
makeStaticFieldAccess(code, field, mgen.getClassGen());
|
||||
}
|
||||
|
||||
public String useClass(ClassInfo cls) {
|
||||
return mgen.getClassGen().useClass(cls);
|
||||
public void useClass(CodeWriter code, ClassInfo cls) {
|
||||
mgen.getClassGen().useClass(code, cls);
|
||||
}
|
||||
|
||||
private String useType(ArgType type) {
|
||||
return TypeGen.translate(mgen.getClassGen(), type);
|
||||
private void useType(CodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -208,7 +222,8 @@ public class InsnGen {
|
||||
|
||||
case CONST_CLASS:
|
||||
ArgType clsType = ((ConstClassNode) insn).getClsType();
|
||||
code.add(useType(clsType)).add(".class");
|
||||
useType(code, clsType);
|
||||
code.add(".class");
|
||||
break;
|
||||
|
||||
case CONST:
|
||||
@@ -227,7 +242,7 @@ public class InsnGen {
|
||||
code.add('(');
|
||||
}
|
||||
code.add('(');
|
||||
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
|
||||
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
|
||||
code.add(") ");
|
||||
addArg(code, insn.getArg(0), true);
|
||||
if (wrap) {
|
||||
@@ -299,7 +314,7 @@ public class InsnGen {
|
||||
}
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" instanceof ");
|
||||
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
|
||||
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
@@ -315,7 +330,8 @@ public class InsnGen {
|
||||
|
||||
case NEW_ARRAY: {
|
||||
ArgType arrayType = insn.getResult().getType();
|
||||
code.add("new ").add(useType(arrayType.getArrayRootElement()));
|
||||
code.add("new ");
|
||||
useType(code, arrayType.getArrayRootElement());
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(']');
|
||||
@@ -368,11 +384,12 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
case SGET:
|
||||
code.add(staticField((FieldInfo) ((IndexInsnNode) insn).getIndex()));
|
||||
staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
|
||||
break;
|
||||
case SPUT:
|
||||
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
code.add(staticField(field)).add(" = ");
|
||||
staticField(code, field);
|
||||
code.add(" = ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
|
||||
@@ -474,7 +491,8 @@ public class InsnGen {
|
||||
|
||||
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
int c = insn.getArgsCount();
|
||||
code.add("new ").add(useType(insn.getResult().getType()));
|
||||
code.add("new ");
|
||||
useType(code, insn.getResult().getType());
|
||||
code.add('{');
|
||||
for (int i = 0; i < c; i++) {
|
||||
addArg(code, insn.getArg(i));
|
||||
@@ -539,7 +557,9 @@ public class InsnGen {
|
||||
}
|
||||
int len = str.length();
|
||||
str.delete(len - 2, len);
|
||||
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
|
||||
code.add("new ");
|
||||
useType(code, elType);
|
||||
code.add("[]{").add(str.toString()).add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
@@ -562,7 +582,13 @@ public class InsnGen {
|
||||
defCtr.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
code.add("() ");
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
|
||||
return;
|
||||
}
|
||||
@@ -574,7 +600,8 @@ public class InsnGen {
|
||||
} else if (insn.isThis()) {
|
||||
code.add("this");
|
||||
} else {
|
||||
code.add("new ").add(useClass(insn.getClassType()));
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
}
|
||||
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
}
|
||||
@@ -612,7 +639,8 @@ public class InsnGen {
|
||||
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (!insnCls.equals(declClass)) {
|
||||
code.add(useClass(declClass)).add('.');
|
||||
useClass(code, declClass);
|
||||
code.add('.');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -637,7 +665,9 @@ public class InsnGen {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
ArgType origType = originalType.get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(').add(useType(origType)).add(')');
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(')');
|
||||
addArg(code, arg, true);
|
||||
} else {
|
||||
addArg(code, arg, false);
|
||||
|
||||
@@ -93,7 +93,7 @@ public class MethodGen {
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
code.add(TypeGen.translate(classGen, mth.getReturnType()));
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
code.add(mth.getName());
|
||||
}
|
||||
@@ -138,14 +138,14 @@ public class MethodGen {
|
||||
ArgType type = arg.getType();
|
||||
if (type.isArray()) {
|
||||
ArgType elType = type.getArrayElement();
|
||||
argsCode.add(TypeGen.translate(classGen, elType));
|
||||
classGen.useType(argsCode, elType);
|
||||
argsCode.add(" ...");
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
|
||||
argsCode.add(TypeGen.translate(classGen, arg.getType()));
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
}
|
||||
} else {
|
||||
argsCode.add(TypeGen.translate(classGen, arg.getType()));
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
}
|
||||
argsCode.add(' ');
|
||||
argsCode.add(makeArgName(arg));
|
||||
@@ -181,7 +181,8 @@ public class MethodGen {
|
||||
if (type.isPrimitive()) {
|
||||
return base + type.getPrimitiveType().getShortName().toLowerCase();
|
||||
} else {
|
||||
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
|
||||
// TODO: prettify variable name
|
||||
return base + "_" + Utils.escape(type.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ public class RegionGen extends InsnGen {
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof IndexInsnNode) {
|
||||
code.add(staticField((FieldInfo) ((IndexInsnNode) k).getIndex()));
|
||||
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
|
||||
} else {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
}
|
||||
@@ -270,7 +270,11 @@ public class RegionGen extends InsnGen {
|
||||
IContainer region = handler.getHandlerRegion();
|
||||
if (region != null) {
|
||||
code.startLine("} catch (");
|
||||
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
useClass(code, handler.getCatchType());
|
||||
}
|
||||
code.add(' ');
|
||||
code.add(mgen.assignNamedArg(handler.getArg()));
|
||||
code.add(") {");
|
||||
|
||||
@@ -8,20 +8,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class TypeGen {
|
||||
|
||||
public static String translate(ClassGen clsGen, ArgType type) {
|
||||
final PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null) {
|
||||
return type.toString();
|
||||
}
|
||||
if (stype == PrimitiveType.OBJECT) {
|
||||
return clsGen.useClass(type);
|
||||
}
|
||||
if (stype == PrimitiveType.ARRAY) {
|
||||
return translate(clsGen, type.getArrayElement()) + "[]";
|
||||
}
|
||||
return stype.getLongName();
|
||||
}
|
||||
|
||||
public static String signature(ArgType type) {
|
||||
final PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == PrimitiveType.OBJECT) {
|
||||
|
||||
@@ -57,7 +57,6 @@ final class LocalVar extends RegisterArg {
|
||||
} else if (el.isGenericType()) {
|
||||
apply = true;
|
||||
} else {
|
||||
LOG.debug("Local var signature from debug info not generic: {}, parsed: {}", sign, gType);
|
||||
apply = false;
|
||||
}
|
||||
return apply;
|
||||
|
||||
@@ -36,7 +36,6 @@ public class JadxWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void saveAll(final File dir, final ProgressMonitor progressMonitor) {
|
||||
Runnable save = new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -106,6 +106,10 @@ public class JClass extends JNode {
|
||||
return jParent.getRootClass();
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine() {
|
||||
return cls.getDecompiledLine();
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.gui.ui;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.utils.Position;
|
||||
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.SwingUtilities;
|
||||
@@ -64,7 +65,7 @@ class CodeArea extends RSyntaxTextArea {
|
||||
|
||||
private boolean isJumpToken(Token token) {
|
||||
if (token.getType() == TokenTypes.IDENTIFIER) {
|
||||
CodePosition pos = getCodePosition(cls, this, token.getOffset());
|
||||
Position pos = getPosition(cls, this, token.getOffset());
|
||||
if (pos != null) {
|
||||
return true;
|
||||
}
|
||||
@@ -80,15 +81,22 @@ class CodeArea extends RSyntaxTextArea {
|
||||
return super.getUnderlineForToken(t);
|
||||
}
|
||||
|
||||
static CodePosition getCodePosition(JClass jCls, RSyntaxTextArea textArea, int offset) {
|
||||
static Position getPosition(JClass jCls, RSyntaxTextArea textArea, int offset) {
|
||||
try {
|
||||
int line = textArea.getLineOfOffset(offset);
|
||||
int lineOffset = offset - textArea.getLineStartOffset(line);
|
||||
return jCls.getCls().getDefinitionPosition(line + 1, lineOffset + 1);
|
||||
CodePosition pos = jCls.getCls().getDefinitionPosition(line + 1, lineOffset + 1);
|
||||
if (pos != null && pos.isSet()) {
|
||||
return new Position(pos);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
LOG.error("Can't get line by offset", e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Position getCurrentPosition() {
|
||||
return new Position(cls, getCaretLineNumber());
|
||||
}
|
||||
|
||||
void scrollToLine(int line) {
|
||||
@@ -145,14 +153,14 @@ class CodeArea extends RSyntaxTextArea {
|
||||
if (token != null) {
|
||||
offset = token.getOffset();
|
||||
}
|
||||
final CodePosition defPos = getCodePosition(jCls, textArea, offset);
|
||||
final Position defPos = getPosition(jCls, textArea, offset);
|
||||
if (defPos != null) {
|
||||
final int sourceOffset = offset;
|
||||
return new LinkGeneratorResult() {
|
||||
@Override
|
||||
public HyperlinkEvent execute() {
|
||||
return new HyperlinkEvent(defPos, HyperlinkEvent.EventType.ACTIVATED, null,
|
||||
defPos.getJavaClass().getFullName());
|
||||
defPos.getCls().getFullName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,11 +178,13 @@ class CodeArea extends RSyntaxTextArea {
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent e) {
|
||||
Object obj = e.getSource();
|
||||
if (obj instanceof CodePosition) {
|
||||
CodePosition pos = (CodePosition) obj;
|
||||
JClass cls = new JClass(pos.getJavaClass());
|
||||
codePanel.getCodePanel().showCode(cls, pos.getLine());
|
||||
if (obj instanceof Position) {
|
||||
Position pos = (Position) obj;
|
||||
LOG.debug("Code jump to: {}", pos);
|
||||
TabbedPane tabbedPane = codePanel.getTabbedPane();
|
||||
tabbedPane.getJumpManager().addPosition(getCurrentPosition());
|
||||
tabbedPane.getJumpManager().addPosition(pos);
|
||||
tabbedPane.showCode(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ class CodePanel extends JPanel {
|
||||
|
||||
private static final long serialVersionUID = 5310536092010045565L;
|
||||
|
||||
private final TabbedPane codePanel;
|
||||
private final TabbedPane tabbedPane;
|
||||
private final JClass jClass;
|
||||
private final SearchBar searchBar;
|
||||
private final CodeArea codeArea;
|
||||
private final RTextScrollPane scrollPane;
|
||||
|
||||
CodePanel(TabbedPane panel, JClass cls) {
|
||||
codePanel = panel;
|
||||
tabbedPane = panel;
|
||||
jClass = cls;
|
||||
codeArea = new CodeArea(this);
|
||||
searchBar = new SearchBar(codeArea);
|
||||
@@ -49,8 +49,8 @@ class CodePanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
TabbedPane getCodePanel() {
|
||||
return codePanel;
|
||||
TabbedPane getTabbedPane() {
|
||||
return tabbedPane;
|
||||
}
|
||||
|
||||
JClass getCls() {
|
||||
|
||||
@@ -61,6 +61,9 @@ public class MainWindow extends JFrame {
|
||||
private static final ImageIcon ICON_FLAT_PKG = Utils.openIcon("empty_logical_package_obj");
|
||||
private static final ImageIcon ICON_SEARCH = Utils.openIcon("magnifier");
|
||||
|
||||
private static final ImageIcon ICON_BACK = Utils.openIcon("icon_back");
|
||||
private static final ImageIcon ICON_FORWARD = Utils.openIcon("icon_forward");
|
||||
|
||||
private final JadxWrapper wrapper;
|
||||
|
||||
private JPanel mainPanel;
|
||||
@@ -219,6 +222,26 @@ public class MainWindow extends JFrame {
|
||||
|
||||
toolbar.addSeparator();
|
||||
|
||||
final JButton backButton = new JButton(ICON_BACK);
|
||||
backButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
tabbedPane.navBack();
|
||||
}
|
||||
});
|
||||
backButton.setToolTipText(NLS.str("nav.back"));
|
||||
toolbar.add(backButton);
|
||||
|
||||
final JButton forwardButton = new JButton(ICON_FORWARD);
|
||||
forwardButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
tabbedPane.navForward();
|
||||
}
|
||||
});
|
||||
forwardButton.setToolTipText(NLS.str("nav.forward"));
|
||||
toolbar.add(forwardButton);
|
||||
|
||||
mainPanel.add(toolbar, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package jadx.gui.ui;
|
||||
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.utils.JumpManager;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.Position;
|
||||
import jadx.gui.utils.Utils;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
@@ -36,6 +38,7 @@ class TabbedPane extends JTabbedPane {
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private final Map<JClass, CodePanel> openTabs = new LinkedHashMap<JClass, CodePanel>();
|
||||
private JumpManager jumps = new JumpManager();
|
||||
|
||||
TabbedPane(MainWindow window) {
|
||||
mainWindow = window;
|
||||
@@ -63,18 +66,40 @@ class TabbedPane extends JTabbedPane {
|
||||
}
|
||||
|
||||
void showCode(final JClass cls, final int line) {
|
||||
final CodePanel codePanel = getCodePanel(cls);
|
||||
showCode(new Position(cls, line));
|
||||
}
|
||||
|
||||
void showCode(final Position pos) {
|
||||
final CodePanel codePanel = getCodePanel(pos.getCls());
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSelectedComponent(codePanel);
|
||||
CodeArea codeArea = codePanel.getCodeArea();
|
||||
codeArea.scrollToLine(line);
|
||||
codeArea.scrollToLine(pos.getLine());
|
||||
codeArea.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void navBack() {
|
||||
Position pos = jumps.getPrev();
|
||||
if (pos != null) {
|
||||
showCode(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void navForward() {
|
||||
Position pos = jumps.getNext();
|
||||
if (pos != null) {
|
||||
showCode(pos);
|
||||
}
|
||||
}
|
||||
|
||||
public JumpManager getJumpManager() {
|
||||
return jumps;
|
||||
}
|
||||
|
||||
private void addCodePanel(CodePanel codePanel) {
|
||||
openTabs.put(codePanel.getCls(), codePanel);
|
||||
add(codePanel);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JumpManager {
|
||||
|
||||
private List<Position> list = new ArrayList<Position>();
|
||||
private int currentPos = 0;
|
||||
|
||||
public void addPosition(Position pos) {
|
||||
if (pos.equals(getCurrent())) {
|
||||
return;
|
||||
}
|
||||
currentPos++;
|
||||
if (currentPos >= list.size()) {
|
||||
list.add(pos);
|
||||
currentPos = list.size() - 1;
|
||||
} else {
|
||||
list.set(currentPos, pos);
|
||||
int size = list.size();
|
||||
for (int i = currentPos + 1; i < size; i++) {
|
||||
list.set(i, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Position getCurrent() {
|
||||
if (currentPos < list.size()) {
|
||||
return list.get(currentPos);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Position getPrev() {
|
||||
if (currentPos == 0) {
|
||||
return null;
|
||||
}
|
||||
currentPos--;
|
||||
return list.get(currentPos);
|
||||
}
|
||||
|
||||
public Position getNext() {
|
||||
int newPos = currentPos + 1;
|
||||
if (newPos >= list.size()) {
|
||||
currentPos = list.size() - 1;
|
||||
return null;
|
||||
}
|
||||
Position position = list.get(newPos);
|
||||
if (position == null) {
|
||||
return null;
|
||||
}
|
||||
currentPos = newPos;
|
||||
return position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
|
||||
public class Position {
|
||||
private final JClass cls;
|
||||
private final int line;
|
||||
|
||||
public Position(CodePosition pos) {
|
||||
this.cls = new JClass(pos.getJavaClass());
|
||||
this.line = pos.getLine();
|
||||
}
|
||||
|
||||
public Position(JClass cls, int line) {
|
||||
this.cls = cls;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
public JClass getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Position)) {
|
||||
return false;
|
||||
}
|
||||
Position position = (Position) obj;
|
||||
return line == position.line && cls.equals(position.cls);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * cls.hashCode() + line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Position: " + cls + " : " + line;
|
||||
}
|
||||
}
|
||||
@@ -20,3 +20,6 @@ search.find=Find
|
||||
tabs.close=Close
|
||||
tabs.closeOthers=Close Others
|
||||
tabs.closeAll=Close All
|
||||
|
||||
nav.back=Back
|
||||
nav.forward=Forward
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 422 B |
Binary file not shown.
|
After Width: | Height: | Size: 403 B |
@@ -0,0 +1,107 @@
|
||||
package jadx.gui.tests
|
||||
|
||||
import jadx.gui.utils.JumpManager
|
||||
import jadx.gui.utils.Position
|
||||
import spock.lang.Specification
|
||||
|
||||
class TestJumpManager extends Specification {
|
||||
|
||||
JumpManager jm
|
||||
|
||||
def setup() {
|
||||
jm = new JumpManager()
|
||||
}
|
||||
|
||||
def "empty history"() {
|
||||
expect:
|
||||
jm.getPrev() == null
|
||||
jm.getNext() == null
|
||||
}
|
||||
|
||||
def "1 element"() {
|
||||
when:
|
||||
jm.addPosition(Mock(Position))
|
||||
then:
|
||||
jm.getPrev() == null
|
||||
jm.getNext() == null
|
||||
}
|
||||
|
||||
def "2 elements"() {
|
||||
when:
|
||||
def mock1 = Mock(Position)
|
||||
jm.addPosition(mock1)
|
||||
def mock2 = Mock(Position)
|
||||
jm.addPosition(mock2)
|
||||
// 1 - 2@
|
||||
then:
|
||||
noExceptionThrown()
|
||||
jm.getPrev() == mock1
|
||||
jm.getNext() == mock2
|
||||
jm.getNext() == null
|
||||
}
|
||||
|
||||
def "navigation"() {
|
||||
expect:
|
||||
def mock1 = Mock(Position)
|
||||
jm.addPosition(mock1)
|
||||
// 1@
|
||||
def mock2 = Mock(Position)
|
||||
jm.addPosition(mock2)
|
||||
// 1 - 2@
|
||||
jm.getPrev() == mock1
|
||||
// 1@ - 2
|
||||
def mock3 = Mock(Position)
|
||||
jm.addPosition(mock3)
|
||||
// 1 - 3@
|
||||
jm.getNext() == null
|
||||
jm.getPrev() == mock1
|
||||
// 1@ - 3
|
||||
jm.getNext() == mock3
|
||||
}
|
||||
|
||||
def "navigation2"() {
|
||||
expect:
|
||||
def mock1 = Mock(Position)
|
||||
jm.addPosition(mock1)
|
||||
// 1@
|
||||
def mock2 = Mock(Position)
|
||||
jm.addPosition(mock2)
|
||||
// 1 - 2@
|
||||
def mock3 = Mock(Position)
|
||||
jm.addPosition(mock3)
|
||||
// 1 - 2 - 3@
|
||||
def mock4 = Mock(Position)
|
||||
jm.addPosition(mock4)
|
||||
// 1 - 2 - 3 - 4@
|
||||
jm.getPrev() == mock3
|
||||
// 1 - 2 - 3@ - 4
|
||||
jm.getPrev() == mock2
|
||||
// 1 - 2@ - 3 - 4
|
||||
def mock5 = Mock(Position)
|
||||
jm.addPosition(mock5)
|
||||
// 1 - 2 - 5@
|
||||
jm.getNext() == null
|
||||
jm.getNext() == null
|
||||
jm.getPrev() == mock2
|
||||
// 1 - 2@ - 5
|
||||
jm.getPrev() == mock1
|
||||
// 1@ - 2 - 5
|
||||
jm.getPrev() == null
|
||||
jm.getNext() == mock2
|
||||
// 1 - 2@ - 5
|
||||
jm.getNext() == mock5
|
||||
// 1 - 2 - 5@
|
||||
jm.getNext() == null
|
||||
}
|
||||
|
||||
def "add same element"() {
|
||||
when:
|
||||
def mock = Mock(Position)
|
||||
jm.addPosition(mock)
|
||||
jm.addPosition(mock)
|
||||
then:
|
||||
noExceptionThrown()
|
||||
jm.getPrev() == null
|
||||
jm.getNext() == null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user