Skip to content
Open
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
5 changes: 5 additions & 0 deletions common/internal/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,8 @@ cel_android_library(
name = "date_time_helpers_android",
exports = ["//common/src/main/java/dev/cel/common/internal:date_time_helpers_android"],
)

java_library(
name = "reflection_util",
exports = ["//common/src/main/java/dev/cel/common/internal:reflection_util"],
)
4 changes: 4 additions & 0 deletions common/src/main/java/dev/cel/common/internal/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,12 @@ java_library(
java_library(
name = "reflection_util",
srcs = ["ReflectionUtil.java"],
tags = [
"alt_dep=//common/internal:reflection_util",
],
deps = [
"//common/annotations",
"@maven//:com_google_guava_guava",
],
)

Expand Down
49 changes: 49 additions & 0 deletions common/src/main/java/dev/cel/common/internal/ReflectionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@

package dev.cel.common.internal;

import com.google.common.reflect.TypeToken;
import dev.cel.common.annotations.Internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* Utility class for invoking Java reflection.
Expand Down Expand Up @@ -48,5 +54,48 @@ public static Object invoke(Method method, Object object, Object... params) {
}
}

/**
* Extracts the element type of a container type (List, Map, or Optional). Returns the type itself
* if it's not a container or if generic type info is missing.
*/
public static Class<?> getElementType(Class<?> type, Type genericType) {
TypeToken<?> token = TypeToken.of(genericType);

if (List.class.isAssignableFrom(type)) {
return token.resolveType(List.class.getTypeParameters()[0]).getRawType();
}
if (Map.class.isAssignableFrom(type)) {
return token.resolveType(Map.class.getTypeParameters()[1]).getRawType();
}
if (type == Optional.class) {
return token.resolveType(Optional.class.getTypeParameters()[0]).getRawType();
}

return type;
}

/**
* Extracts the raw Class from a Type. Handles Class, ParameterizedType, and WildcardType (returns
* upper bound). Returns Object.class as fallback.
*/
public static Class<?> getRawType(Type type) {
return TypeToken.of(type).getRawType();
}

/**
* Extracts the actual type arguments from a ParameterizedType, if it has at least the expected
* minimum number of arguments. Returns Optional.empty() if the type is not parameterized or has
* fewer arguments than expected.
*/
public static Optional<Type[]> getTypeArguments(Type type, int minArgs) {
if (type instanceof ParameterizedType) {
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
if (args.length >= minArgs) {
return Optional.of(args);
}
}
return Optional.empty();
}

private ReflectionUtil() {}
}
66 changes: 55 additions & 11 deletions common/src/main/java/dev/cel/common/values/CelValueConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.annotations.Internal;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.RandomAccess;
import java.util.function.Function;

/**
Expand Down Expand Up @@ -57,19 +61,37 @@ public Object maybeUnwrap(Object value) {
return unwrap((CelValue) value);
}

Object mapped = mapContainer(value, maybeUnwrapFunction);
if (mapped != value) {
return mapped;
}

return value;
return mapContainer(value, maybeUnwrapFunction);
}

/**
* Maps a container (Collection or Map) by applying the provided mapper function to its elements.
* Returns the original value if it's not a supported container.
*/
protected Object mapContainer(Object value, Function<Object, Object> mapper) {
if (value instanceof List && value instanceof RandomAccess) {
List<Object> list = (List<Object>) value;
for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
Object mapped = mapper.apply(element);

if (mapped != element) {
ImmutableList.Builder<Object> builder =
ImmutableList.builderWithExpectedSize(list.size());
for (int j = 0; j < i; j++) {
builder.add(list.get(j));
}
builder.add(mapped);
for (int j = i + 1; j < list.size(); j++) {
builder.add(mapper.apply(list.get(j)));
}
return builder.build();
}
}
return value;
}

