View Javadoc

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