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