1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mortbay.jetty.annotations;
17
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.lang.reflect.Array;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Method;
23 import java.net.URL;
24 import java.net.URLClassLoader;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.jar.JarEntry;
32 import java.util.regex.Pattern;
33
34 import org.mortbay.jetty.webapp.JarScanner;
35 import org.mortbay.log.Log;
36 import org.mortbay.resource.Resource;
37 import org.mortbay.util.Loader;
38 import org.objectweb.asm.AnnotationVisitor;
39 import org.objectweb.asm.ClassReader;
40 import org.objectweb.asm.FieldVisitor;
41 import org.objectweb.asm.MethodVisitor;
42 import org.objectweb.asm.Type;
43 import org.objectweb.asm.commons.EmptyVisitor;
44
45
46
47
48
49
50
51
52
53
54 public class AnnotationFinder
55 {
56 private Map<String,ParsedClass> parsedClasses = new HashMap<String, ParsedClass>();
57
58
59 public static String normalize (String name)
60 {
61 if (name==null)
62 return null;
63
64 if (name.startsWith("L") && name.endsWith(";"))
65 name = name.substring(1, name.length()-1);
66
67 if (name.endsWith(".class"))
68 name = name.substring(0, name.length()-".class".length());
69
70 name = name.replace('$', '.');
71
72 return name.replace('/', '.');
73 }
74
75 public static Class convertType (org.objectweb.asm.Type t)
76 throws Exception
77 {
78 if (t == null)
79 return (Class)null;
80
81 switch (t.getSort())
82 {
83 case Type.BOOLEAN:
84 {
85 return Boolean.TYPE;
86 }
87 case Type.ARRAY:
88 {
89 Class clazz = convertType(t.getElementType());
90 return Array.newInstance(clazz, 0).getClass();
91 }
92 case Type.BYTE:
93 {
94 return Byte.TYPE;
95 }
96 case Type.CHAR:
97 {
98 return Character.TYPE;
99 }
100 case Type.DOUBLE:
101 {
102 return Double.TYPE;
103 }
104 case Type.FLOAT:
105 {
106 return Float.TYPE;
107 }
108 case Type.INT:
109 {
110 return Integer.TYPE;
111 }
112 case Type.LONG:
113 {
114 return Long.TYPE;
115 }
116 case Type.OBJECT:
117 {
118 return (Loader.loadClass(null, t.getClassName()));
119 }
120 case Type.SHORT:
121 {
122 return Short.TYPE;
123 }
124 case Type.VOID:
125 {
126 return null;
127 }
128 default:
129 return null;
130 }
131
132 }
133
134 public static Class[] convertTypes (Type[] types)
135 throws Exception
136 {
137 if (types==null)
138 return new Class[0];
139
140 Class[] classArray = new Class[types.length];
141
142 for (int i=0; i<types.length; i++)
143 {
144 classArray[i] = convertType(types[i]);
145 }
146 return classArray;
147 }
148
149
150
151
152
153
154
155 public static class AnnotatedStructure extends EmptyVisitor
156 {
157 Map<String, Map<String, Object>> annotations = new HashMap<String, Map<String,Object>>();
158
159
160 public AnnotationVisitor addAnnotation (final String name)
161 {
162 final HashMap<String,Object> annotationValues = new HashMap<String,Object>();
163 this.annotations.put(normalize(name), annotationValues);
164 return new AnnotationVisitor()
165 {
166 public void visit(String name, Object value)
167 {
168 annotationValues.put(name, value);
169 }
170
171 public AnnotationVisitor visitAnnotation(String name, String desc)
172 {
173 return null;
174 }
175
176 public AnnotationVisitor visitArray(String arg0)
177 {
178 return null;
179 }
180
181 public void visitEnd()
182 {
183 }
184
185 public void visitEnum(String name, String desc, String value)
186 {
187 }
188 };
189 }
190
191 public Map<String, Map<String, Object>> getAnnotations ()
192 {
193 return annotations;
194 }
195
196
197 public String toString()
198 {
199 StringBuffer strbuff = new StringBuffer();
200
201 for (Map.Entry<String, Map<String,Object>> e: annotations.entrySet())
202 {
203 strbuff.append(e.getKey()+"\n");
204 for (Map.Entry<String,Object> v: e.getValue().entrySet())
205 {
206 strbuff.append("\t"+v.getKey()+"="+v.getValue()+", ");
207 }
208 }
209 return strbuff.toString();
210 }
211 }
212
213
214
215
216
217
218
219 public static class ParsedClass extends AnnotatedStructure
220 {
221 String className;
222 String superClassName;
223 Class clazz;
224 List<ParsedMethod> methods = new ArrayList<ParsedMethod>();
225 List<ParsedField> fields = new ArrayList<ParsedField>();
226
227
228 public ParsedClass (String className, String superClassName)
229 {
230 this.className = normalize(className);
231 this.superClassName = normalize(superClassName);
232 }
233
234 public String getClassName()
235 {
236 return this.className;
237 }
238
239 public String getSuperClassName ()
240 {
241 return this.superClassName;
242 }
243
244 public Class toClass ()
245 throws ClassNotFoundException
246 {
247 if (clazz==null)
248 clazz = Loader.loadClass(null, className);
249 return clazz;
250 }
251
252 public List<ParsedMethod> getMethods ()
253 {
254 return methods;
255 }
256
257 public ParsedMethod getMethod(String name, String paramString)
258 {
259 Iterator<ParsedMethod> itor = methods.iterator();
260 ParsedMethod method = null;
261 while (itor.hasNext() && method==null)
262 {
263 ParsedMethod m = itor.next();
264 if (m.matches(name, paramString))
265 method = m;
266 }
267
268 return method;
269 }
270
271 public void addMethod (ParsedMethod m)
272 {
273 if (getMethod(m.methodName, m.paramString)!= null)
274 return;
275 methods.add(m);
276 }
277
278 public List<ParsedField> getFields()
279 {
280 return fields;
281 }
282
283 public ParsedField getField(String name)
284 {
285 Iterator<ParsedField> itor = fields.iterator();
286 ParsedField field = null;
287 while (itor.hasNext() && field==null)
288 {
289 ParsedField f = itor.next();
290 if (f.matches(name))
291 field=f;
292 }
293 return field;
294 }
295
296 public void addField (ParsedField f)
297 {
298 if (getField(f.fieldName) != null)
299 return;
300 fields.add(f);
301 }
302
303 public String toString ()
304 {
305 StringBuffer strbuff = new StringBuffer();
306 strbuff.append(this.className+"\n");
307 strbuff.append("Class annotations\n"+super.toString());
308 strbuff.append("\n");
309 strbuff.append("Method annotations\n");
310 for (ParsedMethod p:methods)
311 strbuff.append(p+"\n");
312 strbuff.append("\n");
313 strbuff.append("Field annotations\n");
314 for (ParsedField f:fields)
315 strbuff.append(f+"\n");
316 strbuff.append("\n");
317 return strbuff.toString();
318 }
319 }
320
321
322
323
324
325
326
327 public static class ParsedMethod extends AnnotatedStructure
328 {
329 ParsedClass pclass;
330 String methodName;
331 String paramString;
332 Method method;
333
334
335 public ParsedMethod(ParsedClass pclass, String name, String paramString)
336 {
337 this.pclass=pclass;
338 this.methodName=name;
339 this.paramString=paramString;
340 }
341
342
343
344 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
345 {
346 this.pclass.addMethod(this);
347 return addAnnotation(desc);
348 }
349
350 public Method toMethod ()
351 throws Exception
352 {
353 if (method == null)
354 {
355 Type[] types = null;
356 if (paramString!=null)
357 types = Type.getArgumentTypes(paramString);
358
359 Class[] args = convertTypes(types);
360 method = pclass.toClass().getDeclaredMethod(methodName, args);
361 }
362
363 return method;
364 }
365
366 public boolean matches (String name, String paramString)
367 {
368 if (!methodName.equals(name))
369 return false;
370
371 if (this.paramString!=null && this.paramString.equals(paramString))
372 return true;
373
374 return (this.paramString == paramString);
375 }
376
377 public String toString ()
378 {
379 return pclass.getClassName()+"."+methodName+"\n\t"+super.toString();
380 }
381 }
382
383
384
385
386
387
388
389 public static class ParsedField extends AnnotatedStructure
390 {
391 ParsedClass pclass;
392 String fieldName;
393 Field field;
394
395 public ParsedField (ParsedClass pclass, String name)
396 {
397 this.pclass=pclass;
398 this.fieldName=name;
399 }
400
401 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
402 {
403 this.pclass.addField(this);
404 return addAnnotation(desc);
405 }
406
407 public Field toField ()
408 throws Exception
409 {
410 if (field==null)
411 {
412 field=this.pclass.toClass().getDeclaredField(fieldName);
413 }
414 return field;
415 }
416
417
418 public boolean matches (String name)
419 {
420 if (fieldName.equals(name))
421 return true;
422
423 return false;
424 }
425
426 public String toString ()
427 {
428 return pclass.getClassName()+"."+fieldName+"\n\t"+super.toString();
429 }
430 }
431
432
433
434
435
436
437
438
439 public class MyClassVisitor extends EmptyVisitor
440 {
441 ParsedClass pclass;
442
443
444 public void visit (int version,
445 int access,
446 String name,
447 String signature,
448 String superName,
449 String[] interfaces)
450 {
451 pclass = new ParsedClass(name, superName);
452 }
453
454 public AnnotationVisitor visitAnnotation (String desc, boolean visible)
455 {
456 if (!parsedClasses.containsKey(pclass.getClassName()))
457 parsedClasses.put(pclass.getClassName(), pclass);
458
459 return pclass.addAnnotation(desc);
460 }
461
462 public MethodVisitor visitMethod (int access,
463 String name,
464 String desc,
465 String signature,
466 String[] exceptions)
467 {
468 if (!parsedClasses.values().contains(pclass))
469 parsedClasses.put(pclass.getClassName(),pclass);
470
471 ParsedMethod method = pclass.getMethod(name, desc);
472 if (method==null)
473 method = new ParsedMethod(pclass, name, desc);
474 return method;
475 }
476
477 public FieldVisitor visitField (int access,
478 String name,
479 String desc,
480 String signature,
481 Object value)
482 {
483 if (!parsedClasses.values().contains(pclass))
484 parsedClasses.put(pclass.getClassName(),pclass);
485
486 ParsedField field = pclass.getField(name);
487 if (field==null)
488 field = new ParsedField(pclass, name);
489 return field;
490 }
491 }
492
493
494
495
496
497
498 public void find (String className, ClassNameResolver resolver)
499 throws Exception
500 {
501 if (className == null)
502 return;
503
504 if (!resolver.isExcluded(className))
505 {
506 if ((parsedClasses.get(className) == null) || (resolver.shouldOverride(className)))
507 {
508 parsedClasses.remove(className);
509 className = className.replace('.', '/')+".class";
510 URL resource = Loader.getResource(this.getClass(), className, false);
511 if (resource!= null)
512 scanClass(resource.openStream());
513 }
514 }
515 }
516
517 public void find (String[] classNames, ClassNameResolver resolver)
518 throws Exception
519 {
520 if (classNames == null)
521 return;
522
523 find(Arrays.asList(classNames), resolver);
524 }
525
526 public void find (List<String> classNames, ClassNameResolver resolver)
527 throws Exception
528 {
529 for (String s:classNames)
530 {
531 if (!resolver.isExcluded(s))
532 {
533 if ((parsedClasses.get(s) == null) || (resolver.shouldOverride(s)))
534 {
535 parsedClasses.remove(s);
536 s = s.replace('.', '/')+".class";
537 URL resource = Loader.getResource(this.getClass(), s, false);
538 if (resource!= null)
539 scanClass(resource.openStream());
540 }
541 }
542 }
543 }
544
545 public void find (Resource dir, ClassNameResolver resolver)
546 throws Exception
547 {
548 if (!dir.isDirectory() || !dir.exists())
549 return;
550
551
552 String[] files=dir.list();
553 for (int f=0;files!=null && f<files.length;f++)
554 {
555 try
556 {
557 Resource res = dir.addPath(files[f]);
558 if (res.isDirectory())
559 find(res, resolver);
560 String name = res.getName();
561 if (name.endsWith(".class"))
562 {
563 if (!resolver.isExcluded(name))
564 {
565 if ((parsedClasses.get(name) == null) || (resolver.shouldOverride(name)))
566 {
567 parsedClasses.remove(name);
568 scanClass(res.getURL().openStream());
569 }
570 }
571 }
572 }
573 catch (Exception ex)
574 {
575 Log.warn(Log.EXCEPTION,ex);
576 }
577 }
578 }
579
580
581 public void find (ClassLoader loader, boolean visitParents, String jarNamePattern, boolean nullInclusive, final ClassNameResolver resolver)
582 throws Exception
583 {
584 if (loader==null)
585 return;
586
587 if (!(loader instanceof URLClassLoader))
588 return;
589
590 JarScanner scanner = new JarScanner()
591 {
592 public void processEntry(URL jarUrl, JarEntry entry)
593 {
594 try
595 {
596 String name = entry.getName();
597 if (name.toLowerCase().endsWith(".class"))
598 {
599 String shortName = name.replace('/', '.').substring(0,name.length()-6);
600 if (!resolver.isExcluded(shortName))
601 {
602 if ((parsedClasses.get(shortName) == null) || (resolver.shouldOverride(shortName)))
603 {
604 parsedClasses.remove(shortName);
605 Resource clazz = Resource.newResource("jar:"+jarUrl+"!/"+name);
606 scanClass(clazz.getInputStream());
607 }
608 }
609 }
610 }
611 catch (Exception e)
612 {
613 Log.warn("Problem processing jar entry "+entry, e);
614 }
615 }
616
617 };
618 Pattern pattern = null;
619 if (jarNamePattern!=null)
620 pattern = Pattern.compile(jarNamePattern);
621
622 scanner.scan(pattern, loader, nullInclusive, visitParents);
623 }
624
625
626
627
628
629
630
631
632 protected boolean excludeClass (String name)
633 {
634 return false;
635 }
636
637
638
639 public List<Class<?>> getClassesForAnnotation(Class<?> annotationClass)
640 throws Exception
641 {
642 List<Class<?>> classes = new ArrayList<Class<?>>();
643 for (Map.Entry<String, ParsedClass> e: parsedClasses.entrySet())
644 {
645 ParsedClass pc = e.getValue();
646 Map<String, Map<String,Object>> annotations = pc.getAnnotations();
647 for (String key:annotations.keySet())
648 {
649 if (key.equals(annotationClass.getName()))
650 {
651 classes.add(pc.toClass());
652 }
653 }
654 }
655 return classes;
656
657 }
658
659
660
661 public List<Method> getMethodsForAnnotation (Class<?> annotationClass)
662 throws Exception
663 {
664
665 List<Method> methods = new ArrayList<Method>();
666
667 for (Map.Entry<String, ParsedClass> e: parsedClasses.entrySet())
668 {
669 ParsedClass pc = e.getValue();
670
671 List<ParsedMethod> pmethods = pc.getMethods();
672 for (ParsedMethod p:pmethods)
673 {
674 for (String key:p.getAnnotations().keySet())
675 {
676 if (key.equals(annotationClass.getName()))
677 {
678 methods.add(p.toMethod());
679 }
680 }
681 }
682 }
683 return methods;
684
685 }
686
687
688 public List<Field> getFieldsForAnnotation (Class<?> annotation)
689 throws Exception
690 {
691
692 List<Field> fields = new ArrayList<Field>();
693 for (Map.Entry<String, ParsedClass> e: parsedClasses.entrySet())
694 {
695 ParsedClass pc = e.getValue();
696
697 List<ParsedField> pfields = pc.getFields();
698 for (ParsedField f:pfields)
699 {
700 for (String key:f.getAnnotations().keySet())
701 {
702 if (key.equals(annotation.getName()))
703 {
704 fields.add(f.toField());
705 }
706 }
707 }
708 }
709 return fields;
710 }
711
712
713 public String toString ()
714 {
715 StringBuffer strbuff = new StringBuffer();
716 for (Map.Entry<String, ParsedClass> e:parsedClasses.entrySet())
717 {
718 strbuff.append(e.getValue());
719 strbuff.append("\n");
720 }
721 return strbuff.toString();
722 }
723
724
725 private void scanClass (InputStream is)
726 throws IOException
727 {
728 ClassReader reader = new ClassReader(is);
729 reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
730 }
731 }