// Fallback for cases where the collection is unordered, or random access is impossible.
if (value instanceof Collection) {
Collection<Object> collection = (Collection<Object>) value;
ImmutableList.Builder<Object> builder =
Expand All @@ -82,12 +104,34 @@ protected Object mapContainer(Object value, Function<Object, Object> mapper) {

if (value instanceof Map) {
Map<Object, Object> map = (Map<Object, Object>) value;
ImmutableMap.Builder<Object, Object> builder =
ImmutableMap.builderWithExpectedSize(map.size());
for (Map.Entry<Object, Object> entry : map.entrySet()) {
builder.put(mapper.apply(entry.getKey()), mapper.apply(entry.getValue()));
Iterator<Entry<Object, Object>> iterator = map.entrySet().iterator();

while (iterator.hasNext()) {
Map.Entry<Object, Object> entry = iterator.next();
Object mappedKey = mapper.apply(entry.getKey());
Object mappedValue = mapper.apply(entry.getValue());

if (mappedKey != entry.getKey() || mappedValue != entry.getValue()) {
ImmutableMap.Builder<Object, Object> builder =
ImmutableMap.builderWithExpectedSize(map.size());

for (Map.Entry<Object, Object> prevEntry : map.entrySet()) {
if (prevEntry.getKey() == entry.getKey()) {
break;
}
builder.put(mapper.apply(prevEntry.getKey()), mapper.apply(prevEntry.getValue()));
}

builder.put(mappedKey, mappedValue);

while (iterator.hasNext()) {
Map.Entry<Object, Object> nextEntry = iterator.next();
builder.put(mapper.apply(nextEntry.getKey()), mapper.apply(nextEntry.getValue()));
}
return builder.buildOrThrow();
}
}
return builder.buildOrThrow();
return value;
}

return value;
Expand Down
5 changes: 5 additions & 0 deletions extensions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,8 @@ java_library(
name = "comprehensions",
exports = ["//extensions/src/main/java/dev/cel/extensions:comprehensions"],
)

java_library(
name = "native",
exports = ["//extensions/src/main/java/dev/cel/extensions:native"],
)
24 changes: 24 additions & 0 deletions extensions/src/main/java/dev/cel/extensions/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ java_library(
":encoders",
":lists",
":math",
":native",
":optional_library",
":protos",
":regex",
Expand Down Expand Up @@ -318,3 +319,26 @@ java_library(
"@maven//:com_google_guava_guava",
],
)

java_library(
name = "native",
srcs = ["CelNativeTypesExtensions.java"],
tags = [
],
deps = [
"//checker:checker_builder",
"//common/exceptions:attribute_not_found",
"//common/internal:reflection_util",
"//common/types",
"//common/types:type_providers",
"//common/values",
"//common/values:cel_byte_string",
"//common/values:cel_value",
"//common/values:cel_value_provider",
"//compiler:compiler_builder",
"//runtime",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:org_jspecify_jspecify",
],
)
29 changes: 20 additions & 9 deletions extensions/src/main/java/dev/cel/extensions/CelExtensions.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
package dev.cel.extensions;

import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static java.util.Arrays.stream;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.errorprone.annotations.InlineMe;
import dev.cel.common.CelOptions;
import dev.cel.extensions.CelMathExtensions.Function;
import java.util.EnumSet;
import java.util.Set;

/**
Expand Down Expand Up @@ -350,6 +350,18 @@ public static CelComprehensionsExtensions comprehensions() {
return COMPREHENSIONS_EXTENSIONS;
}

/**
* Extensions for supporting native Java types (POJOs) in CEL.
*
* <p>Refer to README.md for details on property discovery, type mapping, and limitations.
*
* <p>Note: Passing classes with unsupported types or anonymous/local classes will result in an
* {@link IllegalArgumentException} when the runtime is built.
*/
public static CelNativeTypesExtensions nativeTypes(Class<?>... classes) {
return CelNativeTypesExtensions.nativeTypes(classes);
}

/**
* Retrieves all function names used by every extension libraries.
*
Expand All @@ -359,18 +371,17 @@ public static CelComprehensionsExtensions comprehensions() {
*/
public static ImmutableSet<String> getAllFunctionNames() {
return Streams.concat(
stream(CelMathExtensions.Function.values())
.map(CelMathExtensions.Function::getFunction),
stream(CelStringExtensions.Function.values())
EnumSet.allOf(Function.class).stream().map(CelMathExtensions.Function::getFunction),
EnumSet.allOf(CelStringExtensions.Function.class).stream()
.map(CelStringExtensions.Function::getFunction),
stream(SetsFunction.values()).map(SetsFunction::getFunction),
stream(CelEncoderExtensions.Function.values())
EnumSet.allOf(SetsFunction.class).stream().map(SetsFunction::getFunction),
EnumSet.allOf(CelEncoderExtensions.Function.class).stream()
.map(CelEncoderExtensions.Function::getFunction),
stream(CelListsExtensions.Function.values())
EnumSet.allOf(CelListsExtensions.Function.class).stream()
.map(CelListsExtensions.Function::getFunction),
stream(CelRegexExtensions.Function.values())
EnumSet.allOf(CelRegexExtensions.Function.class).stream()
.map(CelRegexExtensions.Function::getFunction),
stream(CelComprehensionsExtensions.Function.values())
EnumSet.allOf(CelComprehensionsExtensions.Function.class).stream()
.map(CelComprehensionsExtensions.Function::getFunction))
.collect(toImmutableSet());
}
Expand Down
Loading
Loading