| /* |
| * Copyright (C) 2019 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.kotlin; |
| |
| import static dagger.internal.codegen.base.MoreAnnotationValues.getIntArrayValue; |
| import static dagger.internal.codegen.base.MoreAnnotationValues.getIntValue; |
| import static dagger.internal.codegen.base.MoreAnnotationValues.getOptionalIntValue; |
| import static dagger.internal.codegen.base.MoreAnnotationValues.getOptionalStringValue; |
| import static dagger.internal.codegen.base.MoreAnnotationValues.getStringArrayValue; |
| import static dagger.internal.codegen.base.MoreAnnotationValues.getStringValue; |
| import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap; |
| import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror; |
| import static dagger.internal.codegen.langmodel.DaggerElements.getFieldDescriptor; |
| import static dagger.internal.codegen.langmodel.DaggerElements.getMethodDescriptor; |
| import static kotlinx.metadata.Flag.ValueParameter.DECLARES_DEFAULT_VALUE; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.auto.value.extension.memoized.Memoized; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import dagger.internal.codegen.extension.DaggerCollectors; |
| import dagger.internal.codegen.langmodel.DaggerElements; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.function.Function; |
| import javax.annotation.Nullable; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.util.ElementFilter; |
| import kotlin.Metadata; |
| import kotlinx.metadata.Flag; |
| import kotlinx.metadata.KmClassVisitor; |
| import kotlinx.metadata.KmConstructorExtensionVisitor; |
| import kotlinx.metadata.KmConstructorVisitor; |
| import kotlinx.metadata.KmExtensionType; |
| import kotlinx.metadata.KmFunctionExtensionVisitor; |
| import kotlinx.metadata.KmFunctionVisitor; |
| import kotlinx.metadata.KmPropertyExtensionVisitor; |
| import kotlinx.metadata.KmPropertyVisitor; |
| import kotlinx.metadata.KmValueParameterVisitor; |
| import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor; |
| import kotlinx.metadata.jvm.JvmFieldSignature; |
| import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor; |
| import kotlinx.metadata.jvm.JvmMethodSignature; |
| import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor; |
| import kotlinx.metadata.jvm.KotlinClassHeader; |
| import kotlinx.metadata.jvm.KotlinClassMetadata; |
| |
| /** Data class of a TypeElement and its Kotlin metadata. */ |
| @AutoValue |
| abstract class KotlinMetadata { |
| // Kotlin suffix for fields that are for a delegated property. |
| // See: |
| // https://github.com/JetBrains/kotlin/blob/master/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAbi.kt#L32 |
| private static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate"; |
| |
| // Map that associates field elements with its Kotlin synthetic method for annotations. |
| private final Map<VariableElement, Optional<MethodForAnnotations>> |
| elementFieldAnnotationMethodMap = new HashMap<>(); |
| |
| // Map that associates field elements with its Kotlin getter method. |
| private final Map<VariableElement, Optional<ExecutableElement>> elementFieldGetterMethodMap = |
| new HashMap<>(); |
| |
| abstract TypeElement typeElement(); |
| |
| abstract ClassMetadata classMetadata(); |
| |
| @Memoized |
| ImmutableMap<String, ExecutableElement> methodDescriptors() { |
| return ElementFilter.methodsIn(typeElement().getEnclosedElements()).stream() |
| .collect(toImmutableMap(DaggerElements::getMethodDescriptor, Function.identity())); |
| } |
| |
| /** Returns true if any constructor of the defined a default parameter. */ |
| @Memoized |
| boolean containsConstructorWithDefaultParam() { |
| return classMetadata().constructors().stream() |
| .flatMap(constructor -> constructor.parameters().stream()) |
| .anyMatch(parameter -> parameter.flags(DECLARES_DEFAULT_VALUE)); |
| } |
| |
| /** Gets the synthetic method for annotations of a given field element. */ |
| Optional<ExecutableElement> getSyntheticAnnotationMethod(VariableElement fieldElement) { |
| return getAnnotationMethod(fieldElement) |
| .map( |
| methodForAnnotations -> { |
| if (methodForAnnotations == MethodForAnnotations.MISSING) { |
| throw new IllegalStateException( |
| "Method for annotations is missing for " + fieldElement); |
| } |
| return methodForAnnotations.method(); |
| }); |
| } |
| |
| /** |
| * Returns true if the synthetic method for annotations is missing. This can occur when inspecting |
| * the Kotlin metadata of a property from another compilation unit. |
| */ |
| boolean isMissingSyntheticAnnotationMethod(VariableElement fieldElement) { |
| return getAnnotationMethod(fieldElement) |
| .map(methodForAnnotations -> methodForAnnotations == MethodForAnnotations.MISSING) |
| // This can be missing if there was no property annotation at all (e.g. no annotations or |
| // the qualifier is already properly attached to the field). For these cases, it isn't |
| // considered missing since there was no method to look for in the first place. |
| .orElse(false); |
| } |
| |
| private Optional<MethodForAnnotations> getAnnotationMethod(VariableElement fieldElement) { |
| return elementFieldAnnotationMethodMap.computeIfAbsent( |
| fieldElement, this::getAnnotationMethodUncached); |
| } |
| |
| private Optional<MethodForAnnotations> getAnnotationMethodUncached(VariableElement fieldElement) { |
| return findProperty(fieldElement) |
| .methodForAnnotationsSignature() |
| .map( |
| signature -> |
| Optional.ofNullable(methodDescriptors().get(signature)) |
| .map(MethodForAnnotations::create) |
| // The method may be missing across different compilations. |
| // See https://youtrack.jetbrains.com/issue/KT-34684 |
| .orElse(MethodForAnnotations.MISSING)); |
| } |
| |
| /** Gets the getter method of a given field element corresponding to a property. */ |
| Optional<ExecutableElement> getPropertyGetter(VariableElement fieldElement) { |
| return elementFieldGetterMethodMap.computeIfAbsent( |
| fieldElement, this::getPropertyGetterUncached); |
| } |
| |
| private Optional<ExecutableElement> getPropertyGetterUncached(VariableElement fieldElement) { |
| return findProperty(fieldElement) |
| .getterSignature() |
| .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature))); |
| } |
| |
| private PropertyMetadata findProperty(VariableElement field) { |
| String fieldDescriptor = getFieldDescriptor(field); |
| if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) { |
| return classMetadata().propertiesByFieldSignature().get(fieldDescriptor); |
| } else { |
| // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124 |
| final String propertyName = getPropertyNameFromField(field); |
| return classMetadata().propertiesByFieldSignature().values().stream() |
| .filter(property -> propertyName.contentEquals(property.name())) |
| .collect(DaggerCollectors.onlyElement()); |
| } |
| } |
| |
| private static String getPropertyNameFromField(VariableElement field) { |
| String name = field.getSimpleName().toString(); |
| if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) { |
| return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length()); |
| } else { |
| return name; |
| } |
| } |
| |
| FunctionMetadata getFunctionMetadata(ExecutableElement method) { |
| return classMetadata().functionsBySignature().get(getMethodDescriptor(method)); |
| } |
| |
| /** Parse Kotlin class metadata from a given type element * */ |
| static KotlinMetadata from(TypeElement typeElement) { |
| return new AutoValue_KotlinMetadata( |
| typeElement, ClassVisitor.createClassMetadata(metadataOf(typeElement))); |
| } |
| |
| private static KotlinClassMetadata.Class metadataOf(TypeElement typeElement) { |
| Optional<AnnotationMirror> metadataAnnotation = |
| getAnnotationMirror(typeElement, Metadata.class); |
| Preconditions.checkState(metadataAnnotation.isPresent()); |
| KotlinClassHeader header = |
| new KotlinClassHeader( |
| getIntValue(metadataAnnotation.get(), "k"), |
| getIntArrayValue(metadataAnnotation.get(), "mv"), |
| getIntArrayValue(metadataAnnotation.get(), "bv"), |
| getStringArrayValue(metadataAnnotation.get(), "d1"), |
| getStringArrayValue(metadataAnnotation.get(), "d2"), |
| getStringValue(metadataAnnotation.get(), "xs"), |
| getOptionalStringValue(metadataAnnotation.get(), "pn").orElse(null), |
| getOptionalIntValue(metadataAnnotation.get(), "xi").orElse(null)); |
| KotlinClassMetadata metadata = KotlinClassMetadata.read(header); |
| if (metadata == null) { |
| // Should only happen on Kotlin < 1.0 (i.e. metadata version < 1.1) |
| throw new IllegalStateException( |
| "Unsupported metadata version. Check that your Kotlin version is >= 1.0"); |
| } |
| if (metadata instanceof KotlinClassMetadata.Class) { |
| // TODO(danysantiago): If when we need other types of metadata then move to right method. |
| return (KotlinClassMetadata.Class) metadata; |
| } else { |
| throw new IllegalStateException("Unsupported metadata type: " + metadata); |
| } |
| } |
| |
| private static final class ClassVisitor extends KmClassVisitor { |
| static ClassMetadata createClassMetadata(KotlinClassMetadata.Class data) { |
| ClassVisitor visitor = new ClassVisitor(); |
| data.accept(visitor); |
| return visitor.classMetadata.build(); |
| } |
| |
| private final ClassMetadata.Builder classMetadata = ClassMetadata.builder(); |
| |
| @Override |
| public void visit(int flags, String name) { |
| classMetadata.flags(flags).name(name); |
| } |
| |
| @Override |
| public KmConstructorVisitor visitConstructor(int flags) { |
| return new KmConstructorVisitor() { |
| private final FunctionMetadata.Builder constructor = |
| FunctionMetadata.builder(flags, "<init>"); |
| |
| @Override |
| public KmValueParameterVisitor visitValueParameter(int flags, String name) { |
| constructor.addParameter(ValueParameterMetadata.create(flags, name)); |
| return super.visitValueParameter(flags, name); |
| } |
| |
| @Override |
| public KmConstructorExtensionVisitor visitExtensions(KmExtensionType kmExtensionType) { |
| return kmExtensionType.equals(JvmConstructorExtensionVisitor.TYPE) |
| ? new JvmConstructorExtensionVisitor() { |
| @Override |
| public void visit(JvmMethodSignature jvmMethodSignature) { |
| constructor.signature(jvmMethodSignature.asString()); |
| } |
| } |
| : null; |
| } |
| |
| @Override |
| public void visitEnd() { |
| classMetadata.addConstructor(constructor.build()); |
| } |
| }; |
| } |
| |
| @Override |
| public KmFunctionVisitor visitFunction(int flags, String name) { |
| return new KmFunctionVisitor() { |
| private final FunctionMetadata.Builder function = FunctionMetadata.builder(flags, name); |
| |
| @Override |
| public KmValueParameterVisitor visitValueParameter(int flags, String name) { |
| function.addParameter(ValueParameterMetadata.create(flags, name)); |
| return super.visitValueParameter(flags, name); |
| } |
| |
| @Override |
| public KmFunctionExtensionVisitor visitExtensions(KmExtensionType kmExtensionType) { |
| return kmExtensionType.equals(JvmFunctionExtensionVisitor.TYPE) |
| ? new JvmFunctionExtensionVisitor() { |
| @Override |
| public void visit(JvmMethodSignature jvmMethodSignature) { |
| function.signature(jvmMethodSignature.asString()); |
| } |
| } |
| : null; |
| } |
| |
| @Override |
| public void visitEnd() { |
| classMetadata.addFunction(function.build()); |
| } |
| }; |
| } |
| |
| @Override |
| public void visitCompanionObject(String companionObjectName) { |
| classMetadata.companionObjectName(companionObjectName); |
| } |
| |
| @Override |
| public KmPropertyVisitor visitProperty( |
| int flags, String name, int getterFlags, int setterFlags) { |
| return new KmPropertyVisitor() { |
| private final PropertyMetadata.Builder property = PropertyMetadata.builder(flags, name); |
| |
| @Override |
| public KmPropertyExtensionVisitor visitExtensions(KmExtensionType kmExtensionType) { |
| if (!kmExtensionType.equals(JvmPropertyExtensionVisitor.TYPE)) { |
| return null; |
| } |
| |
| return new JvmPropertyExtensionVisitor() { |
| @Override |
| public void visit( |
| int jvmFlags, |
| @Nullable JvmFieldSignature jvmFieldSignature, |
| @Nullable JvmMethodSignature jvmGetterSignature, |
| @Nullable JvmMethodSignature jvmSetterSignature) { |
| property.fieldSignature( |
| Optional.ofNullable(jvmFieldSignature).map(JvmFieldSignature::asString)); |
| property.getterSignature( |
| Optional.ofNullable(jvmGetterSignature).map(JvmMethodSignature::asString)); |
| } |
| |
| @Override |
| public void visitSyntheticMethodForAnnotations( |
| @Nullable JvmMethodSignature methodSignature) { |
| property.methodForAnnotationsSignature( |
| Optional.ofNullable(methodSignature).map(JvmMethodSignature::asString)); |
| } |
| }; |
| } |
| |
| @Override |
| public void visitEnd() { |
| classMetadata.addProperty(property.build()); |
| } |
| }; |
| } |
| } |
| |
| @AutoValue |
| abstract static class ClassMetadata extends BaseMetadata { |
| abstract Optional<String> companionObjectName(); |
| |
| abstract ImmutableSet<FunctionMetadata> constructors(); |
| |
| abstract ImmutableMap<String, FunctionMetadata> functionsBySignature(); |
| |
| abstract ImmutableMap<String, PropertyMetadata> propertiesByFieldSignature(); |
| |
| static Builder builder() { |
| return new AutoValue_KotlinMetadata_ClassMetadata.Builder(); |
| } |
| |
| @AutoValue.Builder |
| abstract static class Builder implements BaseMetadata.Builder<Builder> { |
| abstract Builder companionObjectName(String companionObjectName); |
| |
| abstract ImmutableSet.Builder<FunctionMetadata> constructorsBuilder(); |
| |
| abstract ImmutableMap.Builder<String, FunctionMetadata> functionsBySignatureBuilder(); |
| |
| abstract ImmutableMap.Builder<String, PropertyMetadata> propertiesByFieldSignatureBuilder(); |
| |
| Builder addConstructor(FunctionMetadata constructor) { |
| constructorsBuilder().add(constructor); |
| return this; |
| } |
| |
| Builder addFunction(FunctionMetadata function) { |
| functionsBySignatureBuilder().put(function.signature(), function); |
| return this; |
| } |
| |
| Builder addProperty(PropertyMetadata property) { |
| if (property.fieldSignature().isPresent()) { |
| propertiesByFieldSignatureBuilder().put(property.fieldSignature().get(), property); |
| } |
| return this; |
| } |
| |
| abstract ClassMetadata build(); |
| } |
| } |
| |
| @AutoValue |
| abstract static class FunctionMetadata extends BaseMetadata { |
| abstract String signature(); |
| |
| abstract ImmutableList<ValueParameterMetadata> parameters(); |
| |
| static Builder builder(int flags, String name) { |
| return new AutoValue_KotlinMetadata_FunctionMetadata.Builder().flags(flags).name(name); |
| } |
| |
| @AutoValue.Builder |
| abstract static class Builder implements BaseMetadata.Builder<Builder> { |
| abstract Builder signature(String signature); |
| |
| abstract ImmutableList.Builder<ValueParameterMetadata> parametersBuilder(); |
| |
| Builder addParameter(ValueParameterMetadata parameter) { |
| parametersBuilder().add(parameter); |
| return this; |
| } |
| |
| abstract FunctionMetadata build(); |
| } |
| } |
| |
| @AutoValue |
| abstract static class PropertyMetadata extends BaseMetadata { |
| /** Returns the JVM field descriptor of the backing field of this property. */ |
| abstract Optional<String> fieldSignature(); |
| |
| abstract Optional<String> getterSignature(); |
| |
| /** Returns JVM method descriptor of the synthetic method for property annotations. */ |
| abstract Optional<String> methodForAnnotationsSignature(); |
| |
| static Builder builder(int flags, String name) { |
| return new AutoValue_KotlinMetadata_PropertyMetadata.Builder().flags(flags).name(name); |
| } |
| |
| @AutoValue.Builder |
| interface Builder extends BaseMetadata.Builder<Builder> { |
| Builder fieldSignature(Optional<String> signature); |
| |
| Builder getterSignature(Optional<String> signature); |
| |
| Builder methodForAnnotationsSignature(Optional<String> signature); |
| |
| PropertyMetadata build(); |
| } |
| } |
| |
| @AutoValue |
| abstract static class ValueParameterMetadata extends BaseMetadata { |
| private static ValueParameterMetadata create(int flags, String name) { |
| return new AutoValue_KotlinMetadata_ValueParameterMetadata(flags, name); |
| } |
| } |
| |
| abstract static class BaseMetadata { |
| /** Returns the Kotlin metadata flags for this property. */ |
| abstract int flags(); |
| |
| /** returns {@code true} if the given flag (e.g. {@link Flag.IS_PRIVATE}) applies. */ |
| boolean flags(Flag flag) { |
| return flag.invoke(flags()); |
| } |
| |
| /** Returns the simple name of this property. */ |
| abstract String name(); |
| |
| interface Builder<BuilderT> { |
| BuilderT flags(int flags); |
| |
| BuilderT name(String name); |
| } |
| } |
| |
| @AutoValue |
| abstract static class MethodForAnnotations { |
| static MethodForAnnotations create(ExecutableElement method) { |
| return new AutoValue_KotlinMetadata_MethodForAnnotations(method); |
| } |
| |
| static final MethodForAnnotations MISSING = MethodForAnnotations.create(null); |
| |
| @Nullable |
| abstract ExecutableElement method(); |
| } |
| } |