Skip to content

Commit 7cd3a97

Browse files
l46kokcopybara-github
authored andcommitted
Implement Native Extensions
Future iterations may introduce an annotation based field mapping (ex: `@CelName('foo')`. PiperOrigin-RevId: 904571869
1 parent f566450 commit 7cd3a97

12 files changed

Lines changed: 2119 additions & 11 deletions

File tree

common/internal/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,8 @@ cel_android_library(
147147
name = "date_time_helpers_android",
148148
exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"],
149149
)
150+
151+
java_library(
152+
name = "reflection_util",
153+
exports = ["//common/src/main/java/dev/cel/common/internal:reflection_util"],
154+
)

common/src/main/java/dev/cel/common/internal/BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,12 @@ java_library(
398398
java_library(
399399
name = "reflection_util",
400400
srcs = ["ReflectionUtil.java"],
401+
tags = [
402+
"alt_dep=//common/internal:reflection_util",
403+
],
401404
deps = [
402405
"//common/annotations",
406+
"@maven//:com_google_guava_guava",
403407
],
404408
)
405409

common/src/main/java/dev/cel/common/internal/ReflectionUtil.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414

1515
package dev.cel.common.internal;
1616

17+
import com.google.common.reflect.TypeToken;
1718
import dev.cel.common.annotations.Internal;
1819
import java.lang.reflect.InvocationTargetException;
1920
import java.lang.reflect.Method;
21+
import java.lang.reflect.ParameterizedType;
22+
import java.lang.reflect.Type;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Optional;
2026

2127
/**
2228
* Utility class for invoking Java reflection.
@@ -48,5 +54,48 @@ public static Object invoke(Method method, Object object, Object... params) {
4854
}
4955
}
5056

57+
/**
58+
* Extracts the element type of a container type (List, Map, or Optional). Returns the type itself
59+
* if it's not a container or if generic type info is missing.
60+
*/
61+
public static Class<?> getElementType(Class<?> type, Type genericType) {
62+
TypeToken<?> token = TypeToken.of(genericType);
63+
64+
if (List.class.isAssignableFrom(type)) {
65+
return token.resolveType(List.class.getTypeParameters()[0]).getRawType();
66+
}
67+
if (Map.class.isAssignableFrom(type)) {
68+
return token.resolveType(Map.class.getTypeParameters()[1]).getRawType();
69+
}
70+
if (type == Optional.class) {
71+
return token.resolveType(Optional.class.getTypeParameters()[0]).getRawType();
72+
}
73+
74+
return type;
75+
}
76+
77+
/**
78+
* Extracts the raw Class from a Type. Handles Class, ParameterizedType, and WildcardType (returns
79+
* upper bound). Returns Object.class as fallback.
80+
*/
81+
public static Class<?> getRawType(Type type) {
82+
return TypeToken.of(type).getRawType();
83+
}
84+
85+
/**
86+
* Extracts the actual type arguments from a ParameterizedType, if it has at least the expected
87+
* minimum number of arguments. Returns Optional.empty() if the type is not parameterized or has
88+
* fewer arguments than expected.
89+
*/
90+
public static Optional<Type[]> getTypeArguments(Type type, int minArgs) {
91+
if (type instanceof ParameterizedType) {
92+
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
93+
if (args.length >= minArgs) {
94+
return Optional.of(args);
95+
}
96+
}
97+
return Optional.empty();
98+
}
99+
51100
private ReflectionUtil() {}
52101
}

extensions/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,8 @@ java_library(
5656
name = "comprehensions",
5757
exports = ["//extensions/src/main/java/dev/cel/extensions:comprehensions"],
5858
)
59+
60+
java_library(
61+
name = "native",
62+
exports = ["//extensions/src/main/java/dev/cel/extensions:native"],
63+
)

extensions/src/main/java/dev/cel/extensions/BUILD.bazel

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ java_library(
3434
":encoders",
3535
":lists",
3636
":math",
37+
":native",
3738
":optional_library",
3839
":protos",
3940
":regex",
@@ -318,3 +319,26 @@ java_library(
318319
"@maven//:com_google_guava_guava",
319320
],
320321
)
322+
323+
java_library(
324+
name = "native",
325+
srcs = ["CelNativeTypesExtensions.java"],
326+
tags = [
327+
],
328+
deps = [
329+
"//checker:checker_builder",
330+
"//common/exceptions:attribute_not_found",
331+
"//common/internal:reflection_util",
332+
"//common/types",
333+
"//common/types:type_providers",
334+
"//common/values",
335+
"//common/values:cel_byte_string",
336+
"//common/values:cel_value",
337+
"//common/values:cel_value_provider",
338+
"//compiler:compiler_builder",
339+
"//runtime",
340+
"@maven//:com_google_errorprone_error_prone_annotations",
341+
"@maven//:com_google_guava_guava",
342+
"@maven//:org_jspecify_jspecify",
343+
],
344+
)

extensions/src/main/java/dev/cel/extensions/CelExtensions.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
package dev.cel.extensions;
1616

1717
import static com.google.common.collect.ImmutableSet.toImmutableSet;
18-
import static java.util.Arrays.stream;
1918

2019
import com.google.common.collect.ImmutableSet;
2120
import com.google.common.collect.Streams;
2221
import com.google.errorprone.annotations.InlineMe;
2322
import dev.cel.common.CelOptions;
2423
import dev.cel.extensions.CelMathExtensions.Function;
24+
import java.util.EnumSet;
2525
import java.util.Set;
2626

2727
/**
@@ -350,6 +350,18 @@ public static CelComprehensionsExtensions comprehensions() {
350350
return COMPREHENSIONS_EXTENSIONS;
351351
}
352352

353+
/**
354+
* Extensions for supporting native Java types (POJOs) in CEL.
355+
*
356+
* <p>Refer to README.md for details on property discovery, type mapping, and limitations.
357+
*
358+
* <p>Note: Passing classes with unsupported types or anonymous/local classes will result in an
359+
* {@link IllegalArgumentException} when the runtime is built.
360+
*/
361+
public static CelNativeTypesExtensions nativeTypes(Class<?>... classes) {
362+
return CelNativeTypesExtensions.nativeTypes(classes);
363+
}
364+
353365
/**
354366
* Retrieves all function names used by every extension libraries.
355367
*
@@ -359,18 +371,17 @@ public static CelComprehensionsExtensions comprehensions() {
359371
*/
360372
public static ImmutableSet<String> getAllFunctionNames() {
361373
return Streams.concat(
362-
stream(CelMathExtensions.Function.values())
363-
.map(CelMathExtensions.Function::getFunction),
364-
stream(CelStringExtensions.Function.values())
374+
EnumSet.allOf(Function.class).stream().map(CelMathExtensions.Function::getFunction),
375+
EnumSet.allOf(CelStringExtensions.Function.class).stream()
365376
.map(CelStringExtensions.Function::getFunction),
366-
stream(SetsFunction.values()).map(SetsFunction::getFunction),
367-
stream(CelEncoderExtensions.Function.values())
377+
EnumSet.allOf(SetsFunction.class).stream().map(SetsFunction::getFunction),
378+
EnumSet.allOf(CelEncoderExtensions.Function.class).stream()
368379
.map(CelEncoderExtensions.Function::getFunction),
369-
stream(CelListsExtensions.Function.values())
380+
EnumSet.allOf(CelListsExtensions.Function.class).stream()
370381
.map(CelListsExtensions.Function::getFunction),
371-
stream(CelRegexExtensions.Function.values())
382+
EnumSet.allOf(CelRegexExtensions.Function.class).stream()
372383
.map(CelRegexExtensions.Function::getFunction),
373-
stream(CelComprehensionsExtensions.Function.values())
384+
EnumSet.allOf(CelComprehensionsExtensions.Function.class).stream()
374385
.map(CelComprehensionsExtensions.Function::getFunction))
375386
.collect(toImmutableSet());
376387
}

0 commit comments

Comments
 (0)