• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package dagger.internal.codegen;
17 
18 import com.google.auto.common.MoreTypes;
19 import com.google.common.base.Equivalence;
20 import com.google.common.collect.Iterables;
21 import com.google.common.collect.LinkedHashMultimap;
22 import com.google.common.collect.Multimap;
23 import java.lang.annotation.Annotation;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import javax.lang.model.element.Element;
29 import javax.lang.model.element.ExecutableElement;
30 import javax.lang.model.element.Modifier;
31 import javax.lang.model.element.TypeElement;
32 import javax.lang.model.type.ExecutableType;
33 import javax.lang.model.type.TypeKind;
34 import javax.lang.model.type.TypeMirror;
35 import javax.lang.model.util.ElementFilter;
36 import javax.lang.model.util.Elements;
37 import javax.lang.model.util.Types;
38 
39 import static com.google.auto.common.MoreElements.isAnnotationPresent;
40 import static com.google.common.base.Preconditions.checkArgument;
41 import static com.google.common.collect.Iterables.getOnlyElement;
42 import static javax.lang.model.element.Modifier.ABSTRACT;
43 import static javax.lang.model.element.Modifier.PRIVATE;
44 import static javax.lang.model.element.Modifier.STATIC;
45 
46 /**
47  * Validates {@link dagger.Component.Builder} annotations.
48  *
49  * @author sameb@google.com (Sam Berlin)
50  */
51 class BuilderValidator {
52   private final Elements elements;
53   private final Types types;
54   private final ComponentDescriptor.Kind componentType;
55 
BuilderValidator(Elements elements, Types types, ComponentDescriptor.Kind componentType)56   BuilderValidator(Elements elements, Types types, ComponentDescriptor.Kind componentType) {
57     this.elements = elements;
58     this.types = types;
59     this.componentType = componentType;
60   }
61 
validate(TypeElement subject)62   public ValidationReport<TypeElement> validate(TypeElement subject) {
63     ValidationReport.Builder<TypeElement> builder = ValidationReport.about(subject);
64 
65     Element componentElement = subject.getEnclosingElement();
66     ErrorMessages.ComponentBuilderMessages msgs = ErrorMessages.builderMsgsFor(componentType);
67     Class<? extends Annotation> componentAnnotation = componentType.annotationType();
68     Class<? extends Annotation> builderAnnotation = componentType.builderAnnotationType();
69     checkArgument(subject.getAnnotation(builderAnnotation) != null);
70 
71     if (!isAnnotationPresent(componentElement, componentAnnotation)) {
72       builder.addError(msgs.mustBeInComponent(), subject);
73     }
74 
75     switch (subject.getKind()) {
76       case CLASS:
77         List<? extends Element> allElements = subject.getEnclosedElements();
78         List<ExecutableElement> cxtors = ElementFilter.constructorsIn(allElements);
79         if (cxtors.size() != 1 || getOnlyElement(cxtors).getParameters().size() != 0) {
80           builder.addError(msgs.cxtorOnlyOneAndNoArgs(), subject);
81         }
82         break;
83       case INTERFACE:
84         break;
85       default:
86         // If not the correct type, exit early since the rest of the messages will be bogus.
87         builder.addError(msgs.mustBeClassOrInterface(), subject);
88         return builder.build();
89     }
90 
91     if (!subject.getTypeParameters().isEmpty()) {
92       builder.addError(msgs.generics(), subject);
93     }
94 
95     Set<Modifier> modifiers = subject.getModifiers();
96     if (modifiers.contains(PRIVATE)) {
97       builder.addError(msgs.isPrivate(), subject);
98     }
99     if (!modifiers.contains(STATIC)) {
100       builder.addError(msgs.mustBeStatic(), subject);
101     }
102     // Note: Must be abstract, so no need to check for final.
103     if (!modifiers.contains(ABSTRACT)) {
104       builder.addError(msgs.mustBeAbstract(), subject);
105     }
106 
107     ExecutableElement buildMethod = null;
108     Multimap<Equivalence.Wrapper<TypeMirror>, ExecutableElement> methodsPerParam =
109         LinkedHashMultimap.create();
110     for (ExecutableElement method : Util.getUnimplementedMethods(elements, subject)) {
111       ExecutableType resolvedMethodType =
112           MoreTypes.asExecutable(types.asMemberOf(MoreTypes.asDeclared(subject.asType()), method));
113       TypeMirror returnType = resolvedMethodType.getReturnType();
114       if (method.getParameters().size() == 0) {
115         // If this is potentially a build() method, validate it returns the correct type.
116         if (types.isSameType(returnType, componentElement.asType())) {
117           if (buildMethod != null) {
118             // If we found more than one build-like method, fail.
119             error(builder, method, msgs.twoBuildMethods(), msgs.inheritedTwoBuildMethods(),
120                 buildMethod);
121           }
122         } else {
123           error(builder, method, msgs.buildMustReturnComponentType(),
124               msgs.inheritedBuildMustReturnComponentType());
125         }
126         // We set the buildMethod regardless of the return type to reduce error spam.
127         buildMethod = method;
128       } else if (method.getParameters().size() > 1) {
129         // If this is a setter, make sure it has one arg.
130         error(builder, method, msgs.methodsMustTakeOneArg(), msgs.inheritedMethodsMustTakeOneArg());
131       } else if (returnType.getKind() != TypeKind.VOID
132           && !types.isSubtype(subject.asType(), returnType)) {
133         // If this correctly had one arg, make sure the return types are valid.
134         error(builder, method, msgs.methodsMustReturnVoidOrBuilder(),
135             msgs.inheritedMethodsMustReturnVoidOrBuilder());
136       } else {
137         // If the return types are valid, record the method.
138         methodsPerParam.put(
139             MoreTypes.equivalence().<TypeMirror>wrap(
140                 Iterables.getOnlyElement(resolvedMethodType.getParameterTypes())),
141             method);
142       }
143 
144       if (!method.getTypeParameters().isEmpty()) {
145         error(builder, method, msgs.methodsMayNotHaveTypeParameters(),
146             msgs.inheritedMethodsMayNotHaveTypeParameters());
147       }
148     }
149 
150     if (buildMethod == null) {
151       builder.addError(msgs.missingBuildMethod(), subject);
152     }
153 
154     // Go back through each recorded method per param type.  If we had more than one method
155     // for a given param, fail.
156     for (Map.Entry<Equivalence.Wrapper<TypeMirror>, Collection<ExecutableElement>> entry :
157         methodsPerParam.asMap().entrySet()) {
158       if (entry.getValue().size() > 1) {
159         TypeMirror type = entry.getKey().get();
160         builder.addError(String.format(msgs.manyMethodsForType(), type, entry.getValue()), subject);
161       }
162     }
163 
164     // Note: there's more validation in BindingGraphValidator,
165     // specifically to make sure the setter methods mirror the deps.
166 
167     return builder.build();
168   }
169 
170   /**
171    * Generates one of two error messages. If the method is enclosed in the subject, we target the
172    * error to the method itself. Otherwise we target the error to the subject and list the method as
173    * an argumnent. (Otherwise we have no way of knowing if the method is being compiled in this pass
174    * too, so javac might not be able to pinpoint it's line of code.)
175    */
176   /*
177    * For Component.Builder, the prototypical example would be if someone had:
178    *    libfoo: interface SharedBuilder { void badSetter(A a, B b); }
179    *    libbar: BarComponent { BarBuilder extends SharedBuilder } }
180    * ... the compiler only validates BarBuilder when compiling libbar, but it fails because
181    * of libfoo's SharedBuilder (which could have been compiled in a previous pass).
182    * So we can't point to SharedBuilder#badSetter as the subject of the BarBuilder validation
183    * failure.
184    *
185    * This check is a little more strict than necessary -- ideally we'd check if method's enclosing
186    * class was included in this compile run.  But that's hard, and this is close enough.
187    */
error( ValidationReport.Builder<TypeElement> builder, ExecutableElement method, String enclosedError, String inheritedError, Object... extraArgs)188   private void error(
189       ValidationReport.Builder<TypeElement> builder,
190       ExecutableElement method,
191       String enclosedError,
192       String inheritedError,
193       Object... extraArgs) {
194     if (method.getEnclosingElement().equals(builder.getSubject())) {
195       builder.addError(String.format(enclosedError, extraArgs), method);
196     } else {
197       Object[] newArgs = new Object[extraArgs.length + 1];
198       newArgs[0] = method;
199       System.arraycopy(extraArgs, 0, newArgs, 1, extraArgs.length);
200       builder.addError(String.format(inheritedError, newArgs), builder.getSubject());
201     }
202   }
203 }
204