diff --git a/jadx-core/src/main/java/jadx/api/CodePosition.java b/jadx-core/src/main/java/jadx/api/CodePosition.java index 86da18b6c..a52860668 100644 --- a/jadx-core/src/main/java/jadx/api/CodePosition.java +++ b/jadx-core/src/main/java/jadx/api/CodePosition.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java index 4804b2906..2a1dc75dd 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/AnnotationGen.java @@ -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 vl = a.getValues(); if (!vl.isEmpty()) { code.add('('); @@ -102,7 +102,7 @@ public class AnnotationGen { code.add(" throws "); for (Iterator it = ((List) 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(); diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 4680c73b9..1e7f528a2 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -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 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 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 it = enumFields.getFields().iterator(); it.hasNext(); ) { - EnumField f = it.next(); - code.startLine(f.getName()); - if (f.getArgs().size() != 0) { - code.add('('); - for (Iterator 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 it = enumFields.getFields().iterator(); it.hasNext(); ) { + EnumField f = it.next(); + code.startLine(f.getName()); + if (f.getArgs().size() != 0) { + code.add('('); + for (Iterator 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) { diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 1df32c37d..2c51b2c50 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 4cae3eb5c..0ddd9d998 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -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()); } } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index 79c5e7758..852d17436 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -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(") {"); diff --git a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java index 6fbab4770..526fd530a 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java index 6a03fc649..4c1ffc081 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/LocalVar.java @@ -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; diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index c5ac4631b..17fee2997 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -36,7 +36,6 @@ public class JadxWrapper { } } - public void saveAll(final File dir, final ProgressMonitor progressMonitor) { Runnable save = new Runnable() { @Override diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 4fc052930..8d01f37df 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -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(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java index b8b8b40ba..ab0b7ca46 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CodeArea.java @@ -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); } } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java index 7a07f1871..ef206714a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CodePanel.java @@ -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() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 906544e08..6ce5d6ab2 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index 54c7b4b7a..a1d679315 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -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 openTabs = new LinkedHashMap(); + 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); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java b/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java new file mode 100644 index 000000000..bf2b341a2 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/JumpManager.java @@ -0,0 +1,56 @@ +package jadx.gui.utils; + +import java.util.ArrayList; +import java.util.List; + +public class JumpManager { + + private List list = new ArrayList(); + 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; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Position.java b/jadx-gui/src/main/java/jadx/gui/utils/Position.java new file mode 100644 index 000000000..a8a4e0629 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/Position.java @@ -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; + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 938e5f912..eca81b8ed 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -20,3 +20,6 @@ search.find=Find tabs.close=Close tabs.closeOthers=Close Others tabs.closeAll=Close All + +nav.back=Back +nav.forward=Forward diff --git a/jadx-gui/src/main/resources/icons-16/icon_back.png b/jadx-gui/src/main/resources/icons-16/icon_back.png new file mode 100644 index 000000000..eabefb77e Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/icon_back.png differ diff --git a/jadx-gui/src/main/resources/icons-16/icon_forward.png b/jadx-gui/src/main/resources/icons-16/icon_forward.png new file mode 100644 index 000000000..97aaa4192 Binary files /dev/null and b/jadx-gui/src/main/resources/icons-16/icon_forward.png differ diff --git a/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy b/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy new file mode 100644 index 000000000..0f46e53a1 --- /dev/null +++ b/jadx-gui/src/test/groovy/jadx/gui/tests/TestJumpManager.groovy @@ -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 + } +}