• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (C) 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.inject.internal;
18 
19 import com.google.common.base.Function;
20 import com.google.common.base.Joiner;
21 import com.google.common.base.Joiner.MapJoiner;
22 import com.google.common.base.Preconditions;
23 import com.google.common.cache.CacheBuilder;
24 import com.google.common.cache.CacheLoader;
25 import com.google.common.cache.LoadingCache;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.Maps;
28 import com.google.inject.BindingAnnotation;
29 import com.google.inject.Key;
30 import com.google.inject.ScopeAnnotation;
31 import com.google.inject.TypeLiteral;
32 import com.google.inject.internal.util.Classes;
33 import com.google.inject.name.Named;
34 import com.google.inject.name.Names;
35 
36 import java.lang.annotation.Annotation;
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.lang.reflect.InvocationHandler;
40 import java.lang.reflect.Member;
41 import java.lang.reflect.Method;
42 import java.lang.reflect.Proxy;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.Map;
46 
47 import javax.inject.Qualifier;
48 
49 /**
50  * Annotation utilities.
51  *
52  * @author crazybob@google.com (Bob Lee)
53  */
54 public class Annotations {
55 
56   /**
57    * Returns {@code true} if the given annotation type has no attributes.
58    */
isMarker(Class<? extends Annotation> annotationType)59   public static boolean isMarker(Class<? extends Annotation> annotationType) {
60     return annotationType.getDeclaredMethods().length == 0;
61   }
62 
isAllDefaultMethods(Class<? extends Annotation> annotationType)63   public static boolean isAllDefaultMethods(Class<? extends Annotation> annotationType) {
64     boolean hasMethods = false;
65     for (Method m : annotationType.getDeclaredMethods()) {
66       hasMethods = true;
67       if (m.getDefaultValue() == null) {
68         return false;
69       }
70     }
71     return hasMethods;
72   }
73 
74   private static final LoadingCache<Class<? extends Annotation>, Annotation> cache =
75       CacheBuilder.newBuilder().weakKeys().build(
76           new CacheLoader<Class<? extends Annotation>, Annotation>() {
77             @Override
78             public Annotation load(Class<? extends Annotation> input) {
79               return generateAnnotationImpl(input);
80             }
81           });
82 
83   /**
84    * Generates an Annotation for the annotation class. Requires that the annotation is all
85    * optionals.
86    */
generateAnnotation(Class<T> annotationType)87   public static <T extends Annotation> T generateAnnotation(Class<T> annotationType) {
88     Preconditions.checkState(
89         isAllDefaultMethods(annotationType), "%s is not all default methods", annotationType);
90     return (T)cache.getUnchecked(annotationType);
91   }
92 
generateAnnotationImpl(final Class<T> annotationType)93   private static <T extends Annotation> T generateAnnotationImpl(final Class<T> annotationType) {
94     final Map<String, Object> members = resolveMembers(annotationType);
95     return annotationType.cast(Proxy.newProxyInstance(
96         annotationType.getClassLoader(),
97         new Class<?>[] { annotationType },
98         new InvocationHandler() {
99           @Override
100           public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
101             String name = method.getName();
102             if (name.equals("annotationType")) {
103               return annotationType;
104             } else if (name.equals("toString")) {
105               return annotationToString(annotationType, members);
106             } else if (name.equals("hashCode")) {
107               return annotationHashCode(annotationType, members);
108             } else if (name.equals("equals")) {
109               return annotationEquals(annotationType, members, args[0]);
110             } else {
111               return members.get(name);
112             }
113           }
114         }));
115   }
116 
117   private static ImmutableMap<String, Object> resolveMembers(
118       Class<? extends Annotation> annotationType) {
119     ImmutableMap.Builder<String, Object> result = ImmutableMap.builder();
120     for (Method method : annotationType.getDeclaredMethods()) {
121       result.put(method.getName(), method.getDefaultValue());
122     }
123     return result.build();
124   }
125 
126   /** Implements {@link Annotation#equals}. */
127   private static boolean annotationEquals(Class<? extends Annotation> type,
128       Map<String, Object> members, Object other) throws Exception {
129     if (!type.isInstance(other)) {
130       return false;
131     }
132     for (Method method : type.getDeclaredMethods()) {
133       String name = method.getName();
134       if (!Arrays.deepEquals(
135           new Object[] {method.invoke(other)}, new Object[] {members.get(name)})) {
136         return false;
137       }
138     }
139     return true;
140   }
141 
142   /** Implements {@link Annotation#hashCode}. */
143   private static int annotationHashCode(Class<? extends Annotation> type,
144       Map<String, Object> members) throws Exception {
145     int result = 0;
146     for (Method method : type.getDeclaredMethods()) {
147       String name = method.getName();
148       Object value = members.get(name);
149       result += (127 * name.hashCode()) ^ (Arrays.deepHashCode(new Object[] {value}) - 31);
150     }
151     return result;
152   }
153 
154   private static final MapJoiner JOINER = Joiner.on(", ").withKeyValueSeparator("=");
155 
156   private static final Function<Object, String> DEEP_TO_STRING_FN = new Function<Object, String>() {
157     @Override
158     public String apply(Object arg) {
159       String s = Arrays.deepToString(new Object[] {arg});
160       return s.substring(1, s.length() - 1); // cut off brackets
161     }
162   };
163 
164   /** Implements {@link Annotation#toString}. */
165   private static String annotationToString(Class<? extends Annotation> type,
166       Map<String, Object> members) throws Exception {
167     StringBuilder sb = new StringBuilder().append("@").append(type.getName()).append("(");
168     JOINER.appendTo(sb, Maps.transformValues(members, DEEP_TO_STRING_FN));
169     return sb.append(")").toString();
170   }
171 
172   /**
173    * Returns true if the given annotation is retained at runtime.
174    */
175   public static boolean isRetainedAtRuntime(Class<? extends Annotation> annotationType) {
176     Retention retention = annotationType.getAnnotation(Retention.class);
177     return retention != null && retention.value() == RetentionPolicy.RUNTIME;
178   }
179 
180   /** Returns the scope annotation on {@code type}, or null if none is specified. */
181   public static Class<? extends Annotation> findScopeAnnotation(
182       Errors errors, Class<?> implementation) {
183     return findScopeAnnotation(errors, implementation.getAnnotations());
184   }
185 
186   /** Returns the scoping annotation, or null if there isn't one. */
187   public static Class<? extends Annotation> findScopeAnnotation(Errors errors, Annotation[] annotations) {
188     Class<? extends Annotation> found = null;
189 
190     for (Annotation annotation : annotations) {
191       Class<? extends Annotation> annotationType = annotation.annotationType();
192       if (isScopeAnnotation(annotationType)) {
193         if (found != null) {
194           errors.duplicateScopeAnnotations(found, annotationType);
195         } else {
196           found = annotationType;
197         }
198       }
199     }
200 
201     return found;
202   }
203 
204   static boolean containsComponentAnnotation(Annotation[] annotations) {
205     for (Annotation annotation : annotations) {
206       // TODO(user): Should we scope this down to dagger.Component?
207       if (annotation.annotationType().getSimpleName().equals("Component")) {
208         return true;
209       }
210     }
211 
212     return false;
213   }
214 
215   /**
216    * Checks for the presence of annotations. Caches results because Android doesn't.
217    */
218   static class AnnotationChecker {
219     private final Collection<Class<? extends Annotation>> annotationTypes;
220 
221     /** Returns true if the given class has one of the desired annotations. */
222     private CacheLoader<Class<? extends Annotation>, Boolean> hasAnnotations =
223         new CacheLoader<Class<? extends Annotation>, Boolean>() {
224       public Boolean load(Class<? extends Annotation> annotationType) {
225         for (Annotation annotation : annotationType.getAnnotations()) {
226           if (annotationTypes.contains(annotation.annotationType())) {
227             return true;
228           }
229         }
230         return false;
231       }
232     };
233 
234     final LoadingCache<Class<? extends Annotation>, Boolean> cache = CacheBuilder.newBuilder().weakKeys()
235         .build(hasAnnotations);
236 
237     /**
238      * Constructs a new checker that looks for annotations of the given types.
239      */
240     AnnotationChecker(Collection<Class<? extends Annotation>> annotationTypes) {
241       this.annotationTypes = annotationTypes;
242     }
243 
244     /**
245      * Returns true if the given type has one of the desired annotations.
246      */
247     boolean hasAnnotations(Class<? extends Annotation> annotated) {
248       return cache.getUnchecked(annotated);
249     }
250   }
251 
252   private static final AnnotationChecker scopeChecker = new AnnotationChecker(
253       Arrays.asList(ScopeAnnotation.class, javax.inject.Scope.class));
254 
255   public static boolean isScopeAnnotation(Class<? extends Annotation> annotationType) {
256     return scopeChecker.hasAnnotations(annotationType);
257   }
258 
259   /**
260    * Adds an error if there is a misplaced annotations on {@code type}. Scoping
261    * annotations are not allowed on abstract classes or interfaces.
262    */
263   public static void checkForMisplacedScopeAnnotations(
264       Class<?> type, Object source, Errors errors) {
265     if (Classes.isConcrete(type)) {
266       return;
267     }
268 
269     Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, type);
270     if (scopeAnnotation != null
271         // We let Dagger Components through to aid migrations.
272         && !containsComponentAnnotation(type.getAnnotations())) {
273       errors.withSource(type).scopeAnnotationOnAbstractType(scopeAnnotation, type, source);
274     }
275   }
276 
277   /** Gets a key for the given type, member and annotations. */
278   public static Key<?> getKey(TypeLiteral<?> type, Member member, Annotation[] annotations,
279       Errors errors) throws ErrorsException {
280     int numErrorsBefore = errors.size();
281     Annotation found = findBindingAnnotation(errors, member, annotations);
282     errors.throwIfNewErrors(numErrorsBefore);
283     return found == null ? Key.get(type) : Key.get(type, found);
284   }
285 
286   /**
287    * Returns the binding annotation on {@code member}, or null if there isn't one.
288    */
289   public static Annotation findBindingAnnotation(
290       Errors errors, Member member, Annotation[] annotations) {
291     Annotation found = null;
292 
293     for (Annotation annotation : annotations) {
294       Class<? extends Annotation> annotationType = annotation.annotationType();
295       if (isBindingAnnotation(annotationType)) {
296         if (found != null) {
297           errors.duplicateBindingAnnotations(member, found.annotationType(), annotationType);
298         } else {
299           found = annotation;
300         }
301       }
302     }
303 
304     return found;
305   }
306 
307   private static final AnnotationChecker bindingAnnotationChecker = new AnnotationChecker(
308       Arrays.asList(BindingAnnotation.class, Qualifier.class));
309 
310   /**
311    * Returns true if annotations of the specified type are binding annotations.
312    */
313   public static boolean isBindingAnnotation(Class<? extends Annotation> annotationType) {
314     return bindingAnnotationChecker.hasAnnotations(annotationType);
315   }
316 
317   /**
318    * If the annotation is an instance of {@code javax.inject.Named}, canonicalizes to
319    * com.google.guice.name.Named.  Returns the given annotation otherwise.
320    */
321   public static Annotation canonicalizeIfNamed(Annotation annotation) {
322     if(annotation instanceof javax.inject.Named) {
323       return Names.named(((javax.inject.Named)annotation).value());
324     } else {
325       return annotation;
326     }
327   }
328 
329   /**
330    * If the annotation is the class {@code javax.inject.Named}, canonicalizes to
331    * com.google.guice.name.Named. Returns the given annotation class otherwise.
332    */
333   public static Class<? extends Annotation> canonicalizeIfNamed(
334       Class<? extends Annotation> annotationType) {
335     if (annotationType == javax.inject.Named.class) {
336       return Named.class;
337     } else {
338       return annotationType;
339     }
340   }
341 }
342