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