Skip to content

Commit f03660e

Browse files
l46kokcopybara-github
authored andcommitted
Avoid container copy when there's no value adaptations needed
PiperOrigin-RevId: 905288441
1 parent f566450 commit f03660e

13 files changed

Lines changed: 2176 additions & 22 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
}

common/src/main/java/dev/cel/common/values/CelValueConverter.java

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@
2020
import com.google.errorprone.annotations.Immutable;
2121
import dev.cel.common.annotations.Internal;
2222
import java.util.Collection;
23+
import java.util.Iterator;
24+
import java.util.List;
2325
import java.util.Map;
26+
import java.util.Map.Entry;
2427
import java.util.Optional;
28+
import java.util.RandomAccess;
2529
import java.util.function.Function;
2630

2731
/**
@@ -57,19 +61,37 @@ public Object maybeUnwrap(Object value) {
5761
return unwrap((CelValue) value);
5862
}
5963

60-
Object mapped = mapContainer(value, maybeUnwrapFunction);
61-
if (mapped != value) {
62-
return mapped;
63-
}
64-
65-
return value;
64+
return mapContainer(value, maybeUnwrapFunction);
6665
}
6766

6867
/**
6968
* Maps a container (Collection or Map) by applying the provided mapper function to its elements.
7069
* Returns the original value if it's not a supported container.
7170
*/
7271
protected Object mapContainer(Object value, Function<Object, Object> mapper) {
72+
if (value instanceof List && value instanceof RandomAccess) {
73+
List<Object> list = (List<Object>) value;
74+
for (int i = 0; i < list.size(); i++) {
75+
Object element = list.get(i);
76+
Object mapped = mapper.apply(element);
77+
78+
if (mapped != element) {
79+
ImmutableList.Builder<Object> builder =
80+
ImmutableList.builderWithExpectedSize(list.size());
81+
for (int j = 0; j < i; j++) {
82+
builder.add(list.get(j));
83+
}
84+
builder.add(mapped);
85+
for (int j = i + 1; j < list.size(); j++) {
86+
builder.add(mapper.apply(list.get(j)));
87+
}
88+
return builder.build();
89+
}
90+
}
91+
return value;
92+
}
93+
94+
// Fallback for cases where the collection is unordered, or random access is impossible.
7395
if (value instanceof Collection) {
7496
Collection<Object> collection = (Collection<Object>) value;
7597
ImmutableList.Builder<Object> builder =
@@ -82,12 +104,34 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {
82104

83105
if (value instanceof Map) {
84106
Map<Object, Object> map = (Map<Object, Object>) value;
85-
ImmutableMap.Builder<Object, Object> builder =
86-
ImmutableMap.builderWithExpectedSize(map.size());
87-
for (Map.Entry<Object, Object> entry : map.entrySet()) {
88-
builder.put(mapper.apply(entry.getKey()), mapper.apply(entry.getValue()));
107+
Iterator<Entry<Object, Object>> iterator = map.entrySet().iterator();
108+
109+
while (iterator.hasNext()) {
110+
Map.Entry<Object, Object> entry = iterator.next();
111+
Object mappedKey = mapper.apply(entry.getKey());
112+
Object mappedValue = mapper.apply(entry.getValue());
113+
114+
if (mappedKey != entry.getKey() || mappedValue != entry.getValue()) {
115+
ImmutableMap.Builder<Object, Object> builder =
116+
ImmutableMap.builderWithExpectedSize(map.size());
117+
118+
for (Map.Entry<Object, Object> prevEntry : map.entrySet()) {
119+
if (prevEntry.getKey() == entry.getKey()) {
120+
break;
121+
}
122+
builder.put(mapper.apply(prevEntry.getKey()), mapper.apply(prevEntry.getValue()));
123+
}
124+
125+
builder.put(mappedKey, mappedValue);
126+
127+
while (iterator.hasNext()) {
128+
Map.Entry<Object, Object> nextEntry = iterator.next();
129+
builder.put(mapper.apply(nextEntry.getKey()), mapper.apply(nextEntry.getValue()));
130+
}
131+
return builder.buildOrThrow();
132+
}
89133
}
90-
return builder.buildOrThrow();
134+
return value;
91135
}
92136

93137
return value;

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)