/*
* Copyright 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jdave.runner;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import net.sf.cglib.asm.Attribute;
import net.sf.cglib.asm.ClassReader;
import net.sf.cglib.asm.ClassVisitor;
import net.sf.cglib.asm.CodeVisitor;
import net.sf.cglib.asm.Label;
/**
* @author Esko Luontola
* @since 14.2.2008
*/
public class AsmLineNumberStrategy implements LineNumberStrategy {
private final Map<Class<?>, LineNumberClassVisitor> cache = new HashMap<Class<?>, LineNumberClassVisitor>();
public int firstLineNumber(Class<?> clazz, int defaultValue) {
int line = analyze(clazz).firstClassLineNumber();
return line < Integer.MAX_VALUE ? line : defaultValue;
}
public int firstLineNumber(Method method, int defaultValue) {
Integer line = analyze(method.getDeclaringClass()).firstMethodLineNumber(method);
return line != null ? line : defaultValue;
}
private LineNumberClassVisitor analyze(Class<?> clazz) {
LineNumberClassVisitor visitor = cache.get(clazz);
if (visitor != null) {
return visitor;
}
try {
InputStream classAsStream = toStream(clazz);
ClassReader reader = new ClassReader(classAsStream);
visitor = new LineNumberClassVisitor();
reader.accept(visitor, false);
classAsStream.close();
cache.put(clazz, visitor);
return visitor;
} catch (IOException e) {
throw new RuntimeException("Error reading class: " + clazz, e);
}
}
private static InputStream toStream(Class<?> clazz) {
// WORKAROUND: org.objectweb.asm.ClassReader(java.lang.String) has a bug which causes it to
// not find the class file in some situations
String name = clazz.getName();
int i = name.lastIndexOf('.');
if (i > 0) {
name = name.substring(i + 1);
}
return clazz.getResourceAsStream(name + ".class");
}
private static class LineNumberClassVisitor extends NullClassVisitor {
private final LineNumberCodeVisitor codeVisitor = new LineNumberCodeVisitor(this);
private final Map<String, Integer> methodLines = new HashMap<String, Integer>();
private int minLine = Integer.MAX_VALUE;
private String nextMethod;
@Override
public CodeVisitor visitMethod(int access, String name, String desc, String[] exceptions, Attribute attrs) {
nextMethod = name;
return codeVisitor;
}
public void visitLineNumber(int line) {
if (nextMethod != null) {
minLine = Math.min(minLine, line);
methodLines.put(nextMethod, line);
nextMethod = null;
}
}
public Integer firstMethodLineNumber(Method method) {
// TODO: does not handle overloaded methods properly - ignores method parameters
return methodLines.get(method.getName());
}
public int firstClassLineNumber() {
return minLine;
}
}
private static class LineNumberCodeVisitor extends NullCodeVisitor {
private final LineNumberClassVisitor classVisitor;
public LineNumberCodeVisitor(LineNumberClassVisitor classVisitor) {
this.classVisitor = classVisitor;
}
@Override
public void visitLineNumber(int line, Label start) {
classVisitor.visitLineNumber(line);
}
}
private static class NullClassVisitor implements ClassVisitor {
public void visit(int version, int access, String name, String superName, String[] interfaces, String sourceFile) {
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
}
public void visitField(int access, String name, String desc, Object value, Attribute attrs) {
}
public CodeVisitor visitMethod(int access, String name, String desc, String[] exceptions, Attribute attrs) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitEnd() {
}
}
private static class NullCodeVisitor implements CodeVisitor {
public void visitInsn(int opcode) {
}
public void visitIntInsn(int opcode, int operand) {
}
public void visitVarInsn(int opcode, int var) {
}
public void visitTypeInsn(int opcode, String desc) {
}
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
}
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
}
public void visitJumpInsn(int opcode, Label label) {
}
public void visitLabel(Label label) {
}
public void visitLdcInsn(Object cst) {
}
public void visitIincInsn(int var, int increment) {
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) {
}
public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) {
}
public void visitMultiANewArrayInsn(String desc, int dims) {
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
}
public void visitMaxs(int maxStack, int maxLocals) {
}
public void visitLocalVariable(String name, String desc, Label start, Label end, int index) {
}
public void visitLineNumber(int line, Label start) {
}
public void visitAttribute(Attribute attr) {
}
}
}