diff --git a/value/processor/pom.xml b/value/processor/pom.xml index 77cf829098..0d4669aac7 100644 --- a/value/processor/pom.xml +++ b/value/processor/pom.xml @@ -78,11 +78,6 @@ com.squareup javapoet - - org.jetbrains.kotlinx - kotlinx-metadata-jvm - 0.9.0 - org.ow2.asm asm @@ -233,18 +228,8 @@ *:* - - - META-INF/*.kotlin_module - - - - - com.google @@ -257,14 +242,6 @@ com.squareup.javapoet autovalue.shaded.com.squareup.javapoet - - kotlin - autovalue.shaded.kotlin - - - kotlinx - autovalue.shaded.kotlinx - net.ltgt.gradle.incap autovalue.shaded.net.ltgt.gradle.incap diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java index 6113e90b26..281d517120 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java @@ -21,11 +21,9 @@ import static com.google.auto.common.MoreStreams.toImmutableList; 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.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION; import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME; import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME; -import static com.google.auto.value.processor.ClassNames.KOTLIN_METADATA_NAME; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toMap; import static javax.lang.model.util.ElementFilter.constructorsIn; @@ -48,7 +46,6 @@ import java.lang.reflect.Field; import java.util.AbstractMap.SimpleEntry; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NavigableSet; @@ -72,12 +69,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.JavaFileObject; -import kotlinx.metadata.Flag; -import kotlinx.metadata.KmClass; -import kotlinx.metadata.KmConstructor; -import kotlinx.metadata.KmValueParameter; -import kotlinx.metadata.jvm.KotlinClassHeader; -import kotlinx.metadata.jvm.KotlinClassMetadata; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; @@ -464,12 +455,12 @@ private Executable findExecutable( private ImmutableList findRelevantExecutables( TypeElement ofClass, String callMethod, TypeElement autoBuilderType) { - Optional kotlinMetadata = kotlinMetadataAnnotation(ofClass); + Optional kotlinMetadata = KotlinMetadata.kotlinMetadataAnnotation(ofClass); List elements = ofClass.getEnclosedElements(); Stream relevantExecutables = callMethod.isEmpty() ? kotlinMetadata - .map(a -> kotlinConstructorsIn(a, ofClass).stream()) + .map(a -> KotlinMetadata.kotlinConstructorsIn(a, ofClass).stream()) .orElseGet(() -> constructorsIn(elements).stream().map(Executable::of)) : methodsIn(elements).stream() .filter(m -> m.getSimpleName().contentEquals(callMethod)) @@ -582,91 +573,6 @@ private boolean visibleFrom(Element element, PackageElement fromPackage) { } } - private Optional kotlinMetadataAnnotation(Element element) { - // It would be MUCH simpler if we could just use ofClass.getAnnotation(Metadata.class). - // However that would be unsound. We want to shade the Kotlin runtime, including - // kotlin.Metadata, so as not to interfere with other things on the annotation classpath that - // might have a different version of the runtime. That means that if we referenced - // kotlin.Metadata.class here we would actually be referencing - // autovalue.shaded.kotlin.Metadata.class. Obviously the Kotlin class doesn't have that - // annotation. - return element.getAnnotationMirrors().stream() - .filter( - a -> - asTypeElement(a.getAnnotationType()) - .getQualifiedName() - .contentEquals(KOTLIN_METADATA_NAME)) - .map(a -> a) // get rid of that stupid wildcard - .findFirst(); - } - - /** - * Use Kotlin reflection to build {@link Executable} instances for the constructors in {@code - * ofClass} that include information about which parameters have default values. - */ - private ImmutableList kotlinConstructorsIn( - AnnotationMirror metadata, TypeElement ofClass) { - 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); - KotlinClassHeader header = - new KotlinClassHeader( - (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()); - KotlinClassMetadata.Class classMetadata = - (KotlinClassMetadata.Class) KotlinClassMetadata.read(header); - KmClass kmClass = classMetadata.toKmClass(); - ImmutableList.Builder kotlinConstructorsBuilder = ImmutableList.builder(); - for (KmConstructor constructor : kmClass.getConstructors()) { - ImmutableSet.Builder allBuilder = ImmutableSet.builder(); - ImmutableSet.Builder optionalBuilder = ImmutableSet.builder(); - for (KmValueParameter param : constructor.getValueParameters()) { - String name = param.getName(); - allBuilder.add(name); - if (Flag.ValueParameter.DECLARES_DEFAULT_VALUE.invoke(param.getFlags())) { - 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 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); - } - - private static ImmutableSet 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); + } +}