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 */
16package dagger.internal.codegen;
17
18import com.google.auto.common.MoreTypes;
19import com.google.common.base.Equivalence;
20import com.google.common.collect.Iterables;
21import com.google.common.collect.LinkedHashMultimap;
22import com.google.common.collect.Multimap;
23import java.lang.annotation.Annotation;
24import java.util.Collection;
25import java.util.List;
26import java.util.Map;
27import java.util.Set;
28import javax.lang.model.element.Element;
29import javax.lang.model.element.ExecutableElement;
30import javax.lang.model.element.Modifier;
31import javax.lang.model.element.TypeElement;
32import javax.lang.model.type.ExecutableType;
33import javax.lang.model.type.TypeKind;
34import javax.lang.model.type.TypeMirror;
35import javax.lang.model.util.ElementFilter;
36import javax.lang.model.util.Elements;
37import javax.lang.model.util.Types;
38
39import static com.google.auto.common.MoreElements.isAnnotationPresent;
40import static com.google.common.base.Preconditions.checkArgument;
41import static com.google.common.collect.Iterables.getOnlyElement;
42import static javax.lang.model.element.Modifier.ABSTRACT;
43import static javax.lang.model.element.Modifier.PRIVATE;
44import 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 */
51class BuilderValidator {
52  private final Elements elements;
53  private final Types types;
54  private final ComponentDescriptor.Kind componentType;
55
56  BuilderValidator(Elements elements, Types types, ComponentDescriptor.Kind componentType) {
57    this.elements = elements;
58    this.types = types;
59    this.componentType = componentType;
60  }
61
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   */
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