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 15:06:14 -0400 (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 }