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