1 package com.atlassian.plugins.rest.module.scanner;
2
3 import org.apache.commons.lang.StringUtils;
4 import org.apache.commons.lang.Validate;
5 import org.objectweb.asm.AnnotationVisitor;
6 import org.objectweb.asm.Attribute;
7 import org.objectweb.asm.ClassReader;
8 import org.objectweb.asm.ClassVisitor;
9 import org.objectweb.asm.FieldVisitor;
10 import org.objectweb.asm.Label;
11 import org.objectweb.asm.MethodVisitor;
12 import org.objectweb.asm.Opcodes;
13 import org.osgi.framework.Bundle;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.io.UnsupportedEncodingException;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.net.URLDecoder;
24 import java.util.Enumeration;
25 import java.util.HashSet;
26 import java.util.Set;
27 import java.util.jar.JarEntry;
28 import java.util.jar.JarFile;
29
30
31
32
33
34
35 public final class AnnotatedClassScanner
36 {
37 private final static Logger LOGGER = LoggerFactory.getLogger(AnnotatedClassScanner.class);
38
39 private final Bundle bundle;
40 private final Set<String> annotations;
41
42 public AnnotatedClassScanner(Bundle bundle, Class<?>... annotations)
43 {
44 Validate.notNull(bundle);
45 Validate.notEmpty(annotations, "You gotta scan for something!");
46
47 this.bundle = bundle;
48 this.annotations = getAnnotationSet(annotations);
49 }
50
51 public Set<Class<?>> scan(String... basePackages)
52 {
53 final File bundleFile = getBundleFile(bundle);
54 if (!bundleFile.isFile() || !bundleFile.exists())
55 {
56 throw new RuntimeException("Could not identify Bundle at location <" + bundle.getLocation() + ">");
57 }
58 return indexJar(bundleFile, preparePackages(basePackages));
59 }
60
61 File getBundleFile(Bundle bundle)
62 {
63 final String bundleLocation = bundle.getLocation();
64 final File bundleFile;
65 if (bundleLocation.startsWith("file:"))
66 {
67 try
68 {
69 bundleFile = new File(URLDecoder.decode(new URL(bundleLocation).getFile(), "UTF-8"));
70 }
71 catch (MalformedURLException e)
72 {
73 throw new RuntimeException("Could not parse Bundle location as URL <" + bundleLocation + ">", e);
74 }
75 catch (UnsupportedEncodingException e)
76 {
77 throw new IllegalStateException("Obviously something is wrong with your JVM... It doesn't support UTF-8 !?!", e);
78 }
79 }
80 else
81 {
82 bundleFile = new File(bundleLocation);
83 }
84 return bundleFile;
85 }
86
87 private Set<String> preparePackages(String... packages)
88 {
89 final Set<String> packageNames = new HashSet<String>();
90 for (String packageName : packages)
91 {
92 final String newPackageName = StringUtils.replaceChars(packageName, '.', '/');
93
94
95
96 if (!newPackageName.endsWith("/"))
97 {
98 packageNames.add(newPackageName + '/');
99 }
100 else
101 {
102 packageNames.add(newPackageName);
103 }
104 }
105
106 return packageNames;
107 }
108
109 private Set<String> getAnnotationSet(Class... annotations)
110 {
111 final Set<String> formatedAnnotations = new HashSet<String>();
112 for (Class cls : annotations)
113 {
114 formatedAnnotations.add("L" + cls.getName().replaceAll("\\.", "/") + ";");
115 }
116 return formatedAnnotations;
117 }
118
119 private Set<Class<?>> indexJar(File file, Set<String> packageNames)
120 {
121
122 final Set<Class<?>> classes = new HashSet<Class<?>>()
123 {
124 @Override
125 public boolean add(Class<?> c)
126 {
127 return c != null && super.add(c);
128 }
129 };
130
131 JarFile jar = null;
132 try
133 {
134 jar = new JarFile(file);
135 for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();)
136 {
137 final JarEntry jarEntry = entries.nextElement();
138 if (!jarEntry.isDirectory() && jarEntry.getName().endsWith(".class"))
139 {
140 if (packageNames.isEmpty())
141 {
142 classes.add(analyzeClassFile(jar, jarEntry));
143 }
144 else
145 {
146 for (String packageName : packageNames)
147 {
148 if (jarEntry.getName().startsWith(packageName))
149 {
150 classes.add(analyzeClassFile(jar, jarEntry));
151 break;
152 }
153 }
154 }
155 }
156 }
157 }
158 catch (Exception e)
159 {
160 LOGGER.error("Exception while processing file, " + file, e);
161 }
162 finally
163 {
164 try
165 {
166 if (jar != null)
167 {
168 jar.close();
169 }
170 }
171 catch (IOException ex)
172 {
173 LOGGER.error("Error closing jar file, {}", jar.getName());
174 }
175 }
176 return classes;
177 }
178
179 private Class analyzeClassFile(JarFile jarFile, JarEntry entry)
180 {
181 final AnnotatedClassVisitor visitor = new AnnotatedClassVisitor(annotations);
182 getClassReader(jarFile, entry).accept(visitor, 0);
183 return visitor.hasAnnotation() ? getClassForName(visitor.className) : null;
184 }
185
186 private ClassReader getClassReader(JarFile jarFile, JarEntry entry)
187 {
188 InputStream is = null;
189 try
190 {
191 is = jarFile.getInputStream(entry);
192 return new ClassReader(is);
193 }
194 catch (IOException ex)
195 {
196 throw new RuntimeException("Error accessing input stream of the jar file, " + jarFile.getName() + ", entry, " + entry.getName(), ex);
197 }
198 finally
199 {
200 try
201 {
202 if (is != null)
203 {
204 is.close();
205 }
206 }
207 catch (IOException ex)
208 {
209 LOGGER.error("Error closing input stream of the jar file, {}, entry, {}, closed.", jarFile.getName(), entry.getName());
210 }
211 }
212 }
213
214 private Class getClassForName(String className)
215 {
216 try
217 {
218 return bundle.loadClass(className.replaceAll("/", "."));
219 }
220 catch (ClassNotFoundException ex)
221 {
222 throw new RuntimeException("A class file of the class name, " + className + " is identified but the class could not be loaded", ex);
223 }
224 }
225
226 private static final class AnnotatedClassVisitor implements ClassVisitor
227 {
228
229 private final Set<String> annotations;
230
231
232
233
234 private String className;
235
236
237
238
239 private boolean isScoped;
240
241
242
243 private boolean isAnnotated;
244
245 public AnnotatedClassVisitor(Set<String> annotations)
246 {
247 this.annotations = annotations;
248 }
249
250 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
251 {
252 className = name;
253 isScoped = (access & Opcodes.ACC_PUBLIC) != 0;
254 isAnnotated = false;
255 }
256
257 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
258 {
259 isAnnotated |= annotations.contains(desc);
260 return null;
261 }
262
263 public void visitInnerClass(String name, String outerName, String innerName, int access)
264 {
265
266
267 if (className.equals(name))
268 {
269 isScoped = (access & Opcodes.ACC_PUBLIC) != 0;
270
271 isScoped &= (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
272 }
273 }
274
275 boolean hasAnnotation()
276 {
277 return isScoped && isAnnotated;
278 }
279
280 public void visitEnd()
281 {
282 }
283
284
285 public void visitOuterClass(String string, String string0, String string1)
286 {
287 }
288
289 public FieldVisitor visitField(int i, String string, String string0, String string1, Object object)
290 {
291 return null;
292 }
293
294 public void visitSource(String string, String string0)
295 {
296 }
297
298 public void visitAttribute(Attribute attribute)
299 {
300 }
301
302 public MethodVisitor visitMethod(int i, String string, String string0, String string1, String[] string2)
303 {
304 if (isAnnotated)
305 {
306
307 return null;
308 }
309
310 return new MethodVisitor()
311 {
312 public AnnotationVisitor visitAnnotationDefault()
313 {
314 return null;
315 }
316
317 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
318 {
319 isAnnotated |= annotations.contains(desc);
320 return null;
321 }
322
323 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible)
324 {
325 return null;
326 }
327
328 public void visitAttribute(Attribute attr)
329 {
330 }
331
332 public void visitCode()
333 {
334 }
335
336 public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack)
337 {
338 }
339
340 public void visitInsn(int opcode)
341 {
342 }
343
344 public void visitIntInsn(int opcode, int operand)
345 {
346 }
347
348 public void visitVarInsn(int opcode, int var)
349 {
350 }
351
352 public void visitTypeInsn(int opcode, String type)
353 {
354 }
355
356 public void visitFieldInsn(int opcode, String owner, String name, String desc)
357 {
358 }
359
360 public void visitMethodInsn(int opcode, String owner, String name, String desc)
361 {
362 }
363
364 public void visitJumpInsn(int opcode, Label label)
365 {
366 }
367
368 public void visitLabel(Label label)
369 {
370 }
371
372 public void visitLdcInsn(Object cst)
373 {
374 }
375
376 public void visitIincInsn(int var, int increment)
377 {
378 }
379
380 public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels)
381 {
382 }
383
384 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)
385 {
386 }
387
388 public void visitMultiANewArrayInsn(String desc, int dims)
389 {
390 }
391
392 public void visitTryCatchBlock(Label start, Label end, Label handler, String type)
393 {
394 }
395
396 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)
397 {
398 }
399
400 public void visitLineNumber(int line, Label start)
401 {
402 }
403
404 public void visitMaxs(int maxStack, int maxLocals)
405 {
406 }
407
408 public void visitEnd()
409 {
410 }
411 };
412 }
413 }
414 }