• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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