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