1 /* 2 * Copyright (C) 2014 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 package dagger.internal.codegen; 17 18 import com.google.common.collect.ImmutableSet; 19 import com.google.common.collect.Iterables; 20 import dagger.Module; 21 import dagger.Provides; 22 import java.util.Set; 23 import javax.lang.model.element.AnnotationMirror; 24 import javax.lang.model.element.Element; 25 import javax.lang.model.element.ExecutableElement; 26 import javax.lang.model.element.Modifier; 27 import javax.lang.model.element.TypeElement; 28 import javax.lang.model.type.DeclaredType; 29 import javax.lang.model.type.TypeKind; 30 import javax.lang.model.type.TypeMirror; 31 import javax.lang.model.util.Elements; 32 33 import static com.google.auto.common.MoreElements.isAnnotationPresent; 34 import static com.google.common.base.Preconditions.checkArgument; 35 import static com.google.common.base.Preconditions.checkNotNull; 36 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_ABSTRACT; 37 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_MUST_RETURN_A_VALUE; 38 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_NOT_IN_MODULE; 39 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_NOT_MAP_HAS_MAP_KEY; 40 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_PRIVATE; 41 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_SET_VALUES_RAW_SET; 42 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_TYPE_PARAMETER; 43 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_MULTIPLE_MAP_KEY; 44 import static dagger.internal.codegen.ErrorMessages.BINDING_METHOD_WITH_NO_MAP_KEY; 45 import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_RETURN_TYPE; 46 import static dagger.internal.codegen.ErrorMessages.PROVIDES_METHOD_SET_VALUES_RETURN_SET; 47 import static dagger.internal.codegen.ErrorMessages.PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS; 48 import static dagger.internal.codegen.InjectionAnnotations.getQualifiers; 49 import static dagger.internal.codegen.MapKeys.getMapKeys; 50 import static javax.lang.model.element.Modifier.ABSTRACT; 51 import static javax.lang.model.element.Modifier.PRIVATE; 52 import static javax.lang.model.type.TypeKind.ARRAY; 53 import static javax.lang.model.type.TypeKind.DECLARED; 54 import static javax.lang.model.type.TypeKind.VOID; 55 56 /** 57 * A {@linkplain ValidationReport validator} for {@link Provides} methods. 58 * 59 * @author Gregory Kick 60 * @since 2.0 61 */ 62 final class ProvidesMethodValidator { 63 private final Elements elements; 64 ProvidesMethodValidator(Elements elements)65 ProvidesMethodValidator(Elements elements) { 66 this.elements = checkNotNull(elements); 67 } 68 getSetElement()69 private TypeElement getSetElement() { 70 return elements.getTypeElement(Set.class.getCanonicalName()); 71 } 72 validate(ExecutableElement providesMethodElement)73 ValidationReport<ExecutableElement> validate(ExecutableElement providesMethodElement) { 74 ValidationReport.Builder<ExecutableElement> builder = 75 ValidationReport.about(providesMethodElement); 76 77 Provides providesAnnotation = providesMethodElement.getAnnotation(Provides.class); 78 checkArgument(providesAnnotation != null); 79 80 Element enclosingElement = providesMethodElement.getEnclosingElement(); 81 if (!isAnnotationPresent(enclosingElement, Module.class)) { 82 builder.addError( 83 formatModuleErrorMessage(BINDING_METHOD_NOT_IN_MODULE), providesMethodElement); 84 } 85 86 if (!providesMethodElement.getTypeParameters().isEmpty()) { 87 builder.addError(formatErrorMessage(BINDING_METHOD_TYPE_PARAMETER), providesMethodElement); 88 } 89 90 Set<Modifier> modifiers = providesMethodElement.getModifiers(); 91 if (modifiers.contains(PRIVATE)) { 92 builder.addError(formatErrorMessage(BINDING_METHOD_PRIVATE), providesMethodElement); 93 } 94 if (modifiers.contains(ABSTRACT)) { 95 builder.addError(formatErrorMessage(BINDING_METHOD_ABSTRACT), providesMethodElement); 96 } 97 98 TypeMirror returnType = providesMethodElement.getReturnType(); 99 TypeKind returnTypeKind = returnType.getKind(); 100 if (returnTypeKind.equals(VOID)) { 101 builder.addError( 102 formatErrorMessage(BINDING_METHOD_MUST_RETURN_A_VALUE), providesMethodElement); 103 } 104 105 // check mapkey is right 106 if (!providesAnnotation.type().equals(Provides.Type.MAP) 107 && !getMapKeys(providesMethodElement).isEmpty()) { 108 builder.addError( 109 formatErrorMessage(BINDING_METHOD_NOT_MAP_HAS_MAP_KEY), providesMethodElement); 110 } 111 112 validateMethodQualifiers(builder, providesMethodElement); 113 114 switch (providesAnnotation.type()) { 115 case UNIQUE: // fall through 116 case SET: 117 validateKeyType(builder, returnType); 118 break; 119 case MAP: 120 validateKeyType(builder, returnType); 121 ImmutableSet<? extends AnnotationMirror> mapKeys = getMapKeys(providesMethodElement); 122 switch (mapKeys.size()) { 123 case 0: 124 builder.addError( 125 formatErrorMessage(BINDING_METHOD_WITH_NO_MAP_KEY), providesMethodElement); 126 break; 127 case 1: 128 break; 129 default: 130 builder.addError( 131 formatErrorMessage(BINDING_METHOD_WITH_MULTIPLE_MAP_KEY), providesMethodElement); 132 break; 133 } 134 break; 135 case SET_VALUES: 136 if (!returnTypeKind.equals(DECLARED)) { 137 builder.addError(PROVIDES_METHOD_SET_VALUES_RETURN_SET, providesMethodElement); 138 } else { 139 DeclaredType declaredReturnType = (DeclaredType) returnType; 140 // TODO(gak): should we allow "covariant return" for set values? 141 if (!declaredReturnType.asElement().equals(getSetElement())) { 142 builder.addError(PROVIDES_METHOD_SET_VALUES_RETURN_SET, providesMethodElement); 143 } else if (declaredReturnType.getTypeArguments().isEmpty()) { 144 builder.addError( 145 formatErrorMessage(BINDING_METHOD_SET_VALUES_RAW_SET), providesMethodElement); 146 } else { 147 validateKeyType(builder, 148 Iterables.getOnlyElement(declaredReturnType.getTypeArguments())); 149 } 150 } 151 break; 152 default: 153 throw new AssertionError(); 154 } 155 156 return builder.build(); 157 } 158 159 /** Validates that a Provides or Produces method doesn't have multiple qualifiers. */ validateMethodQualifiers(ValidationReport.Builder<ExecutableElement> builder, ExecutableElement methodElement)160 static void validateMethodQualifiers(ValidationReport.Builder<ExecutableElement> builder, 161 ExecutableElement methodElement) { 162 ImmutableSet<? extends AnnotationMirror> qualifiers = getQualifiers(methodElement); 163 if (qualifiers.size() > 1) { 164 for (AnnotationMirror qualifier : qualifiers) { 165 builder.addError(PROVIDES_OR_PRODUCES_METHOD_MULTIPLE_QUALIFIERS, methodElement, qualifier); 166 } 167 } 168 } 169 formatErrorMessage(String msg)170 private String formatErrorMessage(String msg) { 171 return String.format(msg, Provides.class.getSimpleName()); 172 } 173 formatModuleErrorMessage(String msg)174 private String formatModuleErrorMessage(String msg) { 175 return String.format(msg, Provides.class.getSimpleName(), Module.class.getSimpleName()); 176 } 177 validateKeyType(ValidationReport.Builder<? extends Element> reportBuilder, TypeMirror type)178 private void validateKeyType(ValidationReport.Builder<? extends Element> reportBuilder, 179 TypeMirror type) { 180 TypeKind kind = type.getKind(); 181 if (!(kind.isPrimitive() || kind.equals(DECLARED) || kind.equals(ARRAY))) { 182 reportBuilder.addError(PROVIDES_METHOD_RETURN_TYPE, reportBuilder.getSubject()); 183 } 184 } 185 } 186