View Javadoc

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