View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  package org.eclipse.jetty.annotations;
14  
15  import java.io.IOException;
16  import java.io.InputStream;
17  import java.net.URI;
18  import java.net.URL;
19  import java.net.URLClassLoader;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.jar.JarEntry;
28  
29  import org.eclipse.jetty.util.Loader;
30  import org.eclipse.jetty.util.log.Log;
31  import org.eclipse.jetty.util.log.Logger;
32  import org.eclipse.jetty.util.resource.Resource;
33  import org.eclipse.jetty.webapp.JarScanner;
34  import org.objectweb.asm.AnnotationVisitor;
35  import org.objectweb.asm.ClassReader;
36  import org.objectweb.asm.FieldVisitor;
37  import org.objectweb.asm.MethodVisitor;
38  import org.objectweb.asm.commons.EmptyVisitor;
39  
40  /**
41   * AnnotationParser
42   * 
43   * Use asm to scan classes for annotations. A SAX-style parsing is done, with
44   * a handler being able to be registered to handle each annotation type.
45   */
46  public class AnnotationParser
47  {
48      private static final Logger LOG = Log.getLogger(AnnotationParser.class);
49   
50      protected List<String> _parsedClassNames = new ArrayList<String>();
51      protected Map<String, List<DiscoverableAnnotationHandler>> _annotationHandlers = new HashMap<String, List<DiscoverableAnnotationHandler>>();
52      protected List<ClassHandler> _classHandlers = new ArrayList<ClassHandler>();
53      protected List<MethodHandler> _methodHandlers = new ArrayList<MethodHandler>();
54      protected List<FieldHandler> _fieldHandlers = new ArrayList<FieldHandler>();
55      
56      public static String normalize (String name)
57      {
58          if (name==null)
59              return null;
60          
61          if (name.startsWith("L") && name.endsWith(";"))
62              name = name.substring(1, name.length()-1);
63          
64          if (name.endsWith(".class"))
65              name = name.substring(0, name.length()-".class".length());
66          
67          return name.replace('/', '.');
68      }
69      
70  
71      
72      public abstract class Value
73      {
74          String _name;
75          
76          public Value (String name)
77          {
78              _name = name;
79          }
80          
81          public String getName()
82          {
83              return _name;
84          }
85          
86          public abstract Object getValue();
87             
88      }
89      
90     
91   
92      
93      public class SimpleValue extends Value
94      {
95          Object _val;
96          
97          public SimpleValue(String name)
98          {
99              super(name);
100         }
101         
102         public void setValue(Object val)
103         {
104             _val=val;
105         } 
106         public Object getValue()
107         {
108             return _val;
109         } 
110         
111         public String toString()
112         {
113             return "("+getName()+":"+_val+")";
114         }
115     }
116     
117     public class ListValue extends Value
118     {
119         List<Value> _val;
120         
121         public ListValue (String name)
122         {
123             super(name);
124             _val = new ArrayList<Value>();
125         }
126       
127         public Object getValue()
128         {
129             return _val;
130         }
131         
132         public List<Value> getList()
133         {
134             return _val;
135         }
136         
137         public void addValue (Value v)
138         {
139             _val.add(v);
140         }
141         
142         public int size ()
143         {
144             return _val.size();
145         }
146         
147         public String toString()
148         {
149             StringBuffer buff = new StringBuffer();
150             buff.append("(");
151             buff.append(getName());
152             buff.append(":");
153             for (Value n: _val)
154             {
155                 buff.append(" "+n.toString());
156             }
157             buff.append(")");
158             
159             return buff.toString();
160         }
161     }
162     
163     
164     
165     public interface DiscoverableAnnotationHandler
166     {
167         public void handleClass (String className, int version, int access, 
168                                  String signature, String superName, String[] interfaces, 
169                                  String annotation, List<Value>values);
170         
171         public void handleMethod (String className, String methodName, int access,  
172                                   String desc, String signature,String[] exceptions, 
173                                   String annotation, List<Value>values);
174         
175         public void handleField (String className, String fieldName,  int access, 
176                                  String fieldType, String signature, Object value, 
177                                  String annotation, List<Value>values);
178     }
179     
180     
181     public interface ClassHandler
182     {
183         public void handle (String className, int version, int access, String signature, String superName, String[] interfaces);
184     }
185     
186     public interface MethodHandler
187     {
188         public void handle (String className, String methodName, int access,  String desc, String signature,String[] exceptions);
189     }
190     
191     public interface FieldHandler
192     {
193         public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value);
194     }
195     
196     public class MyAnnotationVisitor implements AnnotationVisitor
197     {
198         List<Value> _annotationValues;
199         String _annotationName;
200         
201         public MyAnnotationVisitor (String annotationName, List<Value> values)
202         {
203             _annotationValues = values;
204             _annotationName = annotationName;
205         }
206         
207         public List<Value> getAnnotationValues()
208         {
209             return _annotationValues;
210         }
211 
212         /** 
213          * Visit a single-valued (name,value) pair for this annotation
214          * @see org.objectweb.asm.AnnotationVisitor#visit(java.lang.String, java.lang.Object)
215          */
216         public void visit(String aname, Object avalue)
217         {
218            SimpleValue v = new SimpleValue(aname);
219            v.setValue(avalue);
220            _annotationValues.add(v);
221         }
222 
223         /** 
224          * Visit a (name,value) pair whose value is another Annotation
225          * @see org.objectweb.asm.AnnotationVisitor#visitAnnotation(java.lang.String, java.lang.String)
226          */
227         public AnnotationVisitor visitAnnotation(String name, String desc)
228         {
229             String s = normalize(desc);
230             ListValue v = new ListValue(s);
231             _annotationValues.add(v);
232             MyAnnotationVisitor visitor = new MyAnnotationVisitor(s, v.getList());
233             return visitor; 
234         }
235 
236         /** 
237          * Visit an array valued (name, value) pair for this annotation
238          * @see org.objectweb.asm.AnnotationVisitor#visitArray(java.lang.String)
239          */
240         public AnnotationVisitor visitArray(String name)
241         {
242             ListValue v = new ListValue(name);
243             _annotationValues.add(v);
244             MyAnnotationVisitor visitor = new MyAnnotationVisitor(null, v.getList());
245             return visitor; 
246         }
247 
248         /** 
249          * Visit a enum-valued (name,value) pair for this annotation
250          * @see org.objectweb.asm.AnnotationVisitor#visitEnum(java.lang.String, java.lang.String, java.lang.String)
251          */
252         public void visitEnum(String name, String desc, String value)
253         {
254             //TODO
255         }
256         
257         public void visitEnd()
258         {   
259         }
260     }
261     
262     
263 
264     
265     /**
266      * MyClassVisitor
267      *
268      * ASM visitor for a class.
269      */
270     public class MyClassVisitor extends EmptyVisitor
271     {
272         String _className;
273         int _access;
274         String _signature;
275         String _superName;
276         String[] _interfaces;
277         int _version;
278 
279 
280         public void visit (int version,
281                            final int access,
282                            final String name,
283                            final String signature,
284                            final String superName,
285                            final String[] interfaces)
286         {     
287             _className = normalize(name);
288             _access = access;
289             _signature = signature;
290             _superName = superName;
291             _interfaces = interfaces;
292             _version = version;
293             
294             _parsedClassNames.add(_className);
295             //call all registered ClassHandlers
296             String[] normalizedInterfaces = null;
297             if (interfaces!= null)
298             {
299                 normalizedInterfaces = new String[interfaces.length];
300                 int i=0;
301                 for (String s : interfaces)
302                     normalizedInterfaces[i++] = normalize(s);
303             }
304             
305             for (ClassHandler h : AnnotationParser.this._classHandlers)
306             {
307                 h.handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces);
308             }
309         }
310 
311         public AnnotationVisitor visitAnnotation (String desc, boolean visible)
312         {                
313             MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
314             {
315                 public void visitEnd()
316                 {   
317                     super.visitEnd();
318 
319                     //call all AnnotationHandlers with classname, annotation name + values
320                     List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName);
321                     if (handlers != null)
322                     {
323                         for (DiscoverableAnnotationHandler h:handlers)
324                         {
325                             h.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues);
326                         }
327                     }
328                 }
329             };
330             
331             return visitor;
332         }
333 
334         public MethodVisitor visitMethod (final int access,
335                                           final String name,
336                                           final String methodDesc,
337                                           final String signature,
338                                           final String[] exceptions)
339         {   
340 
341             return new EmptyVisitor ()
342             {
343                 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
344                 {
345                     MyAnnotationVisitor visitor = new MyAnnotationVisitor (normalize(desc), new ArrayList<Value>())
346                     {
347                         public void visitEnd()
348                         {   
349                             super.visitEnd();
350                             //call all AnnotationHandlers with classname, method, annotation name + values
351                             List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName);
352                             if (handlers != null)
353                             {
354                                 for (DiscoverableAnnotationHandler h:handlers)
355                                 {
356                                     h.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues);
357                                 }
358                             }
359                         }
360                     };
361                    
362                     return visitor;
363                 }
364             };
365         }
366 
367         public FieldVisitor visitField (final int access,
368                                         final String fieldName,
369                                         final String fieldType,
370                                         final String signature,
371                                         final Object value)
372         {
373 
374             return new EmptyVisitor ()
375             {
376                 public AnnotationVisitor visitAnnotation(String desc, boolean visible)
377                 {
378                     MyAnnotationVisitor visitor = new MyAnnotationVisitor(normalize(desc), new ArrayList<Value>())
379                     {
380                         public void visitEnd()
381                         {
382                             super.visitEnd();
383                             List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName);
384                             if (handlers != null)
385                             {
386                                 for (DiscoverableAnnotationHandler h:handlers)
387                                 {
388                                     h.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues);
389                                 }
390                             }
391                         }
392                     };
393                     return visitor;
394                 }
395             };
396         }
397     }
398     
399     
400     /**
401      * Register a handler that will be called back when the named annotation is
402      * encountered on a class.
403      * 
404      * @param annotationName
405      * @param handler
406      */
407     public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler)
408     {
409         List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName);
410         if (handlers == null)
411         {
412             handlers = new ArrayList<DiscoverableAnnotationHandler>();
413             _annotationHandlers.put(annotationName, handlers);
414         }
415         handlers.add(handler);
416     }
417     
418     public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName)
419     {
420         List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName);
421         if (handlers == null)
422             return Collections.emptyList();
423         return new ArrayList<DiscoverableAnnotationHandler>();
424     }
425 
426     public List<DiscoverableAnnotationHandler> getAnnotationHandlers()
427     {
428         List<DiscoverableAnnotationHandler> allHandlers = new ArrayList<DiscoverableAnnotationHandler>();
429         for (List<DiscoverableAnnotationHandler> list:_annotationHandlers.values())
430             allHandlers.addAll(list);
431         return allHandlers;
432     }
433 
434     public void registerClassHandler (ClassHandler handler)
435     {
436         _classHandlers.add(handler);
437     }
438 
439     public boolean isParsed (String className)
440     {
441         return _parsedClassNames.contains(className);
442     }
443     
444     public void parse (String className, ClassNameResolver resolver) 
445     throws Exception
446     {
447         if (className == null)
448             return;
449         
450         if (!resolver.isExcluded(className))
451         {
452             if (!isParsed(className) || resolver.shouldOverride(className))
453             {
454                 className = className.replace('.', '/')+".class";
455                 URL resource = Loader.getResource(this.getClass(), className, false);
456                 if (resource!= null)
457                 {
458                     Resource r = Resource.newResource(resource);
459                     scanClass(r.getInputStream());
460                 }
461             }
462         }
463     }
464     
465     public void parse (Class clazz, ClassNameResolver resolver, boolean visitSuperClasses)
466     throws Exception
467     {
468         Class cz = clazz;
469         while (cz != null)
470         {
471             if (!resolver.isExcluded(cz.getName()))
472             {
473                 if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
474                 {
475                     String nameAsResource = cz.getName().replace('.', '/')+".class";
476                     URL resource = Loader.getResource(this.getClass(), nameAsResource, false);
477                     if (resource!= null)
478                     {
479                         Resource r = Resource.newResource(resource);
480                         scanClass(r.getInputStream());
481                     }
482                 }
483             }
484             if (visitSuperClasses)
485                 cz = cz.getSuperclass();
486             else
487                 cz = null;
488         }
489     }
490     
491     public void parse (String[] classNames, ClassNameResolver resolver)
492     throws Exception
493     {
494         if (classNames == null)
495             return;
496        
497         parse(Arrays.asList(classNames), resolver); 
498     }
499     
500     public void parse (List<String> classNames, ClassNameResolver resolver)
501     throws Exception
502     {
503         for (String s:classNames)
504         {
505             if ((resolver == null) || (!resolver.isExcluded(s) &&  (!isParsed(s) || resolver.shouldOverride(s))))
506             {            
507                 s = s.replace('.', '/')+".class"; 
508                 URL resource = Loader.getResource(this.getClass(), s, false);
509                 if (resource!= null)
510                 {
511                     Resource r = Resource.newResource(resource);
512                     scanClass(r.getInputStream());
513                 }
514             }
515         }
516     }
517     
518     public void parse (Resource dir, ClassNameResolver resolver)
519     throws Exception
520     {
521         if (!dir.isDirectory() || !dir.exists())
522             return;
523         
524         
525         String[] files=dir.list();
526         for (int f=0;files!=null && f<files.length;f++)
527         {
528             try 
529             {
530                 Resource res = dir.addPath(files[f]);
531                 if (res.isDirectory())
532                     parse(res, resolver);
533                 String name = res.getName();
534                 if (name.endsWith(".class"))
535                 {
536                     if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
537                     {
538                         Resource r = Resource.newResource(res.getURL());
539                         scanClass(r.getInputStream());
540                     }
541 
542                 }
543             }
544             catch (Exception ex)
545             {
546                 LOG.warn(Log.EXCEPTION,ex);
547             }
548         }
549     }
550     
551     
552     /**
553      * Find annotations on classes in the supplied classloader. 
554      * Only class files in jar files will be scanned.
555      * @param loader
556      * @param visitParents
557      * @param nullInclusive
558      * @param resolver
559      * @throws Exception
560      */
561     public void parse (ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
562     throws Exception
563     {
564         if (loader==null)
565             return;
566         
567         if (!(loader instanceof URLClassLoader))
568             return; //can't extract classes?
569        
570         JarScanner scanner = new JarScanner()
571         {
572             public void processEntry(URI jarUri, JarEntry entry)
573             {   
574                 try
575                 {
576                     String name = entry.getName();
577                     if (name.toLowerCase().endsWith(".class"))
578                     {
579                         String shortName =  name.replace('/', '.').substring(0,name.length()-6);
580                         if ((resolver == null)
581                              ||
582                             (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
583                         {
584 
585                             Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);                     
586                             scanClass(clazz.getInputStream());
587                         }
588                     }
589                 }
590                 catch (Exception e)
591                 {
592                     LOG.warn("Problem processing jar entry "+entry, e);
593                 }
594             }
595             
596         };
597 
598         scanner.scan(null, loader, nullInclusive, visitParents);
599     }
600     
601     
602     /**
603      * Find annotations in classes in the supplied url of jar files.
604      * @param uris
605      * @param resolver
606      * @throws Exception
607      */
608     public void parse (URI[] uris, final ClassNameResolver resolver)
609     throws Exception
610     {
611         if (uris==null)
612             return;
613         
614         JarScanner scanner = new JarScanner()
615         {
616             public void processEntry(URI jarUri, JarEntry entry)
617             {   
618                 try
619                 {
620                     String name = entry.getName();
621                     if (name.toLowerCase().endsWith(".class"))
622                     {
623                         String shortName =  name.replace('/', '.').substring(0,name.length()-6);
624 
625                         if ((resolver == null)
626                              ||
627                             (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
628                         {
629                             Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);                     
630                             scanClass(clazz.getInputStream());
631 
632                         }
633                     }
634                 }
635                 catch (Exception e)
636                 {
637                     LOG.warn("Problem processing jar entry "+entry, e);
638                 }
639             }
640             
641         };        
642         scanner.scan(null, uris, true);
643     }
644     
645     public void parse (URI uri, final ClassNameResolver resolver)
646     throws Exception
647     {
648         if (uri == null)
649             return;
650         URI[] uris = {uri};
651         parse(uris, resolver);
652     }
653 
654     private void scanClass (InputStream is)
655     throws IOException
656     {
657         ClassReader reader = new ClassReader(is);
658         reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
659     }
660 }