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

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

There is a newer version: 2.45-kim-rc1
Show 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.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 com.squareup.javapoet.ClassName;
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>
      elementFieldAnnotationMethodMap = new HashMap<>();

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

  abstract TypeElement typeElement();

  abstract ClassMetadata classMetadata();

  @Memoized
  ImmutableMap 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 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 getAnnotationMethod(VariableElement fieldElement) {
    return elementFieldAnnotationMethodMap.computeIfAbsent(
        fieldElement, this::getAnnotationMethodUncached);
  }

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

  private Optional 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 metadataAnnotation =
        getAnnotationMirror(typeElement, ClassName.get(Metadata.class));
    Preconditions.checkState(metadataAnnotation.isPresent());
    KotlinClassHeader header =
        new KotlinClassHeader(
            getIntValue(metadataAnnotation.get(), "k"),
            getIntArrayValue(metadataAnnotation.get(), "mv"),
            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, "");

        @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 companionObjectName();

    abstract ImmutableSet constructors();

    abstract ImmutableMap functionsBySignature();

    abstract ImmutableMap propertiesByFieldSignature();

    static Builder builder() {
      return new AutoValue_KotlinMetadata_ClassMetadata.Builder();
    }

    @AutoValue.Builder
    abstract static class Builder implements BaseMetadata.Builder {
      abstract Builder companionObjectName(String 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 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 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(ExecutableElement method) {
      return new AutoValue_KotlinMetadata_MethodForAnnotations(method);
    }

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

    @Nullable
    abstract ExecutableElement method();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy