All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.kotlin.KotlinMetadata Maven / Gradle / Ivy

The newest version!
/*
 * 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.extension.DaggerStreams.toImmutableMap;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;

import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XFieldElement;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XTypeElement;
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.javapoet.TypeNames;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nullable;
import kotlin.Metadata;
import kotlinx.metadata.Flag;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmMetadataUtil;
import kotlinx.metadata.jvm.JvmMethodSignature;
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> elementFieldAnnotationMethodMap =
      new HashMap<>();

  // Map that associates field elements with its Kotlin getter method.
  private final Map> elementFieldGetterMethodMap =
      new HashMap<>();

  abstract XTypeElement typeElement();

  abstract ClassMetadata classMetadata();

  @Memoized
  ImmutableMap methodDescriptors() {
    return typeElement().getDeclaredMethods().stream()
        .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity()));
  }

  /** Gets the synthetic method for annotations of a given field element. */
  Optional getSyntheticAnnotationMethod(XFieldElement 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(XFieldElement 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 getAnnotationMethod(XFieldElement fieldElement) {
    return elementFieldAnnotationMethodMap.computeIfAbsent(
        fieldElement, this::getAnnotationMethodUncached);
  }

  private Optional getAnnotationMethodUncached(XFieldElement 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 getPropertyGetter(XFieldElement fieldElement) {
    return elementFieldGetterMethodMap.computeIfAbsent(
        fieldElement, this::getPropertyGetterUncached);
  }

  private Optional getPropertyGetterUncached(XFieldElement fieldElement) {
    return findProperty(fieldElement)
        .getterSignature()
        .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature)));
  }

  private PropertyMetadata findProperty(XFieldElement field) {
    String fieldDescriptor = field.getJvmDescriptor();
    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(XFieldElement field) {
    String name = getSimpleName(field);
    if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) {
      return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length());
    } else {
      return name;
    }
  }

  /** Parse Kotlin class metadata from a given type element. */
  static KotlinMetadata from(XTypeElement typeElement) {
    return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.create(metadataOf(typeElement)));
  }

  private static KotlinClassMetadata.Class metadataOf(XTypeElement typeElement) {
    XAnnotation annotationMirror = typeElement.getAnnotation(TypeNames.KOTLIN_METADATA);
    Preconditions.checkNotNull(annotationMirror);
    Metadata metadataAnnotation =
        JvmMetadataUtil.Metadata(
            annotationMirror.getAsInt("k"),
            annotationMirror.getAsIntList("mv").stream().mapToInt(Integer::intValue).toArray(),
            annotationMirror.getAsStringList("d1").toArray(new String[0]),
            annotationMirror.getAsStringList("d2").toArray(new String[0]),
            annotationMirror.getAsString("xs"),
            annotationMirror.getAnnotationValue("pn").hasStringValue()
                ? annotationMirror.getAsString("pn")
                : null,
            annotationMirror.getAnnotationValue("xi").hasIntValue()
                ? annotationMirror.getAsInt("xi")
                : null);
    KotlinClassMetadata metadata = KotlinClassMetadata.read(metadataAnnotation);
    if (metadata == null) {
      // Can happen if Kotlin < 1.0 or if metadata version is not supported, i.e.
      // kotlinx-metadata-jvm is outdated.
      throw new IllegalStateException(
          "Unable to read Kotlin metadata due to unsupported metadata version.");
    }
    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);
    }
  }

  @AutoValue
  abstract static class ClassMetadata extends BaseMetadata {
    abstract Optional companionObjectName();

    abstract ImmutableSet constructors();

    abstract ImmutableMap functionsBySignature();

    abstract ImmutableMap propertiesByFieldSignature();

    static ClassMetadata create(KotlinClassMetadata.Class metadata) {
      KmClass kmClass = metadata.toKmClass();
      ClassMetadata.Builder builder =
          ClassMetadata.builder(
              kmClass.getFlags(), kmClass.getName()); // // SUPPRESS_GET_NAME_CHECK
      builder.companionObjectName(Optional.ofNullable(kmClass.getCompanionObject()));
      kmClass.getConstructors().forEach(it -> builder.addConstructor(FunctionMetadata.create(it)));
      kmClass.getFunctions().forEach(it -> builder.addFunction(FunctionMetadata.create(it)));
      kmClass.getProperties().forEach(it -> builder.addProperty(PropertyMetadata.create(it)));
      return builder.build();
    }

    private static Builder builder(int flags, String name) {
      return new AutoValue_KotlinMetadata_ClassMetadata.Builder().flags(flags).name(name);
    }

    @AutoValue.Builder
    abstract static class Builder implements BaseMetadata.Builder {
      abstract Builder companionObjectName(Optional companionObjectName);

      abstract ImmutableSet.Builder constructorsBuilder();

      abstract ImmutableMap.Builder functionsBySignatureBuilder();

      abstract ImmutableMap.Builder propertiesByFieldSignatureBuilder();

      Builder addConstructor(FunctionMetadata constructor) {
        constructorsBuilder().add(constructor);
        functionsBySignatureBuilder().put(constructor.signature(), 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 parameters();

    static FunctionMetadata create(KmConstructor metadata) {
      FunctionMetadata.Builder builder = FunctionMetadata.builder(metadata.getFlags(), "");
      metadata
          .getValueParameters()
          .forEach(
              it ->
                  builder.addParameter(
                      ValueParameterMetadata.create(
                          it.getFlags(), it.getName()))); // SUPPRESS_GET_NAME_CHECK
      builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString());
      return builder.build();
    }

    static FunctionMetadata create(KmFunction metadata) {
      FunctionMetadata.Builder builder =
          FunctionMetadata.builder(
              metadata.getFlags(), metadata.getName()); // SUPPRESS_GET_NAME_CHECK
      metadata
          .getValueParameters()
          .forEach(
              it ->
                  builder.addParameter(
                      ValueParameterMetadata.create(
                          it.getFlags(), it.getName()))); // SUPPRESS_GET_NAME_CHECK
      builder.signature(Objects.requireNonNull(JvmExtensionsKt.getSignature(metadata)).asString());
      return builder.build();
    }

    private 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 {
      abstract Builder signature(String signature);

      abstract ImmutableList.Builder 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 fieldSignature();

    abstract Optional getterSignature();

    /** Returns JVM method descriptor of the synthetic method for property annotations. */
    abstract Optional methodForAnnotationsSignature();

    static PropertyMetadata create(KmProperty metadata) {
      PropertyMetadata.Builder builder =
          PropertyMetadata.builder(
              metadata.getFlags(), metadata.getName()); // SUPPRESS_GET_NAME_CHECK
      builder.fieldSignature(
          Optional.ofNullable(JvmExtensionsKt.getFieldSignature(metadata))
              .map(JvmFieldSignature::asString));
      builder.getterSignature(
          Optional.ofNullable(JvmExtensionsKt.getGetterSignature(metadata))
              .map(JvmMethodSignature::asString));
      builder.methodForAnnotationsSignature(
          Optional.ofNullable(JvmExtensionsKt.getSyntheticMethodForAnnotations(metadata))
              .map(JvmMethodSignature::asString));
      return builder.build();
    }

    private 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 fieldSignature(Optional signature);

      Builder getterSignature(Optional signature);

      Builder methodForAnnotationsSignature(Optional 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 flags(int flags);

      BuilderT name(String name);
    }
  }

  @AutoValue
  abstract static class MethodForAnnotations {
    static MethodForAnnotations create(XMethodElement method) {
      return new AutoValue_KotlinMetadata_MethodForAnnotations(method);
    }

    static final MethodForAnnotations MISSING = MethodForAnnotations.create(null);

    @Nullable
    abstract XMethodElement method();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy