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                     scanClass(resource.openStream());
458             }
459         }
460     }
461     
462     public void parse (Class clazz, ClassNameResolver resolver, boolean visitSuperClasses)
463     throws Exception
464     {
465         Class cz = clazz;
466         while (cz != null)
467         {
468             if (!resolver.isExcluded(cz.getName()))
469             {
470                 if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
471                 {
472                     String nameAsResource = cz.getName().replace('.', '/')+".class";
473                     URL resource = Loader.getResource(this.getClass(), nameAsResource, false);
474                     if (resource!= null)
475                         scanClass(resource.openStream());
476                 }
477             }
478             if (visitSuperClasses)
479                 cz = cz.getSuperclass();
480             else
481                 cz = null;
482         }
483     }
484     
485     public void parse (String[] classNames, ClassNameResolver resolver)
486     throws Exception
487     {
488         if (classNames == null)
489             return;
490        
491         parse(Arrays.asList(classNames), resolver); 
492     }
493     
494     public void parse (List<String> classNames, ClassNameResolver resolver)
495     throws Exception
496     {
497         for (String s:classNames)
498         {
499             if ((resolver == null) || (!resolver.isExcluded(s) &&  (!isParsed(s) || resolver.shouldOverride(s))))
500             {            
501                 s = s.replace('.', '/')+".class"; 
502                 URL resource = Loader.getResource(this.getClass(), s, false);
503                 if (resource!= null)
504                     scanClass(resource.openStream());
505             }
506         }
507     }
508     
509     public void parse (Resource dir, ClassNameResolver resolver)
510     throws Exception
511     {
512         if (!dir.isDirectory() || !dir.exists())
513             return;
514         
515         
516         String[] files=dir.list();
517         for (int f=0;files!=null && f<files.length;f++)
518         {
519             try 
520             {
521                 Resource res = dir.addPath(files[f]);
522                 if (res.isDirectory())
523                     parse(res, resolver);
524                 String name = res.getName();
525                 if (name.endsWith(".class"))
526                 {
527                     if ((resolver == null)|| (!resolver.isExcluded(name) && (!isParsed(name) || resolver.shouldOverride(name))))
528                         scanClass(res.getURL().openStream());
529 
530                 }
531             }
532             catch (Exception ex)
533             {
534                 LOG.warn(Log.EXCEPTION,ex);
535             }
536         }
537     }
538     
539     
540     /**
541      * Find annotations on classes in the supplied classloader. 
542      * Only class files in jar files will be scanned.
543      * @param loader
544      * @param visitParents
545      * @param nullInclusive
546      * @param resolver
547      * @throws Exception
548      */
549     public void parse (ClassLoader loader, boolean visitParents, boolean nullInclusive, final ClassNameResolver resolver)
550     throws Exception
551     {
552         if (loader==null)
553             return;
554         
555         if (!(loader instanceof URLClassLoader))
556             return; //can't extract classes?
557        
558         JarScanner scanner = new JarScanner()
559         {
560             public void processEntry(URI jarUri, JarEntry entry)
561             {   
562                 try
563                 {
564                     String name = entry.getName();
565                     if (name.toLowerCase().endsWith(".class"))
566                     {
567                         String shortName =  name.replace('/', '.').substring(0,name.length()-6);
568                         if ((resolver == null)
569                              ||
570                             (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
571                         {
572 
573                             Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);                     
574                             scanClass(clazz.getInputStream());
575                         }
576                     }
577                 }
578                 catch (Exception e)
579                 {
580                     LOG.warn("Problem processing jar entry "+entry, e);
581                 }
582             }
583             
584         };
585 
586         scanner.scan(null, loader, nullInclusive, visitParents);
587     }
588     
589     
590     /**
591      * Find annotations in classes in the supplied url of jar files.
592      * @param uris
593      * @param resolver
594      * @throws Exception
595      */
596     public void parse (URI[] uris, final ClassNameResolver resolver)
597     throws Exception
598     {
599         if (uris==null)
600             return;
601         
602         JarScanner scanner = new JarScanner()
603         {
604             public void processEntry(URI jarUri, JarEntry entry)
605             {   
606                 try
607                 {
608                     String name = entry.getName();
609                     if (name.toLowerCase().endsWith(".class"))
610                     {
611                         String shortName =  name.replace('/', '.').substring(0,name.length()-6);
612 
613                         if ((resolver == null)
614                              ||
615                             (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
616                         {
617                             Resource clazz = Resource.newResource("jar:"+jarUri+"!/"+name);                     
618                             scanClass(clazz.getInputStream());
619 
620                         }
621                     }
622                 }
623                 catch (Exception e)
624                 {
625                     LOG.warn("Problem processing jar entry "+entry, e);
626                 }
627             }
628             
629         };        
630         scanner.scan(null, uris, true);
631     }
632     
633     public void parse (URI uri, final ClassNameResolver resolver)
634     throws Exception
635     {
636         if (uri == null)
637             return;
638         URI[] uris = {uri};
639         parse(uris, resolver);
640     }
641 
642     protected void scanClass (InputStream is)
643     throws IOException
644     {
645         ClassReader reader = new ClassReader(is);
646         reader.accept(new MyClassVisitor(), ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
647     }
648 }
649