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