diff --git a/factory/pom.xml b/factory/pom.xml index 5377aee9aa..66ae529065 100644 --- a/factory/pom.xml +++ b/factory/pom.xml @@ -106,12 +106,19 @@ javapoet 1.13.0 + javax.inject javax.inject 1 + test + + + jakarta.inject + jakarta.inject-api + 2.0.1 + test - com.google.testing.compile compile-testing diff --git a/factory/src/main/java/com/google/auto/factory/Provided.java b/factory/src/main/java/com/google/auto/factory/Provided.java index 226a16f48c..18029af557 100644 --- a/factory/src/main/java/com/google/auto/factory/Provided.java +++ b/factory/src/main/java/com/google/auto/factory/Provided.java @@ -20,8 +20,13 @@ import java.lang.annotation.Target; /** - * An annotation to be applied to parameters that should be provided by an - * {@linkplain javax.inject.Inject injected} {@link javax.inject.Provider} in a generated factory. + * An annotation to be applied to parameters that should be provided by an injected {@code Provider} + * in a generated factory. + * + *

The {@code @Inject} and {@code Provider} classes come from either the legacy package {@code + * javax.inject} or the updated package {@code jakarta.inject}. {@code jakarta.inject} is used if it + * is on the classpath. Compile with {@code -Acom.google.auto.factory.InjectApi=javax} if you want + * to use {@code javax.inject} even when {@code jakarta.inject} is available. * * @author Gregory Kick */ diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java index 0654eed10e..05f631898c 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java +++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java @@ -41,11 +41,13 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -60,6 +62,7 @@ import javax.tools.Diagnostic.Kind; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; +import org.checkerframework.checker.nullness.qual.Nullable; /** * The annotation processor that generates factories for {@link AutoFactory} annotations. @@ -68,13 +71,25 @@ */ @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING) @AutoService(Processor.class) +@SupportedOptions(AutoFactoryProcessor.INJECT_API_OPTION) public final class AutoFactoryProcessor extends AbstractProcessor { + static final String INJECT_API_OPTION = "com.google.auto.factory.InjectApi"; + + private static final ImmutableSet INJECT_APIS = ImmutableSet.of("jakarta", "javax"); + private FactoryDescriptorGenerator factoryDescriptorGenerator; private AutoFactoryDeclaration.Factory declarationFactory; private ProvidedChecker providedChecker; private Messager messager; private Elements elements; private Types types; + private InjectApi injectApi; + + /** + * If non-null, we will call this whenever the {@link #process} method is called, giving it one of + * the {@code @AutoFactory} elements, and do nothing else. + */ + private @Nullable Consumer<@Nullable Element> errorFunction; @Override public synchronized void init(ProcessingEnvironment processingEnv) { @@ -82,14 +97,40 @@ public synchronized void init(ProcessingEnvironment processingEnv) { elements = processingEnv.getElementUtils(); types = processingEnv.getTypeUtils(); messager = processingEnv.getMessager(); + String api = processingEnv.getOptions().get(INJECT_API_OPTION); + if (api != null && !INJECT_APIS.contains(api)) { + messager.printMessage( + Kind.ERROR, + "Usage: -A" + + INJECT_API_OPTION + + "=, where is " + + String.join(" or ", INJECT_APIS)); + errorFunction = unused -> {}; + return; + } + try { + injectApi = InjectApi.from(elements, api); + } catch (IllegalStateException e) { + errorFunction = element -> { + messager.printMessage(Kind.ERROR, e.getMessage(), element); + errorFunction = unused -> {}; // Only print the error once. + }; + return; + } providedChecker = new ProvidedChecker(messager); declarationFactory = new AutoFactoryDeclaration.Factory(elements, messager); factoryDescriptorGenerator = - new FactoryDescriptorGenerator(messager, types, declarationFactory); + new FactoryDescriptorGenerator(messager, types, declarationFactory, injectApi); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (errorFunction != null) { + Set elements = roundEnv.getElementsAnnotatedWith(AutoFactory.class); + Element anElement = elements.isEmpty() ? null : elements.iterator().next(); + errorFunction.accept(anElement); + return false; + } try { doProcess(roundEnv); } catch (Throwable e) { @@ -141,7 +182,8 @@ private void doProcess(RoundEnvironment roundEnv) { indexedMethodsBuilder.build(); ImmutableSetMultimap factoriesBeingCreated = simpleNamesToNames(indexedMethods.keySet()); - FactoryWriter factoryWriter = new FactoryWriter(processingEnv, factoriesBeingCreated); + FactoryWriter factoryWriter = + new FactoryWriter(processingEnv, injectApi, factoriesBeingCreated); indexedMethods .asMap() @@ -216,7 +258,10 @@ private ImmutableSet implementationMethods( Elements2.getExecutableElementAsMemberOf(types, implementationMethod, supertype); ImmutableSet passedParameters = Parameter.forParameterList( - implementationMethod.getParameters(), methodType.getParameterTypes(), types); + implementationMethod.getParameters(), + methodType.getParameterTypes(), + types, + injectApi); implementationMethodsBuilder.add( ImplementationMethodDescriptor.builder() .name(implementationMethod.getSimpleName().toString()) @@ -270,7 +315,9 @@ private void checkAnnotationsToApply(Element annotation) { Set seenAnnotations = new HashSet<>(); for (ExecutableElement annotationMember : methodsIn(annotation.getEnclosedElements())) { TypeMirror memberType = annotationMember.getReturnType(); - boolean isAnnotation = memberType.getKind().equals(DECLARED) && asElement(memberType).getKind().equals(ANNOTATION_TYPE); + boolean isAnnotation = + memberType.getKind().equals(DECLARED) + && asElement(memberType).getKind().equals(ANNOTATION_TYPE); if (!isAnnotation && !memberType.getKind().equals(ERROR)) { messager.printMessage( Kind.ERROR, diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java index 70a21ea263..584b342eb8 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java @@ -52,12 +52,17 @@ final class FactoryDescriptorGenerator { private final Messager messager; private final Types types; private final AutoFactoryDeclaration.Factory declarationFactory; + private final InjectApi injectApi; FactoryDescriptorGenerator( - Messager messager, Types types, AutoFactoryDeclaration.Factory declarationFactory) { + Messager messager, + Types types, + AutoFactoryDeclaration.Factory declarationFactory, + InjectApi injectApi) { this.messager = messager; this.types = types; this.declarationFactory = declarationFactory; + this.injectApi = injectApi; } ImmutableSet generateDescriptor(Element element) { @@ -132,16 +137,17 @@ FactoryMethodDescriptor generateDescriptorForConstructor( // The map returned by partitioningBy always has entries for both key values but our // null-checker isn't yet smart enough to know that. ImmutableSet providedParameters = - Parameter.forParameterList(requireNonNull(parameterMap.get(true)), types); + Parameter.forParameterList(requireNonNull(parameterMap.get(true)), types, injectApi); ImmutableSet passedParameters = - Parameter.forParameterList(requireNonNull(parameterMap.get(false)), types); + Parameter.forParameterList(requireNonNull(parameterMap.get(false)), types, injectApi); return FactoryMethodDescriptor.builder(declaration) .name("create") .returnType(classElement.asType()) .publicMethod(classElement.getModifiers().contains(PUBLIC)) .providedParameters(providedParameters) .passedParameters(passedParameters) - .creationParameters(Parameter.forParameterList(constructor.getParameters(), types)) + .creationParameters( + Parameter.forParameterList(constructor.getParameters(), types, injectApi)) .isVarArgs(constructor.isVarArgs()) .exceptions(constructor.getThrownTypes()) .overridingMethod(false) diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java index a887c57715..c0a7959133 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java +++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java @@ -49,8 +49,6 @@ import java.util.List; import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; -import javax.inject.Inject; -import javax.inject.Provider; import javax.lang.model.SourceVersion; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; @@ -60,6 +58,7 @@ final class FactoryWriter { + private final InjectApi injectApi; private final Filer filer; private final Elements elements; private final SourceVersion sourceVersion; @@ -67,7 +66,9 @@ final class FactoryWriter { FactoryWriter( ProcessingEnvironment processingEnv, + InjectApi injectApi, ImmutableSetMultimap factoriesBeingCreated) { + this.injectApi = injectApi; this.filer = processingEnv.getFiler(); this.elements = processingEnv.getElementUtils(); this.sourceVersion = processingEnv.getSourceVersion(); @@ -118,7 +119,8 @@ private static void addFactoryTypeParameters( private void addConstructorAndProviderFields( TypeSpec.Builder factory, FactoryDescriptor descriptor) { - MethodSpec.Builder constructor = constructorBuilder().addAnnotation(Inject.class); + MethodSpec.Builder constructor = + constructorBuilder().addAnnotation(ClassName.get(injectApi.inject())); if (descriptor.publicType()) { constructor.addModifiers(PUBLIC); } @@ -127,7 +129,8 @@ private void addConstructorAndProviderFields( for (ProviderField provider : providerFields) { ++argumentNumber; TypeName typeName = resolveTypeName(provider.key().type().get()).box(); - TypeName providerType = ParameterizedTypeName.get(ClassName.get(Provider.class), typeName); + TypeName providerType = + ParameterizedTypeName.get(ClassName.get(injectApi.provider()), typeName); factory.addField(providerType, provider.name(), PRIVATE, FINAL); if (provider.key().qualifier().isPresent()) { // only qualify the constructor parameter @@ -181,7 +184,7 @@ private void addFactoryMethods( } else { ProviderField provider = requireNonNull(descriptor.providers().get(parameter.key())); argument = CodeBlock.of(provider.name()); - if (parameter.isProvider()) { + if (injectApi.isProvider(parameter.type().get())) { // Providers are checked for nullness in the Factory's constructor. checkNotNull = false; } else { diff --git a/factory/src/main/java/com/google/auto/factory/processor/InjectApi.java b/factory/src/main/java/com/google/auto/factory/processor/InjectApi.java new file mode 100644 index 0000000000..d1689fed25 --- /dev/null +++ b/factory/src/main/java/com/google/auto/factory/processor/InjectApi.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Google LLC + * + * 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 com.google.auto.factory.processor; + +import static com.google.auto.common.MoreStreams.toImmutableMap; +import static java.util.stream.Collectors.joining; + +import com.google.auto.common.MoreTypes; +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.AbstractMap.SimpleEntry; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** Encapsulates the choice of {@code jakarta.inject} or {@code javax.inject}. */ +@AutoValue +abstract class InjectApi { + abstract TypeElement inject(); + + abstract TypeElement provider(); + + abstract TypeElement qualifier(); + + private static final ImmutableList PREFIXES_IN_ORDER = + ImmutableList.of("jakarta.inject.", "javax.inject."); + + static InjectApi from(Elements elementUtils, @Nullable String apiPrefix) { + ImmutableList apiPackages = + (apiPrefix == null) ? PREFIXES_IN_ORDER : ImmutableList.of(apiPrefix + ".inject."); + for (String apiPackage : apiPackages) { + ImmutableMap apiMap = apiMap(elementUtils, apiPackage); + TypeElement inject = apiMap.get("Inject"); + TypeElement provider = apiMap.get("Provider"); + TypeElement qualifier = apiMap.get("Qualifier"); + if (inject != null && provider != null && qualifier != null) { + return new AutoValue_InjectApi(inject, provider, qualifier); + } + } + String classes = "{" + String.join(",", API_CLASSES) + "}"; + String missing = apiPackages.stream().sorted().map(s -> s + classes).collect(joining(" or ")); + throw new IllegalStateException("Class path for AutoFactory class must include " + missing); + } + + /** True if {@code type} is a {@code Provider}. */ + boolean isProvider(TypeMirror type) { + return type.getKind().equals(TypeKind.DECLARED) + && MoreTypes.asTypeElement(type).equals(provider()); + } + + private static ImmutableMap apiMap( + Elements elementUtils, String apiPackage) { + return API_CLASSES.stream() + .map(name -> new SimpleEntry<>(name, elementUtils.getTypeElement(apiPackage + name))) + .filter(entry -> entry.getValue() != null) + .collect(toImmutableMap(SimpleEntry::getKey, SimpleEntry::getValue)); + } + + private static final ImmutableSet API_CLASSES = + ImmutableSet.of("Inject", "Provider", "Qualifier"); +} diff --git a/factory/src/main/java/com/google/auto/factory/processor/Key.java b/factory/src/main/java/com/google/auto/factory/processor/Key.java index 6dc7644554..8347ce95f2 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Key.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Key.java @@ -16,7 +16,6 @@ package com.google.auto.factory.processor; import static com.google.auto.common.MoreElements.isAnnotationPresent; -import static com.google.auto.factory.processor.Mirrors.isProvider; import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence; import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence; @@ -26,7 +25,6 @@ import com.google.common.base.Equivalence; import java.util.Collection; import java.util.Optional; -import javax.inject.Qualifier; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; @@ -37,7 +35,6 @@ * @author Gregory Kick */ @AutoValue -// TODO(ronshapiro): reuse dagger.model.Key? abstract class Key { abstract Equivalence.Wrapper type(); @@ -49,7 +46,7 @@ Optional qualifier() { } /** - * Constructs a key based on the type {@code type} and any {@link Qualifier}s in {@code + * Constructs a key based on the type {@code type} and any {@code Qualifier}s in {@code * annotations}. * *

If {@code type} is a {@code Provider}, the returned {@link Key}'s {@link #type()} is @@ -57,6 +54,7 @@ Optional qualifier() { * corresponding {@linkplain Types#boxedClass(PrimitiveType) boxed type}. * *

For example: + * * *
Input type {@code Key.type()} *
{@code String} {@code String} @@ -64,18 +62,19 @@ Optional qualifier() { *
{@code int} {@code Integer} *
*/ - static Key create(TypeMirror type, Collection annotations, Types types) { + static Key create( + TypeMirror type, Collection annotations, Types types, InjectApi injectApi) { // TODO(gak): check for only one qualifier rather than using the first Optional qualifier = annotations.stream() .filter( annotation -> isAnnotationPresent( - annotation.getAnnotationType().asElement(), Qualifier.class)) + annotation.getAnnotationType().asElement(), injectApi.qualifier())) .findFirst(); TypeMirror keyType = - isProvider(type) + injectApi.isProvider(type) ? MoreTypes.asDeclared(type).getTypeArguments().get(0) : boxedType(type, types); return new AutoValue_Key( diff --git a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java index 5739511ecf..c81623d469 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java @@ -15,14 +15,12 @@ */ package com.google.auto.factory.processor; -import com.google.auto.common.MoreTypes; import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableMap; import java.lang.annotation.Annotation; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -30,7 +28,6 @@ import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor6; final class Mirrors { @@ -53,11 +50,6 @@ public Name visitType(TypeElement e, Void p) { null); } - /** {@code true} if {@code type} is a {@link Provider}. */ - static boolean isProvider(TypeMirror type) { - return MoreTypes.isType(type) && MoreTypes.isTypeOf(Provider.class, type); - } - /** * Returns an annotation value map with {@link String} keys instead of {@link ExecutableElement} * instances. diff --git a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java index bb586a1cf3..16a419a8c3 100644 --- a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java +++ b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java @@ -34,7 +34,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import javax.inject.Provider; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; @@ -49,15 +48,11 @@ abstract class Parameter { /** - * The original type of the parameter, while {@code key().type()} erases the wrapped {@link + * The original type of the parameter, while {@code key().type()} erases the wrapped {@code * Provider}, if any. */ abstract Equivalence.Wrapper type(); - boolean isProvider() { - return Mirrors.isProvider(type().get()); - } - boolean isPrimitive() { return type().get().getKind().isPrimitive(); } @@ -79,15 +74,16 @@ ImmutableList annotations() { Optional nullable() { return unwrapOptionalEquivalence(nullableWrapper()); } + private static Parameter forVariableElement( - VariableElement variable, TypeMirror type, Types types) { + VariableElement variable, TypeMirror type, Types types, InjectApi injectApi) { ImmutableList allAnnotations = Stream.of(variable.getAnnotationMirrors(), type.getAnnotationMirrors()) .flatMap(List::stream) .collect(toImmutableList()); Optional nullable = allAnnotations.stream().filter(Parameter::isNullable).findFirst(); - Key key = Key.create(type, allAnnotations, types); + Key key = Key.create(type, allAnnotations, types, injectApi); ImmutableSet> typeAnnotationWrappers = type.getAnnotationMirrors().stream() @@ -120,12 +116,14 @@ private static boolean isNullable(AnnotationMirror annotation) { static ImmutableSet forParameterList( List variables, List variableTypes, - Types types) { + Types types, + InjectApi injectApi) { checkArgument(variables.size() == variableTypes.size()); ImmutableSet.Builder builder = ImmutableSet.builder(); Set names = Sets.newHashSetWithExpectedSize(variables.size()); for (int i = 0; i < variables.size(); i++) { - Parameter parameter = forVariableElement(variables.get(i), variableTypes.get(i), types); + Parameter parameter = + forVariableElement(variables.get(i), variableTypes.get(i), types, injectApi); checkArgument(names.add(parameter.name()), "Duplicate parameter name: %s", parameter.name()); builder.add(parameter); } @@ -135,11 +133,11 @@ static ImmutableSet forParameterList( } static ImmutableSet forParameterList( - List variables, Types types) { + List variables, Types types, InjectApi injectApi) { List variableTypes = Lists.newArrayListWithExpectedSize(variables.size()); for (VariableElement var : variables) { variableTypes.add(var.asType()); } - return forParameterList(variables, variableTypes, types); + return forParameterList(variables, variableTypes, types, injectApi); } } diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java index 4998aa57e6..c9907b057d 100644 --- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java +++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java @@ -15,12 +15,17 @@ */ package com.google.auto.factory.processor; +import static com.google.common.truth.Truth.assertThat; import static com.google.testing.compile.CompilationSubject.assertThat; +import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; import com.google.testing.compile.JavaFileObjects; +import java.io.File; +import java.net.URL; import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -178,4 +183,46 @@ public void annotationsToApplyNotAnnotations() { .inFile(file) .onLineContaining("andWhatIsThis"); } + + @Test + public void noInjectApi() throws Exception { + URL autoFactoryUrl = + Class.forName("com.google.auto.factory.AutoFactory") + .getProtectionDomain() + .getCodeSource() + .getLocation(); + assertThat(autoFactoryUrl.getProtocol()).isEqualTo("file"); + File autoFactoryFile = new File(autoFactoryUrl.getPath()); + Compiler compiler = + Compiler.javac() + .withProcessors(new AutoFactoryProcessor()) + .withClasspath(ImmutableList.of(autoFactoryFile)); + JavaFileObject file = JavaFileObjects.forResource("good/SimpleClass.java"); + Compilation compilation = compiler.compile(file); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Class path for AutoFactory class must include" + + " jakarta.inject.{Inject,Provider,Qualifier} or" + + " javax.inject.{Inject,Provider,Qualifier}"); + assertThat(compilation).hadErrorCount(1); + } + + /** + * AutoFactoryProcessor shouldn't complain about the absence of {@code javax.inject} if there are + * no {@code @AutoFactory} classes being compiled. Its {@code init} will be called and will see + * the problem, but will say nothing. + */ + @Test + public void noInjectApiButNoAutoFactoryEither() { + Compiler compiler = + Compiler.javac() + .withProcessors(new AutoFactoryProcessor()) + .withClasspath(ImmutableList.of()); + JavaFileObject file = + JavaFileObjects.forSourceString("test.Foo", "package test; public class Foo {}"); + Compilation compilation = compiler.compile(file); + assertThat(compilation).succeededWithoutWarnings(); + assertThat(compilation).generatedFile(StandardLocation.CLASS_OUTPUT, "test", "Foo.class"); + } } diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java index 845ea58a42..14026b17d6 100644 --- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java +++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java @@ -63,16 +63,36 @@ public AutoFactoryProcessorTest(@TestParameter Config config) { private enum InjectPackage { JAVAX, + JAKARTA } + /** + * Each test configuration specifies whether javax or jakarta or both are on the classpath, which + * one is expected to be chosen, and any {@code -A} options. + */ private enum Config { - JAVAX_ONLY_ON_CLASSPATH(ImmutableList.of(InjectPackage.JAVAX), InjectPackage.JAVAX); - - /** Config that is used for negative tests, and to update the golden files. */ - static final Config DEFAULT = JAVAX_ONLY_ON_CLASSPATH; + JAVAX_ONLY_ON_CLASSPATH(ImmutableList.of(InjectPackage.JAVAX), InjectPackage.JAVAX), + JAKARTA_ONLY_ON_CLASSPATH(ImmutableList.of(InjectPackage.JAKARTA), InjectPackage.JAKARTA), + BOTH_ON_CLASSPATH( + ImmutableList.of(InjectPackage.JAVAX, InjectPackage.JAKARTA), + InjectPackage.JAKARTA), + EXPLICIT_JAVAX( + ImmutableList.of(InjectPackage.JAVAX, InjectPackage.JAKARTA), + InjectPackage.JAVAX, + ImmutableList.of("-A" + AutoFactoryProcessor.INJECT_API_OPTION + "=javax")), + EXPLICIT_JAKARTA( + ImmutableList.of(InjectPackage.JAVAX, InjectPackage.JAKARTA), + InjectPackage.JAKARTA, + ImmutableList.of("-A" + AutoFactoryProcessor.INJECT_API_OPTION + "=jakarta")); + + /** + * Config that is used for negative tests, and to update the golden files. Since those files use + * {@code javax.inject}, we need a config that specifies that package. + */ + static final Config DEFAULT = EXPLICIT_JAVAX; final ImmutableList packagesOnClasspath; - final InjectPackage unusedExpectedPackage; + final InjectPackage expectedPackage; final ImmutableList options; Config(ImmutableList packagesOnClasspath, InjectPackage expectedPackage) { @@ -84,7 +104,7 @@ private enum Config { InjectPackage expectedPackage, ImmutableList options) { this.packagesOnClasspath = packagesOnClasspath; - this.unusedExpectedPackage = expectedPackage; + this.expectedPackage = expectedPackage; this.options = options; } @@ -95,6 +115,7 @@ private enum Config { fileForClass("javax.annotation.Nullable"), fileForClass("org.checkerframework.checker.nullness.compatqual.NullableType")); static final File JAVAX_CLASSPATH = fileForClass("javax.inject.Provider"); + static final File JAKARTA_CLASSPATH = fileForClass("jakarta.inject.Provider"); static File fileForClass(String className) { Class c; @@ -114,6 +135,9 @@ ImmutableList classpath() { if (packagesOnClasspath.contains(InjectPackage.JAVAX)) { classpathBuilder.add(JAVAX_CLASSPATH); } + if (packagesOnClasspath.contains(InjectPackage.JAKARTA)) { + classpathBuilder.add(JAKARTA_CLASSPATH); + } return classpathBuilder.build(); } @@ -184,6 +208,9 @@ private JavaFileObject goldenFile(String resourceName) { try { URL resourceUrl = Resources.getResource(resourceName); String source = Resources.toString(resourceUrl, UTF_8); + if (config.expectedPackage.equals(InjectPackage.JAKARTA)) { + source = source.replace("javax.inject", "jakarta.inject"); + } String className = resourceName.replaceFirst("\\.java$", "").replace('/', '.'); return JavaFileObjects.forSourceString(className, source); } catch (IOException e) { @@ -369,57 +396,6 @@ public void mixedDepsImplementingInterfaces() { "expected/MixedDepsImplementingInterfacesFactory.java")); } - @Test - public void failsWithMixedFinals() { - JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining( - "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.") - .inFile(file) - .onLine(24); - assertThat(compilation) - .hadErrorContaining( - "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.") - .inFile(file) - .onLine(27); - } - - @Test - public void providedButNoAutoFactory() { - JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining( - "@Provided may only be applied to constructors requesting an auto-factory") - .inFile(file) - .onLineContaining("@Provided"); - } - - @Test - public void providedOnMethodParameter() { - JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining("@Provided may only be applied to constructor parameters") - .inFile(file) - .onLineContaining("@Provided"); - } - - @Test - public void invalidCustomName() { - JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining("\"SillyFactory!\" is not a valid Java identifier") - .inFile(file) - .onLineContaining("SillyFactory!"); - } - @Test public void factoryExtendingAbstractClass() { goldenTest( @@ -438,21 +414,6 @@ public void factoryWithConstructorThrowsClauseExtendingAbstractClass() { "expected/FactoryExtendingAbstractClassThrowsFactory.java")); } - @Test - public void factoryExtendingAbstractClass_withConstructorParams() { - JavaFileObject file = - JavaFileObjects.forResource("bad/FactoryExtendingAbstractClassWithConstructorParams.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining( - "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory is not a" - + " valid supertype for a factory. Factory supertypes must have a no-arg" - + " constructor.") - .inFile(file) - .onLineContaining("@AutoFactory"); - } - @Test public void factoryExtendingAbstractClass_multipleConstructors() { goldenTest( @@ -460,45 +421,6 @@ public void factoryExtendingAbstractClass_multipleConstructors() { ImmutableMap.of()); } - @Test - public void factoryExtendingInterface() { - JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining( - "java.lang.Runnable is not a valid supertype for a factory. Supertypes must be" - + " non-final classes.") - .inFile(file) - .onLineContaining("@AutoFactory"); - } - - @Test - public void factoryExtendingEnum() { - JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining( - "java.util.concurrent.TimeUnit is not a valid supertype for a factory. Supertypes must" - + " be non-final classes.") - .inFile(file) - .onLineContaining("@AutoFactory"); - } - - @Test - public void factoryExtendingFinalClass() { - JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java"); - Compilation compilation = config.javac().compile(file); - assertThat(compilation).failed(); - assertThat(compilation) - .hadErrorContaining( - "java.lang.Boolean is not a valid supertype for a factory. Supertypes must be" - + " non-final classes.") - .inFile(file) - .onLineContaining("@AutoFactory"); - } - @Test public void factoryImplementingGenericInterfaceExtension() { goldenTest( @@ -684,6 +606,9 @@ private void rewriteImports(List sourceLines) { ? "import javax.annotation.Generated;" : line); } + if (config.expectedPackage.equals(InjectPackage.JAKARTA)) { + importLines.replaceAll(line -> line.replace("javax.inject", "jakarta.inject")); + } Collections.sort(importLines); } }