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