Expand support to arrays to Native type extensions by copybara-service[bot] · Pull Request #1069 · cel-expr/cel-java · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.checker.CelCheckerBuilder;
import dev.cel.common.exceptions.CelAttributeNotFoundException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.internal.ReflectionUtil;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand All @@ -47,6 +48,7 @@
import dev.cel.runtime.CelRuntimeLibrary;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -289,6 +291,15 @@ private static CelType mapJavaTypeToCelType(
return celType;
}

if (type.isArray()) {
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
return ListType.create(
mapJavaTypeToCelType(componentToken.getRawType(), componentToken.getType(), classMap));
}

if (type.isInterface()
&& !List.class.isAssignableFrom(type)
&& !Map.class.isAssignableFrom(type)) {
Expand Down Expand Up @@ -416,6 +427,14 @@ private void discover(Type type) {
TypeToken<?> token = TypeToken.of(type);
Class<?> rawType = token.getRawType();

if (rawType.isArray()) {
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
discover(componentToken.getType());
return;
}

if (List.class.isAssignableFrom(rawType)) {
discover(ReflectionUtil.resolveGenericParameter(token, List.class, 0));
return;
Expand Down Expand Up @@ -775,6 +794,9 @@ private static Object getDefaultValue(Class<?> targetType) {
if (Map.class.isAssignableFrom(targetType)) {
return ImmutableMap.of();
}
if (targetType.isArray()) {
return Array.newInstance(targetType.getComponentType(), 0);
}

try {
Constructor<?> constructor = targetType.getDeclaredConstructor();
Expand Down Expand Up @@ -822,6 +844,10 @@ public Object toRuntimeValue(Object value) {
return new PojoStructValue(value, accessors, registry.classToTypeMap.get(clazz));
}

if (clazz.isArray() && clazz != byte[].class) {
return convertArrayToList(value);
}

return super.toRuntimeValue(value);
}

Expand All @@ -844,8 +870,14 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {
return ((CelByteString) value).toByteArray();
}

if (List.class.isAssignableFrom(targetType) && value instanceof List) {
return convertListToNative((List<?>) value, targetType, genericType);
if (value instanceof List) {
List<?> listValue = (List<?>) value;
if (List.class.isAssignableFrom(targetType)) {
return convertListToNative(listValue, targetType, genericType);
}
if (targetType.isArray()) {
return convertListToArray(listValue, targetType, genericType);
}
}

if (Map.class.isAssignableFrom(targetType) && value instanceof Map) {
Expand All @@ -857,7 +889,7 @@ Object toNative(Object value, Class<?> targetType, Type genericType) {

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
private List<?> convertListToNative(List<?> list, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type elementType = ReflectionUtil.resolveGenericParameter(token, List.class, 0);
Class<?> componentType = ReflectionUtil.getRawType(elementType);
Expand Down Expand Up @@ -909,7 +941,7 @@ private Object convertListToNative(List<?> list, Class<?> targetType, Type gener

// Safe reflection collection cast.
@SuppressWarnings("unchecked")
private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
private Map<?, ?> convertMapToNative(Map<?, ?> map, Class<?> targetType, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);
Type keyType = ReflectionUtil.resolveGenericParameter(token, Map.class, 0);
Type valueType = ReflectionUtil.resolveGenericParameter(token, Map.class, 1);
Expand Down Expand Up @@ -970,6 +1002,36 @@ private Object convertMapToNative(Map<?, ?> map, Class<?> targetType, Type gener
return builder.buildOrThrow();
}

private Object convertListToArray(List<?> list, Class<?> targetType, Type genericType) {
Class<?> componentType = targetType.getComponentType();
Object array = Array.newInstance(componentType, list.size());
TypeToken<?> token = TypeToken.of(genericType);
TypeToken<?> componentToken =
Preconditions.checkNotNull(
token.getComponentType(), "Array component type cannot be null");
Type componentGenericType = componentToken.getType();

for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object converted = toNative(element, componentType, componentGenericType);
Array.set(array, i, converted);
}
return array;
}

private ImmutableList<Object> convertArrayToList(Object array) {
int length = Array.getLength(array);
ImmutableList.Builder<Object> builder = ImmutableList.builderWithExpectedSize(length);
for (int i = 0; i < length; i++) {
Object element = Array.get(array, i);
if (element == null) {
throw new CelInvalidArgumentException(String.format("Element at index %d is null.", i));
}
builder.add(toRuntimeValue(element));
}
return builder.build();
}

private Object downcastPrimitives(Object value, Class<?> targetType) {
Class<?> wrappedTargetType = Primitives.wrap(targetType);
if (wrappedTargetType == Integer.class && value instanceof Long) {
Expand Down
4 changes: 2 additions & 2 deletions extensions/src/main/java/dev/cel/extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1114,14 +1114,14 @@ The type-mapping between Java and CEL is as follows:
| `String` | `string` |
| `java.time.Duration` | `duration` |
| `java.time.Instant` | `timestamp` |
| `java.util.List` | `list` |
| `java.util.List`, `T[]` (except `byte[]`) | `list` |
| `java.util.Map` | `map` |
| `java.util.Optional` | `optional_type` |

### Notes

* This is only supported for the planner runtime (e.g., `CelRuntimeFactory.plannerRuntimeBuilder()`).
* Native Java arrays (except `byte[]`) are not supported. Use `java.util.List` instead.
* Native Java arrays are supported. `byte[]` maps to `bytes`, while other arrays map to `list`.
* Java `enum` properties are not currently supported and will be safely ignored during scanning.
* If there is a name collision with a Protobuf type, the protobuf type will take precedence.
* Instantiating new struct values (e.g., `Account{id: 1234}`) requires the class to have a no-argument constructor (public, protected, package-private, or private).
Expand Down
1 change: 1 addition & 0 deletions extensions/src/test/java/dev/cel/extensions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ java_library(
"//common/exceptions:attribute_not_found",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down
Loading