Commit fb30f061 authored by andrei's avatar andrei

cr 306012, first part: multiline selection in java editor selects bytecode range in BCO

parent a6145bce
......@@ -7,6 +7,7 @@ import java.util.Map;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jface.text.ITextSelection;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
......@@ -22,7 +23,7 @@ public class DecompiledClass {
public static final String ATTR_JAVA_VERSION = "java.version";
public static final String ATTR_ACCESS_FLAGS = "access";
/** key is DecompiledMethod, value is IJavaElement (Member)*/
/** key is DecompiledMethod, value is IJavaElement (Member) */
private Map methodToJavaElt;
private List text;
/**
......@@ -37,18 +38,18 @@ public class DecompiledClass {
methodToJavaElt = new HashMap();
}
public void setAttribute(String key, String value){
public void setAttribute(String key, String value) {
classAttributesMap.put(key, value);
}
/**
* @return the class's access flags (see {@link Opcodes}). This
* parameter also indicates if the class is deprecated.
* @return the class's access flags (see {@link Opcodes}). This parameter also
* indicates if the class is deprecated.
*/
public int getAccessFlags(){
public int getAccessFlags() {
int result = 0;
String flags = (String) classAttributesMap.get(ATTR_ACCESS_FLAGS);
if(flags == null){
if (flags == null) {
return result;
}
try {
......@@ -63,14 +64,14 @@ public class DecompiledClass {
/**
* @return true if the class is either abstract or interface
*/
public boolean isAbstractOrInterface(){
public boolean isAbstractOrInterface() {
int accessFlags = getAccessFlags();
return ((accessFlags & Opcodes.ACC_ABSTRACT) != 0) ||
((accessFlags & Opcodes.ACC_INTERFACE) != 0);
return ((accessFlags & Opcodes.ACC_ABSTRACT) != 0)
|| ((accessFlags & Opcodes.ACC_INTERFACE) != 0);
}
public String getAttribute(String key){
return (String)classAttributesMap.get(key);
public String getAttribute(String key) {
return (String) classAttributesMap.get(key);
}
public String getText() {
......@@ -105,14 +106,15 @@ public class DecompiledClass {
return (String[][]) lines.toArray(new String[lines.size()][]);
}
public int getBytecodeOffest(final int decompiledLine) {
public int getBytecodeOffset(final int decompiledLine) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
Integer offset = m.getBytecodeOffset(decompiledLine - currentDecompiledLine);
if(offset != null){
Integer offset = m.getBytecodeOffset(decompiledLine
- currentDecompiledLine);
if (offset != null) {
return offset.intValue();
}
currentDecompiledLine += m.getLineCount();
......@@ -124,22 +126,23 @@ public class DecompiledClass {
}
public int getBytecodeInsn(final int decompiledLine) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
Integer opcode = m.getBytecodeInsn(decompiledLine - currentDecompiledLine);
if(opcode != null){
return opcode.intValue();
}
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return -1;
}
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
Integer opcode = m.getBytecodeInsn(decompiledLine
- currentDecompiledLine);
if (opcode != null) {
return opcode.intValue();
}
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return -1;
}
public int getSourceLine(final int decompiledLine) {
int currentDecompiledLine = 0;
......@@ -182,7 +185,7 @@ public class DecompiledClass {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
if(signature.equals(m.getSignature())){
if (signature.equals(m.getSignature())) {
return m;
}
}
......@@ -190,13 +193,14 @@ public class DecompiledClass {
return null;
}
public IJavaElement getJavaElement(int decompiledLine, IClassFile clazz){
public IJavaElement getJavaElement(int decompiledLine, IClassFile clazz) {
DecompiledMethod method = getMethod(decompiledLine);
if(method != null){
IJavaElement javaElement = (IJavaElement) methodToJavaElt.get(method);
if(javaElement == null) {
if (method != null) {
IJavaElement javaElement = (IJavaElement) methodToJavaElt
.get(method);
if (javaElement == null) {
javaElement = JdtUtils.getMethod(clazz, method.getSignature());
if(javaElement != null) {
if (javaElement != null) {
methodToJavaElt.put(method, javaElement);
} else {
javaElement = clazz;
......@@ -207,13 +211,13 @@ public class DecompiledClass {
return clazz;
}
public int getDecompiledLine(String methSignature){
public int getDecompiledLine(String methSignature) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
if(methSignature.equals(m.getSignature())){
if (methSignature.equals(m.getSignature())) {
return currentDecompiledLine;
}
currentDecompiledLine += m.getLineCount();
......@@ -225,46 +229,48 @@ public class DecompiledClass {
}
/**
*
* @param decompiledLine
* @return array with two elements, first is the local variables table,
* second is the operands stack content. "null" value could be returned too.
* @return array with two elements, first is the local variables table, second is the
* operands stack content. "null" value could be returned too.
*/
public String [] getFrame(final int decompiledLine, final boolean showQualifiedNames) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
String [] frame = m.getFrame(decompiledLine
- currentDecompiledLine, showQualifiedNames);
if (frame != null) {
return frame;
}
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return null;
public String[] getFrame(final int decompiledLine,
final boolean showQualifiedNames) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
String[] frame = m.getFrame(decompiledLine
- currentDecompiledLine, showQualifiedNames);
if (frame != null) {
return frame;
}
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return null;
}
public String[][][] getFrameTables(final int decompiledLine, boolean useQualifiedNames) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
String[][][] frame = m.getFrameTables(decompiledLine - currentDecompiledLine, useQualifiedNames);
if (frame != null) {
return frame;
}
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return null;
public String[][][] getFrameTables(final int decompiledLine,
boolean useQualifiedNames) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
String[][][] frame = m.getFrameTables(decompiledLine
- currentDecompiledLine, useQualifiedNames);
if (frame != null) {
return frame;
}
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return null;
}
public int getDecompiledLine(final int sourceLine) {
......@@ -285,6 +291,29 @@ public class DecompiledClass {
return -1;
}
/**
* Converts method relative decompiled line to class absolute decompiled position
* @param m1 method for which we need absolute line position
* @param decompiledLine decompiled line, relative to given method (non global coord)
* @return
*/
public int getDecompiledLine(final DecompiledMethod m1, final int decompiledLine) {
int currentDecompiledLine = 0;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
if (o == m1){
return currentDecompiledLine + decompiledLine;
}
DecompiledMethod m = (DecompiledMethod) o;
currentDecompiledLine += m.getLineCount();
} else {
currentDecompiledLine++;
}
}
return -1;
}
public List getErrorLines() {
List errors = new ArrayList();
int currentDecompiledLine = 0;
......@@ -304,29 +333,96 @@ public class DecompiledClass {
return errors;
}
public int getBestDecompiledMatch(int sourceLine) {
int bestMatch = -1;
public DecompiledMethod getBestDecompiledMatch(int sourceLine) {
DecompiledMethod bestM = null;
for (int i = 0; i < text.size(); ++i) {
Object o = text.get(i);
if (o instanceof DecompiledMethod) {
DecompiledMethod m = (DecompiledMethod) o;
int line = m.getFirstSourceLine();
if(line >= sourceLine){
if(bestMatch == -1 || line < bestMatch){
bestMatch = line;
int line = m.getBestDecompiledLine(sourceLine);
if (line > 0) {
// doesn't work if it is a <init> or <cinit> which spawns over
// multiple locations in code
if(m.isInit()){
if(bestM != null){
int d1 = sourceLine - bestM.getFirstSourceLine();
int d2 = sourceLine - m.getFirstSourceLine();
if(d2 < d1){
bestM = m;
}
} else {
bestM = m;
}
} else {
return m;
}
} else {
// check for init blocks which composed from different code lines
if(bestM != null && bestM.isInit()){
if(bestM.getFirstSourceLine() < m.getFirstSourceLine()
&& bestM.getLastSourceLine() > m.getLastSourceLine()){
bestM = null;
}
}
}
}
}
return bestMatch;
return bestM;
}
public LineRange getDecompiledRange(ITextSelection sourceRange) {
int startLine = sourceRange.getStartLine() + 1;
int endLine = sourceRange.getEndLine() + 1;
int startDecompiledLine = getDecompiledLine(startLine);
DecompiledMethod m1 = null;
DecompiledMethod m2 = null;
if (startDecompiledLine < 0) {
m1 = getBestDecompiledMatch(startLine);
m2 = getBestDecompiledMatch(endLine);
if (m1 != null && m1.equals(m2)) {
int methodStartLine = getDecompiledLine(m1.getSignature());
startDecompiledLine = m1.getBestDecompiledLine(startLine);
if (startDecompiledLine >= 0) {
startDecompiledLine = methodStartLine + startDecompiledLine;
} else {
startDecompiledLine = methodStartLine + m1.getLineCount();
}
}
}
int endDecompiledLine = getDecompiledLine(endLine);
if (endDecompiledLine < 0) {
if(m2 == null) {
m2 = getBestDecompiledMatch(endLine);
}
if (m2 != null && m2.equals(m1)) {
int methodStartLine = getDecompiledLine(m2.getSignature());
endDecompiledLine = m2.getBestDecompiledLine(endLine);
if (endDecompiledLine >= 0) {
endDecompiledLine = methodStartLine + endDecompiledLine;
} else {
endDecompiledLine = methodStartLine + m2.getLineCount();
}
// TODO dirty workaround
if(endDecompiledLine < startDecompiledLine){
endDecompiledLine = startDecompiledLine + 1;
}
}
}
return new LineRange(startDecompiledLine, endDecompiledLine);
}
public LineRange getSourceRange(LineRange decompiledRange) {
int startSourceLine = getSourceLine(decompiledRange.startLine);
int endSourceLine = getSourceLine(decompiledRange.endLine);
return new LineRange(startSourceLine, endSourceLine);
}
public void setClassNode(ClassNode classNode) {
this.classNode = classNode;
}
public ClassNode getClassNode(){
public ClassNode getClassNode() {
return classNode;
}
}
......@@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.objectweb.asm.Opcodes;
......@@ -32,19 +33,42 @@ public class DecompiledMethod {
private List localVariables;
private Map sourceLines; // decompiled line -> source line
/**
* decompiled line -> source line
*/
private Map sourceLines;
private Map decompiledLines; // source line -> decompiled line
/**
* source line -> decompiled line
*/
private Map decompiledLines;
private Map insns; // decompiled line -> insn
/**
* decompiled line -> insn
*/
private Map insns;
private Map opcodes; // decompiled line -> opcode
/**
* decompiled line -> opcode
*/
private Map opcodes;
private Map insnLines; // insn -> decompile line
/**
* insn -> decompile line
*/
private Map insnLines;
private int lineCount;
private int firstSourceLine; // first source line, if any
/**
* first source line, if any
*/
private int firstSourceLine;
/**
* last source line, if any
*/
private int lastSourceLine;
private MethodNode meth;
......@@ -54,8 +78,12 @@ public class DecompiledMethod {
private int errorInsn;
private final String owner;
public DecompiledMethod(final String owner, final List inputText,
final Map lineNumbers, final MethodNode meth, final ClassLoader cl, BitSet modes) {
this.owner = owner;
this.text = new ArrayList();
this.localVariables = meth.localVariables;
this.sourceLines = new HashMap();
......@@ -70,10 +98,15 @@ public class DecompiledMethod {
if (modes.get(BCOConstants.F_SHOW_ANALYZER)
&& (meth.access & Opcodes.ACC_ABSTRACT) == 0) {
analyzeMethod(owner, cl);
analyzeMethod(cl);
}
}
public boolean isInit() {
return ("<init>".equals(meth.name) && "()V".equals(meth.desc))
|| "<clinit>".equals(meth.name);
}
public boolean hasSourceLinesInfo(){
return ! sourceLines.isEmpty();
}
......@@ -86,12 +119,49 @@ public class DecompiledMethod {
return meth.name + meth.desc;
}
public boolean containsSource(int sourceLine){
return sourceLine >= getFirstSourceLine() && sourceLine <= getLastSourceLine();
}
/**
* @param sourceLine
* @return nearest match above given source line or the given line for perfect match
* or -1 for no match. The return value is method-relative, and need to be transformed
* to class absolute
*/
public int getBestDecompiledLine(final int sourceLine){
if(!containsSource(sourceLine)){
return -1;
}
Set set = decompiledLines.keySet();
if(set.size() == 0){
return -1;
}
int bestMatch = -1;
for (Iterator iter = set.iterator(); iter.hasNext();) {
int line = ((Integer) iter.next()).intValue();
int delta = sourceLine - line;
if(delta < 0){
continue;
} else if(delta == 0){
return line;
}
if(bestMatch < 0 || delta < sourceLine - bestMatch){
bestMatch = line;
}
}
if(bestMatch < 0){
return -1;
}
return ((Integer)decompiledLines.get(new Integer(bestMatch))).intValue();
}
/**
* @param owner
* @param meth
* @param cl
*/
private void analyzeMethod(final String owner, final ClassLoader cl) {
private void analyzeMethod(final ClassLoader cl) {
Analyzer a = new Analyzer(new SimpleVerifier() {
protected Class getClass(final Type t) {
......@@ -188,12 +258,13 @@ public class DecompiledMethod {
}
private void computeMaps(final Map lineNumbers) {
int currentSourceLine = -1;
int currentDecompiledLine = 0;
int currentInsn = -1;
int currentOpcode = -1;
int firstLine = -1;
int lastLine = -1;
for (int i = 0; i < text.size(); ++i) {
int currentOpcode = -1;
int currentInsn = -1;
int currentSourceLine = -1;
Object o = text.get(i);
if (o instanceof Index) {
Index index = (Index) o;
......@@ -206,19 +277,24 @@ public class DecompiledMethod {
if(firstLine == -1 || currentSourceLine < firstLine){
firstLine = currentSourceLine;
}
if(lastLine == -1 || currentSourceLine > lastLine){
lastLine = currentSourceLine;
}
}
currentInsn = index.insn;
currentOpcode = index.opcode;
} else {
++currentDecompiledLine;
}
Integer csl = new Integer(currentSourceLine);
Integer cdl = new Integer(currentDecompiledLine);
Integer ci = new Integer(currentInsn);
Integer co = new Integer(currentOpcode);
sourceLines.put(cdl, csl);
if (decompiledLines.get(csl) == null) {
decompiledLines.put(csl, cdl);
if(currentSourceLine >= 0){
Integer csl = new Integer(currentSourceLine);
sourceLines.put(cdl, csl);
if (decompiledLines.get(csl) == null) {
decompiledLines.put(csl, cdl);
}
}
insns.put(cdl, ci);
opcodes.put(cdl, co);
......@@ -228,6 +304,7 @@ public class DecompiledMethod {
}
lineCount = currentDecompiledLine;
firstSourceLine = firstLine;
lastSourceLine = lastLine;
}
public String getText() {
......@@ -323,6 +400,10 @@ public class DecompiledMethod {
return firstSourceLine;
}
public int getLastSourceLine(){
return lastSourceLine;
}
public int getSourceLine(final int decompiledLine) {
Integer i = (Integer) sourceLines.get(new Integer(decompiledLine));
return i == null
......@@ -503,4 +584,23 @@ public class DecompiledMethod {
? -1
: i.intValue();
}
/**
* Returns <code>true</code> if this <code>DecompiledMethod</code> is the same as the o argument.
*
* @return <code>true</code> if this <code>DecompiledMethod</code> is the same as the o argument.
*/
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DecompiledMethod)) {
return false;
}
DecompiledMethod another = (DecompiledMethod) o;
return getSignature().equals(another.getSignature())
&& (owner != null? owner.equals(another.owner) : true);
}
}