diff options
Diffstat (limited to 'java/dagger/internal/codegen/base')
7 files changed, 256 insertions, 10 deletions
diff --git a/java/dagger/internal/codegen/base/ElementFormatter.java b/java/dagger/internal/codegen/base/ElementFormatter.java index 16381e32f..8e3edc955 100644 --- a/java/dagger/internal/codegen/base/ElementFormatter.java +++ b/java/dagger/internal/codegen/base/ElementFormatter.java @@ -36,7 +36,8 @@ import javax.inject.Inject; * * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. * - * <p>Parameters are given with their enclosing executable, with other parameters elided. + * <p>If the element is a parameter, the returned string will include the enclosing executable, + * with other parameters elided. */ public final class ElementFormatter extends Formatter<XElement> { @Inject @@ -52,15 +53,29 @@ public final class ElementFormatter extends Formatter<XElement> { * * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. * - * <p>Parameters are given with their enclosing executable, with other parameters elided. + * <p>If the element is a parameter, the returned string will include the enclosing executable, + * with other parameters elided. */ public static String elementToString(XElement element) { + return elementToString(element, /* elideMethodParameterTypes= */ false); + } + + /** + * Returns a useful string form for an element. + * + * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name. + * + * <p>Parameters are given with their enclosing executable, with other parameters elided. + */ + public static String elementToString(XElement element, boolean elideMethodParameterTypes) { if (isExecutable(element)) { return enclosingTypeAndMemberName(element) .append( - asExecutable(element).getParameters().stream() - .map(parameter -> XTypes.toStableString(parameter.getType())) - .collect(joining(", ", "(", ")"))) + elideMethodParameterTypes + ? (asExecutable(element).getParameters().isEmpty() ? "()" : "(…)") + : asExecutable(element).getParameters().stream() + .map(parameter -> XTypes.toStableString(parameter.getType())) + .collect(joining(", ", "(", ")"))) .toString(); } else if (isMethodParameter(element)) { XExecutableElement methodOrConstructor = asMethodParameter(element).getEnclosingElement(); diff --git a/java/dagger/internal/codegen/base/FrameworkTypes.java b/java/dagger/internal/codegen/base/FrameworkTypes.java index 59588caf5..e39aeabea 100644 --- a/java/dagger/internal/codegen/base/FrameworkTypes.java +++ b/java/dagger/internal/codegen/base/FrameworkTypes.java @@ -29,6 +29,7 @@ import java.util.Set; * type that the framework itself defines. */ public final class FrameworkTypes { + // TODO(erichang): Add the Jakarta Provider here private static final ImmutableSet<ClassName> PROVISION_TYPES = ImmutableSet.of(TypeNames.PROVIDER, TypeNames.LAZY, TypeNames.MEMBERS_INJECTOR); diff --git a/java/dagger/internal/codegen/base/MapType.java b/java/dagger/internal/codegen/base/MapType.java index 00401ed72..c4ba838e8 100644 --- a/java/dagger/internal/codegen/base/MapType.java +++ b/java/dagger/internal/codegen/base/MapType.java @@ -115,6 +115,13 @@ public abstract class MapType { return isMap(key.type().xprocessing()); } + public static boolean isMapOfProvider(XType keyType) { + if (MapType.isMap(keyType)) { + return MapType.from(keyType).valuesAreTypeOf(TypeNames.PROVIDER); + } + return false; + } + /** * Returns a {@link MapType} for {@code type}. * diff --git a/java/dagger/internal/codegen/base/OptionalType.java b/java/dagger/internal/codegen/base/OptionalType.java index 79b638d4a..5544eaeba 100644 --- a/java/dagger/internal/codegen/base/OptionalType.java +++ b/java/dagger/internal/codegen/base/OptionalType.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; import static dagger.internal.codegen.extension.DaggerStreams.valuesOf; import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; +import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf; import androidx.room.compiler.processing.XType; import androidx.room.compiler.processing.XTypeElement; @@ -155,4 +156,14 @@ public abstract class OptionalType { public static OptionalType from(Key key) { return from(key.type().xprocessing()); } + + public static boolean isOptionalProviderType(XType type) { + if (OptionalType.isOptional(type)) { + OptionalType optionalType = OptionalType.from(type); + if (isTypeOf(optionalType.valueType(), TypeNames.PROVIDER)) { + return true; + } + } + return false; + } } diff --git a/java/dagger/internal/codegen/base/SourceFileGenerator.java b/java/dagger/internal/codegen/base/SourceFileGenerator.java index c43499a50..dfe14b1d5 100644 --- a/java/dagger/internal/codegen/base/SourceFileGenerator.java +++ b/java/dagger/internal/codegen/base/SourceFileGenerator.java @@ -18,6 +18,7 @@ package dagger.internal.codegen.base; import static androidx.room.compiler.processing.JavaPoetExtKt.addOriginatingElement; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.CAST; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.KOTLIN_INTERNAL; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; @@ -100,13 +101,12 @@ public abstract class SourceFileGenerator<T> { .build()); generatedAnnotation.ifPresent(typeSpecBuilder::addAnnotation); - // TODO(b/134590785): Remove UNCHECKED/RAWTYPES and suppress locally where necessary. // TODO(b/263891456): Remove KOTLIN_INTERNAL and use Object/raw types where necessary. typeSpecBuilder.addAnnotation( AnnotationSpecs.suppressWarnings( ImmutableSet.<Suppression>builder() .addAll(warningSuppressions()) - .add(UNCHECKED, RAWTYPES, KOTLIN_INTERNAL) + .add(UNCHECKED, RAWTYPES, KOTLIN_INTERNAL, CAST) .build())); String packageName = closestEnclosingTypeElement(originatingElement).getPackageName(); diff --git a/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java b/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java new file mode 100644 index 000000000..6857c366f --- /dev/null +++ b/java/dagger/internal/codegen/base/SourceFileHjarGenerator.java @@ -0,0 +1,211 @@ +/* + * 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.internal.codegen.base; + +import static com.squareup.javapoet.MethodSpec.constructorBuilder; +import static com.squareup.javapoet.MethodSpec.methodBuilder; +import static com.squareup.javapoet.TypeSpec.classBuilder; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; +import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement; +import static javax.lang.model.element.Modifier.PRIVATE; + +import androidx.room.compiler.processing.XConstructorElement; +import androidx.room.compiler.processing.XElement; +import androidx.room.compiler.processing.XExecutableParameterElement; +import androidx.room.compiler.processing.XProcessingEnv; +import androidx.room.compiler.processing.XType; +import androidx.room.compiler.processing.XTypeElement; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import dagger.internal.codegen.javapoet.CodeBlocks; +import dagger.internal.codegen.javapoet.TypeNames; +import java.util.Optional; +import javax.lang.model.element.Modifier; + +/** + * A source file generator that only writes the relevant code necessary for Bazel to create a + * correct header (ABI) jar. + */ +public final class SourceFileHjarGenerator<T> extends SourceFileGenerator<T> { + public static <T> SourceFileGenerator<T> wrap( + SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) { + return new SourceFileHjarGenerator<>(delegate, processingEnv); + } + + private final SourceFileGenerator<T> delegate; + private final XProcessingEnv processingEnv; + + private SourceFileHjarGenerator(SourceFileGenerator<T> delegate, XProcessingEnv processingEnv) { + super(delegate); + this.delegate = delegate; + this.processingEnv = processingEnv; + } + + @Override + public XElement originatingElement(T input) { + return delegate.originatingElement(input); + } + + @Override + public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) { + String packageName = closestEnclosingTypeElement(originatingElement(input)).getPackageName(); + return delegate.topLevelTypes(input).stream() + .map(completeType -> skeletonType(packageName, completeType.build())) + .collect(toImmutableList()); + } + + private TypeSpec.Builder skeletonType(String packageName, TypeSpec completeType) { + TypeSpec.Builder skeleton = + classBuilder(completeType.name) + .addSuperinterfaces(completeType.superinterfaces) + .addTypeVariables(completeType.typeVariables) + .addModifiers(completeType.modifiers.toArray(new Modifier[0])) + .addAnnotations(completeType.annotations); + + if (!completeType.superclass.equals(ClassName.OBJECT)) { + skeleton.superclass(completeType.superclass); + } + + completeType.methodSpecs.stream() + .filter(method -> !method.modifiers.contains(PRIVATE) || method.isConstructor()) + .map(completeMethod -> skeletonMethod(packageName, completeType, completeMethod)) + .forEach(skeleton::addMethod); + + completeType.fieldSpecs.stream() + .filter(field -> !field.modifiers.contains(PRIVATE)) + .map(this::skeletonField) + .forEach(skeleton::addField); + + completeType.typeSpecs.stream() + .map(type -> skeletonType(packageName, type).build()) + .forEach(skeleton::addType); + + completeType.alwaysQualifiedNames + .forEach(skeleton::alwaysQualify); + + return skeleton; + } + + private MethodSpec skeletonMethod( + String packageName, TypeSpec completeType, MethodSpec completeMethod) { + MethodSpec.Builder skeleton = + completeMethod.isConstructor() + ? constructorBuilder() + : methodBuilder(completeMethod.name).returns(completeMethod.returnType); + + if (completeMethod.isConstructor()) { + getRequiredSuperCall(packageName, completeType) + .ifPresent(superCall -> skeleton.addStatement("$L", superCall)); + } else if (!completeMethod.returnType.equals(TypeName.VOID)) { + skeleton.addStatement("return $L", getDefaultValueCodeBlock(completeMethod.returnType)); + } + + return skeleton + .addModifiers(completeMethod.modifiers) + .addTypeVariables(completeMethod.typeVariables) + .addParameters(completeMethod.parameters) + .addExceptions(completeMethod.exceptions) + .varargs(completeMethod.varargs) + .addAnnotations(completeMethod.annotations) + .build(); + } + + private Optional<CodeBlock> getRequiredSuperCall(String packageName, TypeSpec completeType) { + if (completeType.superclass.equals(TypeName.OBJECT)) { + return Optional.empty(); + } + + ClassName rawSuperClass = (ClassName) TypeNames.rawTypeName(completeType.superclass); + XTypeElement superTypeElement = + processingEnv.requireTypeElement(rawSuperClass.canonicalName()); + + ImmutableSet<XConstructorElement> accessibleConstructors = + superTypeElement.getConstructors().stream() + .filter( + constructor -> + // isElementAccessibleFrom doesn't take protected into account so check manually + constructor.isProtected() + || isElementAccessibleFrom(constructor, packageName)) + .collect(toImmutableSet()); + + // If there's an accessible default constructor we don't need to call super() manually. + if (accessibleConstructors.isEmpty() + || accessibleConstructors.stream() + .anyMatch(constructor -> constructor.getParameters().isEmpty())) { + return Optional.empty(); + } + + return Optional.of( + CodeBlock.of( + "super($L)", + CodeBlocks.makeParametersCodeBlock( + // We just choose the first constructor (it doesn't really matter since we're just + // trying to ensure the constructor body compiles). + accessibleConstructors.stream().findFirst().get().getParameters().stream() + .map(XExecutableParameterElement::getType) + .map(XType::getTypeName) + .map(SourceFileHjarGenerator::getDefaultValueCodeBlock) + .collect(toImmutableList())))); + } + + /** + * Returns a {@link CodeBlock} containing the default value for the given {@code typeName}. + * + * <p>See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html. + */ + private static CodeBlock getDefaultValueCodeBlock(TypeName typeName) { + if (typeName.isPrimitive()) { + if (typeName.equals(TypeName.BOOLEAN)) { + return CodeBlock.of("false"); + } else if (typeName.equals(TypeName.CHAR)) { + return CodeBlock.of("'\u0000'"); + } else if (typeName.equals(TypeName.BYTE)) { + return CodeBlock.of("0"); + } else if (typeName.equals(TypeName.SHORT)) { + return CodeBlock.of("0"); + } else if (typeName.equals(TypeName.INT)) { + return CodeBlock.of("0"); + } else if (typeName.equals(TypeName.LONG)) { + return CodeBlock.of("0L"); + } else if (typeName.equals(TypeName.FLOAT)) { + return CodeBlock.of("0.0f"); + } else if (typeName.equals(TypeName.DOUBLE)) { + return CodeBlock.of("0.0d"); + } else { + throw new AssertionError("Unexpected type: " + typeName); + } + } + return CodeBlock.of("null"); + } + + private FieldSpec skeletonField(FieldSpec completeField) { + return FieldSpec.builder( + completeField.type, + completeField.name, + completeField.modifiers.toArray(new Modifier[0])) + .addAnnotations(completeField.annotations) + .build(); + } +} diff --git a/java/dagger/internal/codegen/base/TarjanSCCs.java b/java/dagger/internal/codegen/base/TarjanSCCs.java index ab9a0fdae..b089333b0 100644 --- a/java/dagger/internal/codegen/base/TarjanSCCs.java +++ b/java/dagger/internal/codegen/base/TarjanSCCs.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkState; import static java.lang.Math.min; import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -39,7 +40,7 @@ import java.util.Set; public final class TarjanSCCs { /** Returns the set of strongly connected components in reverse topological order. */ - public static <NodeT> ImmutableSet<ImmutableSet<NodeT>> compute( + public static <NodeT> ImmutableList<ImmutableSet<NodeT>> compute( ImmutableCollection<NodeT> nodes, SuccessorsFunction<NodeT> successorsFunction) { return new TarjanSCC<>(nodes, successorsFunction).compute(); } @@ -62,14 +63,14 @@ public final class TarjanSCCs { this.lowLinks = Maps.newHashMapWithExpectedSize(nodes.size()); } - private ImmutableSet<ImmutableSet<NodeT>> compute() { + private ImmutableList<ImmutableSet<NodeT>> compute() { checkState(indexes.isEmpty(), "TarjanSCC#compute() can only be called once per instance!"); for (NodeT node : nodes) { if (!indexes.containsKey(node)) { stronglyConnect(node); } } - return ImmutableSet.copyOf(stronglyConnectedComponents); + return ImmutableList.copyOf(stronglyConnectedComponents); } private void stronglyConnect(NodeT node) { |