1 /* 2 * Copyright (C) 2017 The Dagger Authors. 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 17 package dagger.android.processor; 18 19 import androidx.room.compiler.processing.JavaPoetExtKt; 20 import androidx.room.compiler.processing.XAnnotation; 21 import androidx.room.compiler.processing.XAnnotationValue; 22 import androidx.room.compiler.processing.XElement; 23 import androidx.room.compiler.processing.XExecutableElement; 24 import androidx.room.compiler.processing.XMessager; 25 import androidx.room.compiler.processing.XMethodElement; 26 import androidx.room.compiler.processing.XType; 27 import androidx.room.compiler.processing.XTypeElement; 28 import com.google.auto.value.AutoValue; 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableSet; 31 import com.google.common.collect.Sets; 32 import com.squareup.javapoet.AnnotationSpec; 33 import com.squareup.javapoet.ClassName; 34 import dagger.internal.codegen.xprocessing.XElements; 35 import java.util.Optional; 36 import javax.tools.Diagnostic.Kind; 37 38 /** 39 * A descriptor of a generated {@link dagger.Module} and {@link dagger.Subcomponent} to be generated 40 * from a {@code ContributesAndroidInjector} method. 41 */ 42 @AutoValue 43 abstract class AndroidInjectorDescriptor { 44 /** The type to be injected; the return type of the {@code ContributesAndroidInjector} method. */ injectedType()45 abstract ClassName injectedType(); 46 47 /** Scopes to apply to the generated {@link dagger.Subcomponent}. */ scopes()48 abstract ImmutableSet<AnnotationSpec> scopes(); 49 50 /** See {@code ContributesAndroidInjector#modules()} */ modules()51 abstract ImmutableSet<ClassName> modules(); 52 53 /** The {@link dagger.Module} that contains the {@code ContributesAndroidInjector} method. */ enclosingModule()54 abstract ClassName enclosingModule(); 55 56 /** The method annotated with {@code ContributesAndroidInjector}. */ method()57 abstract XExecutableElement method(); 58 59 @AutoValue.Builder 60 abstract static class Builder { injectedType(ClassName injectedType)61 abstract Builder injectedType(ClassName injectedType); 62 scopesBuilder()63 abstract ImmutableSet.Builder<AnnotationSpec> scopesBuilder(); 64 modulesBuilder()65 abstract ImmutableSet.Builder<ClassName> modulesBuilder(); 66 enclosingModule(ClassName enclosingModule)67 abstract Builder enclosingModule(ClassName enclosingModule); 68 method(XExecutableElement method)69 abstract Builder method(XExecutableElement method); 70 build()71 abstract AndroidInjectorDescriptor build(); 72 } 73 74 static final class Validator { 75 private final XMessager messager; 76 Validator(XMessager messager)77 Validator(XMessager messager) { 78 this.messager = messager; 79 } 80 81 /** 82 * Validates a {@code ContributesAndroidInjector} method, returning an {@link 83 * AndroidInjectorDescriptor} if it is valid, or {@link Optional#empty()} otherwise. 84 */ createIfValid(XMethodElement method)85 Optional<AndroidInjectorDescriptor> createIfValid(XMethodElement method) { 86 ErrorReporter reporter = new ErrorReporter(method, messager); 87 88 if (!method.isAbstract()) { 89 reporter.reportError("@ContributesAndroidInjector methods must be abstract"); 90 } 91 92 if (!method.getParameters().isEmpty()) { 93 reporter.reportError("@ContributesAndroidInjector methods cannot have parameters"); 94 } 95 96 AndroidInjectorDescriptor.Builder builder = 97 new AutoValue_AndroidInjectorDescriptor.Builder().method(method); 98 XTypeElement enclosingElement = XElements.asTypeElement(method.getEnclosingElement()); 99 if (!enclosingElement.hasAnnotation(TypeNames.MODULE)) { 100 reporter.reportError("@ContributesAndroidInjector methods must be in a @Module"); 101 } 102 builder.enclosingModule(enclosingElement.getClassName()); 103 104 XType injectedType = method.getReturnType(); 105 if (injectedType.getTypeArguments().isEmpty()) { 106 builder.injectedType(injectedType.getTypeElement().getClassName()); 107 } else { 108 reporter.reportError( 109 "@ContributesAndroidInjector methods cannot return parameterized types"); 110 } 111 112 XAnnotation annotation = method.getAnnotation(TypeNames.CONTRIBUTES_ANDROID_INJECTOR); 113 for (XType module : getTypeList(annotation.getAnnotationValue("modules"))) { 114 if (module.getTypeElement().hasAnnotation(TypeNames.MODULE)) { 115 builder.modulesBuilder().add((ClassName) module.getTypeName()); 116 } else { 117 reporter.reportError(String.format("%s is not a @Module", module), annotation); 118 } 119 } 120 121 for (XAnnotation scope : 122 Sets.union( 123 method.getAnnotationsAnnotatedWith(TypeNames.SCOPE), 124 method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX))) { 125 builder.scopesBuilder().add(JavaPoetExtKt.toAnnotationSpec(scope)); 126 } 127 128 for (XAnnotation qualifier : 129 Sets.union( 130 method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER), 131 method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX))) { 132 reporter.reportError( 133 "@ContributesAndroidInjector methods cannot have qualifiers", qualifier); 134 } 135 136 return reporter.hasError ? Optional.empty() : Optional.of(builder.build()); 137 } 138 getTypeList(XAnnotationValue annotationValue)139 private static ImmutableList<XType> getTypeList(XAnnotationValue annotationValue) { 140 if (annotationValue.hasTypeListValue()) { 141 return ImmutableList.copyOf(annotationValue.asTypeList()); 142 } 143 if (annotationValue.hasTypeValue()) { 144 return ImmutableList.of(annotationValue.asType()); 145 } 146 throw new IllegalArgumentException("Does not have type list"); 147 } 148 149 // TODO(ronshapiro): use ValidationReport once it is moved out of the compiler 150 private static class ErrorReporter { 151 private final XElement subject; 152 private final XMessager messager; 153 private boolean hasError; 154 ErrorReporter(XElement subject, XMessager messager)155 ErrorReporter(XElement subject, XMessager messager) { 156 this.subject = subject; 157 this.messager = messager; 158 } 159 reportError(String error)160 void reportError(String error) { 161 hasError = true; 162 messager.printMessage(Kind.ERROR, error, subject); 163 } 164 reportError(String error, XAnnotation annotation)165 void reportError(String error, XAnnotation annotation) { 166 hasError = true; 167 messager.printMessage(Kind.ERROR, error, subject, annotation); 168 } 169 } 170 } 171 } 172