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