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