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