diff options
Diffstat (limited to 'java/dagger/android/processor')
-rw-r--r-- | java/dagger/android/processor/AndroidInjectorDescriptor.java | 116 | ||||
-rw-r--r-- | java/dagger/android/processor/AndroidMapKeyProcessingStep.java | 163 | ||||
-rw-r--r-- | java/dagger/android/processor/AndroidMapKeyValidator.java | 175 | ||||
-rw-r--r-- | java/dagger/android/processor/AndroidMapKeys.java | 21 | ||||
-rw-r--r-- | java/dagger/android/processor/AndroidProcessor.java | 59 | ||||
-rw-r--r-- | java/dagger/android/processor/BUILD | 60 | ||||
-rw-r--r-- | java/dagger/android/processor/BaseProcessingStep.java | 93 | ||||
-rw-r--r-- | java/dagger/android/processor/ContributesAndroidInjectorProcessingStep.java (renamed from java/dagger/android/processor/ContributesAndroidInjectorGenerator.java) | 114 | ||||
-rw-r--r-- | java/dagger/android/processor/DelegateAndroidProcessor.java | 55 | ||||
-rw-r--r-- | java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java | 59 | ||||
-rw-r--r-- | java/dagger/android/processor/KspAndroidProcessor.java | 53 | ||||
-rw-r--r-- | java/dagger/android/processor/MoreDaggerElements.java | 71 | ||||
-rw-r--r-- | java/dagger/android/processor/MoreDaggerTypes.java | 114 |
13 files changed, 575 insertions, 578 deletions
diff --git a/java/dagger/android/processor/AndroidInjectorDescriptor.java b/java/dagger/android/processor/AndroidInjectorDescriptor.java index 12f2edf24..758d895aa 100644 --- a/java/dagger/android/processor/AndroidInjectorDescriptor.java +++ b/java/dagger/android/processor/AndroidInjectorDescriptor.java @@ -16,29 +16,23 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; -import static dagger.android.processor.MoreDaggerElements.getAnnotatedAnnotations; -import static java.util.stream.Collectors.toList; -import static javax.lang.model.element.Modifier.ABSTRACT; - -import com.google.auto.common.MoreElements; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.JavaPoetExtKt; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XAnnotationValue; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableElement; +import androidx.room.compiler.processing.XMessager; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.TypeName; -import java.util.List; +import dagger.internal.codegen.xprocessing.XElements; import java.util.Optional; -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleAnnotationValueVisitor8; import javax.tools.Diagnostic.Kind; /** @@ -60,7 +54,7 @@ abstract class AndroidInjectorDescriptor { abstract ClassName enclosingModule(); /** The method annotated with {@code ContributesAndroidInjector}. */ - abstract ExecutableElement method(); + abstract XExecutableElement method(); @AutoValue.Builder abstract static class Builder { @@ -72,15 +66,15 @@ abstract class AndroidInjectorDescriptor { abstract Builder enclosingModule(ClassName enclosingModule); - abstract Builder method(ExecutableElement method); + abstract Builder method(XExecutableElement method); abstract AndroidInjectorDescriptor build(); } static final class Validator { - private final Messager messager; + private final XMessager messager; - Validator(Messager messager) { + Validator(XMessager messager) { this.messager = messager; } @@ -88,10 +82,10 @@ abstract class AndroidInjectorDescriptor { * Validates a {@code ContributesAndroidInjector} method, returning an {@link * AndroidInjectorDescriptor} if it is valid, or {@link Optional#empty()} otherwise. */ - Optional<AndroidInjectorDescriptor> createIfValid(ExecutableElement method) { + Optional<AndroidInjectorDescriptor> createIfValid(XMethodElement method) { ErrorReporter reporter = new ErrorReporter(method, messager); - if (!method.getModifiers().contains(ABSTRACT)) { + if (!method.isAbstract()) { reporter.reportError("@ContributesAndroidInjector methods must be abstract"); } @@ -101,41 +95,40 @@ abstract class AndroidInjectorDescriptor { AndroidInjectorDescriptor.Builder builder = new AutoValue_AndroidInjectorDescriptor.Builder().method(method); - TypeElement enclosingElement = MoreElements.asType(method.getEnclosingElement()); - if (!MoreDaggerElements.isAnnotationPresent(enclosingElement, TypeNames.MODULE)) { + XTypeElement enclosingElement = XElements.asTypeElement(method.getEnclosingElement()); + if (!enclosingElement.hasAnnotation(TypeNames.MODULE)) { reporter.reportError("@ContributesAndroidInjector methods must be in a @Module"); } - builder.enclosingModule(ClassName.get(enclosingElement)); + builder.enclosingModule(enclosingElement.getClassName()); - TypeMirror injectedType = method.getReturnType(); - if (MoreTypes.asDeclared(injectedType).getTypeArguments().isEmpty()) { - builder.injectedType(ClassName.get(MoreTypes.asTypeElement(injectedType))); + XType injectedType = method.getReturnType(); + if (injectedType.getTypeArguments().isEmpty()) { + builder.injectedType(injectedType.getTypeElement().getClassName()); } else { reporter.reportError( "@ContributesAndroidInjector methods cannot return parameterized types"); } - AnnotationMirror annotation = - MoreDaggerElements.getAnnotationMirror(method, TypeNames.CONTRIBUTES_ANDROID_INJECTOR) - .get(); - for (TypeMirror module : - getAnnotationValue(annotation, "modules").accept(new AllTypesVisitor(), null)) { - if (MoreDaggerElements.isAnnotationPresent(MoreTypes.asElement(module), TypeNames.MODULE)) { - builder.modulesBuilder().add((ClassName) TypeName.get(module)); + XAnnotation annotation = method.getAnnotation(TypeNames.CONTRIBUTES_ANDROID_INJECTOR); + for (XType module : getTypeList(annotation.getAnnotationValue("modules"))) { + if (module.getTypeElement().hasAnnotation(TypeNames.MODULE)) { + builder.modulesBuilder().add((ClassName) module.getTypeName()); } else { reporter.reportError(String.format("%s is not a @Module", module), annotation); } } - for (AnnotationMirror scope : Sets.union( - getAnnotatedAnnotations(method, TypeNames.SCOPE), - getAnnotatedAnnotations(method, TypeNames.SCOPE_JAVAX))) { - builder.scopesBuilder().add(AnnotationSpec.get(scope)); + for (XAnnotation scope : + Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE), + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX))) { + builder.scopesBuilder().add(JavaPoetExtKt.toAnnotationSpec(scope)); } - for (AnnotationMirror qualifier : Sets.union( - getAnnotatedAnnotations(method, TypeNames.QUALIFIER), - getAnnotatedAnnotations(method, TypeNames.QUALIFIER_JAVAX))) { + for (XAnnotation qualifier : + Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER), + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX))) { reporter.reportError( "@ContributesAndroidInjector methods cannot have qualifiers", qualifier); } @@ -143,13 +136,23 @@ abstract class AndroidInjectorDescriptor { return reporter.hasError ? Optional.empty() : Optional.of(builder.build()); } + private static ImmutableList<XType> getTypeList(XAnnotationValue annotationValue) { + if (annotationValue.hasTypeListValue()) { + return ImmutableList.copyOf(annotationValue.asTypeList()); + } + if (annotationValue.hasTypeValue()) { + return ImmutableList.of(annotationValue.asType()); + } + throw new IllegalArgumentException("Does not have type list"); + } + // TODO(ronshapiro): use ValidationReport once it is moved out of the compiler private static class ErrorReporter { - private final Element subject; - private final Messager messager; + private final XElement subject; + private final XMessager messager; private boolean hasError; - ErrorReporter(Element subject, Messager messager) { + ErrorReporter(XElement subject, XMessager messager) { this.subject = subject; this.messager = messager; } @@ -159,29 +162,10 @@ abstract class AndroidInjectorDescriptor { messager.printMessage(Kind.ERROR, error, subject); } - void reportError(String error, AnnotationMirror annotation) { + void reportError(String error, XAnnotation annotation) { hasError = true; messager.printMessage(Kind.ERROR, error, subject, annotation); } } } - - private static final class AllTypesVisitor - extends SimpleAnnotationValueVisitor8<ImmutableSet<TypeMirror>, Void> { - @Override - public ImmutableSet<TypeMirror> visitArray(List<? extends AnnotationValue> values, Void aVoid) { - return ImmutableSet.copyOf( - values.stream().flatMap(v -> v.accept(this, null).stream()).collect(toList())); - } - - @Override - public ImmutableSet<TypeMirror> visitType(TypeMirror a, Void aVoid) { - return ImmutableSet.of(a); - } - - @Override - protected ImmutableSet<TypeMirror> defaultAction(Object o, Void aVoid) { - throw new AssertionError(o); - } - } } diff --git a/java/dagger/android/processor/AndroidMapKeyProcessingStep.java b/java/dagger/android/processor/AndroidMapKeyProcessingStep.java new file mode 100644 index 000000000..8a7e83c77 --- /dev/null +++ b/java/dagger/android/processor/AndroidMapKeyProcessingStep.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2017 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.android.processor; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; +import static dagger.internal.codegen.xprocessing.XTypes.toStableString; + +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XMethodElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.squareup.javapoet.ClassName; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.internal.codegen.xprocessing.XTypes; +import javax.tools.Diagnostic.Kind; + +/** Validates the correctness of {@link dagger.MapKey}s used with {@code dagger.android}. */ +final class AndroidMapKeyProcessingStep extends BaseProcessingStep { + private final XProcessingEnv processingEnv; + + AndroidMapKeyProcessingStep(XProcessingEnv processingEnv) { + this.processingEnv = processingEnv; + } + + @Override + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.ANDROID_INJECTION_KEY, TypeNames.CLASS_KEY); + } + + @Override + public void process(XElement element, ImmutableSet<ClassName> annotationNames) { + for (ClassName annotationName : annotationNames) { + validateMethod(annotationName, XElements.asMethod(element)); + } + } + + private void validateMethod(ClassName annotation, XMethodElement method) { + if (!Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER), + method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX)) + .isEmpty()) { + return; + } + + XType returnType = method.getReturnType(); + if (!factoryElement().getType().getRawType().isAssignableFrom(returnType.getRawType())) { + // if returnType is not related to AndroidInjector.Factory, ignore the method + return; + } + + if (!Sets.union( + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE), + method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX)) + .isEmpty()) { + XAnnotation suppressedWarnings = method.getAnnotation(ClassName.get(SuppressWarnings.class)); + if (suppressedWarnings == null + || !ImmutableSet.copyOf(suppressedWarnings.getAsStringList("value")) + .contains("dagger.android.ScopedInjectorFactory")) { + XAnnotation mapKeyAnnotation = + getOnlyElement(method.getAnnotationsAnnotatedWith(TypeNames.MAP_KEY)); + XTypeElement mapKeyValueElement = + processingEnv.requireTypeElement(injectedTypeFromMapKey(mapKeyAnnotation).get()); + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "%s bindings should not be scoped. Scoping this method may leak instances of" + + " %s.", + TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName(), + mapKeyValueElement.getQualifiedName()), + method); + } + } + + validateReturnType(method); + + // @Binds methods should only have one parameter, but we can't guarantee the order of Processors + // in javac, so do a basic check for valid form + if (method.hasAnnotation(TypeNames.BINDS) && method.getParameters().size() == 1) { + validateMapKeyMatchesBindsParameter(annotation, method); + } + } + + /** Report an error if the method's return type is not {@code AndroidInjector.Factory<?>}. */ + private void validateReturnType(XMethodElement method) { + XType returnType = method.getReturnType(); + XType requiredReturnType = injectorFactoryOf(processingEnv.getWildcardType(null, null)); + + // TODO(b/311460276) use XType.isSameType when the bug is fixed. + if (!returnType.getTypeName().equals(requiredReturnType.getTypeName())) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "%s should bind %s, not %s. See https://dagger.dev/android", + method, toStableString(requiredReturnType), toStableString(returnType)), + method); + } + } + + /** + * A valid @Binds method could bind an {@code AndroidInjector.Factory} for one type, while giving + * it a map key of a different type. The return type and parameter type would pass typical @Binds + * validation, but the map lookup in {@code DispatchingAndroidInjector} would retrieve the wrong + * injector factory. + * + * <pre>{@code + * {@literal @Binds} + * {@literal @IntoMap} + * {@literal @ClassKey(GreenActivity.class)} + * abstract AndroidInjector.Factory<?> bindBlueActivity( + * BlueActivityComponent.Builder builder); + * }</pre> + */ + private void validateMapKeyMatchesBindsParameter( + ClassName annotationName, XMethodElement method) { + XType parameterType = getOnlyElement(method.getParameters()).getType(); + XAnnotation annotation = method.getAnnotation(annotationName); + XType mapKeyType = + processingEnv.requireTypeElement(injectedTypeFromMapKey(annotation).get()).getType(); + if (!XTypes.isAssignableTo(parameterType, injectorFactoryOf(mapKeyType))) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + String.format( + "%s does not implement AndroidInjector<%s>", + toStableString(parameterType), toStableString(mapKeyType)), + method, + annotation); + } + } + + /** Returns a {@link XType} for {@code AndroidInjector.Factory<implementationType>}. */ + private XType injectorFactoryOf(XType implementationType) { + return processingEnv.getDeclaredType(factoryElement(), implementationType); + } + + private XTypeElement factoryElement() { + return processingEnv.requireTypeElement(TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName()); + } +} diff --git a/java/dagger/android/processor/AndroidMapKeyValidator.java b/java/dagger/android/processor/AndroidMapKeyValidator.java deleted file mode 100644 index 8d674666b..000000000 --- a/java/dagger/android/processor/AndroidMapKeyValidator.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2017 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.android.processor; - -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; -import static dagger.android.processor.MoreDaggerElements.getAnnotatedAnnotations; - -import com.google.auto.common.BasicAnnotationProcessor.Step; -import com.google.auto.common.MoreElements; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.Sets; -import com.squareup.javapoet.ClassName; -import dagger.MapKey; -import javax.annotation.processing.Messager; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic.Kind; - -/** Validates the correctness of {@link dagger.MapKey}s used with {@code dagger.android}. */ -final class AndroidMapKeyValidator implements Step { - private static final ImmutableMap<String, ClassName> SUPPORTED_ANNOTATIONS = - ImmutableMap.of( - TypeNames.ANDROID_INJECTION_KEY.toString(), TypeNames.ANDROID_INJECTION_KEY, - TypeNames.CLASS_KEY.toString(), TypeNames.CLASS_KEY); - - private final Elements elements; - private final Types types; - private final Messager messager; - - AndroidMapKeyValidator(Elements elements, Types types, Messager messager) { - this.elements = elements; - this.types = types; - this.messager = messager; - } - - @Override - public ImmutableSet<String> annotations() { - return SUPPORTED_ANNOTATIONS.keySet(); - } - - @Override - public ImmutableSet<Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation) { - ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); - elementsByAnnotation - .entries() - .forEach( - entry -> { - try { - validateMethod(entry.getKey(), MoreElements.asExecutable(entry.getValue())); - } catch (TypeNotPresentException e) { - deferredElements.add(entry.getValue()); - } - }); - return deferredElements.build(); - } - - private void validateMethod(String annotation, ExecutableElement method) { - if (!Sets.union(getAnnotatedAnnotations(method, TypeNames.QUALIFIER), - getAnnotatedAnnotations(method, TypeNames.QUALIFIER_JAVAX)).isEmpty()) { - return; - } - - TypeMirror returnType = method.getReturnType(); - if (!types.isAssignable(types.erasure(returnType), factoryElement().asType())) { - // if returnType is not related to AndroidInjector.Factory, ignore the method - return; - } - - if (!Sets.union(getAnnotatedAnnotations(method, TypeNames.SCOPE), - getAnnotatedAnnotations(method, TypeNames.SCOPE_JAVAX)).isEmpty()) { - SuppressWarnings suppressedWarnings = method.getAnnotation(SuppressWarnings.class); - if (suppressedWarnings == null - || !ImmutableSet.copyOf(suppressedWarnings.value()) - .contains("dagger.android.ScopedInjectorFactory")) { - AnnotationMirror mapKeyAnnotation = - getOnlyElement(getAnnotatedAnnotations(method, MapKey.class)); - TypeElement mapKeyValueElement = - elements.getTypeElement(injectedTypeFromMapKey(mapKeyAnnotation).get()); - messager.printMessage( - Kind.ERROR, - String.format( - "%s bindings should not be scoped. Scoping this method may leak instances of %s.", - TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName(), - mapKeyValueElement.getQualifiedName()), - method); - } - } - - validateReturnType(method); - - // @Binds methods should only have one parameter, but we can't guarantee the order of Processors - // in javac, so do a basic check for valid form - if (MoreDaggerElements.isAnnotationPresent(method, TypeNames.BINDS) - && method.getParameters().size() == 1) { - validateMapKeyMatchesBindsParameter(annotation, method); - } - } - - /** Report an error if the method's return type is not {@code AndroidInjector.Factory<?>}. */ - private void validateReturnType(ExecutableElement method) { - TypeMirror returnType = method.getReturnType(); - DeclaredType requiredReturnType = injectorFactoryOf(types.getWildcardType(null, null)); - - if (!types.isSameType(returnType, requiredReturnType)) { - messager.printMessage( - Kind.ERROR, - String.format( - "%s should bind %s, not %s. See https://dagger.dev/android", - method, requiredReturnType, returnType), - method); - } - } - - /** - * A valid @Binds method could bind an {@code AndroidInjector.Factory} for one type, while giving - * it a map key of a different type. The return type and parameter type would pass typical @Binds - * validation, but the map lookup in {@code DispatchingAndroidInjector} would retrieve the wrong - * injector factory. - * - * <pre>{@code - * {@literal @Binds} - * {@literal @IntoMap} - * {@literal @ClassKey(GreenActivity.class)} - * abstract AndroidInjector.Factory<?> bindBlueActivity( - * BlueActivityComponent.Builder builder); - * }</pre> - */ - private void validateMapKeyMatchesBindsParameter(String annotation, ExecutableElement method) { - TypeMirror parameterType = getOnlyElement(method.getParameters()).asType(); - AnnotationMirror annotationMirror = - MoreDaggerElements.getAnnotationMirror(method, SUPPORTED_ANNOTATIONS.get(annotation)).get(); - TypeMirror mapKeyType = - elements.getTypeElement(injectedTypeFromMapKey(annotationMirror).get()).asType(); - if (!types.isAssignable(parameterType, injectorFactoryOf(mapKeyType))) { - messager.printMessage( - Kind.ERROR, - String.format("%s does not implement AndroidInjector<%s>", parameterType, mapKeyType), - method, - annotationMirror); - } - } - - /** Returns a {@link DeclaredType} for {@code AndroidInjector.Factory<implementationType>}. */ - private DeclaredType injectorFactoryOf(TypeMirror implementationType) { - return types.getDeclaredType(factoryElement(), implementationType); - } - - private TypeElement factoryElement() { - return elements.getTypeElement(TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName()); - } -} diff --git a/java/dagger/android/processor/AndroidMapKeys.java b/java/dagger/android/processor/AndroidMapKeys.java index 28da2715a..e3d890e29 100644 --- a/java/dagger/android/processor/AndroidMapKeys.java +++ b/java/dagger/android/processor/AndroidMapKeys.java @@ -16,13 +16,9 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; - -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XAnnotationValue; import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; final class AndroidMapKeys { /** @@ -30,13 +26,12 @@ final class AndroidMapKeys { * it's {@link dagger.multibindings.ClassKey}, returns the fully-qualified class name of the * annotation value. Otherwise returns {@link Optional#empty()}. */ - static Optional<String> injectedTypeFromMapKey(AnnotationMirror mapKey) { - Object mapKeyClass = getAnnotationValue(mapKey, "value").getValue(); - if (mapKeyClass instanceof String) { - return Optional.of((String) mapKeyClass); - } else if (mapKeyClass instanceof TypeMirror) { - TypeElement type = MoreTypes.asTypeElement((TypeMirror) mapKeyClass); - return Optional.of(type.getQualifiedName().toString()); + static Optional<String> injectedTypeFromMapKey(XAnnotation mapKey) { + XAnnotationValue mapKeyClass = mapKey.getAnnotationValue("value"); + if (mapKeyClass.hasStringValue()) { + return Optional.of(mapKeyClass.asString()); + } else if (mapKeyClass.hasTypeValue()) { + return Optional.of(mapKeyClass.asType().getTypeElement().getQualifiedName()); } else { return Optional.empty(); } diff --git a/java/dagger/android/processor/AndroidProcessor.java b/java/dagger/android/processor/AndroidProcessor.java index 2a8ab345b..beb8d0de7 100644 --- a/java/dagger/android/processor/AndroidProcessor.java +++ b/java/dagger/android/processor/AndroidProcessor.java @@ -16,21 +16,15 @@ package dagger.android.processor; -import static javax.tools.Diagnostic.Kind.ERROR; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; -import com.google.auto.common.BasicAnnotationProcessor; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor; import com.google.auto.service.AutoService; -import com.google.common.base.Ascii; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import java.util.Set; -import javax.annotation.processing.Filer; -import javax.annotation.processing.Messager; import javax.annotation.processing.Processor; import javax.lang.model.SourceVersion; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; /** @@ -49,51 +43,22 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; */ @IncrementalAnnotationProcessor(ISOLATING) @AutoService(Processor.class) -public final class AndroidProcessor extends BasicAnnotationProcessor { - private static final String FLAG_EXPERIMENTAL_USE_STRING_KEYS = - "dagger.android.experimentalUseStringKeys"; +public final class AndroidProcessor extends JavacBasicAnnotationProcessor { + private final DelegateAndroidProcessor delegate = new DelegateAndroidProcessor(); @Override - protected Iterable<? extends Step> steps() { - Filer filer = processingEnv.getFiler(); - Messager messager = processingEnv.getMessager(); - Elements elements = processingEnv.getElementUtils(); - Types types = processingEnv.getTypeUtils(); - - return ImmutableList.of( - new AndroidMapKeyValidator(elements, types, messager), - new ContributesAndroidInjectorGenerator( - new AndroidInjectorDescriptor.Validator(messager), - useStringKeys(), - filer, - elements, - processingEnv.getSourceVersion())); + public void initialize(XProcessingEnv env) { + delegate.initialize(env); } - private boolean useStringKeys() { - if (!processingEnv.getOptions().containsKey(FLAG_EXPERIMENTAL_USE_STRING_KEYS)) { - return false; - } - String flagValue = processingEnv.getOptions().get(FLAG_EXPERIMENTAL_USE_STRING_KEYS); - if (flagValue == null || Ascii.equalsIgnoreCase(flagValue, "true")) { - return true; - } else if (Ascii.equalsIgnoreCase(flagValue, "false")) { - return false; - } else { - processingEnv - .getMessager() - .printMessage( - ERROR, - String.format( - "Unknown flag value: %s. %s must be set to either 'true' or 'false'.", - flagValue, FLAG_EXPERIMENTAL_USE_STRING_KEYS)); - return false; - } + @Override + public Iterable<XProcessingStep> processingSteps() { + return delegate.processingSteps(); } @Override - public Set<String> getSupportedOptions() { - return ImmutableSet.of(FLAG_EXPERIMENTAL_USE_STRING_KEYS); + public final ImmutableSet<String> getSupportedOptions() { + return ImmutableSet.of(DelegateAndroidProcessor.FLAG_EXPERIMENTAL_USE_STRING_KEYS); } @Override diff --git a/java/dagger/android/processor/BUILD b/java/dagger/android/processor/BUILD index d545ef8ef..f70c09102 100644 --- a/java/dagger/android/processor/BUILD +++ b/java/dagger/android/processor/BUILD @@ -22,8 +22,7 @@ load( "DOCLINT_REFERENCES", "POM_VERSION", ) -load("//tools:maven.bzl", "pom_file") -load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library") +load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -33,29 +32,65 @@ filegroup( ) java_library( + name = "base_processing_step", + srcs = ["BaseProcessingStep.java"], + deps = [ + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", + "//third_party/java/guava/base", + "//third_party/java/guava/collect", + "//third_party/java/javapoet", + ], +) + +java_library( name = "processor", - srcs = [":srcs"], + srcs = glob( + ["*.java"], + exclude = ["BaseProcessingStep.java"], + ), javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES, tags = ["maven_coordinates=com.google.dagger:dagger-android-processor:" + POM_VERSION], deps = [ + ":base_processing_step", "//java/dagger:core", - "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/codegen/xprocessing", "//java/dagger/spi", - "//third_party/java/auto:common", "//third_party/java/auto:service", "//third_party/java/auto:value", "//third_party/java/guava/base", "//third_party/java/guava/collect", "//third_party/java/incap", "//third_party/java/javapoet", + "@maven//:com_google_devtools_ksp_symbol_processing_api", ], ) -pom_file( - name = "pom", - artifact_id = "dagger-android-processor", +gen_maven_artifact( + name = "artifact", + artifact_coordinates = "com.google.dagger:dagger-android-processor:" + POM_VERSION, artifact_name = "Dagger Android Processor", - targets = [":processor"], + artifact_target = ":processor", + artifact_target_libs = [ + "//java/dagger/internal/codegen/xprocessing", + "//java/dagger/android/processor:base_processing_step", + ], + artifact_target_maven_deps = [ + "com.google.dagger:dagger", + "com.google.devtools.ksp:symbol-processing-api", + "com.google.guava:guava", + "com.squareup:javapoet", + "com.google.code.findbugs:jsr305", + "com.google.dagger:dagger-spi", + "com.google.guava:failureaccess", + "com.squareup:kotlinpoet", + "net.ltgt.gradle.incap:incap", + "org.jetbrains.kotlin:kotlin-stdlib", + ], + javadoc_root_packages = [ + "dagger.android.processor", + ], + javadoc_srcs = [":srcs"], ) java_plugin( @@ -64,10 +99,3 @@ java_plugin( processor_class = "dagger.android.processor.AndroidProcessor", deps = [":processor"], ) - -javadoc_library( - name = "processor-javadoc", - srcs = [":srcs"], - root_packages = ["dagger.android.processor"], - deps = [":processor"], -) diff --git a/java/dagger/android/processor/BaseProcessingStep.java b/java/dagger/android/processor/BaseProcessingStep.java new file mode 100644 index 000000000..100ded0a0 --- /dev/null +++ b/java/dagger/android/processor/BaseProcessingStep.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.android.processor; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Sets.difference; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Maps; +import com.squareup.javapoet.ClassName; +import java.util.Map; +import java.util.Set; + +/** + * A {@link XProcessingStep} that processes one element at a time and defers any for which {@link + * TypeNotPresentException} is thrown. + */ +public abstract class BaseProcessingStep implements XProcessingStep { + @Override + public final ImmutableSet<String> annotations() { + return annotationClassNames().stream().map(ClassName::canonicalName).collect(toImmutableSet()); + } + + // Subclass must ensure all annotated targets are of valid type. + @Override + public ImmutableSet<XElement> process( + XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + ImmutableSet.Builder<XElement> deferredElements = ImmutableSet.builder(); + inverse(elementsByAnnotation) + .forEach( + (element, annotations) -> { + try { + process(element, annotations); + } catch (TypeNotPresentException e) { + deferredElements.add(element); + } + }); + return deferredElements.build(); + } + + /** + * Processes one element. If this method throws {@link TypeNotPresentException}, the element will + * be deferred until the next round of processing. + * + * @param annotations the subset of {@link XProcessingStep#annotations()} that annotate {@code + * element} + */ + protected abstract void process(XElement element, ImmutableSet<ClassName> annotations); + + private ImmutableMap<XElement, ImmutableSet<ClassName>> inverse( + Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) { + ImmutableMap<String, ClassName> annotationClassNames = + annotationClassNames().stream() + .collect(toImmutableMap(ClassName::canonicalName, className -> className)); + checkState( + annotationClassNames.keySet().containsAll(elementsByAnnotation.keySet()), + "Unexpected annotations for %s: %s", + this.getClass().getCanonicalName(), + difference(elementsByAnnotation.keySet(), annotationClassNames.keySet())); + + ImmutableSetMultimap.Builder<XElement, ClassName> builder = ImmutableSetMultimap.builder(); + elementsByAnnotation.forEach( + (annotationName, elementSet) -> + elementSet.forEach( + element -> builder.put(element, annotationClassNames.get(annotationName)))); + + return ImmutableMap.copyOf(Maps.transformValues(builder.build().asMap(), ImmutableSet::copyOf)); + } + + /** Returns the set of annotations processed by this processing step. */ + protected abstract Set<ClassName> annotationClassNames(); +} diff --git a/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java b/java/dagger/android/processor/ContributesAndroidInjectorProcessingStep.java index f3f4d18ab..9e9ed2b20 100644 --- a/java/dagger/android/processor/ContributesAndroidInjectorGenerator.java +++ b/java/dagger/android/processor/ContributesAndroidInjectorProcessingStep.java @@ -16,23 +16,27 @@ package dagger.android.processor; -import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec; +import static androidx.room.compiler.processing.JavaPoetExtKt.addOriginatingElement; import static com.google.common.base.CaseFormat.LOWER_CAMEL; import static com.google.common.base.CaseFormat.UPPER_CAMEL; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static com.squareup.javapoet.TypeSpec.interfaceBuilder; +import static dagger.android.processor.DelegateAndroidProcessor.FLAG_EXPERIMENTAL_USE_STRING_KEYS; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; -import static javax.lang.model.util.ElementFilter.methodsIn; +import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.BasicAnnotationProcessor.Step; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XFiler; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.base.Ascii; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; @@ -41,52 +45,26 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.WildcardTypeName; -import dagger.android.processor.AndroidInjectorDescriptor.Validator; -import java.io.IOException; -import javax.annotation.processing.Filer; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.util.Elements; +import dagger.internal.codegen.xprocessing.XElements; /** Generates the implementation specified in {@code ContributesAndroidInjector}. */ -final class ContributesAndroidInjectorGenerator implements Step { - +final class ContributesAndroidInjectorProcessingStep extends BaseProcessingStep { private final AndroidInjectorDescriptor.Validator validator; - private final Filer filer; - private final Elements elements; - private final boolean useStringKeys; - private final SourceVersion sourceVersion; - - ContributesAndroidInjectorGenerator( - Validator validator, - boolean useStringKeys, - Filer filer, - Elements elements, - SourceVersion sourceVersion) { - this.validator = validator; - this.useStringKeys = useStringKeys; - this.filer = filer; - this.elements = elements; - this.sourceVersion = sourceVersion; + private final XProcessingEnv processingEnv; + + ContributesAndroidInjectorProcessingStep(XProcessingEnv processingEnv) { + this.processingEnv = processingEnv; + this.validator = new AndroidInjectorDescriptor.Validator(processingEnv.getMessager()); } @Override - public ImmutableSet<String> annotations() { - return ImmutableSet.of(TypeNames.CONTRIBUTES_ANDROID_INJECTOR.toString()); + public ImmutableSet<ClassName> annotationClassNames() { + return ImmutableSet.of(TypeNames.CONTRIBUTES_ANDROID_INJECTOR); } @Override - public ImmutableSet<Element> process(ImmutableSetMultimap<String, Element> elementsByAnnotation) { - ImmutableSet.Builder<Element> deferredElements = ImmutableSet.builder(); - for (ExecutableElement method : methodsIn(elementsByAnnotation.values())) { - try { - validator.createIfValid(method).ifPresent(this::generate); - } catch (TypeNotPresentException e) { - deferredElements.add(method); - } - } - return deferredElements.build(); + public void process(XElement element, ImmutableSet<ClassName> annotationNames) { + validator.createIfValid(XElements.asMethod(element)).ifPresent(this::generate); } private void generate(AndroidInjectorDescriptor descriptor) { @@ -97,7 +75,7 @@ final class ContributesAndroidInjectorGenerator implements Step { .peerClass( Joiner.on('_').join(descriptor.enclosingModule().simpleNames()) + "_" - + LOWER_CAMEL.to(UPPER_CAMEL, descriptor.method().getSimpleName().toString())); + + LOWER_CAMEL.to(UPPER_CAMEL, XElements.getSimpleName(descriptor.method()))); String baseName = descriptor.injectedType().simpleName(); ClassName subcomponentName = moduleName.nestedClass(baseName + "Subcomponent"); @@ -105,7 +83,6 @@ final class ContributesAndroidInjectorGenerator implements Step { TypeSpec.Builder module = classBuilder(moduleName) - .addOriginatingElement(descriptor.method()) .addAnnotation( AnnotationSpec.builder(TypeNames.MODULE) .addMember("subcomponents", "$T.class", subcomponentName) @@ -114,16 +91,45 @@ final class ContributesAndroidInjectorGenerator implements Step { .addMethod(bindAndroidInjectorFactory(descriptor, subcomponentFactoryName)) .addType(subcomponent(descriptor, subcomponentName, subcomponentFactoryName)) .addMethod(constructorBuilder().addModifiers(PRIVATE).build()); - generatedAnnotationSpec(elements, sourceVersion, AndroidProcessor.class) - .ifPresent(module::addAnnotation); - - try { - JavaFile.builder(moduleName.packageName(), module.build()) - .skipJavaLangImports(true) - .build() - .writeTo(filer); - } catch (IOException e) { - throw new AssertionError(e); + + addOriginatingElement(module, descriptor.method()); + + XTypeElement generatedAnnotation = processingEnv.findGeneratedAnnotation(); + if (generatedAnnotation != null) { + module.addAnnotation( + AnnotationSpec.builder(generatedAnnotation.getClassName()) + .addMember( + "value", "$S", ClassName.get("dagger.android.processor", "AndroidProcessor")) + .build()); + } + + processingEnv + .getFiler() + .write( + JavaFile.builder(moduleName.packageName(), module.build()) + .skipJavaLangImports(true) + .build(), + XFiler.Mode.Isolating); + } + + private static boolean useStringKeys(XProcessingEnv processingEnv) { + if (!processingEnv.getOptions().containsKey(FLAG_EXPERIMENTAL_USE_STRING_KEYS)) { + return false; + } + String flagValue = processingEnv.getOptions().get(FLAG_EXPERIMENTAL_USE_STRING_KEYS); + if (flagValue == null || Ascii.equalsIgnoreCase(flagValue, "true")) { + return true; + } else if (Ascii.equalsIgnoreCase(flagValue, "false")) { + return false; + } else { + processingEnv + .getMessager() + .printMessage( + ERROR, + String.format( + "Unknown flag value: %s. %s must be set to either 'true' or 'false'.", + flagValue, FLAG_EXPERIMENTAL_USE_STRING_KEYS)); + return false; } } @@ -142,7 +148,7 @@ final class ContributesAndroidInjectorGenerator implements Step { } private AnnotationSpec androidInjectorMapKey(AndroidInjectorDescriptor descriptor) { - if (useStringKeys) { + if (useStringKeys(processingEnv)) { return AnnotationSpec.builder(TypeNames.ANDROID_INJECTION_KEY) .addMember("value", "$S", descriptor.injectedType().toString()) .build(); diff --git a/java/dagger/android/processor/DelegateAndroidProcessor.java b/java/dagger/android/processor/DelegateAndroidProcessor.java new file mode 100644 index 000000000..7c141f041 --- /dev/null +++ b/java/dagger/android/processor/DelegateAndroidProcessor.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.android.processor; + +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingEnvConfig; +import androidx.room.compiler.processing.XProcessingStep; +import com.google.common.collect.ImmutableList; +import dagger.BindsInstance; +import dagger.Component; +import javax.inject.Singleton; + +/** An implementation of Dagger Android processor that is shared between Javac and KSP. */ +final class DelegateAndroidProcessor { + static final XProcessingEnvConfig PROCESSING_ENV_CONFIG = + new XProcessingEnvConfig.Builder().build(); + static final String FLAG_EXPERIMENTAL_USE_STRING_KEYS = + "dagger.android.experimentalUseStringKeys"; + + private XProcessingEnv env; + + public void initialize(XProcessingEnv env) { + this.env = env; + } + + public ImmutableList<XProcessingStep> processingSteps() { + return ImmutableList.of( + new AndroidMapKeyProcessingStep(env), new ContributesAndroidInjectorProcessingStep(env)); + } + + @Singleton + @Component + interface Injector { + void inject(DelegateAndroidProcessor delegateAndroidProcessor); + + @Component.Factory + interface Factory { + Injector create(@BindsInstance XProcessingEnv env); + } + } +} diff --git a/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java b/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java index bcc8e5a1e..22270381c 100644 --- a/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java +++ b/java/dagger/android/processor/DuplicateAndroidInjectorsChecker.java @@ -16,34 +16,34 @@ package dagger.android.processor; -import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; import static com.google.common.collect.Iterables.getOnlyElement; import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; import static javax.tools.Diagnostic.Kind.ERROR; -import com.google.auto.common.MoreTypes; +import androidx.room.compiler.processing.XAnnotation; +import androidx.room.compiler.processing.XType; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Multimaps; -import dagger.MapKey; -import dagger.model.Binding; -import dagger.model.BindingGraph; -import dagger.model.BindingKind; -import dagger.model.Key; -import dagger.spi.BindingGraphPlugin; -import dagger.spi.DiagnosticReporter; +import dagger.internal.codegen.xprocessing.DaggerElements; +import dagger.internal.codegen.xprocessing.XElements; +import dagger.internal.codegen.xprocessing.XTypes; +import dagger.spi.model.Binding; +import dagger.spi.model.BindingGraph; +import dagger.spi.model.BindingGraphPlugin; +import dagger.spi.model.BindingKind; +import dagger.spi.model.DaggerProcessingEnv; +import dagger.spi.model.DiagnosticReporter; +import dagger.spi.model.Key; import java.util.Formatter; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; /** * Validates that the two maps that {@code DispatchingAndroidInjector} injects have logically @@ -53,6 +53,13 @@ import javax.lang.model.type.TypeMirror; */ @AutoService(BindingGraphPlugin.class) public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugin { + private DaggerProcessingEnv processingEnv; + + @Override + public void init(DaggerProcessingEnv processingEnv, Map<String, String> options) { + this.processingEnv = processingEnv; + } + @Override public void visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter) { for (Binding binding : graph.bindings()) { @@ -64,7 +71,10 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi private boolean isDispatchingAndroidInjector(Binding binding) { Key key = binding.key(); - return MoreDaggerTypes.isTypeOf(TypeNames.DISPATCHING_ANDROID_INJECTOR, key.type()) + + return XTypes.isTypeOf( + DaggerElements.toXProcessing(key.type(), processingEnv), + TypeNames.DISPATCHING_ANDROID_INJECTOR) && !key.qualifier().isPresent(); } @@ -79,7 +89,7 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi ImmutableListMultimap.Builder<String, Binding> mapKeyIndex = ImmutableListMultimap.builder(); for (Binding injectorFactory : injectorFactories) { - AnnotationMirror mapKey = mapKey(injectorFactory).get(); + XAnnotation mapKey = mapKey(injectorFactory).get(); Optional<String> injectedType = injectedTypeFromMapKey(mapKey); if (injectedType.isPresent()) { mapKeyIndex.put(injectedType.get(), injectorFactory); @@ -113,21 +123,26 @@ public final class DuplicateAndroidInjectorsChecker implements BindingGraphPlugi .filter(requestedBinding -> requestedBinding.kind().equals(BindingKind.MULTIBOUND_MAP)) .filter( requestedBinding -> { - TypeMirror valueType = - MoreTypes.asDeclared(requestedBinding.key().type()).getTypeArguments().get(1); - if (!MoreDaggerTypes.isTypeOf(TypeNames.PROVIDER, valueType) - || !valueType.getKind().equals(TypeKind.DECLARED)) { + XType valueType = + DaggerElements.toXProcessing(requestedBinding.key().type(), processingEnv) + .getTypeArguments() + .get(1); + if (!XTypes.isTypeOf(valueType, TypeNames.PROVIDER) + || !XTypes.isDeclared(valueType)) { return false; } - TypeMirror providedType = MoreTypes.asDeclared(valueType).getTypeArguments().get(0); - return MoreDaggerTypes.isTypeOf(TypeNames.ANDROID_INJECTOR_FACTORY, providedType); + XType providedType = valueType.getTypeArguments().get(0); + return XTypes.isTypeOf(providedType, TypeNames.ANDROID_INJECTOR_FACTORY); }); } - private Optional<AnnotationMirror> mapKey(Binding binding) { + private Optional<XAnnotation> mapKey(Binding binding) { return binding .bindingElement() - .map(bindingElement -> getAnnotatedAnnotations(bindingElement, MapKey.class)) + .map( + bindingElement -> + XElements.getAnnotatedAnnotations( + DaggerElements.toXProcessing(bindingElement, processingEnv), TypeNames.MAP_KEY)) .flatMap( annotations -> annotations.isEmpty() diff --git a/java/dagger/android/processor/KspAndroidProcessor.java b/java/dagger/android/processor/KspAndroidProcessor.java new file mode 100644 index 000000000..7fe4f52d8 --- /dev/null +++ b/java/dagger/android/processor/KspAndroidProcessor.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Dagger Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dagger.android.processor; + +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XProcessingStep; +import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor; +import com.google.auto.service.AutoService; +import com.google.devtools.ksp.processing.SymbolProcessor; +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment; +import com.google.devtools.ksp.processing.SymbolProcessorProvider; + +/** Ksp Processor for verifying usage of {@code dagger.android} code. */ +final class KspAndroidProcessor extends KspBasicAnnotationProcessor { + private final DelegateAndroidProcessor delegate = new DelegateAndroidProcessor(); + + private KspAndroidProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) { + super(symbolProcessorEnvironment, DelegateAndroidProcessor.PROCESSING_ENV_CONFIG); + } + + @Override + public void initialize(XProcessingEnv env) { + delegate.initialize(env); + } + + @Override + public Iterable<XProcessingStep> processingSteps() { + return delegate.processingSteps(); + } + + /** Provides the {@link KspAndroidProcessor}. */ + @AutoService(SymbolProcessorProvider.class) + public static final class Provider implements SymbolProcessorProvider { + @Override + public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) { + return new KspAndroidProcessor(symbolProcessorEnvironment); + } + } +} diff --git a/java/dagger/android/processor/MoreDaggerElements.java b/java/dagger/android/processor/MoreDaggerElements.java deleted file mode 100644 index 572caa31a..000000000 --- a/java/dagger/android/processor/MoreDaggerElements.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2021 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.android.processor; - -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; - -import com.google.auto.common.MoreElements; -import com.google.common.collect.ImmutableSet; -import com.squareup.javapoet.ClassName; -import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; - -// TODO(bcorso): Dedupe with dagger/internal/codegen/langmodel/DaggerElements.java? -// TODO(bcorso): Contribute upstream to auto common? -/** Similar to auto common, but uses {@link ClassName} rather than {@link Class}. */ -final class MoreDaggerElements { - /** - * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain - * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of - * {@code annotationClass}. This method is a safer alternative to calling {@link - * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with - * annotation proxies. - */ - public static boolean isAnnotationPresent(Element element, ClassName annotationName) { - return getAnnotationMirror(element, annotationName).isPresent(); - } - - /** - * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on - * {@code element}, or {@link Optional#empty()} if no such annotation exists. This method is a - * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with - * annotation proxies. - */ - public static Optional<AnnotationMirror> getAnnotationMirror( - Element element, ClassName annotationName) { - String annotationClassName = annotationName.canonicalName(); - for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { - TypeElement annotationTypeElement = - MoreElements.asType(annotationMirror.getAnnotationType().asElement()); - if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) { - return Optional.of(annotationMirror); - } - } - return Optional.empty(); - } - - public static ImmutableSet<AnnotationMirror> getAnnotatedAnnotations( - Element element, ClassName annotationName) { - return element.getAnnotationMirrors().stream() - .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName)) - .collect(toImmutableSet()); - } - - private MoreDaggerElements() {} -} diff --git a/java/dagger/android/processor/MoreDaggerTypes.java b/java/dagger/android/processor/MoreDaggerTypes.java deleted file mode 100644 index 4bde405e1..000000000 --- a/java/dagger/android/processor/MoreDaggerTypes.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2021 The Dagger Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dagger.android.processor; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.google.auto.common.MoreElements; -import com.squareup.javapoet.ArrayTypeName; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.TypeName; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.ErrorType; -import javax.lang.model.type.NoType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleTypeVisitor8; - -// TODO(bcorso): Dedupe with dagger/internal/codegen/langmodel/DaggerTypes.java? -// TODO(bcorso): Contribute upstream to auto common? -/** Similar to auto common, but uses {@link ClassName} rather than {@link Class}. */ -final class MoreDaggerTypes { - - /** - * Returns true if the raw type underlying the given {@link TypeMirror} represents the same raw - * type as the given {@link Class} and throws an IllegalArgumentException if the {@link - * TypeMirror} does not represent a type that can be referenced by a {@link Class} - */ - public static boolean isTypeOf(final TypeName typeName, TypeMirror type) { - checkNotNull(typeName); - return type.accept(new IsTypeOf(typeName), null); - } - - private static final class IsTypeOf extends SimpleTypeVisitor8<Boolean, Void> { - private final TypeName typeName; - - IsTypeOf(TypeName typeName) { - this.typeName = typeName; - } - - @Override - protected Boolean defaultAction(TypeMirror type, Void ignored) { - throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); - } - - @Override - public Boolean visitNoType(NoType noType, Void p) { - if (noType.getKind().equals(TypeKind.VOID)) { - return typeName.equals(TypeName.VOID); - } - throw new IllegalArgumentException(noType + " cannot be represented as a Class<?>."); - } - - @Override - public Boolean visitError(ErrorType errorType, Void p) { - return false; - } - - @Override - public Boolean visitPrimitive(PrimitiveType type, Void p) { - switch (type.getKind()) { - case BOOLEAN: - return typeName.equals(TypeName.BOOLEAN); - case BYTE: - return typeName.equals(TypeName.BYTE); - case CHAR: - return typeName.equals(TypeName.CHAR); - case DOUBLE: - return typeName.equals(TypeName.DOUBLE); - case FLOAT: - return typeName.equals(TypeName.FLOAT); - case INT: - return typeName.equals(TypeName.INT); - case LONG: - return typeName.equals(TypeName.LONG); - case SHORT: - return typeName.equals(TypeName.SHORT); - default: - throw new IllegalArgumentException(type + " cannot be represented as a Class<?>."); - } - } - - @Override - public Boolean visitArray(ArrayType array, Void p) { - return (typeName instanceof ArrayTypeName) - && isTypeOf(((ArrayTypeName) typeName).componentType, array.getComponentType()); - } - - @Override - public Boolean visitDeclared(DeclaredType type, Void ignored) { - TypeElement typeElement = MoreElements.asType(type.asElement()); - return (typeName instanceof ClassName) - && typeElement.getQualifiedName().contentEquals(((ClassName) typeName).canonicalName()); - } - } - - private MoreDaggerTypes() {} -} |