Skip to content

Commit ce6fe8c

Browse files
l46kokcopybara-github
authored andcommitted
Decouple ProtoCelValueConverter into BaseProtoCelValueConverter
PiperOrigin-RevId: 750802934
1 parent 961fcc9 commit ce6fe8c

File tree

15 files changed

+378
-279
lines changed

15 files changed

+378
-279
lines changed

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ java_library(
7676
":cel_value",
7777
"//:auto_value",
7878
"//common:error_codes",
79-
"//common:options",
8079
"//common:runtime_exception",
8180
"//common/annotations",
8281
"//common/types",
@@ -94,7 +93,25 @@ java_library(
9493
],
9594
deps = [
9695
"@maven//:com_google_errorprone_error_prone_annotations",
96+
],
97+
)
98+
99+
java_library(
100+
name = "base_proto_cel_value_converter",
101+
srcs = ["BaseProtoCelValueConverter.java"],
102+
tags = [
103+
],
104+
deps = [
105+
":cel_byte_string",
106+
":cel_value",
107+
":values",
108+
"//common/annotations",
109+
"//common/internal:well_known_proto",
110+
"@maven//:com_google_errorprone_error_prone_annotations",
97111
"@maven//:com_google_guava_guava",
112+
"@maven//:com_google_protobuf_protobuf_java",
113+
"@maven//:com_google_protobuf_protobuf_java_util",
114+
"@maven_android//:com_google_protobuf_protobuf_javalite",
98115
],
99116
)
100117

