Home | History | Annotate | Download | only in codegen
      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 (at) 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 
     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