View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.annotations;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.net.URI;
24  import java.net.URL;
25  import java.net.URLClassLoader;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Set;
32  import java.util.jar.JarEntry;
33  
34  import org.eclipse.jetty.util.Loader;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  import org.eclipse.jetty.util.resource.Resource;
38  import org.eclipse.jetty.webapp.JarScanner;
39  import org.objectweb.asm.AnnotationVisitor;
40  import org.objectweb.asm.ClassReader;
41  import org.objectweb.asm.FieldVisitor;
42  import org.objectweb.asm.MethodVisitor;
43  import org.objectweb.asm.commons.EmptyVisitor;
44  
45  /**
46   * AnnotationParser
47   *
48   * Use asm to scan classes for annotations. A SAX-style parsing is done, with
49   * a handler being able to be registered to handle each annotation type.
50   */
51  public class AnnotationParser
52  {
53      private static final Logger LOG = Log.getLogger(AnnotationParser.class);
54  
55      protected Set<String> _parsedClassNames = new HashSet<String>();
56      protected List<Handler> _handlers = new ArrayList<Handler>();
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          return name.replace('/', '.');
70      }
71  
72  
73  
74      public abstract class Value
75      {
76          String _name;
77  
78          public Value (String name)
79          {
80              _name = name;
81          }
82  
83          public String getName()
84          {
85              return _name;
86          }
87  
88          public abstract Object getValue();
89  
90      }
91  
92  
93  
94  
95      public class SimpleValue extends Value
96      {
97          Object _val;
98  
99          public SimpleValue(String name)
100         {
101             super(name);
102         }
103 
104         public void setValue(Object val)
105         {
106             _val=val;
107         }
108         @Override
109         public Object getValue()
110         {
111             return _val;
112         }
113 
114         @Override
115         public String toString()
116         {
117             return "("+getName()+":"+_val+")";
118         }
119     }
120 
121     public class ListValue extends Value
122     {
123         List<Value> _val;
124 
125         public ListValue (String name)
126         {
127             super(name);
128             _val = new ArrayList<Value>();
129         }
130 
131         @Override
132         public Object getValue()
133         {
134             return _val;
135         }
136 
137         public List<Value> getList()
138         {
139             return _val;
140         }
141 
142         public void addValue (Value v)
143         {
144             _val.add(v);
145         }
146 
147         public int size ()
148         {
149             return _val.size();
150         }
151 
152         @Override
153         public String toString()
154         {
155             StringBuffer buff = new StringBuffer();
156             buff.append("(");
157             buff.append(getName());
158             buff.append(":");
159             for (Value n: _val)
160             {
161                 buff.append(" "+n.toString());
162             }
163             buff.append(")");
164 
165             return buff.toString();
166         }
167     }
168 
169 
170 
171     /**
172      * Handler
173      *
174      * Signature for all handlers that respond to parsing class files.
175      */
176     public interface Handler
177     {
178        
179     }
180     
181     
182     
183     /**
184      * DiscoverableAnnotationHandler
185      *
186      * Processes an annotation when it is discovered on a class.
187      */
188     public interface DiscoverableAnnotationHandler extends Handler
189     {
190         /**
191          * Process an annotation that was discovered on a class
192          * @param className
193          * @param version
194          * @param access
195          * @param signature
196          * @param superName
197          * @param interfaces
198          * @param annotation
199          * @param values
200          */
201         public void handleClass (String className, int version, int access,
202                                  String signature, String superName, String[] interfaces,
203                                  String annotation, List<Value>values);
204 
205         /**
206          * Process an annotation that was discovered on a method
207          * @param className
208          * @param methodName
209          * @param access
210          * @param desc
211          * @param signature
212          * @param exceptions
213          * @param annotation
214          * @param values
215          */
216         public void handleMethod (String className, String methodName, int access,
217                                   String desc, String signature,String[] exceptions,
218                                   String annotation, List<Value>values);
219 
220         
221         /**
222          * Process an annotation that was discovered on a field
223          * @param className
224          * @param fieldName
225          * @param access
226          * @param fieldType
227          * @param signature
228          * @param value
229          * @param annotation
230          * @param values
231          */
232         public void handleField (String className, String fieldName,  int access,
233                                  String fieldType, String signature, Object value,
234                                  String annotation, List<Value>values);
235         
236         
237         /**
238          * Get the name of the annotation processed by this handler. Can be null
239          * 
240          * @return
241          */
242         public String getAnnotationName();
243     }
244 
245 
246     
247     /**
248      * ClassHandler
249      *
250      * Responds to finding a Class
251      */
252     public interface ClassHandler extends Handler
253     {
254         public void handle (String className, int version, int access, String signature, String superName, String[] interfaces);
255     }
256 
257     
258     
259     /**
260      * MethodHandler
261      *
262      * Responds to finding a Method
263      */
264     public interface MethodHandler extends Handler
265     {
266         public void handle (String className, String methodName, int access,  String desc, String signature,String[] exceptions);
267     }
268 
269     
270     /**
271      * FieldHandler
272      *
273      * Responds to finding a Field
274      */
275     public interface FieldHandler extends Handler
276     {
277         public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value);
278     }
279 
280     
281     
282     /**
283      * MyAnnotationVisitor
284      *
285      * ASM Visitor for Annotations
286      */
287     public class MyAnnotationVisitor implements AnnotationVisitor
288     {
289         List<Value> _annotationValues;
290         String _annotationName;
291 
292         public MyAnnotationVisitor (String annotationName, List<Value> values)
293         {
294             _annotationValues = values;
295             _annotationName = annotationName;
296         }
297 
298         public List<Value> getAnnotationValues()
299         {
300             return _annotationValues;
301         }
302 
303         /**
304          * Visit a single-valued (name,value) pair for this annotation
305          * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
306          */
307         @Override
308         public void visit(String aname, Object avalue)
309         {
310            SimpleValue v = new SimpleValue(aname);
311            v.setValue(avalue);
312            _annotationValues.add(v);
313         }
314 
315         /**
316          * Visit a (name,value) pair whose value is another Annotation
317          * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String)
318          */
319         @Override
320         public AnnotationVisitor visitAnnotation(String name, String desc)
321         {
322             String s = normalize(desc);
323             ListValue v = new ListValue(s);
324             _annotationValues.add(v);
325             MyAnnotationVisitor visitor = new MyAnnotationVisitor(s, v.getList());
326             return visitor;
327         }
328 
329         /**
330          * Visit an array valued (name, value) pair for this annotation
331          * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String)
332          */
333         @Override
334         public AnnotationVisitor visitArray(String name)
335         {
336             ListValue v = new ListValue(name);
337             _annotationValues.add(v);
338             MyAnnotationVisitor visitor = new MyAnnotationVisitor(null, v.getList());
339             return visitor;
340         }
341 
342         /**
343          * Visit a enum-valued (name,value) pair for this annotation
344          * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String)
345          */
346         @Override
347         public void visitEnum(String name, String desc, String value)
348         {
349             //TODO
350         }
351 
352         @Override
353         public void visitEnd()
354         {
355         }
356     }
357 
358 
359 
360 
361     /**
362      * MyClassVisitor
363      *
364      * ASM visitor for a class.
365      */
366     public class MyClassVisitor extends EmptyVisitor
367     {
368         String _className;
369         int _access;
370         String _signature;
371         String _superName;
372         String[] _interfaces;
373         int _version;
374 
375 
376         @Override
377         public void visit (int version,
378                            final int access,
379                            final String name,
380                            final String signature,
381                            final String superName,
382                            final String[] interfaces)
383         {
384             _className = normalize(name);
385             _access = access;
386             _signature = signature;
387             _superName = superName;
388             _interfaces = interfaces;
389             _version = version;
390 
391             _parsedClassNames.add(_className);
392             //call all registered ClassHandlers
393             String[] normalizedInterfaces = null;
394             if (interfaces!= null)
395             {
396                 normalizedInterfaces = new String[interfaces.length];
397                 int i=0;
398                 for (String s : interfaces)
399                     normalizedInterfaces[i++] = normalize(s);
400             }
401 
402             for (Handler h : AnnotationParser.this._handlers)
403             {
404                 if (h instanceof ClassHandler)
405                 {
406                     ((ClassHandler)h).handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces);
407                 }
408             }
409         }
410 
411         @Override
412         public AnnotationVisitor visitAnnotation (String desc, boolean visible)
413         {
414             MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
415             {
416                 @Override
417                 public void visitEnd()
418                 {
419                     super.visitEnd();
420 
421                     //call all AnnotationHandlers with classname, annotation name + values
422                     for (Handler h : AnnotationParser.this._handlers)
423                     {
424                         if (h instanceof DiscoverableAnnotationHandler)
425                         {
426                             DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
427                             if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
428                                 dah.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues);
429                         }
430                     }
431                 }
432             };
433 
434             return visitor;
435         }
436 
437         @Override
438         public MethodVisitor visitMethod (final int access,
439                                           final String name,
440                                           final String methodDesc,
441                                           final String signature,
442                                           final String[] exceptions)
443         {
444 
445             return new EmptyVisitor ()
446             {
447                 @Override
448                 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
449                 {
450                     MyAnnotationVisitor visitor = new MyAnnotationVisitor (normalize(desc), new ArrayList<Value>())
451                     {
452                         @Override
453                         public void visitEnd()
454                         {
455                             super.visitEnd();
456                             //call all AnnotationHandlers with classname, method, annotation name + values
457                             for (Handler h : AnnotationParser.this._handlers)
458                             {
459                                 if (h instanceof DiscoverableAnnotationHandler)
460                                 {
461                                     DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
462                                     if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
463                                         dah.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues);
464                                 }
465                             }
466                         }
467                     };
468 
469                     return visitor;
470                 }
471             };
472         }
473 
474         @Override
475         public FieldVisitor visitField (final int access,
476                                         final String fieldName,
477                                         final String fieldType,
478                                         final String signature,
479                                         final Object value)
480         {
481 
482             return new EmptyVisitor ()
483             {
484                 @Override
485                 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
486                 {
487                     MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
488                     {
489                         @Override
490                         public void visitEnd()
491                         {
492                             super.visitEnd();
493                             for (Handler h : AnnotationParser.this._handlers)
494                             {
495                                 if (h instanceof DiscoverableAnnotationHandler)
496                                 {
497                                     DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
498                                     if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
499                                         dah.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues);
500                                 }
501                             }
502                         }
503                     };
504                     return visitor;
505                 }
506             };
507         }
508     }
509 
510 
511     /**
512      * Register a handler that will be called back when the named annotation is
513      * encountered on a class.
514      *
515      * @deprecated see registerHandler(Handler)
516      * @param annotationName
517      * @param handler
518      */
519     @Deprecated
520     public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler)
521     {
522         _handlers.add(handler);
523     }
524 
525     
526     /**
527      * @deprecated
528      * @param annotationName
529      * @return
530      */
531     @Deprecated
532     public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName)
533     {
534         List<DiscoverableAnnotationHandler> handlers = new ArrayList<DiscoverableAnnotationHandler>();
535         for (Handler h:_handlers)
536         {
537             if (h instanceof DiscoverableAnnotationHandler)
538             {
539                 DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
540                 if (annotationName.equals(dah.getAnnotationName()))
541                     handlers.add(dah);
542             }
543         }
544  
545         return handlers;
546     }
547 
548     /**
549      * @deprecated
550      * @return
551      */
552     @Deprecated
553     public List<DiscoverableAnnotationHandler> getAnnotationHandlers()
554     {
555         List<DiscoverableAnnotationHandler> allAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
556         for (Handler h:_handlers)
557         {
558             if (h instanceof DiscoverableAnnotationHandler)
559             allAnnotationHandlers.add((DiscoverableAnnotationHandler)h);
560         }
561         return allAnnotationHandlers;
562     }
563 
564     /**
565      * @deprecated see registerHandler(Handler)
566      * @param handler
567      */
568     @Deprecated
569     public void registerClassHandler (ClassHandler handler)
570     {
571         _handlers.add(handler);
572     }
573     
574     
575     
576     /**
577      * Add a particular handler
578      * 
579      * @param h
580      */
581     public void registerHandler(Handler h)
582     {
583         if (h == null)
584             return;
585         
586         _handlers.add(h);
587     }
588     
589     
590     /**
591      * Add a list of handlers
592      * 
593      * @param handlers
594      */
595     public void registerHandlers(List<? extends Handler> handlers)
596     {
597         if (handlers == null)
598             return;
599         _handlers.addAll(handlers);
600     }
601     
602     
603     /**
604      * Remove a particular handler
605      * 
606      * @param h
607      * @return
608      */
609     public boolean deregisterHandler(Handler h)
610     {
611         return _handlers.remove(h);
612     }
613     
614     
615     /**
616      * Remove all registered handlers
617      */
618     public void clearHandlers()
619     {
620         _handlers.clear();
621     }
622     
623 
624     /**
625      * True if the class has already been processed, false otherwise
626      * @param className
627      * @return
628      */
629     public boolean isParsed (String className)
630     {
631         return _parsedClassNames.contains(className);
632     }
633 
634     
635     
636     /**
637      * Parse a given class
638      * 
639      * @param className
640      * @param resolver
641      * @throws Exception
642      */
643     public void parse (String className, ClassNameResolver resolver)
644     throws Exception
645     {
646         if (className == null)
647             return;
648 
649         if (!resolver.isExcluded(className))
650         {
651             if (!isParsed(className) || resolver.shouldOverride(className))
652             {
653                 className = className.replace('.', '/')+".class";
654                 URL resource = Loader.getResource(this.getClass(), className, false);
655                 if (resource!= null)
656                 {
657                     Resource r = Resource.newResource(resource);
658                     scanClass(r.getInputStream());
659                 }
660             }
661         }
662     }
663 
664     
665     
666     /**
667      * Parse the given class, optionally walking its inheritance hierarchy
668      * 
669      * @param clazz
670      * @param resolver
671      * @param visitSuperClasses
672      * @throws Exception
673      */
674     public void parse (Class<?> clazz, ClassNameResolver resolver, boolean visitSuperClasses)
675     throws Exception
676     {
677         Class<?> cz = clazz;
678         while (cz != null)
679         {
680             if (!resolver.isExcluded(cz.getName()))
681             {
682                 if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
683                 {
684                     String nameAsResource = cz.getName().replace('.', '/')+".class";
685                     URL resource = Loader.getResource(this.getClass(), nameAsResource, false);
686                     if (resource!= null)
687                     {
688                         Resource r = Resource.newResource(resource);
689                         scanClass(r.getInputStream());
690                     }
691                 }
692             }
693             if (visitSuperClasses)
694                 cz = cz.getSuperclass();
695             else
696                 cz = null;
697         }
698     }
699 
700     
701     
702     /**
703      * Parse the given classes
704      * 
705      * @param classNames
706      * @param resolver
707      * @throws Exception
708      */
709     public void parse (String[] classNames, ClassNameResolver resolver)
710     throws Exception
711     {
712         if (classNames == null)
713             return;
714 
715         parse(Arrays.asList(classNames), resolver);
716     }
717 
718     
719     /**
720      * Parse the given classes
721      * 
722      * @param classNames
723      * @param resolver
724      * @throws Exception
725      */
726     public void parse (List<String> classNames, ClassNameResolver resolver)
727     throws Exception
728     {
729         for (String s:classNames)
730         {
731             if ((resolver == null) || (!resolver.isExcluded(s) &&  (!isParsed(s) || resolver.shouldOverride(s))))
732             {
733                 s = s.replace('.', '/')+".class";
734                 URL resource = Loader.getResource(this.getClass(), s, false);
735                 if (resource!= null)
736                 {
737                     Resource r = Resource.newResource(resource);
738                     scanClass(r.getInputStream());
739                 }
740             }
741         }
742     }
743 
744     
745     /**
746      * Parse all classes in a directory
747      * 
748      * @param dir
749      * @param resolver
750      * @throws Exception
751      */
752     public void parse (Resource dir, ClassNameResolver resolver)
753     throws Exception
754     {
755         if (!dir.isDirectory() || !dir.exists())
756             return;
757 
758 
759         String[] files=dir.list();
760         for (int f=0;files!=null && f<files.length;f++)
761         {
762             try
763             {
764                 Resource res = dir.addPath(files[f]);
765                 if (res.isDirectory())
766                     parse(res, resolver);
767                 String name = res.getName();
768                 if (name.endsWith(".class"))
769                 {
770                     if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
771                     {
772                         Resource r = Resource.newResource(res.getURL());
773                         scanClass(r.getInputStream());
774                     }
775 
776                 }
777             }
778             catch (Exception ex)
779             {
780                 LOG.warn(Log.EXCEPTION,ex);
781             }
782         }
783     }
784 
785 
786     /**
787      * Parse classes in the supplied classloader. 
788      * Only class files in jar files will be scanned.
789      * 
790      * @param loader
791      * @param visitParents
792      * @param nullInclusive
793      * @param resolver
794      * @throws Exception
795      */
796     public void parse (ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
797     throws Exception
798     {
799         if (loader==null)
800             return;
801 
802         if (!(loader instanceof URLClassLoader))
803             return; //can't extract classes?
804 
805         JarScanner scanner = new JarScanner()
806         {
807             @Override
808             public void processEntry(URI jarUri, JarEntry entry)
809             {
810                 try
811                 {
812                     String name = entry.getName();
813                     if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
814                     {
815                         String shortName =  name.replace('/', '.').substring(0,name.length()-6);
816                         if ((resolver == null)
817                              ||
818                             (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
819                         {
820 
821                             Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);
822                             scanClass(clazz.getInputStream());
823                         }
824                     }
825                 }
826                 catch (Exception e)
827                 {
828                     LOG.warn("Problem processing jar entry "+entry, e);
829                 }
830             }
831 
832         };
833 
834         scanner.scan(null, loader, nullInclusive, visitParents);
835     }
836 
837 
838     /**
839      * Parse classes in the supplied url of jar files.
840      * 
841      * @param uris
842      * @param resolver
843      * @throws Exception
844      */
845     public void parse (URI[] uris, final ClassNameResolver resolver)
846     throws Exception
847     {
848         if (uris==null)
849             return;
850 
851         JarScanner scanner = new JarScanner()
852         {
853             @Override
854             public void processEntry(URI jarUri, JarEntry entry)
855             {
856                 try
857                 {
858                     String name = entry.getName();
859                     if (name.toLowerCase(Locale.ENGLISH).endsWith(".class"))
860                     {
861                         String shortName =  name.replace('/', '.').substring(0,name.length()-6);
862 
863                         if ((resolver == null)
864                              ||
865                             (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
866                         {
867                             Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);
868                             scanClass(clazz.getInputStream());
869 
870                         }
871                     }
872                 }
873                 catch (Exception e)
874                 {
875                     LOG.warn("Problem processing jar entry "+entry, e);
876                 }
877             }
878 
879         };
880         scanner.scan(null, uris, true);
881     }
882 
883     /**
884      * Parse a particular resource
885      * @param uri
886      * @param resolver
887      * @throws Exception
888      */
889     public void parse (URI uri, final ClassNameResolver resolver)
890     throws Exception
891     {
892         if (uri == null)
893             return;
894         URI[] uris = {uri};
895         parse(uris, resolver);
896     }
897 
898     
899     
900     /**
901      * Use ASM on a class
902      * 
903      * @param is
904      * @throws IOException
905      */
906     protected void scanClass (InputStream is)
907     throws IOException
908     {
909         ClassReader reader = new ClassReader(is);
910         reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
911     }
912 }
913