@@ -104,22 +121,19 @@ java_library(
104121
tags = [
105122
],
106123
deps = [
124+
":base_proto_cel_value_converter",
107125
":cel_value",
108126
":values",
109127
"//:auto_value",
110-
"//common:options",
111128
"//common/annotations",
112129
"//common/internal:cel_descriptor_pools",
113130
"//common/internal:dynamic_proto",
114131
"//common/internal:well_known_proto",
115132
"//common/types",
116-
"//common/types:cel_types",
117133
"//common/types:type_providers",
118-
"//common/values:cel_byte_string",
119134
"@maven//:com_google_errorprone_error_prone_annotations",
120135
"@maven//:com_google_guava_guava",
121136
"@maven//:com_google_protobuf_protobuf_java",
122-
"@maven//:com_google_protobuf_protobuf_java_util",
123137
"@maven//:org_jspecify_jspecify",
124138
"@maven_android//:com_google_protobuf_protobuf_javalite",
125139
],
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// Copyright 2025 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.collect.ImmutableList.toImmutableList;
18+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
19+
import static com.google.common.math.LongMath.checkedAdd;
20+
import static com.google.common.math.LongMath.checkedSubtract;
21+
22+
import com.google.common.base.Preconditions;
23+
import com.google.errorprone.annotations.Immutable;
24+
import com.google.protobuf.BoolValue;
25+
import com.google.protobuf.ByteString;
26+
import com.google.protobuf.DoubleValue;
27+
import com.google.protobuf.FloatValue;
28+
import com.google.protobuf.Int32Value;
29+
import com.google.protobuf.Int64Value;
30+
import com.google.protobuf.MessageLite;
31+
import com.google.protobuf.MessageLiteOrBuilder;
32+
import com.google.protobuf.StringValue;
33+
import com.google.protobuf.Struct;
34+
import com.google.protobuf.Timestamp;
35+
import com.google.protobuf.UInt32Value;
36+
import com.google.protobuf.UInt64Value;
37+
import com.google.protobuf.Value;
38+
import com.google.protobuf.util.Durations;
39+
import com.google.protobuf.util.Timestamps;
40+
import dev.cel.common.annotations.Internal;
41+
import dev.cel.common.internal.WellKnownProto;
42+
import java.time.Duration;
43+
import java.time.Instant;
44+
import java.util.Optional;
45+
46+
/**
47+
* {@code BaseProtoCelValueConverter} contains the common logic for converting between native Java
48+
* and protobuf objects to {@link CelValue}. This base class is inherited by {@code
49+
* ProtoCelValueConverter} and {@code ProtoLiteCelValueConverter} to perform the conversion using
50+
* full and lite variants of protobuf messages respectively.
51+
*
52+
* <p>CEL Library Internals. Do Not Use.
53+
*/
54+
@Immutable
55+
@Internal
56+
public abstract class BaseProtoCelValueConverter extends CelValueConverter {
57+
58+
public abstract CelValue fromProtoMessageToCelValue(String protoTypeName, MessageLite msg);
59+
60+
/**
61+
* Adapts a {@link CelValue} to a native Java object. The CelValue is adapted into protobuf object
62+
* when an equivalent exists.
63+
*/
64+
@Override
65+
public Object fromCelValueToJavaObject(CelValue celValue) {
66+
Preconditions.checkNotNull(celValue);
67+
68+
if (celValue instanceof TimestampValue) {
69+
return TimeUtils.toProtoTimestamp(((TimestampValue) celValue).value());
70+
} else if (celValue instanceof DurationValue) {
71+
return TimeUtils.toProtoDuration(((DurationValue) celValue).value());
72+
} else if (celValue instanceof BytesValue) {
73+
return ByteString.copyFrom(((BytesValue) celValue).value().toByteArray());
74+
} else if (celValue.equals(NullValue.NULL_VALUE)) {
75+
return com.google.protobuf.NullValue.NULL_VALUE;
76+
}
77+
78+
return super.fromCelValueToJavaObject(celValue);
79+
}
80+
81+
/** {@inheritDoc} Protobuf semantics take precedence for conversion. */
82+
@Override
83+
public CelValue fromJavaObjectToCelValue(Object value) {
84+
Preconditions.checkNotNull(value);
85+
86+
Optional<WellKnownProto> wellKnownProto = WellKnownProto.getByClass(value.getClass());
87+
if (wellKnownProto.isPresent()) {
88+
return fromWellKnownProtoToCelValue((MessageLiteOrBuilder) value, wellKnownProto.get());
89+
}
90+
91+
if (value instanceof ByteString) {
92+
return BytesValue.create(CelByteString.of(((ByteString) value).toByteArray()));
93+
} else if (value instanceof com.google.protobuf.NullValue) {
94+
return NullValue.NULL_VALUE;
95+
}
96+
97+
return super.fromJavaObjectToCelValue(value);
98+
}
99+
100+
protected CelValue fromWellKnownProtoToCelValue(
101+
MessageLiteOrBuilder message, WellKnownProto wellKnownProto) {
102+
switch (wellKnownProto) {
103+
case JSON_VALUE:
104+
return adaptJsonValueToCelValue((Value) message);
105+
case JSON_STRUCT_VALUE:
106+
return adaptJsonStructToCelValue((Struct) message);
107+
case JSON_LIST_VALUE:
108+
return adaptJsonListToCelValue((com.google.protobuf.ListValue) message);
109+
case DURATION:
110+
return DurationValue.create(
111+
TimeUtils.toJavaDuration((com.google.protobuf.Duration) message));
112+
case TIMESTAMP:
113+
return TimestampValue.create(TimeUtils.toJavaInstant((Timestamp) message));
114+
case BOOL_VALUE:
115+
return fromJavaPrimitiveToCelValue(((BoolValue) message).getValue());
116+
case BYTES_VALUE:
117+
return fromJavaPrimitiveToCelValue(
118+
((com.google.protobuf.BytesValue) message).getValue().toByteArray());
119+
case DOUBLE_VALUE:
120+
return fromJavaPrimitiveToCelValue(((DoubleValue) message).getValue());
121+
case FLOAT_VALUE:
122+
return fromJavaPrimitiveToCelValue(((FloatValue) message).getValue());
123+
case INT32_VALUE:
124+
return fromJavaPrimitiveToCelValue(((Int32Value) message).getValue());
125+
case INT64_VALUE:
126+
return fromJavaPrimitiveToCelValue(((Int64Value) message).getValue());
127+
case STRING_VALUE:
128+
return fromJavaPrimitiveToCelValue(((StringValue) message).getValue());
129+
case UINT32_VALUE:
130+
return UintValue.create(((UInt32Value) message).getValue(), true);
131+
case UINT64_VALUE:
132+
return UintValue.create(((UInt64Value) message).getValue(), true);
133+
default:
134+
throw new UnsupportedOperationException(
135+
"Unsupported message to CelValue conversion - " + message);
136+
}
137+
}
138+
139+
private CelValue adaptJsonValueToCelValue(Value value) {
140+
switch (value.getKindCase()) {
141+
case BOOL_VALUE:
142+
return fromJavaPrimitiveToCelValue(value.getBoolValue());
143+
case NUMBER_VALUE:
144+
return fromJavaPrimitiveToCelValue(value.getNumberValue());
145+
case STRING_VALUE:
146+
return fromJavaPrimitiveToCelValue(value.getStringValue());
147+
case LIST_VALUE:
148+
return adaptJsonListToCelValue(value.getListValue());
149+
case STRUCT_VALUE:
150+
return adaptJsonStructToCelValue(value.getStructValue());
151+
case NULL_VALUE:
152+
case KIND_NOT_SET: // Fall-through is intended
153+
return NullValue.NULL_VALUE;
154+
}
155+
throw new UnsupportedOperationException(
156+
"Unsupported Json to CelValue conversion: " + value.getKindCase());
157+
}
158+
159+
private ListValue<CelValue> adaptJsonListToCelValue(com.google.protobuf.ListValue listValue) {
160+
return ImmutableListValue.create(
161+
listValue.getValuesList().stream()
162+
.map(this::adaptJsonValueToCelValue)
163+
.collect(toImmutableList()));
164+
}
165+
166+
// TODO: Investigate changing MapValue key to StringValue
167+
private MapValue<CelValue, CelValue> adaptJsonStructToCelValue(Struct struct) {
168+
return ImmutableMapValue.create(
169+
struct.getFieldsMap().entrySet().stream()
170+
.collect(
171+
toImmutableMap(
172+
e -> fromJavaObjectToCelValue(e.getKey()),
173+
e -> adaptJsonValueToCelValue(e.getValue()))));
174+
}
175+
176+
/** Helper to convert between java.util.time and protobuf duration/timestamp. */
177+
private static class TimeUtils {
178+
private static final int NANOS_PER_SECOND = 1000000000;
179+
180+
private static Instant toJavaInstant(Timestamp timestamp) {
181+
timestamp = normalizedTimestamp(timestamp.getSeconds(), timestamp.getNanos());
182+
return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
183+
}
184+
185+
private static Duration toJavaDuration(com.google.protobuf.Duration duration) {
186+
duration = normalizedDuration(duration.getSeconds(), duration.getNanos());
187+
return java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNanos());
188+
}
189+
190+
private static Timestamp toProtoTimestamp(Instant instant) {
191+
return normalizedTimestamp(instant.getEpochSecond(), instant.getNano());
192+
}
193+
194+
private static com.google.protobuf.Duration toProtoDuration(Duration duration) {
195+
return normalizedDuration(duration.getSeconds(), duration.getNano());
196+
}
197+
198+
private static Timestamp normalizedTimestamp(long seconds, int nanos) {
199+
if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
200+
seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
201+
nanos = nanos % NANOS_PER_SECOND;
202+
}
203+
if (nanos < 0) {
204+
nanos = nanos + NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
205+
seconds = checkedSubtract(seconds, 1);
206+
}
207+
Timestamp timestamp = Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
208+
return Timestamps.checkValid(timestamp);
209+
}
210+
211+
private static com.google.protobuf.Duration normalizedDuration(long seconds, int nanos) {
212+
if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
213+
seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
214+
nanos %= NANOS_PER_SECOND;
215+
}
216+
if (seconds > 0 && nanos < 0) {
217+
nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
218+
seconds--; // no overflow since seconds is positive (and we're decrementing)
219+
}
220+
if (seconds < 0 && nanos > 0) {
221+
nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting)
222+
seconds++; // no overflow since seconds is negative (and we're incrementing)
223+
}
224+
com.google.protobuf.Duration duration =
225+
com.google.protobuf.Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
226+
return Durations.checkValid(duration);
227+
}
228+
}
229+
230+
protected BaseProtoCelValueConverter() {}
231+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
package dev.cel.common.values;
1616

