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