This is an automated email from the ASF dual-hosted git repository.
yhu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push:
new 08b480000ec add generics support to AutoValueUtils helpers (#32977)
08b480000ec is described below
commit 08b480000ec859292d0f7bbadafb72328d3e9e16
Author: Maciej Szwaja <[email protected]>
AuthorDate: Mon Oct 13 17:19:27 2025 +0200
add generics support to AutoValueUtils helpers (#32977)
* add generics support to AutoValueUtils helpers
* walk autovalue class hierarchy when finding builder
---
.../apache/beam/sdk/schemas/AutoValueSchema.java | 2 +-
.../beam/sdk/schemas/utils/AutoValueUtils.java | 101 ++++++++-----
.../org/apache/beam/sdk/values/TypeDescriptor.java | 5 +
.../beam/sdk/schemas/utils/AutoValueUtilsTest.java | 166 +++++++++++++++++++++
4 files changed, 238 insertions(+), 36 deletions(-)
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/AutoValueSchema.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/AutoValueSchema.java
index f35782c2b9a..7016242299a 100644
---
a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/AutoValueSchema.java
+++
b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/AutoValueSchema.java
@@ -123,7 +123,7 @@ public class AutoValueSchema extends
GetterBasedSchemaProviderV2 {
// SchemaTypeCreator for creating AutoValue objects.
SchemaUserTypeCreator creatorFactory =
AutoValueUtils.getBuilderCreator(
- targetTypeDescriptor.getRawType(), schema,
AbstractGetterTypeSupplier.INSTANCE);
+ targetTypeDescriptor, schema, AbstractGetterTypeSupplier.INSTANCE);
if (creatorFactory != null) {
return creatorFactory;
}
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
index 300dce61e2e..7bff2450b85 100644
---
a/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
+++
b/sdks/java/core/src/main/java/org/apache/beam/sdk/schemas/utils/AutoValueUtils.java
@@ -25,7 +25,9 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -70,45 +72,71 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/** Utilities for managing AutoValue schemas. */
@SuppressWarnings({"rawtypes"})
public class AutoValueUtils {
+
+ private static final String AUTO_VALUE_GENERATED_PREFIX = "AutoValue_";
+
+ /**
+ * Walk the class hierarchy upwards and find the topmost {@link
TypeDescriptor} whose super class
+ * is not generated (whose class name doesn't contain the {@code AutoValue_}
prefix).
+ */
+ private static TypeDescriptor<?>
findFirstGeneratedAutoValue(TypeDescriptor<?> typeDescriptor) {
+ Class<?> rawType = typeDescriptor.getRawType();
+ for (Class superClass = rawType.getSuperclass();
+ superClass != null &&
superClass.getName().contains(AUTO_VALUE_GENERATED_PREFIX);
+ superClass = superClass.getSuperclass()) {
+ rawType = superClass;
+ }
+ return typeDescriptor.getSupertype((Class) rawType);
+ }
+
+ @SuppressWarnings("unchecked")
public static @Nullable TypeDescriptor<?> getBaseAutoValueClass(
TypeDescriptor<?> typeDescriptor) {
- // AutoValue extensions may be nested
- @Nullable TypeDescriptor<?> baseTypeDescriptor = typeDescriptor;
- while (baseTypeDescriptor != null
- && baseTypeDescriptor.getRawType().getName().contains("AutoValue_")) {
- baseTypeDescriptor =
- Optional.ofNullable(baseTypeDescriptor.getRawType().getSuperclass())
- .map(TypeDescriptor::of)
- .orElse(null);
+ if
(!typeDescriptor.getRawType().getName().contains(AUTO_VALUE_GENERATED_PREFIX)) {
+ // fast path for types which aren't autogenerated
+ return typeDescriptor;
}
- return baseTypeDescriptor;
+ // AutoValue extensions may be nested
+ TypeDescriptor<?> firstGeneratedTypeDescriptor =
findFirstGeneratedAutoValue(typeDescriptor);
+ return
Optional.ofNullable(firstGeneratedTypeDescriptor.getRawType().getSuperclass())
+ .map(superClass -> firstGeneratedTypeDescriptor.getSupertype((Class)
superClass))
+ .orElse(null);
}
- private static TypeDescriptor<?> getAutoValueGenerated(TypeDescriptor<?>
typeDescriptor) {
+ @SuppressWarnings("unchecked")
+ public static TypeDescriptor<?> getAutoValueGenerated(TypeDescriptor<?>
typeDescriptor) {
String generatedClassName =
getAutoValueGeneratedName(typeDescriptor.getRawType().getName());
try {
- return TypeDescriptor.of(Class.forName(generatedClassName));
+ return typeDescriptor.getSubtype((Class)
Class.forName(generatedClassName));
} catch (ClassNotFoundException e) {
throw new IllegalStateException("AutoValue generated class not found: "
+ generatedClassName);
}
}
- private static @Nullable Class getAutoValueGeneratedBuilder(Class<?> clazz) {
- Class generated;
- try {
- generated = Class.forName(getAutoValueGeneratedName(clazz.getName()));
- } catch (ClassNotFoundException e) {
- return null;
- }
- // Find the first generated class
- Class base = generated;
- while (base != null && base.getName().contains("AutoValue_")) {
- generated = base;
- base = base.getSuperclass();
- }
- String builderName = generated.getName() + "$Builder";
+ public static @Nullable TypeDescriptor<?> getAutoValueGeneratedBuilder(
+ TypeDescriptor<?> typeDescriptor) {
+ TypeDescriptor generated = getAutoValueGenerated(typeDescriptor);
+ TypeDescriptor firstGenerated = findFirstGeneratedAutoValue(generated);
+ String builderName = firstGenerated.getRawType().getName() + "$Builder";
try {
- return Class.forName(builderName);
+ Class builderClass = Class.forName(builderName);
+ Type genericSuperClass = builderClass.getGenericSuperclass();
+ if (builderClass.getTypeParameters().length != 0 && genericSuperClass !=
null) {
+ // we need to get hold of a parameterized type version of the builder
class - here's one way
+ // of doing it:
+ TypeDescriptor resolved =
TypeDescriptor.of(genericSuperClass).getSubtype(builderClass);
+ for (int i = 0; i < builderClass.getTypeParameters().length; i++) {
+ TypeVariable typeVariable = builderClass.getTypeParameters()[i];
+ Type actualType =
+ ((ParameterizedType)
typeDescriptor.getType()).getActualTypeArguments()[i];
+ // Autovalue's builder's type variables correspond 1:1 to their
enclosing class' signature
+ // even to the point of having the same name, let's blindly unify
them
+ resolved = resolved.where(typeVariable, actualType);
+ }
+ return resolved;
+ } else {
+ return TypeDescriptor.of(builderClass);
+ }
} catch (ClassNotFoundException e) {
return null;
}
@@ -199,23 +227,25 @@ public class AutoValueUtils {
* Try to find an accessible builder class for creating an AutoValue class.
Otherwise return null.
*/
public static @Nullable SchemaUserTypeCreator getBuilderCreator(
- Class<?> clazz, Schema schema, FieldValueTypeSupplier
fieldValueTypeSupplier) {
- Class<?> builderClass = getAutoValueGeneratedBuilder(clazz);
- if (builderClass == null) {
+ TypeDescriptor<?> typeDescriptor,
+ Schema schema,
+ FieldValueTypeSupplier fieldValueTypeSupplier) {
+ TypeDescriptor<?> builderTypeDescriptor =
getAutoValueGeneratedBuilder(typeDescriptor);
+ if (builderTypeDescriptor == null) {
return null;
}
Map<String, FieldValueTypeInformation> setterTypes = new HashMap<>();
- ReflectUtils.getMethods(builderClass).stream()
+ ReflectUtils.getMethods(builderTypeDescriptor.getRawType()).stream()
.filter(ReflectUtils::isSetter)
- .map(m ->
FieldValueTypeInformation.forSetter(TypeDescriptor.of(builderClass), m))
+ .map(m -> FieldValueTypeInformation.forSetter(builderTypeDescriptor,
m))
.forEach(fv -> setterTypes.putIfAbsent(fv.getName(), fv));
List<FieldValueTypeInformation> setterMethods =
Lists.newArrayList(); // The builder methods to call in order.
List<FieldValueTypeInformation> schemaTypes =
- fieldValueTypeSupplier.get(TypeDescriptor.of(clazz), schema);
+ fieldValueTypeSupplier.get(typeDescriptor, schema);
for (FieldValueTypeInformation type : schemaTypes) {
String autoValueFieldName =
ReflectUtils.stripGetterPrefix(
@@ -227,7 +257,7 @@ public class AutoValueUtils {
if (setterType == null) {
throw new RuntimeException(
"AutoValue builder class "
- + builderClass
+ + builderTypeDescriptor
+ " did not contain "
+ "a setter for "
+ autoValueFieldName);
@@ -236,11 +266,12 @@ public class AutoValueUtils {
}
Method buildMethod =
- ReflectUtils.getMethods(builderClass).stream()
+ ReflectUtils.getMethods(builderTypeDescriptor.getRawType()).stream()
.filter(m -> m.getName().equals("build"))
.findAny()
.orElseThrow(() -> new RuntimeException("No build method in
builder"));
- return createBuilderCreator(builderClass, setterMethods, buildMethod,
schema, schemaTypes);
+ return createBuilderCreator(
+ builderTypeDescriptor.getRawType(), setterMethods, buildMethod,
schema, schemaTypes);
}
private static final ByteBuddy BYTE_BUDDY = new ByteBuddy();
diff --git
a/sdks/java/core/src/main/java/org/apache/beam/sdk/values/TypeDescriptor.java
b/sdks/java/core/src/main/java/org/apache/beam/sdk/values/TypeDescriptor.java
index 045662d1680..b0197d1a728 100644
---
a/sdks/java/core/src/main/java/org/apache/beam/sdk/values/TypeDescriptor.java
+++
b/sdks/java/core/src/main/java/org/apache/beam/sdk/values/TypeDescriptor.java
@@ -190,6 +190,11 @@ public abstract class TypeDescriptor<T> implements
Serializable {
return new SimpleTypeDescriptor<>(token.getSupertype(superclass));
}
+ /** Returns the generic form of a subtype. */
+ public final TypeDescriptor<? extends T> getSubtype(Class<? extends T>
subclass) {
+ return new SimpleTypeDescriptor<>(token.getSubtype(subclass));
+ }
+
/** Returns true if this type is known to be an array type. */
public final boolean isArray() {
return token.isArray();
diff --git
a/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/AutoValueUtilsTest.java
b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/AutoValueUtilsTest.java
new file mode 100644
index 00000000000..8a7c17173e2
--- /dev/null
+++
b/sdks/java/core/src/test/java/org/apache/beam/sdk/schemas/utils/AutoValueUtilsTest.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.beam.sdk.schemas.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import java.util.Map;
+import org.apache.beam.sdk.values.TypeDescriptor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutoValueUtilsTest {
+
+ @AutoValue
+ public abstract static class SimpleAutoValue {
+ public abstract String getStr();
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder setStr(String value);
+
+ public abstract SimpleAutoValue build();
+ }
+ }
+
+ @AutoValue
+ public abstract static class GenericAutoValue<T, NumberT extends Number> {
+ public abstract T getT();
+
+ public abstract NumberT getN();
+
+ @AutoValue.Builder
+ public abstract static class Builder<T, NumberT extends Number> {
+ public abstract Builder<T, NumberT> setT(T value);
+
+ public abstract Builder<T, NumberT> setN(NumberT value);
+
+ public abstract GenericAutoValue<T, NumberT> build();
+ }
+ }
+
+ @AutoValue
+ public abstract static class GenericAutoValueMemoized<T> {
+ public abstract T getT();
+
+ @Memoized
+ public String getTString() {
+ return getT().toString() + "Memoized";
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder<T> {
+ public abstract Builder<T> setT(T t);
+
+ public abstract GenericAutoValueMemoized<T> build();
+ }
+ }
+
+ @Test
+ public void testGetBaseAutoValueGenericMemoized() throws Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getBaseAutoValueClass(
+ new TypeDescriptor<
+
AutoValue_AutoValueUtilsTest_GenericAutoValueMemoized<Map<String, String>>>()
{});
+
+ assertEquals(new TypeDescriptor<GenericAutoValueMemoized<Map<String,
String>>>() {}, actual);
+ }
+
+ @Test
+ public void testGetAutoValueGeneratedGenericMemoized() throws Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getAutoValueGenerated(
+ new TypeDescriptor<GenericAutoValueMemoized<Map<String,
String>>>() {});
+ assertEquals(
+ new TypeDescriptor<
+ AutoValue_AutoValueUtilsTest_GenericAutoValueMemoized<Map<String,
String>>>() {},
+ actual);
+ }
+
+ @Test
+ public void testGetAutoValueGeneratedBuilderGenericMemoized() throws
Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getAutoValueGeneratedBuilder(
+ new TypeDescriptor<GenericAutoValueMemoized<Map<String,
String>>>() {});
+ assertEquals(
+ new TypeDescriptor<
+ AutoValue_AutoValueUtilsTest_GenericAutoValueMemoized.Builder<
+ Map<String, String>>>() {},
+ actual);
+ }
+
+ @Test
+ public void testGetBaseAutoValueClass() throws Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getBaseAutoValueClass(
+
TypeDescriptor.of(AutoValue_AutoValueUtilsTest_SimpleAutoValue.class));
+
+ assertEquals(TypeDescriptor.of(SimpleAutoValue.class), actual);
+ }
+
+ @Test
+ public void testGetBaseAutoValueClassGeneric() throws Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getBaseAutoValueClass(
+ new TypeDescriptor<
+ AutoValue_AutoValueUtilsTest_GenericAutoValue<String,
Integer>>() {});
+
+ assertEquals(new TypeDescriptor<GenericAutoValue<String, Integer>>() {},
actual);
+ }
+
+ @Test
+ public void testGetAutoValueGenerated() throws Exception {
+ TypeDescriptor<?> actual =
+
AutoValueUtils.getAutoValueGenerated(TypeDescriptor.of(SimpleAutoValue.class));
+
assertEquals(TypeDescriptor.of(AutoValue_AutoValueUtilsTest_SimpleAutoValue.class),
actual);
+ }
+
+ @Test
+ public void testGetAutoValueGeneratedGeneric() throws Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getAutoValueGenerated(
+ new TypeDescriptor<GenericAutoValue<String, Integer>>() {});
+ assertEquals(
+ new
TypeDescriptor<AutoValue_AutoValueUtilsTest_GenericAutoValue<String,
Integer>>() {},
+ actual);
+ }
+
+ @Test
+ public void testGetAutoValueGeneratedBuilder() throws Exception {
+ TypeDescriptor<?> actual =
+
AutoValueUtils.getAutoValueGeneratedBuilder(TypeDescriptor.of(SimpleAutoValue.class));
+ assertEquals(
+
TypeDescriptor.of(AutoValue_AutoValueUtilsTest_SimpleAutoValue.Builder.class),
actual);
+ }
+
+ @Test
+ public void testGetAutoValueGeneratedBuilderGeneric() throws Exception {
+ TypeDescriptor<?> actual =
+ AutoValueUtils.getAutoValueGeneratedBuilder(
+ new TypeDescriptor<GenericAutoValue<Map<String, String>,
Integer>>() {});
+ assertEquals(
+ new TypeDescriptor<
+ AutoValue_AutoValueUtilsTest_GenericAutoValue.Builder<
+ Map<String, String>, Integer>>() {},
+ actual);
+ }
+}