1   //========================================================================
2   //$Id: AnnotationParser.java 1594 2007-02-14 02:45:12Z janb $
3   //Copyright 2006 Mort Bay Consulting Pty. Ltd.
4   //------------------------------------------------------------------------
5   //Licensed under the Apache License, Version 2.0 (the "License");
6   //you may not use this file except in compliance with the License.
7   //You may obtain a copy of the License at 
8   //http://www.apache.org/licenses/LICENSE-2.0
9   //Unless required by applicable law or agreed to in writing, software
10  //distributed under the License is distributed on an "AS IS" BASIS,
11  //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  //See the License for the specific language governing permissions and
13  //limitations under the License.
14  //========================================================================
15  
16  package org.mortbay.jetty.annotations;
17  
18  import java.lang.annotation.Annotation;
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Modifier;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.ListIterator;
27  
28  import org.mortbay.jetty.plus.annotation.InjectionCollection;
29  import org.mortbay.jetty.plus.annotation.LifeCycleCallbackCollection;
30  import org.mortbay.jetty.plus.annotation.RunAsCollection;
31  import org.mortbay.jetty.servlet.Holder;
32  import org.mortbay.jetty.servlet.ServletHolder;
33  import org.mortbay.log.Log;
34  import org.mortbay.util.IntrospectionUtil;
35  
36  /**
37   * AnnotationParser
38   *
39   * None of the common annotations are inheritable, thus
40   * calling getAnnotations() is exactly equivalent to 
41   * getDeclaredAnnotations(). Therefore, in order to find
42   * all relevant annotations, the full inheritance tree of
43   * a class must be considered.
44   * 
45   * From the spec:
46   *  Class-level annotations only affect the class they 
47   *  annotate and their members, that is, its methods and fields. 
48   *  They never affect a member declared by a superclass, even 
49   *  if it is not hidden or overridden by the class in question.
50   * 
51   *  In addition to affecting the annotated class, class-level 
52   *  annotations may act as a shorthand for member-level annotations. 
53   *  If a member carries a specific member-level annotation, any 
54   *  annotations of the same type implied by a class-level annotation 
55   *  are ignored. In other words, explicit member-level annotations
56   *  have priority over member-level annotations implied by a class-level 
57   *  annotation. For example, a @WebService annotation on a class implies 
58   *  that all the public method in the class that it is applied on are 
59   *  annotated with @WebMethod if there is no @WebMethod annotation on 
60   *  any of the methods. However if there is a @WebMethod annotation on 
61   *  any method then the @WebService does not imply the presence of 
62   *  @WebMethod on the other public methods in the class.
63   *  
64   *  The interfaces implemented by a class never contribute annotations 
65   *  to the class itself or any of its members.
66   *  
67   *  Members inherited from a superclass and which are not hidden or 
68   *  overridden maintain the annotations they had in the class that
69   *  declared them, including member-level annotations implied by 
70   *  class-level ones.
71   *  
72   *  Member-level annotations on a hidden or overridden member are 
73   *  always ignored
74   */
75  public class AnnotationParser
76  {
77      /**
78       * Examine the class hierarchy for a class, finding all annotations. Then, merge any 
79       * servlet2.5 spec annotations found with those already existing (from parsing web.xml)
80       * respecting the overriding rules found in the spec.
81       * 
82       * @param clazz the class to inspect
83       * @param runAs any run-as elements from web.xml
84       * @param injections any injections specified in web.xml
85       * @param callbacks any postconstruct/predestroy callbacks in web.xml
86       */
87      public static void parseAnnotations (Class clazz, RunAsCollection runAs, InjectionCollection injections, LifeCycleCallbackCollection callbacks)
88      {
89          if (clazz==null)
90              return;
91          AnnotationCollection annotations = processClass(clazz);       
92          annotations.processRunAsAnnotations(runAs);
93          annotations.processResourcesAnnotations();
94          annotations.processResourceAnnotations(injections);
95          annotations.processLifeCycleCallbackAnnotations(callbacks);
96      }
97      
98      
99  
100     /**
101      * Examine the class hierarchy for this class looking for annotations.
102      * 
103      * @param clazz
104      * @return AnnotationCollection
105      */
106     static AnnotationCollection processClass (Class clazz)
107     { 
108         AnnotationCollection collection = new AnnotationCollection();
109         if (clazz==null)
110             return collection;
111        
112         collection.setTargetClass(clazz);
113         
114         //add any class level annotations
115         collection.addClass(clazz);
116        
117         //Add all the fields with annotations.
118         Field[] fields = clazz.getDeclaredFields();
119         //For each field, get all of it's annotations
120         for (int i=0; i<fields.length; i++)
121         {
122             collection.addField(fields[i]);
123         }
124         
125         //Get all the methods with annotations
126         Method[] methods = clazz.getDeclaredMethods();
127         for (int i=0; i<methods.length;i++)
128         {
129             collection.addMethod(methods[i]);
130         }
131         
132         //process the inheritance hierarchy for the class
133         Class ancestor = clazz.getSuperclass();
134         while (ancestor!=null && (!ancestor.equals(Object.class)))
135         {
136             processHierarchy (clazz, ancestor, collection);
137             ancestor = ancestor.getSuperclass();
138         } 
139         
140         return collection;
141     }
142     
143     
144     
145     /**
146      * Methods which are inherited retain their annotations.
147      * Methods which are not inherited and not overridden or hidden must also have their annotations processed.
148      * An overridden method can remove or change it's annotations.
149      * @param targetClazz
150      * @param ancestor
151      * @param targetClazzMethods
152      */
153     private static void processHierarchy (Class targetClazz, Class ancestor, AnnotationCollection collection)
154     {
155         if (targetClazz==null)
156             return;
157         if (ancestor==null)
158             return;
159         
160         //If the ancestor has class level annotations, remember it
161         collection.addClass(ancestor);
162         
163         //Get annotations on the declared methods of the ancestor class. 
164         //For each declared method that has an annotation, we need to
165         //determine if that method is inheritable&&!overridden or hidden
166         //in derived classes of the ancestor, in which case it contributes
167         //an annotation to the collection
168         //OR
169         //if the method is not inheritable, but has an annotation, it still
170         //contributes an annotation (even private non-inherited methods must
171         //have their annotations honoured)
172         Method[] methods = ancestor.getDeclaredMethods();
173         for (int i=0; i<methods.length;i++)
174         {
175             if (methods[i].getAnnotations().length > 0)
176             {
177                if (!isOverriddenOrHidden(targetClazz, methods[i]))
178                    collection.addMethod(methods[i]);
179             } 
180         }
181         
182         //Get annotations on declared fields. For each field work out if it is
183         //overridden or hidden in targetClazz
184         Field[] fields = ancestor.getDeclaredFields();
185         for (int i=0;i<fields.length;i++)
186         {
187             if (fields[i].getAnnotations().length > 0)
188             {
189                 //the field has annotations, so check to see if it should be inherited
190                 //field is inheritable if it is:
191                 // NOT private
192                 // of package scope and of the same package
193                 if (!isHidden(targetClazz, fields[i]))
194                     collection.addField(fields[i]);
195 
196             }
197         }
198     }
199     
200 
201     
202     
203     /**
204      * isOverriddenOrHidden
205      * 
206      * Find out if method is overridden or hidden in the hierarchy down towards the 
207      * most derived targetClass.
208      * 
209      * case private: 
210      *    never inherited so therefore cannot be overridden or hidden return false;
211      *    
212      * case public:
213      * case protected:
214      *     inherited if no class from derived up to class declaring the method declares a method of the same signature
215      *     
216      * case package:
217      *      inherited if all classes in same package from derived to declaring class and no method of the same signature
218      * 
219      * @param derivedClass the most derived class we are processing
220      * @param superclassMethod a method to check for being overridden or hidden
221      */
222     private static boolean isOverriddenOrHidden (Class derivedClass, Method superclassMethod)
223     {
224         if (Modifier.isPrivate(superclassMethod.getModifiers()))
225             return false; //private methods cannot be inherited therefore cannot be overridden
226         
227         if (Modifier.isPublic(superclassMethod.getModifiers()) || Modifier.isProtected(superclassMethod.getModifiers()))
228         {
229             //check to see if any class from most derived up to the declaring class for the method contains a method of the same sig
230             boolean sameSig = false;
231             Class c = derivedClass;
232             while (c != superclassMethod.getDeclaringClass()&&!sameSig)
233             {
234                 sameSig = IntrospectionUtil.containsSameMethodSignature(superclassMethod, c, false);
235                 c = c.getSuperclass();
236             }
237             return sameSig;
238         }
239         
240         //package protected
241         //check to see if any class from most derived up to declaring class contains method of same sig and that all
242         //intervening classes are of the same package (otherwise inheritance is blocked)
243         boolean sameSig = false;
244         Class c = derivedClass;
245         while (c != superclassMethod.getDeclaringClass() && !sameSig)
246         {
247             sameSig = IntrospectionUtil.containsSameMethodSignature(superclassMethod, c, true);
248             c = c.getSuperclass();
249         }
250         return sameSig;
251     }
252 
253     
254     
255     /**
256      * isHidden determines if a field from a superclass is hidden by field
257      * of the same name in any of the derived classes.
258      * 
259      * We check upwards from the most derived class to the class containing
260      * the field.
261      * @param derivedClass the most derived class
262      * @param superclassField
263      * @return
264      */
265     private static boolean isHidden (Class derivedClass, Field superclassField)
266     {
267         if (Modifier.isPrivate(superclassField.getModifiers()))
268             return false; //private methods are never inherited therefore never hidden
269         
270         if (Modifier.isPublic(superclassField.getModifiers()) || Modifier.isProtected(superclassField.getModifiers()))
271         {
272             boolean hidden = false;
273             Class c = derivedClass;
274             while (!c.equals(superclassField.getDeclaringClass()) && !hidden)
275             {
276                 hidden = IntrospectionUtil.containsSameFieldName(superclassField, c, false);
277                 c=c.getSuperclass();
278             }
279             return hidden;
280         }
281         
282         //Package scope
283         //Derived classes hide the field if they are in the same package and have same field name
284         boolean hidden = false;
285         Class c = derivedClass;
286         while (!c.equals(superclassField.getDeclaringClass()) && !hidden)
287         {
288             hidden = IntrospectionUtil.containsSameFieldName(superclassField, c, true);
289         }
290         return hidden;
291     }
292 }