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);
+  }
+}

Reply via email to