parameterNames(ExecutableElement executableElement) {
- return executableElement.getParameters().stream()
- .map(v -> v.getSimpleName().toString())
- .collect(toImmutableSet());
- }
-
private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord();
private static ElementKind elementKindRecord() {
diff --git a/value/src/main/java/com/google/auto/value/processor/KotlinMetadata.java b/value/src/main/java/com/google/auto/value/processor/KotlinMetadata.java
new file mode 100644
index 0000000000..a3e977e863
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/KotlinMetadata.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2024 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.value.processor;
+
+import static com.google.auto.common.MoreStreams.toImmutableMap;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.auto.common.MoreTypes.asTypeElement;
+import static com.google.auto.value.processor.ClassNames.KOTLIN_METADATA_NAME;
+import static java.lang.invoke.MethodType.methodType;
+import static java.util.stream.Collectors.toMap;
+import static javax.lang.model.util.ElementFilter.constructorsIn;
+
+import com.google.auto.common.AnnotationMirrors;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * Utilities for working with Kotlin metadata.
+ *
+ * We use reflection to avoid referencing the Kotlin metadata API directly. AutoBuilder clients
+ * that don't use Kotlin shouldn't have to have the Kotlin runtime on their classpath, even if it is
+ * only the annotation-processing classpath.
+ */
+final class KotlinMetadata {
+ private KotlinMetadata() {}
+
+ private static final MethodHandle NEW_KOTLIN_CLASS_HEADER;
+ private static final MethodHandle KOTLIN_CLASS_METADATA_READ;
+ private static final MethodHandle KOTLIN_CLASS_METADATA_CLASS_TO_KM_CLASS;
+ private static final MethodHandle KM_CLASS_GET_CONSTRUCTORS;
+ private static final MethodHandle KM_CONSTRUCTOR_GET_VALUE_PARAMETERS;
+ private static final MethodHandle KM_VALUE_PARAMETER_GET_NAME;
+ private static final MethodHandle KM_VALUE_PARAMETER_GET_FLAGS;
+ private static final MethodHandle FLAG_INVOKE;
+ private static final Object DECLARES_DEFAULT_VALUE_FLAG;
+ private static final boolean KOTLIN_METADATA_AVAILABLE;
+
+ static {
+ MethodHandle newKotlinClassHeader = null;
+ MethodHandle kotlinClassMetadataRead = null;
+ MethodHandle kotlinClassMetadataClassToKmClass = null;
+ MethodHandle kmClassGetConstructors = null;
+ MethodHandle kmConstructorGetValueParameters = null;
+ MethodHandle kmValueParameterGetName = null;
+ MethodHandle kmValueParameterGetFlags = null;
+ MethodHandle flagInvoke = null;
+ Object declaresDefaultValueFlag = null;
+ boolean kotlinMetadataAvailable = false;
+ for (String prefix : new String[] {"kotlin.metadata.", "kotlinx.metadata."}) {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.publicLookup();
+ Class> kotlinClassHeaderClass = Class.forName(prefix + "jvm.KotlinClassHeader");
+ newKotlinClassHeader =
+ lookup.findConstructor(
+ kotlinClassHeaderClass,
+ methodType(
+ void.class,
+ Integer.class,
+ int[].class,
+ String[].class,
+ String[].class,
+ String.class,
+ String.class,
+ Integer.class));
+ Class> kotlinClassMetadataClass =
+ Class.forName(prefix + "jvm.KotlinClassMetadata");
+ Class> kotlinMetadataClass = Class.forName("kotlin.Metadata");
+ kotlinClassMetadataRead =
+ lookup.findStatic(
+ kotlinClassMetadataClass,
+ "read",
+ methodType(kotlinClassMetadataClass, kotlinMetadataClass));
+ Class> kotlinClassMetadataClassClass =
+ Class.forName(prefix + "jvm.KotlinClassMetadata$Class");
+ Class> kmClassClass = Class.forName(prefix + "KmClass");
+ kotlinClassMetadataClassToKmClass =
+ lookup.findVirtual(
+ kotlinClassMetadataClassClass, "toKmClass", methodType(kmClassClass));
+ kmClassGetConstructors =
+ lookup.findVirtual(kmClassClass, "getConstructors", methodType(List.class));
+ Class> kmConstructorClass = Class.forName(prefix + "KmConstructor");
+ kmConstructorGetValueParameters =
+ lookup.findVirtual(kmConstructorClass, "getValueParameters", methodType(List.class));
+ Class> kmValueParameterClass = Class.forName(prefix + "KmValueParameter");
+ kmValueParameterGetName =
+ lookup.findVirtual(kmValueParameterClass, "getName", methodType(String.class));
+ kmValueParameterGetFlags =
+ lookup.findVirtual(kmValueParameterClass, "getFlags", methodType(int.class));
+ Class> flagClass = Class.forName(prefix + "Flag");
+ Class> flagValueParameterClass = Class.forName(prefix + "Flag$ValueParameter");
+ declaresDefaultValueFlag =
+ flagValueParameterClass.getField("DECLARES_DEFAULT_VALUE").get(null);
+ flagInvoke = lookup.findVirtual(flagClass, "invoke", methodType(boolean.class, int.class));
+ kotlinMetadataAvailable = true;
+ break;
+ } catch (ReflectiveOperationException e) {
+ // OK: Presumably means that the Kotlin metadata API is not available.
+ }
+ }
+ NEW_KOTLIN_CLASS_HEADER = newKotlinClassHeader;
+ KOTLIN_CLASS_METADATA_READ = kotlinClassMetadataRead;
+ KOTLIN_CLASS_METADATA_CLASS_TO_KM_CLASS = kotlinClassMetadataClassToKmClass;
+ KM_CLASS_GET_CONSTRUCTORS = kmClassGetConstructors;
+ KM_CONSTRUCTOR_GET_VALUE_PARAMETERS = kmConstructorGetValueParameters;
+ KM_VALUE_PARAMETER_GET_NAME = kmValueParameterGetName;
+ KM_VALUE_PARAMETER_GET_FLAGS = kmValueParameterGetFlags;
+ FLAG_INVOKE = flagInvoke;
+ DECLARES_DEFAULT_VALUE_FLAG = declaresDefaultValueFlag;
+ KOTLIN_METADATA_AVAILABLE = kotlinMetadataAvailable;
+ }
+
+ /**
+ * Use Kotlin reflection to build {@link Executable} instances for the constructors in {@code
+ * ofClass} that include information about which parameters have default values.
+ */
+ static ImmutableList kotlinConstructorsIn(
+ AnnotationMirror metadata, TypeElement ofClass) {
+ if (!KOTLIN_METADATA_AVAILABLE) {
+ return ImmutableList.of();
+ }
+ try {
+ return reflectiveKotlinConstructorsIn(metadata, ofClass);
+ } catch (RuntimeException | Error e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new LinkageError(t.getMessage(), t);
+ }
+ }
+
+ private static ImmutableList reflectiveKotlinConstructorsIn(
+ AnnotationMirror metadata, TypeElement ofClass) throws Throwable {
+ ImmutableMap annotationValues =
+ AnnotationMirrors.getAnnotationValuesWithDefaults(metadata).entrySet().stream()
+ .collect(toImmutableMap(e -> e.getKey().getSimpleName().toString(), e -> e.getValue()));
+ // We match the KmConstructor instances with the ExecutableElement instances based on the
+ // parameter names. We could possibly just assume that the constructors are in the same order.
+ Map, ExecutableElement> map =
+ constructorsIn(ofClass.getEnclosedElements()).stream()
+ .collect(toMap(c -> parameterNames(c), c -> c, (a, b) -> a, LinkedHashMap::new));
+ ImmutableMap, ExecutableElement> paramNamesToConstructor =
+ ImmutableMap.copyOf(map);
+ Object /* KotlinClassHeader */ kotlinClassHeader =
+ NEW_KOTLIN_CLASS_HEADER.invoke(
+ (Integer) annotationValues.get("k").getValue(),
+ intArrayValue(annotationValues.get("mv")),
+ stringArrayValue(annotationValues.get("d1")),
+ stringArrayValue(annotationValues.get("d2")),
+ (String) annotationValues.get("xs").getValue(),
+ (String) annotationValues.get("pn").getValue(),
+ (Integer) annotationValues.get("xi").getValue());
+ Object /* KotlinClassMetadata.Class */ classMetadata =
+ KOTLIN_CLASS_METADATA_READ.invoke(kotlinClassHeader);
+ Object /* KmClass */ kmClass = KOTLIN_CLASS_METADATA_CLASS_TO_KM_CLASS.invoke(classMetadata);
+ ImmutableList.Builder kotlinConstructorsBuilder = ImmutableList.builder();
+ for (Object /* KmConstructor */ constructor :
+ (List>) KM_CLASS_GET_CONSTRUCTORS.invoke(kmClass)) {
+ ImmutableSet.Builder allBuilder = ImmutableSet.builder();
+ ImmutableSet.Builder optionalBuilder = ImmutableSet.builder();
+ for (Object /* KmValueParameter */ param :
+ (List>) KM_CONSTRUCTOR_GET_VALUE_PARAMETERS.invoke(constructor)) {
+ String name = (String) KM_VALUE_PARAMETER_GET_NAME.invoke(param);
+ allBuilder.add(name);
+ if ((boolean)
+ FLAG_INVOKE.invoke(
+ DECLARES_DEFAULT_VALUE_FLAG, (int) KM_VALUE_PARAMETER_GET_FLAGS.invoke(param))) {
+ optionalBuilder.add(name);
+ }
+ }
+ ImmutableSet optional = optionalBuilder.build();
+ ImmutableSet all = allBuilder.build();
+ ExecutableElement javaConstructor = paramNamesToConstructor.get(all);
+ if (javaConstructor != null) {
+ kotlinConstructorsBuilder.add(Executable.of(javaConstructor, optional));
+ }
+ }
+ return kotlinConstructorsBuilder.build();
+ }
+
+ private static ImmutableSet parameterNames(ExecutableElement executableElement) {
+ return executableElement.getParameters().stream()
+ .map(v -> v.getSimpleName().toString())
+ .collect(toImmutableSet());
+ }
+
+ static Optional kotlinMetadataAnnotation(Element element) {
+ return element.getAnnotationMirrors().stream()
+ .filter(
+ a ->
+ asTypeElement(a.getAnnotationType())
+ .getQualifiedName()
+ .contentEquals(KOTLIN_METADATA_NAME))
+ .map(a -> a) // get rid of that stupid wildcard
+ .findFirst();
+ }
+
+ private static int[] intArrayValue(AnnotationValue value) {
+ @SuppressWarnings("unchecked")
+ List list = (List) value.getValue();
+ return list.stream().mapToInt(v -> (int) v.getValue()).toArray();
+ }
+
+ private static String[] stringArrayValue(AnnotationValue value) {
+ @SuppressWarnings("unchecked")
+ List list = (List) value.getValue();
+ return list.stream().map(AnnotationValue::getValue).toArray(String[]::new);
+ }
+}