1 /** 2 * Copyright (C) 2015 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.multibindings; 18 19 import com.google.common.collect.ImmutableSet; 20 import com.google.inject.AbstractModule; 21 import com.google.inject.Binder; 22 import com.google.inject.Key; 23 import com.google.inject.Module; 24 import com.google.inject.TypeLiteral; 25 import com.google.inject.spi.InjectionPoint; 26 import com.google.inject.spi.ModuleAnnotatedMethodScanner; 27 28 import java.lang.annotation.Annotation; 29 import java.lang.reflect.InvocationTargetException; 30 import java.lang.reflect.Method; 31 import java.util.Set; 32 33 /** 34 * Scans a module for annotations that signal multibindings, mapbindings, and optional bindings. 35 * 36 * @since 4.0 37 */ 38 public class MultibindingsScanner { 39 40 private MultibindingsScanner() {} 41 42 /** 43 * Returns a module that, when installed, will scan all modules for methods with the annotations 44 * {@literal @}{@link ProvidesIntoMap}, {@literal @}{@link ProvidesIntoSet}, and 45 * {@literal @}{@link ProvidesIntoOptional}. 46 * 47 * <p>This is a convenience method, equivalent to doing 48 * {@code binder().scanModulesForAnnotatedMethods(MultibindingsScanner.scanner())}. 49 */ 50 public static Module asModule() { 51 return new AbstractModule() { 52 @Override protected void configure() { 53 binder().scanModulesForAnnotatedMethods(Scanner.INSTANCE); 54 } 55 }; 56 } 57 58 /** 59 * Returns a {@link ModuleAnnotatedMethodScanner} that, when bound, will scan all modules for 60 * methods with the annotations {@literal @}{@link ProvidesIntoMap}, 61 * {@literal @}{@link ProvidesIntoSet}, and {@literal @}{@link ProvidesIntoOptional}. 62 */ 63 public static ModuleAnnotatedMethodScanner scanner() { 64 return Scanner.INSTANCE; 65 } 66 67 private static class Scanner extends ModuleAnnotatedMethodScanner { 68 private static final Scanner INSTANCE = new Scanner(); 69 70 @Override 71 public Set<? extends Class<? extends Annotation>> annotationClasses() { 72 return ImmutableSet.of( 73 ProvidesIntoSet.class, ProvidesIntoMap.class, ProvidesIntoOptional.class); 74 } 75 76 @SuppressWarnings({"unchecked", "rawtypes"}) // mapKey doesn't know its key type 77 @Override 78 public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key, 79 InjectionPoint injectionPoint) { 80 Method method = (Method) injectionPoint.getMember(); 81 AnnotationOrError mapKey = findMapKeyAnnotation(binder, method); 82 if (annotation instanceof ProvidesIntoSet) { 83 if (mapKey.annotation != null) { 84 binder.addError("Found a MapKey annotation on non map binding at %s.", method); 85 } 86 return Multibinder.newRealSetBinder(binder, key).getKeyForNewItem(); 87 } else if (annotation instanceof ProvidesIntoMap) { 88 if (mapKey.error) { 89 // Already failed on the MapKey, don't bother doing more work. 90 return key; 91 } 92 if (mapKey.annotation == null) { 93 // If no MapKey, make an error and abort. 94 binder.addError("No MapKey found for map binding at %s.", method); 95 return key; 96 } 97 TypeAndValue typeAndValue = typeAndValueOfMapKey(mapKey.annotation); 98 return MapBinder.newRealMapBinder(binder, typeAndValue.type, key) 99 .getKeyForNewValue(typeAndValue.value); 100 } else if (annotation instanceof ProvidesIntoOptional) { 101 if (mapKey.annotation != null) { 102 binder.addError("Found a MapKey annotation on non map binding at %s.", method); 103 } 104 switch (((ProvidesIntoOptional)annotation).value()) { 105 case DEFAULT: 106 return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForDefaultBinding(); 107 case ACTUAL: 108 return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForActualBinding(); 109 } 110 } 111 throw new IllegalStateException("Invalid annotation: " + annotation); 112 } 113 } 114 115 private static class AnnotationOrError { 116 final Annotation annotation; 117 final boolean error; 118 AnnotationOrError(Annotation annotation, boolean error) { 119 this.annotation = annotation; 120 this.error = error; 121 } 122 123 static AnnotationOrError forPossiblyNullAnnotation(Annotation annotation) { 124 return new AnnotationOrError(annotation, false); 125 } 126 127 static AnnotationOrError forError() { 128 return new AnnotationOrError(null, true); 129 } 130 } 131 132 private static AnnotationOrError findMapKeyAnnotation(Binder binder, Method method) { 133 Annotation foundAnnotation = null; 134 for (Annotation annotation : method.getAnnotations()) { 135 MapKey mapKey = annotation.annotationType().getAnnotation(MapKey.class); 136 if (mapKey != null) { 137 if (foundAnnotation != null) { 138 binder.addError("Found more than one MapKey annotations on %s.", method); 139 return AnnotationOrError.forError(); 140 } 141 if (mapKey.unwrapValue()) { 142 try { 143 // validate there's a declared method called "value" 144 Method valueMethod = annotation.annotationType().getDeclaredMethod("value"); 145 if (valueMethod.getReturnType().isArray()) { 146 binder.addError("Array types are not allowed in a MapKey with unwrapValue=true: %s", 147 annotation.annotationType()); 148 return AnnotationOrError.forError(); 149 } 150 } catch (NoSuchMethodException invalid) { 151 binder.addError("No 'value' method in MapKey with unwrapValue=true: %s", 152 annotation.annotationType()); 153 return AnnotationOrError.forError(); 154 } 155 } 156 foundAnnotation = annotation; 157 } 158 } 159 return AnnotationOrError.forPossiblyNullAnnotation(foundAnnotation); 160 } 161 162 @SuppressWarnings({"unchecked", "rawtypes"}) 163 static TypeAndValue<?> typeAndValueOfMapKey(Annotation mapKeyAnnotation) { 164 if (!mapKeyAnnotation.annotationType().getAnnotation(MapKey.class).unwrapValue()) { 165 return new TypeAndValue(TypeLiteral.get(mapKeyAnnotation.annotationType()), mapKeyAnnotation); 166 } else { 167 try { 168 Method valueMethod = mapKeyAnnotation.annotationType().getDeclaredMethod("value"); 169 valueMethod.setAccessible(true); 170 TypeLiteral<?> returnType = 171 TypeLiteral.get(mapKeyAnnotation.annotationType()).getReturnType(valueMethod); 172 return new TypeAndValue(returnType, valueMethod.invoke(mapKeyAnnotation)); 173 } catch (NoSuchMethodException e) { 174 throw new IllegalStateException(e); 175 } catch (SecurityException e) { 176 throw new IllegalStateException(e); 177 } catch (IllegalAccessException e) { 178 throw new IllegalStateException(e); 179 } catch (InvocationTargetException e) { 180 throw new IllegalStateException(e); 181 } 182 } 183 } 184 185 private static class TypeAndValue<T> { 186 final TypeLiteral<T> type; 187 final T value; 188 189 TypeAndValue(TypeLiteral<T> type, T value) { 190 this.type = type; 191 this.value = value; 192 } 193 } 194 } 195