Skip to content

Commit d8d12c7

Browse files
authored
[runners-spark] Use robust constructor resolution in EncoderFactory (#38271)
* [runners-spark] Use robust constructor resolution in EncoderFactory Replace `(Constructor<X>) X.class.getConstructors()[0]` for StaticInvoke, Invoke, and NewInstance with a `primaryConstructor()` helper that picks the constructor with the most parameters. Class.getConstructors() returns constructors in JVM-defined order that is not guaranteed stable, so resolving the widest constructor explicitly makes the lookup robust to future Spark releases that add overloaded constructors. Today this is a no-op: StaticInvoke / Invoke / NewInstance only have one public constructor each in Spark 3.1.x through 3.5.x, so getConstructors()[0] and the widest constructor resolve to the same one. The change is purely defensive. Same fix has already landed in the Spark 4 override in PR #38255 (commit 9c071c5, flagged by Gemini Code Assist round 1). Porting it to the shared base keeps both code paths consistent. * Address Gemini review: guard primaryConstructor against empty ctors If Class.getConstructors() returns empty (e.g. all public constructors removed in a future Spark version), throw an IllegalStateException with the class name instead of letting ctors[0] surface as a cryptic ArrayIndexOutOfBoundsException during static initialization.
1 parent 0884c46 commit d8d12c7

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

  • runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers

runners/spark/src/main/java/org/apache/beam/runners/spark/structuredstreaming/translation/helpers/EncoderFactory.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,32 @@
3131
import scala.reflect.ClassTag;
3232

3333
public class EncoderFactory {
34-
// default constructor to reflectively create static invoke expressions
34+
// Resolve the Scala case-class primary constructor (the one with the most parameters).
35+
// Constructor ordering returned by Class.getConstructors() is JVM-defined and not stable
36+
// across Spark versions, so we pick the widest constructor explicitly and then dispatch on
37+
// parameter count below to pick the right argument shape per Spark version.
3538
private static final Constructor<StaticInvoke> STATIC_INVOKE_CONSTRUCTOR =
36-
(Constructor<StaticInvoke>) StaticInvoke.class.getConstructors()[0];
39+
primaryConstructor(StaticInvoke.class);
3740

38-
private static final Constructor<Invoke> INVOKE_CONSTRUCTOR =
39-
(Constructor<Invoke>) Invoke.class.getConstructors()[0];
41+
private static final Constructor<Invoke> INVOKE_CONSTRUCTOR = primaryConstructor(Invoke.class);
4042

4143
private static final Constructor<NewInstance> NEW_INSTANCE_CONSTRUCTOR =
42-
(Constructor<NewInstance>) NewInstance.class.getConstructors()[0];
44+
primaryConstructor(NewInstance.class);
45+
46+
@SuppressWarnings("unchecked")
47+
private static <T> Constructor<T> primaryConstructor(Class<T> cls) {
48+
Constructor<?>[] ctors = cls.getConstructors();
49+
if (ctors.length == 0) {
50+
throw new IllegalStateException("No public constructors found for " + cls.getName());
51+
}
52+
Constructor<?> widest = ctors[0];
53+
for (int i = 1; i < ctors.length; i++) {
54+
if (ctors[i].getParameterCount() > widest.getParameterCount()) {
55+
widest = ctors[i];
56+
}
57+
}
58+
return (Constructor<T>) widest;
59+
}
4360

4461
static <T> ExpressionEncoder<T> create(
4562
Expression serializer, Expression deserializer, Class<? super T> clazz) {

0 commit comments

Comments
 (0)