001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.xbean.finder; 018 019 import java.io.File; 020 import java.io.IOException; 021 import java.io.InputStream; 022 import java.lang.reflect.Constructor; 023 import java.lang.reflect.Field; 024 import java.lang.reflect.Method; 025 import java.net.JarURLConnection; 026 import java.net.URL; 027 import java.net.URLDecoder; 028 import java.util.ArrayList; 029 import java.util.Arrays; 030 import java.util.Collection; 031 import java.util.List; 032 import java.util.jar.JarEntry; 033 import java.util.jar.JarInputStream; 034 035 /** 036 * ClassFinder searches the classpath of the specified classloader for 037 * packages, classes, constructors, methods, or fields with specific annotations. 038 * 039 * For security reasons ASM is used to find the annotations. Classes are not 040 * loaded unless they match the requirements of a called findAnnotated* method. 041 * Once loaded, these classes are cached. 042 * 043 * The getClassesNotLoaded() method can be used immediately after any find* 044 * method to get a list of classes which matched the find requirements (i.e. 045 * contained the annotation), but were unable to be loaded. 046 * 047 * @author David Blevins 048 * @version $Rev: 924423 $ $Date: 2010-03-17 20:06:14 +0100 (Wed, 17 Mar 2010) $ 049 */ 050 public class ClassFinder extends AbstractFinder { 051 052 private final ClassLoader classLoader; 053 054 /** 055 * Creates a ClassFinder that will search the urls in the specified classloader 056 * excluding the urls in the classloader's parent. 057 * 058 * To include the parent classloader, use: 059 * 060 * new ClassFinder(classLoader, false); 061 * 062 * To exclude the parent's parent, use: 063 * 064 * new ClassFinder(classLoader, classLoader.getParent().getParent()); 065 * 066 * @param classLoader source of classes to scan 067 * @throws Exception if something goes wrong 068 */ 069 public ClassFinder(ClassLoader classLoader) throws Exception { 070 this(classLoader, true); 071 } 072 073 /** 074 * Creates a ClassFinder that will search the urls in the specified classloader. 075 * 076 * @param classLoader source of classes to scan 077 * @param excludeParent Allegedly excludes classes from parent classloader, whatever that might mean 078 * @throws Exception if something goes wrong. 079 */ 080 public ClassFinder(ClassLoader classLoader, boolean excludeParent) throws Exception { 081 this(classLoader, getUrls(classLoader, excludeParent)); 082 } 083 084 /** 085 * Creates a ClassFinder that will search the urls in the specified classloader excluding 086 * the urls in the 'exclude' classloader. 087 * 088 * @param classLoader source of classes to scan 089 * @param exclude source of classes to exclude from scanning 090 * @throws Exception if something goes wrong 091 */ 092 public ClassFinder(ClassLoader classLoader, ClassLoader exclude) throws Exception { 093 this(classLoader, getUrls(classLoader, exclude)); 094 } 095 096 public ClassFinder(ClassLoader classLoader, URL url) { 097 this(classLoader, Arrays.asList(url)); 098 } 099 100 public ClassFinder(ClassLoader classLoader, Collection<URL> urls) { 101 this.classLoader = classLoader; 102 103 List<String> classNames = new ArrayList<String>(); 104 for (URL location : urls) { 105 try { 106 if (location.getProtocol().equals("jar")) { 107 classNames.addAll(jar(location)); 108 } else if (location.getProtocol().equals("file")) { 109 try { 110 // See if it's actually a jar 111 URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); 112 JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); 113 juc.getJarFile(); 114 classNames.addAll(jar(jarUrl)); 115 } catch (IOException e) { 116 classNames.addAll(file(location)); 117 } 118 } 119 } catch (Exception e) { 120 e.printStackTrace(); 121 } 122 } 123 124 for (String className : classNames) { 125 readClassDef(className); 126 } 127 } 128 129 public ClassFinder(Class... classes){ 130 this(Arrays.asList(classes)); 131 } 132 133 public ClassFinder(List<Class> classes){ 134 this.classLoader = null; 135 List<Info> infos = new ArrayList<Info>(); 136 List<Package> packages = new ArrayList<Package>(); 137 for (Class clazz : classes) { 138 139 try { 140 Package aPackage = clazz.getPackage(); 141 if (aPackage != null && !packages.contains(aPackage)){ 142 infos.add(new PackageInfo(aPackage)); 143 packages.add(aPackage); 144 } 145 146 ClassInfo classInfo = new ClassInfo(clazz); 147 infos.add(classInfo); 148 classInfos.add(classInfo); 149 for (Method method : clazz.getDeclaredMethods()) { 150 infos.add(new MethodInfo(classInfo, method)); 151 } 152 153 for (Constructor constructor : clazz.getConstructors()) { 154 infos.add(new MethodInfo(classInfo, constructor)); 155 } 156 157 for (Field field : clazz.getDeclaredFields()) { 158 infos.add(new FieldInfo(classInfo, field)); 159 } 160 } catch (NoClassDefFoundError e) { 161 throw new NoClassDefFoundError("Could not fully load class: " + clazz.getName() + "\n due to:" + e.getMessage() + "\n in classLoader: \n" + clazz.getClassLoader()); 162 } 163 } 164 165 for (Info info : infos) { 166 for (AnnotationInfo annotation : info.getAnnotations()) { 167 List<Info> annotationInfos = getAnnotationInfos(annotation.getName()); 168 annotationInfos.add(info); 169 } 170 } 171 } 172 173 private static Collection<URL> getUrls(ClassLoader classLoader, boolean excludeParent) throws IOException { 174 return getUrls(classLoader, excludeParent? classLoader.getParent() : null); 175 } 176 177 private static Collection<URL> getUrls(ClassLoader classLoader, ClassLoader excludeParent) throws IOException { 178 UrlSet urlSet = new UrlSet(classLoader); 179 if (excludeParent != null){ 180 urlSet = urlSet.exclude(excludeParent); 181 } 182 return urlSet.getUrls(); 183 } 184 185 @Override 186 protected URL getResource(String className) { 187 return classLoader.getResource(className); 188 } 189 190 @Override 191 protected Class<?> loadClass(String fixedName) throws ClassNotFoundException { 192 return classLoader.loadClass(fixedName); 193 } 194 195 196 197 private List<String> file(URL location) { 198 List<String> classNames = new ArrayList<String>(); 199 File dir = new File(URLDecoder.decode(location.getPath())); 200 if (dir.getName().equals("META-INF")) { 201 dir = dir.getParentFile(); // Scrape "META-INF" off 202 } 203 if (dir.isDirectory()) { 204 scanDir(dir, classNames, ""); 205 } 206 return classNames; 207 } 208 209 private void scanDir(File dir, List<String> classNames, String packageName) { 210 File[] files = dir.listFiles(); 211 for (File file : files) { 212 if (file.isDirectory()) { 213 scanDir(file, classNames, packageName + file.getName() + "."); 214 } else if (file.getName().endsWith(".class")) { 215 String name = file.getName(); 216 name = name.replaceFirst(".class$", ""); 217 if (name.contains(".")) continue; 218 classNames.add(packageName + name); 219 } 220 } 221 } 222 223 private List<String> jar(URL location) throws IOException { 224 String jarPath = location.getFile(); 225 if (jarPath.indexOf("!") > -1){ 226 jarPath = jarPath.substring(0, jarPath.indexOf("!")); 227 } 228 URL url = new URL(jarPath); 229 InputStream in = url.openStream(); 230 try { 231 JarInputStream jarStream = new JarInputStream(in); 232 return jar(jarStream); 233 } finally { 234 in.close(); 235 } 236 } 237 238 private List<String> jar(JarInputStream jarStream) throws IOException { 239 List<String> classNames = new ArrayList<String>(); 240 241 JarEntry entry; 242 while ((entry = jarStream.getNextJarEntry()) != null) { 243 if (entry.isDirectory() || !entry.getName().endsWith(".class")) { 244 continue; 245 } 246 String className = entry.getName(); 247 className = className.replaceFirst(".class$", ""); 248 if (className.contains(".")) continue; 249 className = className.replace('/', '.'); 250 classNames.add(className); 251 } 252 253 return classNames; 254 } 255 256 protected void readClassDef(String className) { 257 if (!className.endsWith(".class")) { 258 className = className.replace('.', '/') + ".class"; 259 } 260 try { 261 URL resource = getResource(className); 262 if (resource != null) { 263 InputStream in = resource.openStream(); 264 try { 265 readClassDef(in); 266 } finally { 267 in.close(); 268 } 269 } else { 270 new Exception("Could not load " + className).printStackTrace(); 271 } 272 } catch (IOException e) { 273 e.printStackTrace(); 274 } 275 276 } 277 }