17-
import com.google.common.base.Preconditions;
1817
import com.google.errorprone.annotations.Immutable;
1918
import java.util.Arrays;
2019

@@ -30,7 +29,9 @@ public final class CelByteString {
3029
private volatile int hash = 0;
3130

3231
public static CelByteString of(byte[] buffer) {
33-
Preconditions.checkNotNull(buffer);
32+
if (buffer == null) {
33+
throw new NullPointerException("buffer cannot be null");
34+
}
3435
if (buffer.length == 0) {
3536
return EMPTY;
3637
}

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

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import com.google.common.collect.ImmutableList;
1919
import com.google.common.collect.ImmutableMap;
2020
import com.google.common.primitives.UnsignedLong;
21-
import dev.cel.common.CelOptions;
2221
import dev.cel.common.annotations.Internal;
2322
import java.util.Map;
2423
import java.util.Map.Entry;
@@ -34,8 +33,6 @@
3433
@Internal
3534
abstract class CelValueConverter {
3635

37-
protected final CelOptions celOptions;
38-
3936
/** Adapts a {@link CelValue} to a plain old Java Object. */
4037
public Object fromCelValueToJavaObject(CelValue celValue) {
4138
Preconditions.checkNotNull(celValue);
@@ -112,7 +109,7 @@ protected CelValue fromJavaPrimitiveToCelValue(Object value) {
112109
} else if (value instanceof Float) {
113110
return DoubleValue.create(Double.valueOf((Float) value));
114111
} else if (value instanceof UnsignedLong) {
115-
return UintValue.create(((UnsignedLong) value).longValue(), celOptions.enableUnsignedLongs());
112+
return UintValue.create(((UnsignedLong) value).longValue(), /* enableUnsignedLongs= */ true);
116113
}
117114

118115
// Fall back to an Opaque value, as a custom class was supplied in the runtime. The legacy
@@ -145,8 +142,5 @@ private MapValue<CelValue, CelValue> toMapValue(Map<Object, Object> map) {
145142
return ImmutableMapValue.create(mapBuilder.buildOrThrow());
146143
}
147144

148-
protected CelValueConverter(CelOptions celOptions) {
149-
Preconditions.checkNotNull(celOptions);
150-
this.celOptions = celOptions;
151-
}
145+
protected CelValueConverter() {}
152146
}

0 commit comments

Comments
 (0)