Skip to content

Commit

Permalink
Use reflection to avoid referencing the Kotlin metadata API directly.
Browse files Browse the repository at this point in the history
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. Additionally, the metadata API was recently moved from `kotlinx.*` to `kotlin.*`, and using reflection means we can function with either version.

Fixes #1440.

PiperOrigin-RevId: 637886287
  • Loading branch information
eamonnmcmanus authored and Google Java Core Libraries committed May 28, 2024
1 parent 76be89a commit 260b61e
Show file tree
Hide file tree
Showing 5 changed files with 349 additions and 131 deletions.
28 changes: 0 additions & 28 deletions value/processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,6 @@
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-metadata-jvm</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
Expand Down Expand Up @@ -230,21 +225,6 @@
<exclude>com.google.code.findbugs:jsr305</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<!-- Don't include kotlinx-metadata.kotlin_module, etc. We're shading those
libaries and they're only used from Java. Leaving them in the jar creates
"incompatible version" errors from the Kotlin compiler. -->
<exclude>META-INF/*.kotlin_module</exclude>
</excludes>
</filter>
</filters>
<transformers>
<!-- Needed to avoid "No MetadataExtensions instances found in the classpath" from Kotlin reflection. -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<relocations>
<relocation>
<pattern>com.google</pattern>
Expand All @@ -257,14 +237,6 @@
<pattern>com.squareup.javapoet</pattern>
<shadedPattern>autovalue.shaded.com.squareup.javapoet</shadedPattern>
</relocation>
<relocation>
<pattern>kotlin</pattern>
<shadedPattern>autovalue.shaded.kotlin</shadedPattern>
</relocation>
<relocation>
<pattern>kotlinx</pattern>
<shadedPattern>autovalue.shaded.kotlinx</shadedPattern>
</relocation>
<relocation>
<pattern>net.ltgt.gradle.incap</pattern>
<shadedPattern>autovalue.shaded.net.ltgt.gradle.incap</shadedPattern>
Expand Down
31 changes: 25 additions & 6 deletions value/src/it/functional/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@
<artifactId>auto-value-annotations</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
Expand Down Expand Up @@ -158,6 +153,18 @@
</dependency>
</dependencies>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-metadata-jvm</artifactId>
<version>2.0.0</version>
</path>
</annotationProcessorPaths>
<source>${java.specification.version}</source>
<target>${java.specification.version}</target>
<compilerArgs>
Expand Down Expand Up @@ -202,7 +209,7 @@
</goals>
</execution>
</executions>
</plugin>
</plugin>
</plugins>
</build>

Expand All @@ -223,6 +230,18 @@
</dependency>
</dependencies>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-metadata-jvm</artifactId>
<version>2.0.0</version>
</path>
</annotationProcessorPaths>
<source>${java.specification.version}</source>
<target>${java.specification.version}</target>
<compilerArgs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -72,12 +69,6 @@
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;
import kotlinx.metadata.Attributes;
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;

Expand All @@ -104,11 +95,13 @@ public Set<String> getSupportedOptions() {
}

private TypeMirror javaLangVoid;
private KotlinMetadata kotlinMetadata;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType();
kotlinMetadata = new KotlinMetadata(errorReporter());
}

// The handling of @AutoBuilder to generate annotation implementations needs some explanation.
Expand Down Expand Up @@ -464,12 +457,13 @@ private Executable findExecutable(

private ImmutableList<Executable> findRelevantExecutables(
TypeElement ofClass, String callMethod, TypeElement autoBuilderType) {
Optional<AnnotationMirror> kotlinMetadata = kotlinMetadataAnnotation(ofClass);
Optional<AnnotationMirror> kotlinMetadataAnnotation =
kotlinMetadata.kotlinMetadataAnnotation(ofClass);
List<? extends Element> elements = ofClass.getEnclosedElements();
Stream<Executable> relevantExecutables =
callMethod.isEmpty()
? kotlinMetadata
.map(a -> kotlinConstructorsIn(a, ofClass).stream())
? kotlinMetadataAnnotation
.map(a -> kotlinMetadata.kotlinConstructorsIn(a, ofClass).stream())
.orElseGet(() -> constructorsIn(elements).stream().map(Executable::of))
: methodsIn(elements).stream()
.filter(m -> m.getSimpleName().contentEquals(callMethod))
Expand Down Expand Up @@ -582,91 +576,6 @@ private boolean visibleFrom(Element element, PackageElement fromPackage) {
}
}

private Optional<AnnotationMirror> 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))
.<AnnotationMirror>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<Executable> kotlinConstructorsIn(
AnnotationMirror metadata, TypeElement ofClass) {
ImmutableMap<String, AnnotationValue> 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<ImmutableSet<String>, ExecutableElement> map =
constructorsIn(ofClass.getEnclosedElements()).stream()
.collect(toMap(c -> parameterNames(c), c -> c, (a, b) -> a, LinkedHashMap::new));
ImmutableMap<ImmutableSet<String>, 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.readStrict(header);
KmClass kmClass = classMetadata.getKmClass();
ImmutableList.Builder<Executable> kotlinConstructorsBuilder = ImmutableList.builder();
for (KmConstructor constructor : kmClass.getConstructors()) {
ImmutableSet.Builder<String> allBuilder = ImmutableSet.builder();
ImmutableSet.Builder<String> optionalBuilder = ImmutableSet.builder();
for (KmValueParameter param : constructor.getValueParameters()) {
String name = param.getName();
allBuilder.add(name);
if (Attributes.getDeclaresDefaultValue(param)) {
optionalBuilder.add(name);
}
}
ImmutableSet<String> optional = optionalBuilder.build();
ImmutableSet<String> 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<AnnotationValue> list = (List<AnnotationValue>) value.getValue();
return list.stream().mapToInt(v -> (int) v.getValue()).toArray();
}

private static String[] stringArrayValue(AnnotationValue value) {
@SuppressWarnings("unchecked")
List<AnnotationValue> list = (List<AnnotationValue>) value.getValue();
return list.stream().map(AnnotationValue::getValue).toArray(String[]::new);
}

private static ImmutableSet<String> 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() {
Expand Down
Loading

0 comments on commit 260b61e

Please sign in to comment.