1
2
3
4
5
6
7
8
9
10 package org.mule.util.scan;
11
12 import org.mule.config.ExceptionHelper;
13 import org.mule.util.ClassUtils;
14 import org.mule.util.FileUtils;
15 import org.mule.util.scan.annotations.AnnotationFilter;
16 import org.mule.util.scan.annotations.AnnotationTypeFilter;
17 import org.mule.util.scan.annotations.AnnotationsScanner;
18 import org.mule.util.scan.annotations.ClosableClassReader;
19 import org.mule.util.scan.annotations.MetaAnnotationTypeFilter;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.lang.annotation.Annotation;
26 import java.lang.annotation.ElementType;
27 import java.lang.annotation.Target;
28 import java.lang.reflect.Modifier;
29 import java.net.URL;
30 import java.util.Collection;
31 import java.util.Enumeration;
32 import java.util.HashSet;
33 import java.util.Set;
34 import java.util.jar.JarEntry;
35 import java.util.jar.JarFile;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.objectweb.asm.ClassReader;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public class ClasspathScanner
59 {
60 public static final int INCLUDE_ABSTRACT = 0x01;
61 public static final int INCLUDE_INTERFACE = 0x02;
62 public static final int INCLUDE_INNER = 0x04;
63 public static final int INCLUDE_ANONYMOUS = 0x08;
64
65 public static final int DEFAULT_FLAGS = 0x0;
66
67
68
69
70 protected transient final Log logger = LogFactory.getLog(ClasspathScanner.class);
71
72 private ClassLoader classLoader;
73
74 private String[] basepaths;
75
76 public ClasspathScanner(String... basepaths)
77 {
78 this.classLoader = Thread.currentThread().getContextClassLoader();
79 this.basepaths = basepaths;
80 }
81
82 public ClasspathScanner(ClassLoader classLoader, String... basepaths)
83 {
84 this.classLoader = classLoader;
85 this.basepaths = basepaths;
86 }
87
88 public <T> Set<Class<T>> scanFor(Class<T> clazz) throws IOException
89 {
90 return scanFor(clazz, DEFAULT_FLAGS);
91 }
92
93 public <T> Set<Class<T>> scanFor(Class<T> clazz, int flags) throws IOException
94 {
95 Set<Class<T>> classes = new HashSet<Class<T>>();
96
97 for (int i = 0; i < basepaths.length; i++)
98 {
99 String basepath = basepaths[i];
100
101 Enumeration<URL> urls = classLoader.getResources(basepath.trim());
102 while (urls.hasMoreElements())
103 {
104 URL url = urls.nextElement();
105 if (url.getProtocol().equalsIgnoreCase("file"))
106 {
107 classes.addAll(processFileUrl(url, basepath, clazz, flags));
108 }
109 else if (url.getProtocol().equalsIgnoreCase("jar"))
110 {
111 classes.addAll(processJarUrl(url, basepath, clazz, flags));
112 }
113 else if (url.getProtocol().equalsIgnoreCase("bundleresource"))
114 {
115 logger.debug("Classpath contains an OSGi bundle resource which Mule does not know how to access, therefore this resource will be ignored: + " + url.toString());
116 }
117 else
118 {
119 throw new IllegalArgumentException("Do not understand how to handle protocol: " + url.getProtocol());
120 }
121 }
122 }
123 return classes;
124 }
125
126 protected <T> Set<Class<T>> processJarUrl(URL url, String basepath, Class<T> clazz, int flags) throws IOException
127 {
128 Set<Class<T>> set = new HashSet<Class<T>>();
129 String path = url.getFile().substring(5, url.getFile().indexOf("!"));
130
131 path = path.replaceAll("%20", " ");
132 JarFile jar = new JarFile(path);
133
134 for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();)
135 {
136 JarEntry entry = entries.nextElement();
137 if (entry.getName().startsWith(basepath) && entry.getName().endsWith(".class"))
138 {
139 try
140 {
141 String name = entry.getName();
142
143 if (name.contains("$") && !hasFlag(flags, INCLUDE_INNER))
144 {
145 continue;
146 }
147
148 URL classURL = classLoader.getResource(name);
149 InputStream classStream = classURL.openStream();
150 ClassReader reader = new ClosableClassReader(classStream);
151
152 ClassScanner visitor = getScanner(clazz);
153 reader.accept(visitor, 0);
154 if (visitor.isMatch())
155 {
156 @SuppressWarnings("unchecked")
157 Class<T> loadedClass = (Class<T>) loadClass(visitor.getClassName());
158 addClassToSet(loadedClass, set, flags);
159 }
160 }
161 catch (Exception e)
162 {
163 if (logger.isDebugEnabled())
164 {
165 Throwable t = ExceptionHelper.getRootException(e);
166 logger.debug(String.format("%s: caused by: %s", e.toString(), t.toString()));
167 }
168 }
169 }
170 }
171 jar.close();
172
173 return set;
174 }
175
176 protected boolean hasFlag(int flags, int flag)
177 {
178 return (flags & flag) != 0;
179 }
180
181 protected <T> Set<Class<T>> processFileUrl(URL url, String basepath, Class<T> clazz, int flags) throws IOException
182 {
183 Set<Class<T>> set = new HashSet<Class<T>>();
184 String urlBase = url.getFile();
185
186 urlBase = urlBase.replaceAll("%20", " ");
187 File dir = new File(urlBase);
188 if(!dir.isDirectory())
189 {
190 logger.warn("Cannot process File URL: " + url + ". Path is not a directory");
191 return set;
192 }
193
194 @SuppressWarnings("unchecked")
195 Collection<File> files = FileUtils.listFiles(new File(urlBase), new String[]{"class"}, true);
196 for (File file : files)
197 {
198 try
199 {
200
201 if (file.getName().contains("$") && !hasFlag(flags, INCLUDE_INNER))
202 {
203 continue;
204 }
205 InputStream classStream = new FileInputStream(file);
206 ClassReader reader = new ClosableClassReader(classStream);
207
208 ClassScanner visitor = getScanner(clazz);
209 reader.accept(visitor, 0);
210 if (visitor.isMatch())
211 {
212 @SuppressWarnings("unchecked")
213 Class<T> loadedClass = (Class<T>) loadClass(visitor.getClassName());
214 addClassToSet(loadedClass, set, flags);
215 }
216 }
217 catch (IOException e)
218 {
219 if (logger.isDebugEnabled())
220 {
221 Throwable t = ExceptionHelper.getRootException(e);
222 logger.debug(String.format("%s: caused by: %s", e.toString(), t.toString()));
223 }
224 }
225 }
226 return set;
227 }
228
229 protected <T> void addClassToSet(Class<T> c, Set<Class<T>> set, int flags)
230 {
231 if (c != null)
232 {
233 synchronized (set)
234 {
235 if (c.isInterface())
236 {
237 if (hasFlag(flags, INCLUDE_INTERFACE))
238 {
239 set.add(c);
240 }
241 }
242 else if (Modifier.isAbstract(c.getModifiers()))
243 {
244 if (hasFlag(flags, INCLUDE_ABSTRACT))
245 {
246 set.add(c);
247 }
248 }
249 else
250 {
251 set.add(c);
252 }
253 }
254 }
255 }
256
257 protected Class<?> loadClass(String name)
258 {
259 String c = name.replace("/", ".");
260 try
261 {
262 return ClassUtils.loadClass(c, classLoader);
263 }
264 catch (ClassNotFoundException e)
265 {
266 if (logger.isWarnEnabled())
267 {
268 logger.warn(String.format("%s : %s", c, e.toString()));
269 }
270 return null;
271 }
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285
286 protected ClassScanner getScanner(Class<?> clazz)
287 {
288 if (clazz.isInterface())
289 {
290 if (clazz.isAnnotation())
291 {
292 @SuppressWarnings("unchecked")
293 Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) clazz;
294
295 AnnotationFilter filter = null;
296 Annotation[] annos = clazz.getDeclaredAnnotations();
297 for (int i = 0; i < annos.length; i++)
298 {
299 Annotation anno = annos[i];
300 if (anno instanceof Target)
301 {
302 if (((Target) anno).value()[0] == ElementType.ANNOTATION_TYPE)
303 {
304 filter = new MetaAnnotationTypeFilter(annotationClass, classLoader);
305 }
306 }
307 }
308 if (filter == null)
309 {
310 filter = new AnnotationTypeFilter(annotationClass);
311 }
312 return new AnnotationsScanner(filter);
313 }
314 else
315 {
316 return new InterfaceClassScanner(clazz);
317 }
318 }
319 else
320 {
321 return new ImplementationClassScanner(clazz);
322 }
323 }
324 }