Home | History | Annotate | Download | only in codegen
      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 
     65   ProvidesMethodValidator(Elements elements) {
     66     this.elements = checkNotNull(elements);
     67   }
     68 
     69   private TypeElement getSetElement() {
     70     return elements.getTypeElement(Set.class.getCanonicalName());
     71   }
     72 
     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. */
    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 
    170   private String formatErrorMessage(String msg) {
    171     return String.format(msg, Provides.class.getSimpleName());
    172   }
    173 
    174   private String formatModuleErrorMessage(String msg) {
    175     return String.format(msg, Provides.class.getSimpleName(), Module.class.getSimpleName());
    176   }
    177 
    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