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.net.JarURLConnection;
023 import java.net.URL;
024 import java.net.URLDecoder;
025 import java.util.ArrayList;
026 import java.util.Arrays;
027 import java.util.Collection;
028 import java.util.List;
029 import java.util.jar.JarEntry;
030 import 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: 1064425 $ $Date: 2011-01-28 10:13:40 +0800 (Fri, 28 Jan 2011) $
046 */
047 public 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 for (File file : files) {
180 if (file.isDirectory()) {
181 scanDir(file, classNames, packageName + file.getName() + ".");
182 } else if (file.getName().endsWith(".class")) {
183 String name = file.getName();
184 name = name.replaceFirst(".class$", "");
185 if (name.contains(".")) continue;
186 classNames.add(packageName + name);
187 }
188 }
189 }
190
191 private List<String> jar(URL location) throws IOException {
192 String jarPath = location.getFile();
193 if (jarPath.indexOf("!") > -1){
194 jarPath = jarPath.substring(0, jarPath.indexOf("!"));
195 }
196 URL url = new URL(jarPath);
197 InputStream in = url.openStream();
198 try {
199 JarInputStream jarStream = new JarInputStream(in);
200 return jar(jarStream);
201 } finally {
202 in.close();
203 }
204 }
205
206 private List<String> jar(JarInputStream jarStream) throws IOException {
207 List<String> classNames = new ArrayList<String>();
208
209 JarEntry entry;
210 while ((entry = jarStream.getNextJarEntry()) != null) {
211 if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
212 continue;
213 }
214 String className = entry.getName();
215 className = className.replaceFirst(".class$", "");
216 if (className.contains(".")) continue;
217 className = className.replace('/', '.');
218 classNames.add(className);
219 }
220
221 return classNames;
222 }
223
224 }