com.opensymphony.xwork2.util.finder.ClassFinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xwork Show documentation
Show all versions of xwork Show documentation
XWork is an command-pattern framework that is used to power WebWork
as well as other applications. XWork provides an Inversion of Control
container, a powerful expression language, data type conversion,
validation, and pluggable configuration.
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 com.opensymphony.xwork2.util.finder;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/**
* ClassFinder searches the classpath of the specified classloader for
* packages, classes, constructors, methods, or fields with specific annotations.
*
* For security reasons ASM is used to find the annotations. Classes are not
* loaded unless they match the requirements of a called findAnnotated* method.
* Once loaded, these classes are cached.
*
* The getClassesNotLoaded() method can be used immediately after any find*
* method to get a list of classes which matched the find requirements (i.e.
* contained the annotation), but were unable to be loaded.
*
* @author David Blevins
* @version $Rev$ $Date$
*/
public class ClassFinder {
private static final Logger LOG = LoggerFactory.getLogger(ClassFinder.class);
private final Map> annotated = new HashMap>();
private final Map classInfos = new LinkedHashMap();
private final ClassLoader classLoader;
private final List classesNotLoaded = new ArrayList();
private boolean extractBaseInterfaces;
/**
* Creates a ClassFinder that will search the urls in the specified classloader
* excluding the urls in the classloader's parent.
*
* To include the parent classloader, use:
*
* new ClassFinder(classLoader, false);
*
* To exclude the parent's parent, use:
*
* new ClassFinder(classLoader, classLoader.getParent().getParent());
*
* @param classLoader source of classes to scan
* @throws Exception if something goes wrong
*/
public ClassFinder(ClassLoader classLoader) throws Exception {
this(classLoader, true);
}
/**
* Creates a ClassFinder that will search the urls in the specified classloader.
*
* @param classLoader source of classes to scan
* @param excludeParent Allegedly excludes classes from parent classloader, whatever that might mean
* @throws Exception if something goes wrong.
*/
public ClassFinder(ClassLoader classLoader, boolean excludeParent) throws Exception {
this(classLoader, getUrls(classLoader, excludeParent));
}
/**
* Creates a ClassFinder that will search the urls in the specified classloader excluding
* the urls in the 'exclude' classloader.
*
* @param classLoader source of classes to scan
* @param exclude source of classes to exclude from scanning
* @throws Exception if something goes wrong
*/
public ClassFinder(ClassLoader classLoader, ClassLoader exclude) throws Exception {
this(classLoader, getUrls(classLoader, exclude));
}
public ClassFinder(ClassLoader classLoader, URL url) {
this(classLoader, Arrays.asList(url));
}
public ClassFinder(ClassLoader classLoader, String... dirNames) {
this(classLoader, getURLs(classLoader, dirNames));
}
public ClassFinder(ClassLoader classLoader, Collection urls) {
this(classLoader, urls, false);
}
public ClassFinder(ClassLoader classLoader, Collection urls, boolean extractBaseInterfaces) {
this.classLoader = classLoader;
this.extractBaseInterfaces = extractBaseInterfaces;
List classNames = new ArrayList();
for (URL location : urls) {
try {
if ("jar".equals(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) {
e.printStackTrace();
}
}
for (String className : classNames) {
readClassDef(className);
}
}
public ClassFinder(Class... classes){
this(Arrays.asList(classes));
}
public ClassFinder(List classes){
this.classLoader = null;
List infos = new ArrayList();
List packages = new ArrayList();
for (Class clazz : classes) {
Package aPackage = clazz.getPackage();
if (aPackage != null && !packages.contains(aPackage)){
infos.add(new PackageInfo(aPackage));
packages.add(aPackage);
}
ClassInfo classInfo = new ClassInfo(clazz);
infos.add(classInfo);
classInfos.put(classInfo.getName(), classInfo);
for (Method method : clazz.getDeclaredMethods()) {
infos.add(new MethodInfo(classInfo, method));
}
for (Constructor constructor : clazz.getConstructors()) {
infos.add(new MethodInfo(classInfo, constructor));
}
for (Field field : clazz.getDeclaredFields()) {
infos.add(new FieldInfo(classInfo, field));
}
}
for (Info info : infos) {
for (AnnotationInfo annotation : info.getAnnotations()) {
List annotationInfos = getAnnotationInfos(annotation.getName());
annotationInfos.add(info);
}
}
}
public boolean isAnnotationPresent(Class extends Annotation> annotation) {
List infos = annotated.get(annotation.getName());
return infos != null && !infos.isEmpty();
}
/**
* Returns a list of classes that could not be loaded in last invoked findAnnotated* method.
*
* The list will only contain entries of classes whose byte code matched the requirements
* of last invoked find* method, but were unable to be loaded and included in the results.
*
* The list returned is unmodifiable. Once obtained, the returned list will be a live view of the
* results from the last findAnnotated* method call.
*
* This method is not thread safe.
* @return an unmodifiable live view of classes that could not be loaded in previous findAnnotated* call.
*/
public List getClassesNotLoaded() {
return Collections.unmodifiableList(classesNotLoaded);
}
public List findAnnotatedPackages(Class extends Annotation> annotation) {
classesNotLoaded.clear();
List packages = new ArrayList();
List infos = getAnnotationInfos(annotation.getName());
for (Info info : infos) {
if (info instanceof PackageInfo) {
PackageInfo packageInfo = (PackageInfo) info;
try {
Package pkg = packageInfo.get();
// double check via proper reflection
if (pkg.isAnnotationPresent(annotation)) {
packages.add(pkg);
}
} catch (ClassNotFoundException e) {
classesNotLoaded.add(packageInfo.getName());
}
}
}
return packages;
}
public List findAnnotatedClasses(Class extends Annotation> annotation) {
classesNotLoaded.clear();
List classes = new ArrayList();
List infos = getAnnotationInfos(annotation.getName());
for (Info info : infos) {
if (info instanceof ClassInfo) {
ClassInfo classInfo = (ClassInfo) info;
try {
Class clazz = classInfo.get();
// double check via proper reflection
if (clazz.isAnnotationPresent(annotation)) {
classes.add(clazz);
}
} catch (ClassNotFoundException e) {
classesNotLoaded.add(classInfo.getName());
}
}
}
return classes;
}
public List findAnnotatedMethods(Class extends Annotation> annotation) {
classesNotLoaded.clear();
List seen = new ArrayList();
List methods = new ArrayList();
List infos = getAnnotationInfos(annotation.getName());
for (Info info : infos) {
if (info instanceof MethodInfo && !"".equals(info.getName())) {
MethodInfo methodInfo = (MethodInfo) info;
ClassInfo classInfo = methodInfo.getDeclaringClass();
if (seen.contains(classInfo)) continue;
seen.add(classInfo);
try {
Class clazz = classInfo.get();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(annotation)) {
methods.add(method);
}
}
} catch (ClassNotFoundException e) {
classesNotLoaded.add(classInfo.getName());
}
}
}
return methods;
}
public List findAnnotatedConstructors(Class extends Annotation> annotation) {
classesNotLoaded.clear();
List seen = new ArrayList();
List constructors = new ArrayList();
List infos = getAnnotationInfos(annotation.getName());
for (Info info : infos) {
if (info instanceof MethodInfo && "".equals(info.getName())) {
MethodInfo methodInfo = (MethodInfo) info;
ClassInfo classInfo = methodInfo.getDeclaringClass();
if (seen.contains(classInfo)) continue;
seen.add(classInfo);
try {
Class clazz = classInfo.get();
for (Constructor constructor : clazz.getConstructors()) {
if (constructor.isAnnotationPresent(annotation)) {
constructors.add(constructor);
}
}
} catch (ClassNotFoundException e) {
classesNotLoaded.add(classInfo.getName());
}
}
}
return constructors;
}
public List findAnnotatedFields(Class extends Annotation> annotation) {
classesNotLoaded.clear();
List seen = new ArrayList();
List fields = new ArrayList();
List infos = getAnnotationInfos(annotation.getName());
for (Info info : infos) {
if (info instanceof FieldInfo) {
FieldInfo fieldInfo = (FieldInfo) info;
ClassInfo classInfo = fieldInfo.getDeclaringClass();
if (seen.contains(classInfo)) continue;
seen.add(classInfo);
try {
Class clazz = classInfo.get();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(annotation)) {
fields.add(field);
}
}
} catch (ClassNotFoundException e) {
classesNotLoaded.add(classInfo.getName());
}
}
}
return fields;
}
public List findClassesInPackage(String packageName, boolean recursive) {
classesNotLoaded.clear();
List classes = new ArrayList();
for (ClassInfo classInfo : classInfos.values()) {
try {
if (recursive && classInfo.getPackageName().startsWith(packageName)){
classes.add(classInfo.get());
} else if (classInfo.getPackageName().equals(packageName)){
classes.add(classInfo.get());
}
} catch (ClassNotFoundException e) {
classesNotLoaded.add(classInfo.getName());
}
}
return classes;
}
public List findClasses(Test test) {
classesNotLoaded.clear();
List classes = new ArrayList();
for (ClassInfo classInfo : classInfos.values()) {
try {
if (test.test(classInfo)) {
classes.add(classInfo.get());
}
} catch (Throwable e) {
classesNotLoaded.add(classInfo.getName());
}
}
return classes;
}
public List findClasses() {
classesNotLoaded.clear();
List classes = new ArrayList();
for (ClassInfo classInfo : classInfos.values()) {
try {
classes.add(classInfo.get());
} catch (ClassNotFoundException e) {
classesNotLoaded.add(classInfo.getName());
}
}
return classes;
}
private static List getURLs(ClassLoader classLoader, String[] dirNames) {
List urls = new ArrayList();
for (String dirName : dirNames) {
try {
Enumeration classLoaderURLs = classLoader.getResources(dirName);
while (classLoaderURLs.hasMoreElements()) {
URL url = classLoaderURLs.nextElement();
urls.add(url);
}
} catch (IOException ioe) {
if (LOG.isErrorEnabled())
LOG.error("Could not read driectory [#0]", ioe, dirName);
}
}
return urls;
}
private static Collection getUrls(ClassLoader classLoader, boolean excludeParent) throws IOException {
return getUrls(classLoader, excludeParent? classLoader.getParent() : null);
}
private static Collection getUrls(ClassLoader classLoader, ClassLoader excludeParent) throws IOException {
UrlSet urlSet = new UrlSet(classLoader);
if (excludeParent != null){
urlSet = urlSet.exclude(excludeParent);
}
return urlSet.getUrls();
}
private List file(URL location) {
List classNames = new ArrayList();
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 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$", "");
classNames.add(packageName + name);
}
}
}
private List jar(URL location) throws IOException {
String jarPath = location.getFile();
if (jarPath.contains("!")){
jarPath = jarPath.substring(0, jarPath.indexOf("!"));
}
URL url = new URL(jarPath);
InputStream in = url.openStream();
try {
JarInputStream jarStream = new JarInputStream(in);
return jar(jarStream);
} finally {
in.close();
}
}
private List jar(JarInputStream jarStream) throws IOException {
List classNames = new ArrayList();
JarEntry entry;
while ((entry = jarStream.getNextJarEntry()) != null) {
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
continue;
}
String className = entry.getName();
className = className.replaceFirst(".class$", "");
className = className.replace('/', '.');
classNames.add(className);
}
return classNames;
}
public class Annotatable {
private final List annotations = new ArrayList();
public Annotatable(AnnotatedElement element) {
for (Annotation annotation : element.getAnnotations()) {
annotations.add(new AnnotationInfo(annotation.annotationType().getName()));
}
}
public Annotatable() {
}
public List getAnnotations() {
return annotations;
}
}
public static interface Info {
String getName();
List getAnnotations();
}
public class PackageInfo extends Annotatable implements Info {
private final String name;
private final ClassInfo info;
private final Package pkg;
public PackageInfo(Package pkg){
super(pkg);
this.pkg = pkg;
this.name = pkg.getName();
this.info = null;
}
public PackageInfo(String name) {
info = new ClassInfo(name, null);
this.name = name;
this.pkg = null;
}
public String getName() {
return name;
}
public Package get() throws ClassNotFoundException {
return (pkg != null)?pkg:info.get().getPackage();
}
}
public class ClassInfo extends Annotatable implements Info {
private final String name;
private final List methods = new ArrayList();
private final List constructors = new ArrayList();
private final String superType;
private final List interfaces = new ArrayList();
private final List superInterfaces = new ArrayList();
private final List fields = new ArrayList();
private Class> clazz;
private ClassNotFoundException notFound;
public ClassInfo(Class clazz) {
super(clazz);
this.clazz = clazz;
this.name = clazz.getName();
Class superclass = clazz.getSuperclass();
this.superType = superclass != null ? superclass.getName(): null;
}
public ClassInfo(String name, String superType) {
this.name = name;
this.superType = superType;
}
public String getPackageName(){
return name.indexOf(".") > 0 ? name.substring(0, name.lastIndexOf(".")) : "" ;
}
public List getConstructors() {
return constructors;
}
public List getInterfaces() {
return interfaces;
}
public List getSuperInterfaces() {
return superInterfaces;
}
public List getFields() {
return fields;
}
public List getMethods() {
return methods;
}
public String getName() {
return name;
}
public String getSuperType() {
return superType;
}
public Class get() throws ClassNotFoundException {
if (clazz != null) return clazz;
if (notFound != null) throw notFound;
try {
this.clazz = classLoader.loadClass(name);
return clazz;
} catch (ClassNotFoundException notFound) {
classesNotLoaded.add(name);
this.notFound = notFound;
throw notFound;
}
}
@Override
public String toString() {
return name;
}
}
public class MethodInfo extends Annotatable implements Info {
private final ClassInfo declaringClass;
private final String returnType;
private final String name;
private final List> parameterAnnotations = new ArrayList>();
public MethodInfo(ClassInfo info, Constructor constructor){
super(constructor);
this.declaringClass = info;
this.name = "";
this.returnType = Void.TYPE.getName();
}
public MethodInfo(ClassInfo info, Method method){
super(method);
this.declaringClass = info;
this.name = method.getName();
this.returnType = method.getReturnType().getName();
}
public MethodInfo(ClassInfo declarignClass, String name, String returnType) {
this.declaringClass = declarignClass;
this.name = name;
this.returnType = returnType;
}
public List> getParameterAnnotations() {
return parameterAnnotations;
}
public List getParameterAnnotations(int index) {
if (index >= parameterAnnotations.size()) {
for (int i = parameterAnnotations.size(); i <= index; i++) {
List annotationInfos = new ArrayList();
parameterAnnotations.add(i, annotationInfos);
}
}
return parameterAnnotations.get(index);
}
public String getName() {
return name;
}
public ClassInfo getDeclaringClass() {
return declaringClass;
}
public String getReturnType() {
return returnType;
}
@Override
public String toString() {
return declaringClass + "@" + name;
}
}
public class FieldInfo extends Annotatable implements Info {
private final String name;
private final String type;
private final ClassInfo declaringClass;
public FieldInfo(ClassInfo info, Field field){
super(field);
this.declaringClass = info;
this.name = field.getName();
this.type = field.getType().getName();
}
public FieldInfo(ClassInfo declaringClass, String name, String type) {
this.declaringClass = declaringClass;
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public ClassInfo getDeclaringClass() {
return declaringClass;
}
public String getType() {
return type;
}
@Override
public String toString() {
return declaringClass + "#" + name;
}
}
public class AnnotationInfo extends Annotatable implements Info {
private final String name;
public AnnotationInfo(Annotation annotation){
this(annotation.getClass().getName());
}
public AnnotationInfo(Class extends Annotation> annotation) {
this.name = annotation.getName().intern();
}
public AnnotationInfo(String name) {
name = name.replaceAll("^L|;$", "");
name = name.replace('/', '.');
this.name = name.intern();
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
}
}
private List getAnnotationInfos(String name) {
List infos = annotated.get(name);
if (infos == null) {
infos = new ArrayList();
annotated.put(name, infos);
}
return infos;
}
private void readClassDef(String className) {
if (!className.endsWith(".class")) {
className = className.replace('.', '/') + ".class";
}
try {
URL resource = classLoader.getResource(className);
if (resource != null) {
InputStream in = resource.openStream();
try {
ClassReader classReader = new ClassReader(in);
classReader.accept(new InfoBuildingVisitor(), ClassReader.SKIP_DEBUG);
} finally {
in.close();
}
} else {
new Exception("Could not load " + className).printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public class InfoBuildingVisitor extends EmptyVisitor {
private Info info;
public InfoBuildingVisitor() {
}
public InfoBuildingVisitor(Info info) {
this.info = info;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if (name.endsWith("package-info")) {
info = new PackageInfo(javaName(name));
} else {
ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName));
for (String interfce : interfaces) {
classInfo.getInterfaces().add(javaName(interfce));
}
info = classInfo;
classInfos.put(classInfo.getName(), classInfo);
if (extractBaseInterfaces)
extractSuperInterfaces(classInfo);
}
}
private void extractSuperInterfaces(ClassInfo classInfo) {
String superType = classInfo.getSuperType();
if (superType != null) {
ClassInfo base = classInfos.get(superType);
if (base == null) {
//try to load base
String resource = superType.replace('.', '/') + ".class";
readClassDef(resource);
base = classInfos.get(superType);
}
if (base != null) {
List interfaces = classInfo.getSuperInterfaces();
interfaces.addAll(base.getSuperInterfaces());
interfaces.addAll(base.getInterfaces());
}
}
}
private String javaName(String name) {
return (name == null)? null:name.replace('/', '.');
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
AnnotationInfo annotationInfo = new AnnotationInfo(desc);
info.getAnnotations().add(annotationInfo);
getAnnotationInfos(annotationInfo.getName()).add(info);
return new InfoBuildingVisitor(annotationInfo);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
ClassInfo classInfo = ((ClassInfo) info);
FieldInfo fieldInfo = new FieldInfo(classInfo, name, desc);
classInfo.getFields().add(fieldInfo);
return new InfoBuildingVisitor(fieldInfo);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
ClassInfo classInfo = ((ClassInfo) info);
MethodInfo methodInfo = new MethodInfo(classInfo, name, desc);
classInfo.getMethods().add(methodInfo);
return new InfoBuildingVisitor(methodInfo);
}
@Override
public AnnotationVisitor visitParameterAnnotation(int param, String desc, boolean visible) {
MethodInfo methodInfo = ((MethodInfo) info);
List annotationInfos = methodInfo.getParameterAnnotations(param);
AnnotationInfo annotationInfo = new AnnotationInfo(desc);
annotationInfos.add(annotationInfo);
return new InfoBuildingVisitor(annotationInfo);
}
}
}