Skip to content

Commit d699e28

Browse files
l46kokcopybara-github
authored andcommitted
Add CombinedCelValueConverter
PiperOrigin-RevId: 904770100
1 parent cd71e40 commit d699e28

8 files changed

Lines changed: 298 additions & 49 deletions

File tree

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

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@ cel_android_library(
7878

7979
java_library(
8080
name = "combined_cel_value_provider",
81-
srcs = ["CombinedCelValueProvider.java"],
81+
srcs = [
82+
"CombinedCelValueProvider.java",
83+
],
8284
tags = [
8385
],
8486
deps = [
87+
":combined_cel_value_converter",
88+
":values",
8589
"//common/values:cel_value_provider",
8690
"@maven//:com_google_errorprone_error_prone_annotations",
8791
"@maven//:com_google_guava_guava",
@@ -90,16 +94,52 @@ java_library(
9094

9195
cel_android_library(
9296
name = "combined_cel_value_provider_android",
93-
srcs = ["CombinedCelValueProvider.java"],
97+
srcs = [
98+
"CombinedCelValueProvider.java",
99+
],
94100
tags = [
95101
],
96102
deps = [
103+
":combined_cel_value_converter_android",
104+
":values_android",
97105
"//common/values:cel_value_provider_android",
98106
"@maven//:com_google_errorprone_error_prone_annotations",
99107
"@maven_android//:com_google_guava_guava",
100108
],
101109
)
102110

111+
java_library(
112+
name = "combined_cel_value_converter",
113+
srcs = [
114+
"CombinedCelValueConverter.java",
115+
],
116+
tags = [
117+
],
118+
deps = [
119+
":values",
120+
"//common/annotations",
121+
"@maven//:com_google_errorprone_error_prone_annotations",
122+
"@maven//:com_google_guava_guava",
123+
"@maven//:org_jspecify_jspecify",
124+
],
125+
)
126+
127+
cel_android_library(
128+
name = "combined_cel_value_converter_android",
129+
srcs = [
130+
"CombinedCelValueConverter.java",
131+
],
132+
tags = [
133+
],
134+
deps = [
135+
":values_android",
136+
"//common/annotations",
137+
"@maven//:com_google_errorprone_error_prone_annotations",
138+
"@maven//:org_jspecify_jspecify",
139+
"@maven_android//:com_google_guava_guava",
140+
],
141+
)
142+
103143
java_library(
104144
name = "values",
105145
srcs = CEL_VALUES_SOURCES,

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

Lines changed: 32 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import dev.cel.common.annotations.Internal;
2222
import java.util.Collection;
2323
import java.util.Map;
24-
import java.util.Map.Entry;
2524
import java.util.Optional;
25+
import java.util.function.Function;
2626

2727
/**
2828
* {@code CelValueConverter} handles bidirectional conversion between native Java objects to {@link
@@ -37,6 +37,12 @@ public class CelValueConverter {
3737

3838
private static final CelValueConverter DEFAULT_INSTANCE = new CelValueConverter();
3939

40+
@SuppressWarnings("Immutable") // Method reference is immutable
41+
private final Function<Object, Object> maybeUnwrapFunction;
42+
43+
@SuppressWarnings("Immutable") // Method reference is immutable
44+
private final Function<Object, Object> toRuntimeValueFunction;
45+
4046
public static CelValueConverter getDefaultInstance() {
4147
return DEFAULT_INSTANCE;
4248
}
@@ -51,14 +57,26 @@ public Object maybeUnwrap(Object value) {
5157
return unwrap((CelValue) value);
5258
}
5359

60+
Object mapped = mapContainer(value, maybeUnwrapFunction);
61+
if (mapped != value) {
62+
return mapped;
63+
}
64+
65+
return value;
66+
}
67+
68+
/**
69+
* Maps a container (Collection or Map) by applying the provided mapper function to its elements.
70+
* Returns the original value if it's not a supported container.
71+
*/
72+
protected Object mapContainer(Object value, Function<Object, Object> mapper) {
5473
if (value instanceof Collection) {
5574
Collection<Object> collection = (Collection<Object>) value;
5675
ImmutableList.Builder<Object> builder =
5776
ImmutableList.builderWithExpectedSize(collection.size());
5877
for (Object element : collection) {
59-
builder.add(maybeUnwrap(element));
78+
builder.add(mapper.apply(element));
6079
}
61-
6280
return builder.build();
6381
}
6482

@@ -67,34 +85,30 @@ public Object maybeUnwrap(Object value) {
6785
ImmutableMap.Builder<Object, Object> builder =
6886
ImmutableMap.builderWithExpectedSize(map.size());
6987
for (Map.Entry<Object, Object> entry : map.entrySet()) {
70-
builder.put(maybeUnwrap(entry.getKey()), maybeUnwrap(entry.getValue()));
88+
builder.put(mapper.apply(entry.getKey()), mapper.apply(entry.getValue()));
7189
}
72-
7390
return builder.buildOrThrow();
7491
}
7592

7693
return value;
7794
}
7895

79-
/**
80-
* Canonicalizes an inbound {@code value} into a suitable Java object representation for
81-
* evaluation.
82-
*/
8396
public Object toRuntimeValue(Object value) {
8497
Preconditions.checkNotNull(value);
8598

8699
if (value instanceof CelValue) {
87100
return value;
88101
}
89102

90-
if (value instanceof Collection) {
91-
return toListValue((Collection<Object>) value);
92-
} else if (value instanceof Map) {
93-
return toMapValue((Map<Object, Object>) value);
94-
} else if (value instanceof Optional) {
103+
Object mapped = mapContainer(value, toRuntimeValueFunction);
104+
if (mapped != value) {
105+
return mapped;
106+
}
107+
108+
if (value instanceof Optional) {
95109
Optional<Object> optionalValue = (Optional<Object>) value;
96110
return optionalValue
97-
.map(this::toRuntimeValue)
111+
.map(toRuntimeValueFunction)
98112
.map(OptionalValue::create)
99113
.orElse(OptionalValue.EMPTY);
100114
}
@@ -136,31 +150,8 @@ private Object unwrap(CelValue celValue) {
136150
return celValue.value();
137151
}
138152

139-
private ImmutableList<Object> toListValue(Collection<Object> iterable) {
140-
Preconditions.checkNotNull(iterable);
141-
142-
ImmutableList.Builder<Object> listBuilder =
143-
ImmutableList.builderWithExpectedSize(iterable.size());
144-
for (Object entry : iterable) {
145-
listBuilder.add(toRuntimeValue(entry));
146-
}
147-
148-
return listBuilder.build();
149-
}
150-
151-
private ImmutableMap<Object, Object> toMapValue(Map<Object, Object> map) {
152-
Preconditions.checkNotNull(map);
153-
154-
ImmutableMap.Builder<Object, Object> mapBuilder =
155-
ImmutableMap.builderWithExpectedSize(map.size());
156-
for (Entry<Object, Object> entry : map.entrySet()) {
157-
Object mapKey = toRuntimeValue(entry.getKey());
158-
Object mapValue = toRuntimeValue(entry.getValue());
159-
mapBuilder.put(mapKey, mapValue);
160-
}
161-
162-
return mapBuilder.buildOrThrow();
153+
protected CelValueConverter() {
154+
this.maybeUnwrapFunction = this::maybeUnwrap;
155+
this.toRuntimeValueFunction = this::toRuntimeValue;
163156
}
164-
165-
protected CelValueConverter() {}
166157
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.common.values;
16+
17+
import static com.google.common.base.Preconditions.checkNotNull;
18+
19+
import com.google.common.collect.ImmutableList;
20+
import dev.cel.common.annotations.Internal;
21+
import org.jspecify.annotations.Nullable;
22+
23+
/**
24+
* {@code CombinedCelValueConverter} delegates value conversion to a list of underlying {@link
25+
* CelValueConverter}s.
26+
*/
27+
@Internal
28+
public final class CombinedCelValueConverter extends CelValueConverter {
29+
private final ImmutableList<CelValueConverter> converters;
30+
31+
public static CombinedCelValueConverter combine(ImmutableList<CelValueConverter> converters) {
32+
return new CombinedCelValueConverter(converters);
33+
}
34+
35+
private CombinedCelValueConverter(ImmutableList<CelValueConverter> converters) {
36+
this.converters = checkNotNull(converters);
37+
}
38+
39+
@Override
40+
public @Nullable Object toRuntimeValue(Object value) {
41+
if (value == null) {
42+
return null;
43+
}
44+
45+
// Let the base class handle CelValues, Optionals, Collections, Maps, and primitives.
46+
Object baseResult = super.toRuntimeValue(value);
47+
if (baseResult != value) {
48+
return baseResult;
49+
}
50+
51+
// If the base class left the object unchanged (e.g. a raw POJO), try the delegates.
52+
for (CelValueConverter converter : converters) {
53+
Object result = converter.toRuntimeValue(value);
54+
if (result != value) {
55+
return result;
56+
}
57+
}
58+
59+
return value;
60+
}
61+
62+
@Override
63+
public @Nullable Object maybeUnwrap(Object value) {
64+
if (value == null) {
65+
return null;
66+
}
67+
68+
// Let the base class handle standard unwrapping and container unrolling.
69+
Object baseResult = super.maybeUnwrap(value);
70+
if (baseResult != value) {
71+
return baseResult;
72+
}
73+
74+
// Try delegates for specialized unwrapping.
75+
for (CelValueConverter converter : converters) {
76+
Object result = converter.maybeUnwrap(value);
77+
if (result != value) {
78+
return result;
79+
}
80+
}
81+
82+
return value;
83+
}
84+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.common.base.Preconditions.checkArgument;
1818
import static com.google.common.base.Preconditions.checkNotNull;
19+
import static com.google.common.collect.ImmutableList.toImmutableList;
1920

2021
import com.google.common.collect.ImmutableList;
2122
import com.google.errorprone.annotations.Immutable;
@@ -49,6 +50,14 @@ public Optional<Object> newValue(String structType, Map<String, Object> fields)
4950
return Optional.empty();
5051
}
5152

53+
@Override
54+
public CelValueConverter celValueConverter() {
55+
return CombinedCelValueConverter.combine(
56+
celValueProviders.stream()
57+
.map(CelValueProvider::celValueConverter)
58+
.collect(toImmutableList()));
59+
}
60+
5261
/** Returns the underlying {@link CelValueProvider}s in order. */
5362
public ImmutableList<CelValueProvider> valueProviders() {
5463
return celValueProviders;

common/src/test/java/dev/cel/common/values/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ java_library(
2424
"//common/values",
2525
"//common/values:cel_byte_string",
2626
"//common/values:cel_value_provider",
27+
"//common/values:combined_cel_value_converter",
2728
"//common/values:combined_cel_value_provider",
2829
"//common/values:proto_message_lite_value",
2930
"//common/values:proto_message_lite_value_provider",

0 commit comments

Comments
 (0)