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 */ 017package org.apache.xbean.finder; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.net.JarURLConnection; 023import java.net.URL; 024import java.net.URLDecoder; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collection; 028import java.util.List; 029import java.util.jar.JarEntry; 030import java.util.jar.JarInputStream; 031 032/** 033 * ClassFinder searches the classpath of the specified classloader for 034 * packages, classes, constructors, methods, or fields with specific annotations. 035 * 036 * For security reasons ASM is used to find the annotations. Classes are not 037 * loaded unless they match the requirements of a called findAnnotated* method. 038 * Once loaded, these classes are cached. 039 * 040 * The getClassesNotLoaded() method can be used immediately after any find* 041 * method to get a list of classes which matched the find requirements (i.e. 042 * contained the annotation), but were unable to be loaded. 043 * 044 * @author David Blevins 045 * @version $Rev: 1778104 $ $Date: 2017-01-10 11:05:25 +0100 (Tue, 10 Jan 2017) $ 046 */ 047public class ClassFinder extends AbstractFinder { 048 049 private final ClassLoader classLoader; 050 051 /** 052 * Creates a ClassFinder that will search the urls in the specified classloader 053 * excluding the urls in the classloader's parent. 054 * 055 * To include the parent classloader, use: 056 * 057 * new ClassFinder(classLoader, false); 058 * 059 * To exclude the parent's parent, use: 060 * 061 * new ClassFinder(classLoader, classLoader.getParent().getParent()); 062 * 063 * @param classLoader source of classes to scan 064 * @throws Exception if something goes wrong 065 */ 066 public ClassFinder(ClassLoader classLoader) throws Exception { 067 this(classLoader, true); 068 } 069 070 /** 071 * Creates a ClassFinder that will search the urls in the specified classloader. 072 * 073 * @param classLoader source of classes to scan 074 * @param excludeParent Allegedly excludes classes from parent classloader, whatever that might mean 075 * @throws Exception if something goes wrong. 076 */ 077 public ClassFinder(ClassLoader classLoader, boolean excludeParent) throws Exception { 078 this(classLoader, getUrls(classLoader, excludeParent)); 079 } 080 081 /** 082 * Creates a ClassFinder that will search the urls in the specified classloader excluding 083 * the urls in the 'exclude' classloader. 084 * 085 * @param classLoader source of classes to scan 086 * @param exclude source of classes to exclude from scanning 087 * @throws Exception if something goes wrong 088 */ 089 public ClassFinder(ClassLoader classLoader, ClassLoader exclude) throws Exception { 090 this(classLoader, getUrls(classLoader, exclude)); 091 } 092 093 public ClassFinder(ClassLoader classLoader, URL url) { 094 this(classLoader, Arrays.asList(url)); 095 } 096 097 public ClassFinder(ClassLoader classLoader, Collection<URL> urls) { 098 this.classLoader = classLoader; 099 100 List<String> classNames = new ArrayList<String>(); 101 for (URL location : urls) { 102 try { 103 if (location.getProtocol().equals("jar")) { 104 classNames.addAll(jar(location)); 105 } else if (location.getProtocol().equals("file")) { 106 try { 107 // See if it's actually a jar 108 URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/"); 109 JarURLConnection juc = (JarURLConnection) jarUrl.openConnection(); 110 juc.getJarFile(); 111 classNames.addAll(jar(jarUrl)); 112 } catch (IOException e) { 113 classNames.addAll(file(location)); 114 } 115 } 116 } catch (Exception e) { 117 e.printStackTrace(); 118 } 119 } 120 121 for (String className : classNames) { 122 readClassDef(className); 123 } 124 } 125 126 public ClassFinder(Class<?>... classes){ 127 this(Arrays.asList(classes)); 128 } 129 130 public ClassFinder(List<Class<?>> classes){ 131 this.classLoader = null; 132 for (Class<?> clazz : classes) { 133 try { 134 readClassDef(clazz); 135 } catch (NoClassDefFoundError e) { 136 throw new NoClassDefFoundError("Could not fully load class: " + clazz.getName() + "\n due to:" + e.getMessage() + "\n in classLoader: \n" + clazz.getClassLoader()); 137 } 138 } 139 } 140 141 private static Collection<URL> getUrls(ClassLoader classLoader, boolean excludeParent) throws IOException { 142 return getUrls(classLoader, excludeParent? classLoader.getParent() : null); 143 } 144 145 private static Collection<URL> getUrls(ClassLoader classLoader, ClassLoader excludeParent) throws IOException { 146 UrlSet urlSet = new UrlSet(classLoader); 147 if (excludeParent != null){ 148 urlSet = urlSet.exclude(excludeParent); 149 } 150 return urlSet.getUrls(); 151 } 152 153 @Override 154 protected URL getResource(String className) { 155 return classLoader.getResource(className); 156 } 157 158 @Override 159 protected Class<?> loadClass(String fixedName) throws ClassNotFoundException { 160 return classLoader.loadClass(fixedName); 161 } 162 163 164 165 private List<String> file(URL location) { 166 List<String> classNames = new ArrayList<String>(); 167 File dir = new File(URLDecoder.decode(location.getPath())); 168 if (dir.getName().equals("META-INF")) { 169 dir = dir.getParentFile(); // Scrape "META-INF" off 170 } 171 if (dir.isDirectory()) { 172 scanDir(dir, classNames, ""); 173 } 174 return classNames; 175 } 176 177 private void scanDir(File dir, List<String> classNames, String packageName) { 178 File[] files = dir.listFiles(); 179 if (files == null) { 180 return; 181 } 182 for (File file : files) { 183 if (file.isDirectory()) { 184 scanDir(file, classNames, packageName + file.getName() + "."); 185 } else if (file.getName().endsWith(".class")) { 186 String name = file.getName(); 187 name = name.replaceFirst(".class$", ""); 188 if (name.contains(".")) continue; 189 classNames.add(packageName + name); 190 } 191 } 192 } 193 194 private List<String> jar(URL location) throws IOException { 195 String jarPath = location.getFile(); 196 if (jarPath.indexOf("!") > -1){ 197 jarPath = jarPath.substring(0, jarPath.indexOf("!")); 198 } 199 URL url = new URL(jarPath); 200 InputStream in = url.openStream(); 201 try { 202 JarInputStream jarStream = new JarInputStream(in); 203 return jar(jarStream); 204 } finally { 205 in.close(); 206 } 207 } 208 209 private List<String> jar(JarInputStream jarStream) throws IOException { 210 List<String> classNames = new ArrayList<String>(); 211 212 JarEntry entry; 213 while ((entry = jarStream.getNextJarEntry()) != null) { 214 if (entry.isDirectory() || !entry.getName().endsWith(".class")) { 215 continue; 216 } 217 String className = entry.getName(); 218 className = className.replaceFirst(".class$", ""); 219 if (className.contains(".")) continue; 220 className = className.replace('/', '.'); 221 classNames.add(className); 222 } 223 224 return classNames; 225 } 226 227}