package cn.wensiqun;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.ClassReader;
import cn.wensiqun.asmsupport.utils.StringUtils;
import cn.wensiqun.classloader.ClassLoaderInterface;
import cn.wensiqun.classloader.ClassLoaderInterfaceDelegate;
import cn.wensiqun.grep.annotated.AnnotationGrep;
import cn.wensiqun.grep.annotated.ClassGrep;
import cn.wensiqun.grep.annotated.FieldGrep;
import cn.wensiqun.grep.annotated.MethodGrep;
import cn.wensiqun.info.AnnotationInfo;
import cn.wensiqun.info.ClassInfo;
import cn.wensiqun.info.FieldInfo;
import cn.wensiqun.info.HierarchyInfo;
import cn.wensiqun.info.InvokeInfo;
import cn.wensiqun.info.InvokeInfo.FunctionInfo;
import cn.wensiqun.info.MethodInfo;
import cn.wensiqun.utils.CommonUtils;
import cn.wensiqun.utils.HierarchyInfoContainer;
import cn.wensiqun.utils.URLUtil;
import cn.wensiqun.visitor.annotated.ClassDefVisitor;
public class GrepRobot implements GrepRobotInternal{
private static final Log LOG = LogFactory.getLog(GrepRobot.class);
private ClassLoaderInterface classLoaderInterface;
private boolean excludeParent;
private Test<String> classNameFilter;
private List<String> annotations;
private GrepRobot(){
this.annotations = new ArrayList<String>();
}
/**
* Avoid making getInstance synchronized.
* see: http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
*/
private static class LazyHolder {
public static final GrepRobot INSTANCE = new GrepRobot();
}
/**
* Public method to return the single instance of this class.
*/
@SuppressWarnings("unused")
private static GrepRobotClient getSingleton(){
return LazyHolder.INSTANCE;
}
public static GrepRobotClient getInstance() {
GrepRobot grep = new GrepRobot();
return grep;
}
public void setClassLoader(ClassLoader classLoaderInterface) {
this.classLoaderInterface = new ClassLoaderInterfaceDelegate(classLoaderInterface);
}
public void setExcludeParent(boolean excludeParent) {
this.excludeParent = excludeParent;
}
public boolean isExcludeParent() {
return excludeParent;
}
public void setClassNameFilter(Test<String> classNameFilter) {
this.classNameFilter = classNameFilter;
}
public void startup() throws IOException {
Collection<URL> urls = CommonUtils.getUrls(classLoaderInterface, excludeParent);
Set<String> protocols = new HashSet<String>(){
private static final long serialVersionUID = 1L;
{
add("jar");
}
};
/*Map<Class<?>, Map<Class<?> , HierarchyInfo>> allHierarchyInfoMap = new HashMap<Class<?>, Map<Class<?> , HierarchyInfo>>();
for(Class<?> p : parents){
allHierarchyInfoMap.put(p, new HashMap<Class<?> , HierarchyInfo>());
}*/
HierarchyInfoContainer hierarchyInfoContainer = new HierarchyInfoContainer(parents);
List<String> classNames = new ArrayList<String>();
for (URL location : urls) {
try {
if (protocols.contains(location.getProtocol())) {
classNames.addAll(jar(location));
} else if ("file".equals(location.getProtocol())) {
try {
// See if it's actually a jar
URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/");
JarURLConnection juc = (JarURLConnection) jarUrl.openConnection();
juc.getJarFile();
classNames.addAll(jar(jarUrl));
} catch (IOException e) {
classNames.addAll(file(location));
}
}
} catch (Exception e) {
if (LOG.isErrorEnabled())
LOG.error("Unable to read URL [" + location.toExternalForm() + "]", e);
}
}
StringBuilder unableLoadClass = new StringBuilder("Unable to read class [");
for (String className : classNames) {
if(LOG.isDebugEnabled()){
LOG.debug(" loop [" + className + "]");
}
try {
if (classNameFilter == null || (classNameFilter != null && classNameFilter.test(className))){
if(LOG.isDebugEnabled()){
LOG.debug(" parse [" + className + "]");
}
readClassDef(className);
readClassHierarchyInfo(className, hierarchyInfoContainer);
}
} catch (Throwable e) {
if (LOG.isErrorEnabled()){
classGrep.getNotLoadedClass().add(className);
unableLoadClass.append("\"").append(className).append("\",");
}
}
}
if(unableLoadClass.length() != "Unable to read class [".length()){
unableLoadClass.deleteCharAt(unableLoadClass.length() - 1).append("]");
LOG.error(unableLoadClass);
}
/*//buildHierarchyInfo
for(Entry<Class<?>, Map<Class<?> , HierarchyInfo>> entry : hierarchyInfoMap.entrySet()){
Class<?> top = entry.getKey();
hierarchyInfoMap.put(top, buildHierarchyInfo(top, entry.getValue()));
}*/
hierarchyInfoMap = hierarchyInfoContainer.getExcept();
//set original method or constructor to MethodInfo
for(List<MethodInfo> methodInfos : getAnnotatedMethodInfoMap().values()){
for(MethodInfo methodInfo : methodInfos){
try {
Object original;
String className = methodInfo.getDeclaringClass().getName();
if(methodInfo.getName().equals("<init>")){
original = CommonUtils.reflect2Constructor(className, methodInfo.getDesc(), classLoaderInterface.getClassLoader());
}else if(methodInfo.getName().equals("<cinit>")){
original = CommonUtils.forName(className, true, classLoaderInterface.getClassLoader());
}else{
original = CommonUtils.reflect2Method(className, methodInfo.getName(), methodInfo.getDesc(), classLoaderInterface.getClassLoader());
}
Method setOriginal = MethodInfo.class.getDeclaredMethod("setOriginal", Object.class);
CommonUtils.forceInvokeMethod(methodInfo, setOriginal, original);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
/**
*
* @return
*/
public ClassLoaderInterface getClassLoaderInterface() {
return classLoaderInterface;
}
public void addAnnotation(Class<? extends Annotation> annotation) {
annotations.add(annotation.getName());
}
public List<String> getAnnotationCriterion() {
return annotations;
}
//<<<<<<<<<<<<<<<<<<<<<<<<<ClassGrep<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
private ClassGrep classGrep = new ClassGrep();
public ClassGrep getClassGrep() {
return classGrep;
}
public Map<String, List<ClassInfo>> getAnnotatedClassMap() {
return CommonUtils.unmodifiableMap(classGrep.getAnnotatedClassMap());
}
public List<String> getNotLoadedClass() {
return CommonUtils.unmodifiableList(classGrep.getNotLoadedClass());
}
public List<ClassInfo> findAnnotatedClasses(Class<? extends Annotation> annotation) {
return CommonUtils.unmodifiableList(classGrep.findAnnotatedClasses(annotation));
}
//>>>>>>>>>>>>>>>>>>>>>>>>>ClassGrep>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//<<<<<<<<<<<<<<<<<<<<<<<<<AnnotationGrep<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
private AnnotationGrep annotationGrep = new AnnotationGrep();
public AnnotationGrep getAnnotationGrep() {
return annotationGrep;
}
public Map<String, List<AnnotationInfo>> getAnnotatedAnnotationMap() {
return CommonUtils.unmodifiableMap(annotationGrep.getAnnotatedAnnotationMap());
}
public List<AnnotationInfo> findAnnotatedAnnotations(Class<? extends Annotation> annotation) {
return CommonUtils.unmodifiableList(annotationGrep.findAnnotatedAnnotations(annotation));
}
//>>>>>>>>>>>>>>>>>>>>>>>>>AnnotationGrep>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//<<<<<<<<<<<<<<<<<<<<<<<<<FieldGrep<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
private FieldGrep fieldGrep = new FieldGrep();
public FieldGrep getFieldGrep() {
return fieldGrep;
}
public Map<String, List<FieldInfo>> getAnnotatedFieldMap() {
return CommonUtils.unmodifiableMap(fieldGrep.getAnnotatedFieldMap());
}
public List<FieldInfo> findAnnotatedFields(Class<? extends Annotation> annotation) {
return CommonUtils.unmodifiableList(fieldGrep.findAnnotatedFields(annotation));
}
//>>>>>>>>>>>>>>>>>>>>>>>>>FieldGrep>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//<<<<<<<<<<<<<<<<<<<<<<<<<FieldGrep<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
private MethodGrep methodGrep = new MethodGrep();
public MethodGrep getMethodGrep() {
return methodGrep;
}
public Map<String, List<MethodInfo>> getAnnotatedMethodInfoMap() {
return CommonUtils.unmodifiableMap(methodGrep.getAnnotatedMethodInfoMap());
}
public List<MethodInfo> findAnnotatedMethods(Class<? extends Annotation> annotation) {
return CommonUtils.unmodifiableList(methodGrep.findAnnotatedMethods(annotation));
}
//>>>>>>>>>>>>>>>>>>>>>>>>>FieldGrep>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//<<<<<<<<<<<<<<<<<<<<<<<<<Extract Class>>>>>>>>>>>>>>>>>>>>>>>>>
private List<String> file(URL location) {
List<String> classNames = new ArrayList<String>();
File dir = new File(URLDecoder.decode(location.getPath()));
if ("META-INF".equals(dir.getName())) {
dir = dir.getParentFile(); // Scrape "META-INF" off
}
if (dir.isDirectory()) {
scanDir(dir, classNames, "");
}
return classNames;
}
private void scanDir(File dir, List<String> classNames, String packageName) {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
scanDir(file, classNames, packageName + file.getName() + ".");
} else if (file.getName().endsWith(".class")) {
String name = file.getName();
name = name.replaceFirst(".class$", "");
// Classes packaged in an exploded .war (e.g. in a VFS file system) should not
// have WEB-INF.classes in their package name.
classNames.add(StringUtils.removeStart(packageName, "WEB-INF.classes.") + name);
}
}
}
private List<String> jar(URL location) throws IOException {
URL url = URLUtil.normalizeToFileProtocol(location);
if (url != null) {
InputStream in = url.openStream();
try {
JarInputStream jarStream = new JarInputStream(in);
return jar(jarStream);
} finally {
in.close();
}
} else if (LOG.isDebugEnabled())
LOG.debug("Unable to read [" + location.toExternalForm() + "]");
return Collections.emptyList();
}
private List<String> jar(JarInputStream jarStream) throws IOException {
List<String> classNames = new ArrayList<String>();
JarEntry entry;
while ((entry = jarStream.getNextJarEntry()) != null) {
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
continue;
}
String className = entry.getName();
className = className.replaceFirst(".class$", "");
//war files are treated as .jar files, so takeout WEB-INF/classes
className = StringUtils.removeStart(className, "WEB-INF/classes/");
className = className.replace('/', '.');
classNames.add(className);
}
return classNames;
}
public void readClassDef(String className) {
if (!className.endsWith(".class")) {
className = className.replace('.', '/') + ".class";
}
try {
URL resource = classLoaderInterface.getResource(className);
if (resource != null) {
InputStream in = resource.openStream();
try {
ClassReader classReader = new ClassReader(in);
if(!annotations.isEmpty()){
readClassDef4Annotated(classReader);
}
if(!calledMethod.isEmpty()){
readClassDef4CalledMethod(classReader);
}
} finally {
in.close();
}
} else {
throw new RuntimeException("Could not load " + className);
}
} catch (IOException e) {
throw new RuntimeException("Could not load " + className, e);
}
}
private void readClassDef4Annotated(ClassReader read){
read.accept(new ClassDefVisitor(this), ClassReader.SKIP_DEBUG);
}
private void readClassDef4CalledMethod(ClassReader read){
InvokerGrepRobot invokerGrepRobot = new InvokerGrepRobot(read,
calledMethod.toArray(new Method[calledMethod.size()]),
calledConstructor.toArray(new Constructor[calledConstructor.size()]), classLoaderInterface);
for(Entry<FunctionInfo, List<InvokeInfo>> entry :
invokerGrepRobot.getInvokeInfoMap().entrySet()){
List<InvokeInfo> invokeInfos = invokeInfoMap.get(entry.getKey());
if(invokeInfos == null){
invokeInfoMap.put(entry.getKey(), entry.getValue());
}else{
invokeInfos.addAll(entry.getValue());
}
}
}
private void readClassHierarchyInfo(String className, HierarchyInfoContainer hierarchyInfoContainer){
try {
Class<?> clazz = CommonUtils.forName(className, true, classLoaderInterface.getClassLoader());
/*Set<Class<?>> parents = allHierarchyInfoMap.keySet();
for(Class<?> parent : parents){
if(parent.isAssignableFrom(clazz)){
allHierarchyInfoMap.get(parent).put(clazz, new HierarchyInfo(clazz));
}
}*/
hierarchyInfoContainer.analyzeAndAdd(clazz);
} catch (ClassNotFoundException e) {
LOG.warn("cannot load class : " + className + " cause by : " + e.getMessage(), e);
}
}
/*private HierarchyInfo buildHierarchyInfo(Class<?> top, Map<Class<?> , HierarchyInfo> childrenMap){
for(Class<?> clazz : childrenMap.keySet()){
Class<?> superClazz = clazz.getSuperclass();
HierarchyInfo currentHier = childrenMap.get(clazz);
HierarchyInfo superHier = childrenMap.get(superClazz);
if(superHier != null){
superHier.getChildren().add(currentHier);
}
Class<?>[] interfaces = clazz.getInterfaces();
if(ArrayUtils.isNotEmpty(interfaces)){
for(int i=0; i<interfaces.length; i++){
HierarchyInfo interHier = childrenMap.get(interfaces[i]);
if(interHier != null){
interHier.getChildren().add(currentHier);
}
}
}
}
return childrenMap.get(top);
}*/
//>>>>>>>>>>>>>>>>>>>>>>>>>Extract Class>>>>>>>>>>>>>>>>>>>>>>>>>
private List<Method> calledMethod = new ArrayList<Method>();
private Map<FunctionInfo, List<InvokeInfo>> invokeInfoMap = new HashMap<FunctionInfo, List<InvokeInfo>>();
@Override
public void addCalledMethod(Method method) {
calledMethod.add(method);
}
@Override
public List<Method> getCalledMethods(){
return calledMethod;
}
@Override
public List<InvokeInfo> getInvokeInfos(Method method) {
return invokeInfoMap.get(InvokeInfo.buildCommonFunctionInfo(method));
}
private List<Constructor<?>> calledConstructor = new ArrayList<Constructor<?>>();
@Override
public void addCalledConstructor(Constructor<?> constr) {
calledConstructor.add(constr);
}
@Override
public List<Constructor<?>> getCalledConstructor() {
return calledConstructor;
}
@Override
public List<InvokeInfo> getInvokeInfos(Constructor<?> constr) {
return invokeInfoMap.get(InvokeInfo.buildConstructorFunctionInfo(constr));
}
private List<Class<?>> parents = new ArrayList<Class<?>>();
private Map<Class<?>, HierarchyInfo> hierarchyInfoMap = new HashMap<Class<?>, HierarchyInfo>();
@Override
public void addParentClass(Class<?> clazz) {
parents.add(clazz);
}
@Override
public HierarchyInfo getHierarchyChild(Class<?> clazz) {
return hierarchyInfoMap.get(clazz);
}
@Override
public List<Class<?>> getAllParentClasses() {
return parents;
}
}