Return-Path: X-Original-To: apmail-struts-commits-archive@minotaur.apache.org Delivered-To: apmail-struts-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 30CA81047A for ; Wed, 31 Dec 2014 17:17:29 +0000 (UTC) Received: (qmail 18536 invoked by uid 500); 31 Dec 2014 17:17:25 -0000 Delivered-To: apmail-struts-commits-archive@struts.apache.org Received: (qmail 18392 invoked by uid 500); 31 Dec 2014 17:17:25 -0000 Mailing-List: contact commits-help@struts.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@struts.apache.org Delivered-To: mailing list commits@struts.apache.org Received: (qmail 17957 invoked by uid 99); 31 Dec 2014 17:17:24 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 31 Dec 2014 17:17:24 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 81089A3B090; Wed, 31 Dec 2014 17:17:24 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: lukaszlenart@apache.org To: commits@struts.apache.org Date: Wed, 31 Dec 2014 17:17:35 -0000 Message-Id: <675725962d93458c902e50ef2d543a01@git.apache.org> In-Reply-To: <31f1c5a94f5a4b3998befe32d5a9acc4@git.apache.org> References: <31f1c5a94f5a4b3998befe32d5a9acc4@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [13/50] [abbrv] struts git commit: Extracts interface with default implementation Extracts interface with default implementation Project: http://git-wip-us.apache.org/repos/asf/struts/repo Commit: http://git-wip-us.apache.org/repos/asf/struts/commit/3ce21ac4 Tree: http://git-wip-us.apache.org/repos/asf/struts/tree/3ce21ac4 Diff: http://git-wip-us.apache.org/repos/asf/struts/diff/3ce21ac4 Branch: refs/heads/master Commit: 3ce21ac4832742241663a671163f40cf12083f99 Parents: 6471eec Author: Lukasz Lenart Authored: Sat Dec 20 21:45:30 2014 +0100 Committer: Lukasz Lenart Committed: Sat Dec 20 21:45:30 2014 +0100 ---------------------------------------------------------------------- .../xwork2/util/finder/ClassFinder.java | 640 ++----------------- .../xwork2/util/finder/DefaultClassFinder.java | 609 ++++++++++++++++++ 2 files changed, 648 insertions(+), 601 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/struts/blob/3ce21ac4/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java ---------------------------------------------------------------------- diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java index f337eb9..aed9981 100644 --- a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/ClassFinder.java @@ -1,452 +1,68 @@ -/* - * Copyright 2002-2003,2009 The Apache Software Foundation. - * - * 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 com.opensymphony.xwork2.util.finder; -import com.opensymphony.xwork2.ActionContext; -import com.opensymphony.xwork2.FileManager; -import com.opensymphony.xwork2.FileManagerFactory; -import com.opensymphony.xwork2.XWorkException; -import com.opensymphony.xwork2.util.logging.Logger; -import com.opensymphony.xwork2.util.logging.LoggerFactory; -import org.apache.commons.lang3.StringUtils; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Opcodes; - -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.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; - -/** - * ClassFinder searches the classpath of the specified ClassLoaderInterface 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 List classesNotLoaded = new ArrayList(); - - private boolean extractBaseInterfaces; - private ClassLoaderInterface classLoaderInterface; - private FileManager fileManager; - - public ClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { - this.classLoaderInterface = classLoaderInterface; - this.extractBaseInterfaces = extractBaseInterfaces; - this.fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager(); - - List classNames = new ArrayList(); - 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 [#0]", e, location.toExternalForm()); - } - } - for (String className : classNames) { - try { - if (classNameFilter.test(className)) - readClassDef(className); - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Unable to read class [#0]", e, className); - } - } - } +public interface ClassFinder { - public ClassFinder(Class... classes){ - this(Arrays.asList(classes)); - } + boolean isAnnotationPresent(Class annotation); - public ClassFinder(List classes){ - this.classLoaderInterface = null; - List infos = new ArrayList(); - List packages = new ArrayList(); - for (Class clazz : classes) { + List getClassesNotLoaded(); - Package aPackage = clazz.getPackage(); - if (aPackage != null && !packages.contains(aPackage)){ - infos.add(new PackageInfo(aPackage)); - packages.add(aPackage); - } + List findAnnotatedPackages(Class annotation); - ClassInfo classInfo = new ClassInfo(clazz); - infos.add(classInfo); - classInfos.put(classInfo.getName(), classInfo); - for (Method method : clazz.getDeclaredMethods()) { - infos.add(new MethodInfo(classInfo, method)); - } + List findAnnotatedClasses(Class annotation); - for (Constructor constructor : clazz.getConstructors()) { - infos.add(new MethodInfo(classInfo, constructor)); - } + List findAnnotatedMethods(Class annotation); - for (Field field : clazz.getDeclaredFields()) { - infos.add(new FieldInfo(classInfo, field)); - } - } + List findAnnotatedConstructors(Class annotation); - for (Info info : infos) { - for (AnnotationInfo annotation : info.getAnnotations()) { - List annotationInfos = getAnnotationInfos(annotation.getName()); - annotationInfos.add(info); - } - } - } + List findAnnotatedFields(Class annotation); - public boolean isAnnotationPresent(Class annotation) { - List infos = annotated.get(annotation.getName()); - return infos != null && !infos.isEmpty(); - } + List findClassesInPackage(String packageName, boolean recursive); - /** - * 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); - } + List findClasses(Test test); - public List findAnnotatedPackages(Class 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; - } + List findClasses(); - public List findAnnotatedClasses(Class 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 (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return classes; - } - - public List findAnnotatedMethods(Class 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 (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return methods; - } - - public List findAnnotatedConstructors(Class 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 (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return constructors; - } + ClassLoaderInterface getClassLoaderInterface(); - public List findAnnotatedFields(Class 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 (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - } - return fields; - } + public static interface Info { + String getName(); - 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 (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - return classes; + List getAnnotations(); } - 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) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } - } - return classes; - } + public class AnnotationInfo extends Annotatable implements Info { + private final String name; - public List findClasses() { - classesNotLoaded.clear(); - List classes = new ArrayList(); - for (ClassInfo classInfo : classInfos.values()) { - try { - classes.add(classInfo.get()); - } catch (Throwable e) { - if (LOG.isErrorEnabled()) - LOG.error("Error loading class [#0]", e, classInfo.getName()); - classesNotLoaded.add(classInfo.getName()); - } + public AnnotationInfo(Annotation annotation){ + this(annotation.getClass().getName()); } - return classes; - } - private static List getURLs(ClassLoaderInterface 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); - } + public AnnotationInfo(Class annotation) { + this.name = annotation.getName().intern(); } - return urls; - } - - 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, ""); + public AnnotationInfo(String name) { + name = name.replaceAll("^L|;$", ""); + name = name.replace('/', '.'); + this.name = name.intern(); } - 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$", ""); - // 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); - } + public String getName() { + return name; } - } - - private List jar(URL location) throws IOException { - URL url = fileManager.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 [#0]", location.toExternalForm()); - - return Collections.emptyList(); - } - - 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$", ""); - //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); + @Override + public String toString() { + return name; } - - return classNames; } public class Annotatable { @@ -467,12 +83,6 @@ public class ClassFinder { } - public static interface Info { - String getName(); - - List getAnnotations(); - } - public class PackageInfo extends Annotatable implements Info { private final String name; private final ClassInfo info; @@ -485,8 +95,8 @@ public class ClassFinder { this.info = null; } - public PackageInfo(String name) { - info = new ClassInfo(name, null); + public PackageInfo(String name, ClassFinder classFinder) { + info = new ClassInfo(name, null, classFinder); this.name = name; this.pkg = null; } @@ -509,19 +119,22 @@ public class ClassFinder { private final List superInterfaces = new ArrayList(); private final List fields = new ArrayList(); private Class clazz; + private ClassFinder classFinder; private ClassNotFoundException notFound; - public ClassInfo(Class clazz) { + public ClassInfo(Class clazz, ClassFinder classFinder) { super(clazz); this.clazz = clazz; + this.classFinder = classFinder; this.name = clazz.getName(); Class superclass = clazz.getSuperclass(); this.superType = superclass != null ? superclass.getName(): null; } - public ClassInfo(String name, String superType) { + public ClassInfo(String name, String superType, ClassFinder classFinder) { this.name = name; this.superType = superType; + this.classFinder = classFinder; } public String getPackageName(){ @@ -560,10 +173,10 @@ public class ClassFinder { if (clazz != null) return clazz; if (notFound != null) throw notFound; try { - this.clazz = classLoaderInterface.loadClass(name); + this.clazz = classFinder.getClassLoaderInterface().loadClass(name); return clazz; } catch (ClassNotFoundException notFound) { - classesNotLoaded.add(name); + classFinder.getClassesNotLoaded().add(name); this.notFound = notFound; throw notFound; } @@ -669,179 +282,4 @@ public class ClassFinder { } } - public class AnnotationInfo extends Annotatable implements Info { - private final String name; - - public AnnotationInfo(Annotation annotation){ - this(annotation.getClass().getName()); - } - - public AnnotationInfo(Class 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 = classLoaderInterface.getResource(className); - if (resource != null) { - InputStream in = resource.openStream(); - try { - ClassReader classReader = new ClassReader(in); - classReader.accept(new InfoBuildingClassVisitor(), ClassReader.SKIP_DEBUG); - } finally { - in.close(); - } - } else { - throw new XWorkException("Could not load " + className); - } - } catch (IOException e) { - throw new XWorkException("Could not load " + className, e); - } - - } - - public class InfoBuildingClassVisitor extends ClassVisitor { - private Info info; - - public InfoBuildingClassVisitor() { - super(Opcodes.ASM5); - } - - public InfoBuildingClassVisitor(Info info) { - this(); - 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 null; - } - - @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 null; - } - - @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 InfoBuildingMethodVisitor(methodInfo); - } - } - - public class InfoBuildingMethodVisitor extends MethodVisitor { - private Info info; - - public InfoBuildingMethodVisitor() { - super(Opcodes.ASM5); - } - - public InfoBuildingMethodVisitor(Info info) { - this(); - this.info = info; - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - AnnotationInfo annotationInfo = new AnnotationInfo(desc); - info.getAnnotations().add(annotationInfo); - getAnnotationInfos(annotationInfo.getName()).add(info); - return null; - } - - @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 null; - } - } - - private static final class DefaultClassnameFilterImpl implements Test { - public boolean test(String className) { - return true; - } - } } - http://git-wip-us.apache.org/repos/asf/struts/blob/3ce21ac4/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java ---------------------------------------------------------------------- diff --git a/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java new file mode 100644 index 0000000..192196a --- /dev/null +++ b/xwork-core/src/main/java/com/opensymphony/xwork2/util/finder/DefaultClassFinder.java @@ -0,0 +1,609 @@ +/* + * Copyright 2002-2003,2009 The Apache Software Foundation. + * + * 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 com.opensymphony.xwork2.util.finder; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.FileManager; +import com.opensymphony.xwork2.FileManagerFactory; +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.util.logging.Logger; +import com.opensymphony.xwork2.util.logging.LoggerFactory; +import org.apache.commons.lang3.StringUtils; +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.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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * ClassFinder searches the classpath of the specified ClassLoaderInterface 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 DefaultClassFinder implements ClassFinder { + private static final Logger LOG = LoggerFactory.getLogger(DefaultClassFinder.class); + + private final Map> annotated = new HashMap>(); + private final Map classInfos = new LinkedHashMap(); + + private final List classesNotLoaded = new ArrayList(); + + private boolean extractBaseInterfaces; + private ClassLoaderInterface classLoaderInterface; + private FileManager fileManager; + + public DefaultClassFinder(ClassLoaderInterface classLoaderInterface, Collection urls, boolean extractBaseInterfaces, Set protocols, Test classNameFilter) { + this.classLoaderInterface = classLoaderInterface; + this.extractBaseInterfaces = extractBaseInterfaces; + this.fileManager = ActionContext.getContext().getInstance(FileManagerFactory.class).getFileManager(); + + List classNames = new ArrayList(); + 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 [#0]", e, location.toExternalForm()); + } + } + + for (String className : classNames) { + try { + if (classNameFilter.test(className)) + readClassDef(className); + } catch (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Unable to read class [#0]", e, className); + } + } + } + + public DefaultClassFinder(Class... classes){ + this(Arrays.asList(classes)); + } + + public DefaultClassFinder(List classes){ + this.classLoaderInterface = 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, this); + 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 ClassLoaderInterface getClassLoaderInterface() { + return classLoaderInterface; + } + + public boolean isAnnotationPresent(Class 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 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 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 (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return classes; + } + + public List findAnnotatedMethods(Class 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 (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return methods; + } + + public List findAnnotatedConstructors(Class 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 (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + } + return constructors; + } + + public List findAnnotatedFields(Class 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 (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + 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 (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + 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) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + 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 (Throwable e) { + if (LOG.isErrorEnabled()) + LOG.error("Error loading class [#0]", e, classInfo.getName()); + classesNotLoaded.add(classInfo.getName()); + } + } + return classes; + } + + private static List getURLs(ClassLoaderInterface 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 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$", ""); + // 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 jar(URL location) throws IOException { + URL url = fileManager.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 [#0]", location.toExternalForm()); + + return Collections.emptyList(); + } + + 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$", ""); + + //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 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, ClassFinder classFinder) { + info = new ClassInfo(name, null, classFinder); + this.name = name; + this.pkg = null; + } + + public String getName() { + return name; + } + + public Package get() throws ClassNotFoundException { + return (pkg != null)?pkg:info.get().getPackage(); + } + } + + 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 = classLoaderInterface.getResource(className); + if (resource != null) { + InputStream in = resource.openStream(); + try { + ClassReader classReader = new ClassReader(in); + classReader.accept(new InfoBuildingVisitor(this), ClassReader.SKIP_DEBUG); + } finally { + in.close(); + } + } else { + throw new XWorkException("Could not load " + className); + } + } catch (IOException e) { + throw new XWorkException("Could not load " + className, e); + } + + } + + public class InfoBuildingVisitor extends EmptyVisitor { + private Info info; + private ClassFinder classFinder; + + public InfoBuildingVisitor(ClassFinder classFinder) { + this.classFinder = classFinder; + } + + public InfoBuildingVisitor(Info info, ClassFinder classFinder) { + this.info = info; + this.classFinder = classFinder; + } + + @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), classFinder); + } else { + ClassInfo classInfo = new ClassInfo(javaName(name), javaName(superName), classFinder); + + 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, classFinder); + } + + @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, classFinder); + } + + @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, classFinder); + } + + @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, classFinder); + } + } + + private static final class DefaultClassnameFilterImpl implements Test { + public boolean test(String className) { + return true; + } + } +} +