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