Skip to content

Commit ad9c302

Browse files
l46kokcopybara-github
authored andcommitted
Introduce OptimizedSelectable interface and general traversal logic
PiperOrigin-RevId: 982681694
1 parent ef2f7aa commit ad9c302

14 files changed

Lines changed: 2051 additions & 27 deletions

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ CEL_VALUES_SOURCES = [
1717
"ErrorValue.java",
1818
"NullValue.java",
1919
"OpaqueValue.java",
20+
"OptimizedSelectTraversal.java",
21+
"OptimizedSelectable.java",
2022
"OptionalValue.java",
23+
"SelectField.java",
2124
"SelectableValue.java",
2225
"StructValue.java",
2326
]
@@ -167,6 +170,7 @@ java_library(
167170
":preadapted_list",
168171
"//:auto_value",
169172
"//common/annotations",
173+
"//common/exceptions:attribute_not_found",
170174
"//common/types",
171175
"//common/types:type_providers",
172176
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -218,6 +222,7 @@ cel_android_library(
218222
":preadapted_list_android",
219223
"//:auto_value",
220224
"//common/annotations",
225+
"//common/exceptions:attribute_not_found",
221226
"//common/types:type_providers_android",
222227
"//common/types:types_android",
223228
"@maven//:com_google_errorprone_error_prone_annotations",
@@ -317,6 +322,7 @@ java_library(
317322
srcs = [
318323
"ProtoLiteCelValueConverter.java",
319324
"ProtoMessageLiteValue.java",
325+
"RawProtoMessageLiteValue.java",
320326
],
321327
tags = [
322328
],
@@ -325,6 +331,7 @@ java_library(
325331
":values",
326332
"//:auto_value",
327333
"//common/annotations",
334+
"//common/exceptions:attribute_not_found",
328335
"//common/internal:cel_lite_descriptor_pool",
329336
"//common/internal:well_known_proto",
330337
"//common/types",
@@ -333,6 +340,7 @@ java_library(
333340
"//protobuf:cel_lite_descriptor",
334341
"@maven//:com_google_errorprone_error_prone_annotations",
335342
"@maven//:com_google_guava_guava",
343+
"@maven//:org_jspecify_jspecify",
336344
"@maven_android//:com_google_protobuf_protobuf_javalite",
337345
],
338346
)
@@ -342,6 +350,7 @@ cel_android_library(
342350
srcs = [
343351
"ProtoLiteCelValueConverter.java",
344352
"ProtoMessageLiteValue.java",
353+
"RawProtoMessageLiteValue.java",
345354
],
346355
tags = [
347356
],
@@ -350,6 +359,7 @@ cel_android_library(
350359
":values_android",
351360
"//:auto_value",
352361
"//common/annotations",
362+
"//common/exceptions:attribute_not_found",
353363
"//common/internal:cel_lite_descriptor_pool_android",
354364
"//common/internal:well_known_proto_android",
355365
"//common/types:type_providers_android",
@@ -358,6 +368,7 @@ cel_android_library(
358368
"//protobuf:cel_lite_descriptor",
359369
"@maven//:com_google_errorprone_error_prone_annotations",
360370
"@maven//:com_google_guava_guava",
371+
"@maven//:org_jspecify_jspecify",
361372
"@maven_android//:com_google_guava_guava",
362373
"@maven_android//:com_google_protobuf_protobuf_javalite",
363374
],
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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 com.google.common.collect.ImmutableList;
18+
import dev.cel.common.annotations.Internal;
19+
import dev.cel.common.exceptions.CelAttributeNotFoundException;
20+
import java.util.Map;
21+
import java.util.Optional;
22+
import org.jspecify.annotations.Nullable;
23+
24+
/**
25+
* Walks a chain of {@link SelectField} hops, dispatching each hop over {@link OptimizedSelectable},
26+
* {@link SelectableValue} or {@link Map}.
27+
*
28+
* <p>CEL Library Internals. Do Not Use.
29+
*/
30+
@Internal
31+
public final class OptimizedSelectTraversal {
32+
33+
/**
34+
* Qualifies {@code target} through every hop in {@code fields} and returns the terminal value.
35+
*
36+
* @param celValueConverter Converter for hops not resolved by an {@link OptimizedSelectable};
37+
* superseded by {@link OptimizedSelectable#celValueConverter} once the chain crosses one.
38+
*/
39+
public static Object qualify(
40+
Object target, ImmutableList<SelectField> fields, CelValueConverter celValueConverter) {
41+
Object current = target;
42+
CelValueConverter converter = celValueConverter;
43+
for (int i = 0; i < fields.size(); i++) {
44+
converter = converterFor(current, converter);
45+
current = qualifyHop(current, fields.get(i), converter);
46+
}
47+
return current;
48+
}
49+
50+
/**
51+
* Presence tests the terminal hop of {@code fields}, navigating through all preceding hops.
52+
*
53+
* <p>Absence of any intermediate hop short-circuits to {@code false}.
54+
*/
55+
public static boolean hasField(
56+
Object target, ImmutableList<SelectField> fields, CelValueConverter celValueConverter) {
57+
if (fields.isEmpty()) {
58+
return false;
59+
}
60+
Object current = target;
61+
CelValueConverter converter = celValueConverter;
62+
int terminalIndex = fields.size() - 1;
63+
for (int i = 0; i < terminalIndex; i++) {
64+
converter = converterFor(current, converter);
65+
current = navigateHop(current, fields.get(i), converter);
66+
if (current == null) {
67+
return false;
68+
}
69+
}
70+
return hasTerminalHop(current, fields.get(terminalIndex));
71+
}
72+
73+
private static Object qualifyHop(
74+
Object target, SelectField field, CelValueConverter celValueConverter) {
75+
if (target instanceof OptimizedSelectable) {
76+
return ((OptimizedSelectable) target).optimizedSelect(field);
77+
}
78+
if (target instanceof SelectableValue) {
79+
Optional<Object> found =
80+
SelectField.findField((SelectableValue<?>) target, field.fieldName());
81+
if (found.isPresent()) {
82+
return SelectField.toStepTarget(found.get(), celValueConverter);
83+
}
84+
if (field.defaultValue() != null) {
85+
return field.defaultValue();
86+
}
87+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
88+
}
89+
if (target instanceof Map) {
90+
Map<?, ?> map = (Map<?, ?>) target;
91+
Object mapValue = map.get(field.fieldName());
92+
if (mapValue != null) {
93+
return SelectField.toStepTarget(mapValue, celValueConverter);
94+
}
95+
if (map.containsKey(field.fieldName())) {
96+
return NullValue.NULL_VALUE;
97+
}
98+
throw CelAttributeNotFoundException.forMissingMapKey(field.fieldName());
99+
}
100+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
101+
}
102+
103+
private static @Nullable Object navigateHop(
104+
Object target, SelectField field, CelValueConverter celValueConverter) {
105+
if (target instanceof OptimizedSelectable) {
106+
return ((OptimizedSelectable) target).optimizedFind(field).orElse(null);
107+
}
108+
if (target instanceof SelectableValue) {
109+
Optional<Object> found =
110+
SelectField.findField((SelectableValue<?>) target, field.fieldName());
111+
return found.isPresent() ? SelectField.toStepTarget(found.get(), celValueConverter) : null;
112+
}
113+
if (target instanceof Map) {
114+
Map<?, ?> map = (Map<?, ?>) target;
115+
Object mapValue = map.get(field.fieldName());
116+
if (mapValue != null) {
117+
return SelectField.toStepTarget(mapValue, celValueConverter);
118+
}
119+
return map.containsKey(field.fieldName()) ? NullValue.NULL_VALUE : null;
120+
}
121+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
122+
}
123+
124+
private static boolean hasTerminalHop(Object target, SelectField field) {
125+
if (target instanceof OptimizedSelectable) {
126+
return ((OptimizedSelectable) target).optimizedHasField(field);
127+
}
128+
if (target instanceof SelectableValue) {
129+
return SelectField.findField((SelectableValue<?>) target, field.fieldName()).isPresent();
130+
}
131+
if (target instanceof Map) {
132+
return ((Map<?, ?>) target).containsKey(field.fieldName());
133+
}
134+
throw CelAttributeNotFoundException.forFieldResolution(field.fieldName());
135+
}
136+
137+
/** Returns {@code target}'s own converter if it has one, otherwise {@code fallback}. */
138+
private static CelValueConverter converterFor(Object target, CelValueConverter fallback) {
139+
return target instanceof OptimizedSelectable
140+
? ((OptimizedSelectable) target).celValueConverter()
141+
: fallback;
142+
}
143+
144+
private OptimizedSelectTraversal() {}
145+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 com.google.errorprone.annotations.Immutable;
18+
import dev.cel.common.annotations.Internal;
19+
import java.util.Optional;
20+
21+
/**
22+
* Resolves a single hop of an optimized selection chain, where a hop is one field selection within
23+
* a chain rewritten by the select optimizer ({@code a.b.c} has two hops, {@code .b} and {@code
24+
* .c}).
25+
*
26+
* <p>Implementations only resolve a hop against themselves. Walking the chain, including across
27+
* values that do not implement this interface, belongs to {@link OptimizedSelectTraversal}.
28+
*
29+
* <p>CEL Library Internals. Do Not Use.
30+
*/
31+
@Internal
32+
@Immutable
33+
public interface OptimizedSelectable {
34+
35+
/**
36+
* Returns the converter this value's fields were decoded with. {@link OptimizedSelectTraversal}
37+
* adopts it for the rest of the chain, so values produced downstream are adapted with the same
38+
* descriptor pool that produced them.
39+
*/
40+
CelValueConverter celValueConverter();
41+
42+
/** Selects {@code field}, falling back to its default value or an empty submessage if absent. */
43+
Object optimizedSelect(SelectField field);
44+
45+
/** Returns whether {@code field} is present. */
46+
boolean optimizedHasField(SelectField field);
47+
48+
/**
49+
* Returns the submessage at {@code field} for an intermediate hop of a presence test, or empty if
50+
* absent.
51+
*/
52+
Optional<Object> optimizedFind(SelectField field);
53+
}

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.common.annotations.VisibleForTesting;
2121
import com.google.common.base.Defaults;
2222
import com.google.common.collect.ImmutableList;
23+
import com.google.common.collect.ImmutableListMultimap;
2324
import com.google.common.collect.ImmutableMap;
2425
import com.google.common.collect.Multimap;
2526
import com.google.common.collect.Multimaps;
@@ -45,6 +46,7 @@
4546
import java.util.List;
4647
import java.util.Map;
4748
import java.util.NoSuchElementException;
49+
import java.util.Optional;
4850
import java.util.TreeMap;
4951

5052
/**
@@ -80,7 +82,7 @@ private static Object readPrimitiveField(
8082
case INT64:
8183
return inputStream.readInt64();
8284
case UINT32:
83-
return UnsignedLong.fromLongBits(inputStream.readUInt32());
85+
return UnsignedLong.fromLongBits(Integer.toUnsignedLong(inputStream.readUInt32()));
8486
case UINT64:
8587
return UnsignedLong.fromLongBits(inputStream.readUInt64());
8688
case BOOL:
@@ -160,6 +162,17 @@ Object getDefaultCelValue(String protoTypeName, String fieldName) {
160162
return toRuntimeValue(defaultValue);
161163
}
162164

165+
public Optional<FieldLiteDescriptor> findFieldDescriptor(String protoTypeName, int fieldNumber) {
166+
return descriptorPool
167+
.findDescriptor(protoTypeName)
168+
.flatMap(desc -> desc.findByFieldNumber(fieldNumber));
169+
}
170+
171+
public Optional<Object> findDefaultCelValue(String protoTypeName, int fieldNumber) {
172+
return findFieldDescriptor(protoTypeName, fieldNumber)
173+
.map(fieldDescriptor -> toRuntimeValue(getDefaultValue(fieldDescriptor)));
174+
}
175+
163176
@Override
164177
@SuppressWarnings("LiteProtoToString") // No alternative identifier to use. Debug only info is OK.
165178
public Object toRuntimeValue(Object value) {
@@ -193,7 +206,10 @@ protected Object fromWellKnownProto(MessageLiteOrBuilder msg, WellKnownProto wel
193206
descriptorPool
194207
.findDescriptor(message)
195208
.orElseThrow(
196-
() -> new NoSuchElementException("Could not find a descriptor for: " + message));
209+
() ->
210+
new NoSuchElementException(
211+
"Could not find a descriptor for message of type: "
212+
+ message.getClass().getName()));
197213
return ProtoMessageLiteValue.create(message, descriptor.getProtoTypeName(), this);
198214
}
199215

@@ -367,13 +383,11 @@ MessageFields readAllFields(byte[] bytes, String protoTypeName) throws IOExcepti
367383
return MessageFields.create(fieldValues.buildKeepingLast(), unknownFields);
368384
}
369385

370-
ImmutableMap<String, Object> readAllFields(MessageLite msg, String protoTypeName)
371-
throws IOException {
372-
return readAllFields(msg.toByteArray(), protoTypeName).values();
386+
MessageFields readMessageFields(MessageLite msg, String protoTypeName) throws IOException {
387+
return readAllFields(msg.toByteArray(), protoTypeName);
373388
}
374389

375-
private static Object readUnknownField(int tagWireType, CodedInputStream inputStream)
376-
throws IOException {
390+
static Object readUnknownField(int tagWireType, CodedInputStream inputStream) throws IOException {
377391
switch (tagWireType) {
378392
case WireFormat.WIRETYPE_VARINT:
379393
return inputStream.readInt64();
@@ -393,16 +407,19 @@ private static Object readUnknownField(int tagWireType, CodedInputStream inputSt
393407
}
394408

395409
@AutoValue
396-
@SuppressWarnings("AutoValueImmutableFields") // Unknowns are inaccessible to users.
410+
@AutoValue.CopyAnnotations
411+
@Immutable
412+
@SuppressWarnings("Immutable") // Safe immutable fields
397413
abstract static class MessageFields {
398414

399415
abstract ImmutableMap<String, Object> values();
400416

401-
abstract Multimap<Integer, Object> unknowns();
417+
abstract ImmutableListMultimap<Integer, Object> unknowns();
402418

403419
static MessageFields create(
404420
ImmutableMap<String, Object> fieldValues, Multimap<Integer, Object> unknownFields) {
405-
return new AutoValue_ProtoLiteCelValueConverter_MessageFields(fieldValues, unknownFields);
421+
return new AutoValue_ProtoLiteCelValueConverter_MessageFields(
422+
fieldValues, ImmutableListMultimap.copyOf(unknownFields));
406423
}
407424
}
408425

0 commit comments

Comments
 (0)