/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.test;
import gw.util.StreamUtil;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.Method;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Analyzes byte code of Java classes.
*/
public class ClassByteCodeAnalyzer {
private final Map<Class<?>, List<Method>> cache = new ConcurrentHashMap<Class<?>, List<Method>>();
/**
* Returns list of methods according to their order in the source file.
* <p/>
* Supertype methods go first in the list.
* <p/>
* Returns empty list if cannot find class file for the specified class. Class file is retrieved by
* using {@link Class#getResourceAsStream} so it won't work for classes generated at runtine.
*
* @param clazz class to analyze
* @return list of method names
*/
public List<Method> getMethodsSorted(Class<?> clazz) {
List<Method> allMethods = cache.get(clazz);
if (allMethods != null) {
return allMethods;
}
JavaClass javaClass = parseClass(clazz);
if (javaClass == null) {
return Collections.emptyList();
}
List<Method> currentClassMethods = Arrays.asList(javaClass.getMethods());
Collections.sort(currentClassMethods, new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
return getMethodLineNumber(o1).compareTo(getMethodLineNumber(o2));
}
});
allMethods = new ArrayList<Method>();
// add method of super class first
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
allMethods.addAll(getMethodsSorted(superclass));
}
allMethods.addAll(currentClassMethods);
cache.put(clazz, Collections.unmodifiableList(new ArrayList<Method>(allMethods)));
return allMethods;
}
private JavaClass parseClass(Class<?> clazz) {
String pathToClassFile = "/" + clazz.getName().replace('.', '/') + ".class";
InputStream resourceAsStream = clazz.getResourceAsStream(pathToClassFile);
if (resourceAsStream == null) {
return null;
}
try {
return new ClassParser(resourceAsStream, pathToClassFile).parse();
} catch (IOException e) {
throw new RuntimeException("Error during analyzing byte code of the class " + clazz.getName(), e);
} finally {
StreamUtil.closeNoThrow(resourceAsStream);
}
}
private static Integer getMethodLineNumber(Method method) {
LineNumberTable lineNumberTable = method.getLineNumberTable();
return lineNumberTable == null ? -1 : lineNumberTable.getLineNumberTable()[0].getLineNumber();
}
}