Commit 0a1981f9 authored by Skylot's avatar Skylot

gui: add hyperlinks for classes and fields

parent 0a36bfb0
......@@ -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);
}
code.add(';');
code.newLine();
if (it.hasNext()) {
code.add(',');
}
}
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
code.newLine();
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
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 {
useClass(code, ClassInfo.fromType(type));
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
code.add("[]");
} else {
code.add(stype.getLongName());
}
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;
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
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));
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);
}
} else {
sb.append(TypeGen.translate(this, 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);
}
......
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);
......
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;
}
}
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
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
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment