Skip to content

Commit 4cc5853

Browse files
l46kokcopybara-github
authored andcommitted
Internal Changes
PiperOrigin-RevId: 713781331
1 parent 9f59a54 commit 4cc5853

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+8718
-0
lines changed

legacy/BUILD.bazel

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
load("@rules_java//java:java_library.bzl", "java_library")
2+
3+
package(
4+
default_applicable_licenses = ["//:license"],
5+
default_visibility = [":legacy_users"],
6+
)
7+
8+
# See go/cel-java-migration-guide. This package is not accepting new clients.
9+
package_group(
10+
name = "legacy_users",
11+
packages = [
12+
"//third_party/java/cel/legacy/...",
13+
],
14+
)
15+
16+
java_library(
17+
name = "async_runtime",
18+
exports = ["//third_party/java/cel/legacy/java/dev/cel/legacy/runtime/async"],
19+
)
20+
21+
java_library(
22+
name = "dummy_async_context",
23+
testonly = 1,
24+
exports = ["//third_party/java/cel/legacy/java/dev/cel/legacy/runtime/async:dummy_async_context"],
25+
)

legacy/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This directory contains the deprecated CEL-Java packages that once resided in g3/jcg/api/tools/contract/runtime/interpreter.
2+
3+
This package is no longer accepting any new clients, and the underlying codebase will not be open sourced. For existing clients, please migrate to the new fluent APIs (go/cel-java-migration-guide)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dev.cel.legacy.runtime.async;
2+
3+
import com.google.common.util.concurrent.ListenableFuture;
4+
import java.util.function.Supplier;
5+
6+
/**
7+
* An interface describing an object that can perform a lookup on a given name, returning a future
8+
* computing the value associated with the so-named global variable. The value must be in canonical
9+
* CEL runtime representation.
10+
*/
11+
public interface AsyncCanonicalResolver {
12+
/**
13+
* Resolves the given name to a future returning its value. Neither the returned supplier nor the
14+
* supplied future can be null, and a value computed by the future must be in canonical CEL
15+
* runtime representation (which also excludes null). Returns a failed future if the name is not
16+
* bound or if the value cannot be represented canonically.
17+
*/
18+
Supplier<ListenableFuture<Object>> resolve(String name);
19+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package dev.cel.legacy.runtime.async;
2+
3+
import com.google.common.context.Context;
4+
import com.google.common.context.WithContext;
5+
import com.google.common.util.concurrent.FluentFuture;
6+
import com.google.common.util.concurrent.Futures;
7+
import com.google.common.util.concurrent.ListenableFuture;
8+
import java.util.Optional;
9+
import java.util.concurrent.Executor;
10+
import java.util.function.Supplier;
11+
12+
/**
13+
* Represents a snapshot of the "global context" that a context-dependent operations may reference.
14+
*/
15+
public interface AsyncContext {
16+
17+
/**
18+
* "Performs" an operation by obtaining the result future from the given supplier. May optimize
19+
* by, e.g. memoization based on the given keys. (For example, the keys could list the name the
20+
* overload ID of a strict context-dependent function together with a list of its actual
21+
* arguments.)
22+
*/
23+
<T> ListenableFuture<T> perform(
24+
Supplier<ListenableFuture<T>> resultFutureSupplier, Object... keys);
25+
26+
/** Retrieves the executor used for the futures-based computation expressed in CEL. */
27+
Executor executor();
28+
29+
default Optional<Context> requestContext() {
30+
return Optional.empty();
31+
}
32+
33+
/**
34+
* Indicates that the evaluation is a runtime evaluation (as opposed to, e.g., constant-folding or
35+
* other optimizations that occur during compile-time or preprocessing time).
36+
*/
37+
default boolean isRuntime() {
38+
return true;
39+
}
40+
41+
/**
42+
* Decouples the given future from whatever executor it is currently using and couples it to this
43+
* context. Subsequent transformations that specify {@link MoreExecutors#directExecutor} will run
44+
* on this context's executor.
45+
*/
46+
default <T> ListenableFuture<T> coupleToExecutor(ListenableFuture<T> f) {
47+
return FluentFuture.from(f)
48+
.transform(x -> x, executor())
49+
.catchingAsync(Throwable.class, Futures::immediateFailedFuture, executor());
50+
}
51+
52+
/**
53+
* Runs the given supplier of a future within the request context, if any, and then couples the
54+
* result to the executor.
55+
*/
56+
default <T> ListenableFuture<T> coupleToExecutorInRequestContext(
57+
Supplier<ListenableFuture<T>> futureSupplier) {
58+
return coupleToExecutor(
59+
requestContext()
60+
.map(
61+
c -> {
62+
try (WithContext wc = WithContext.enter(c)) {
63+
return futureSupplier.get();
64+
}
65+
})
66+
.orElseGet(futureSupplier));
67+
}
68+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package dev.cel.legacy.runtime.async;
2+
3+
/**
4+
* Interface to an object that combines a {@link FunctionRegistrar} with a corresponding {@link
5+
* FunctionResolver}.
6+
*/
7+
public interface AsyncDispatcher extends FunctionRegistrar, FunctionResolver {
8+
9+
/**
10+
* Creates an independent copy of the current state of the dispatcher. Further updates to either
11+
* the original or the forked copy do not affect the respective other.
12+
*/
13+
AsyncDispatcher fork();
14+
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
package dev.cel.legacy.runtime.async;
2+
3+
import static dev.cel.legacy.runtime.async.EvaluationHelpers.immediateException;
4+
import static java.util.stream.Collectors.joining;
5+
6+
import com.google.auto.value.AutoValue;
7+
import com.google.common.base.Preconditions;
8+
import com.google.common.collect.ImmutableList;
9+
import com.google.protobuf.MessageLite;
10+
import dev.cel.common.CelErrorCode;
11+
import dev.cel.runtime.InterpreterException;
12+
import dev.cel.runtime.Metadata;
13+
import java.util.ArrayList;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import javax.annotation.Nullable;
18+
19+
/**
20+
* Base implementation of interface {@link AsyncDispatcher}.
21+
*
22+
* <p>A fresh {@link AsyncDispatcherBase} starts out empty, i.e., initially has no bindings at all.
23+
*/
24+
public class AsyncDispatcherBase implements AsyncDispatcher {
25+
26+
// Mappings from overload IDs to either call constructors or runtime-overloadable
27+
// (strict) functions. The demains of these two maps are maintained to be disjoint.
28+
private final Map<String, CallConstructor> constructors;
29+
private final Map<String, OverloadInfo> overloads;
30+
31+
/** Creates new empty registry. */
32+
public AsyncDispatcherBase() {
33+
this.constructors = new HashMap<>();
34+
this.overloads = new HashMap<>();
35+
}
36+
37+
/** Cloning constructor, for implementing snapshots. */
38+
protected AsyncDispatcherBase(AsyncDispatcherBase orig) {
39+
this.constructors = new HashMap<>(orig.constructors);
40+
this.overloads = new HashMap<>(orig.overloads);
41+
}
42+
43+
/**
44+
* Adds a generic call constructor for the given overload ID. Function calls with this overload ID
45+
* will later be handled by the given {@link CallConstructor}. No runtime overloading is possible.
46+
*/
47+
@Override
48+
public void addCallConstructor(String overloadId, CallConstructor callConstructor) {
49+
checkNotAlreadyBound(overloadId);
50+
constructors.put(overloadId, callConstructor);
51+
}
52+
53+
/** Adds a strict function as one possible binding for a runtime-overloadable function. */
54+
@Override
55+
public void addStrictFunction(
56+
String overloadId,
57+
List<Class<?>> argumentTypes,
58+
boolean contextIndependent,
59+
StrictFunction strictFunction) {
60+
checkNotAlreadyBound(overloadId);
61+
overloads.put(
62+
overloadId, OverloadInfo.of(overloadId, argumentTypes, contextIndependent, strictFunction));
63+
}
64+
65+
/**
66+
* Constructs the compiled CEL expression that implements the call of a function at some call
67+
* site.
68+
*
69+
* <p>If multiple overload IDs are given, then a runtime dispatch is implemented. All overload IDs
70+
* must refer to strict functions in that case.
71+
*/
72+
// This lambda implements @Immutable interface 'StrictFunction', but 'List' is mutable
73+
@SuppressWarnings("Immutable")
74+
@Override
75+
public CompiledExpression constructCall(
76+
@Nullable Metadata metadata,
77+
long exprId,
78+
String functionName,
79+
List<String> overloadIds,
80+
List<IdentifiedCompiledExpression> compiledArguments,
81+
MessageProcessor messageProcessor,
82+
StackOffsetFinder stackOffsetFinder)
83+
throws InterpreterException {
84+
Preconditions.checkState(!overloadIds.isEmpty(), "no overloads for call of %s", functionName);
85+
if (overloadIds.size() == 1) {
86+
// Unique binding.
87+
String overloadId = overloadIds.get(0);
88+
if (constructors.containsKey(overloadId)) {
89+
return constructors
90+
.get(overloadId)
91+
.construct(metadata, exprId, compiledArguments, messageProcessor, stackOffsetFinder);
92+
}
93+
}
94+
95+
List<OverloadInfo> candidates = new ArrayList<>();
96+
List<String> unbound = new ArrayList<>();
97+
for (String overloadId : overloadIds) {
98+
if (constructors.containsKey(overloadId)) {
99+
throw new InterpreterException.Builder(
100+
"incompatible overload for function '%s': %s must be resolved at compile time",
101+
functionName, overloadId)
102+
.setLocation(metadata, exprId)
103+
.build();
104+
} else if (overloads.containsKey(overloadId)) {
105+
candidates.add(overloads.get(overloadId));
106+
} else {
107+
unbound.add(overloadId);
108+
}
109+
}
110+
111+
if (!unbound.isEmpty()) {
112+
throw new InterpreterException.Builder("no runtime binding for %s", String.join(",", unbound))
113+
.setLocation(metadata, exprId)
114+
.build();
115+
}
116+
117+
if (candidates.size() == 1) {
118+
OverloadInfo overload = candidates.get(0);
119+
return constructStrictCall(
120+
overload.function(),
121+
overload.overloadId(),
122+
overload.contextIndependent(),
123+
compiledArguments);
124+
}
125+
// Key for memoizing the overload dispatch itself.
126+
String memoizationKey = candidates.stream().map(OverloadInfo::overloadId).collect(joining("|"));
127+
boolean contextIndependent = candidates.stream().allMatch(OverloadInfo::contextIndependent);
128+
return constructStrictCall(
129+
(gctx, arguments) -> {
130+
List<OverloadInfo> matching = new ArrayList<>();
131+
for (OverloadInfo candidate : candidates) {
132+
if (candidate.canHandle(arguments)) {
133+
matching.add(candidate);
134+
}
135+
}
136+
if (matching.isEmpty()) {
137+
return immediateException(
138+
new InterpreterException.Builder(
139+
"No matching overload for function '%s'. Overload candidates: %s",
140+
functionName, String.join(",", overloadIds))
141+
.setErrorCode(CelErrorCode.OVERLOAD_NOT_FOUND)
142+
.setLocation(metadata, exprId)
143+
.build());
144+
}
145+
if (matching.size() > 1) {
146+
return immediateException(
147+
new InterpreterException.Builder(
148+
"Ambiguous overloads for function '%s'. Matching candidates: %s",
149+
functionName,
150+
matching.stream().map(OverloadInfo::overloadId).collect(joining(",")))
151+
.setErrorCode(CelErrorCode.AMBIGUOUS_OVERLOAD)
152+
.setLocation(metadata, exprId)
153+
.build());
154+
}
155+
OverloadInfo match = matching.get(0);
156+
return match.contextIndependent()
157+
? match.function().apply(gctx, arguments)
158+
: gctx.context()
159+
.perform(
160+
() -> match.function().apply(gctx, arguments), match.overloadId(), arguments);
161+
},
162+
memoizationKey,
163+
contextIndependent,
164+
compiledArguments);
165+
}
166+
167+
/**
168+
* Constructs a call of a single strict function. This is a thin wrapper around {@link
169+
* EvaluationHelpers#compileStrictCall}.
170+
*/
171+
private CompiledExpression constructStrictCall(
172+
StrictFunction function,
173+
String overloadId,
174+
boolean contextIndependent,
175+
List<IdentifiedCompiledExpression> compiledArguments)
176+
throws InterpreterException {
177+
return EvaluationHelpers.compileStrictCall(
178+
function,
179+
overloadId,
180+
contextIndependent ? Effect.CONTEXT_INDEPENDENT : Effect.CONTEXT_DEPENDENT,
181+
compiledArguments);
182+
}
183+
184+
/**
185+
* Creates an independent copy of the current state of the dispatcher. Further updates to either
186+
* the original or the forked copy do not affect the respective other.
187+
*/
188+
@Override
189+
public AsyncDispatcher fork() {
190+
return new AsyncDispatcherBase(this);
191+
}
192+
193+
// Not to be overridden in subclasses! This method only checks whether it is locally
194+
// (i.e., only with respect to constructors and overloads of this dispatcher) to add
195+
// a new binding for overloadId.
196+
private boolean isLocallyBound(String overloadId) {
197+
return constructors.containsKey(overloadId) || overloads.containsKey(overloadId);
198+
}
199+
200+
/**
201+
* Determines whether or not the given overload ID corresponds to a known function binding.
202+
* Subclasses that provide additional bindings should override this.
203+
*/
204+
@Override
205+
public boolean isBound(String overloadId) {
206+
return isLocallyBound(overloadId);
207+
}
208+
209+
/** Helper for making sure that no overload ID is bound more than once. */
210+
private void checkNotAlreadyBound(String overloadId) {
211+
Preconditions.checkState(
212+
!isLocallyBound(overloadId), "More than one binding for %s.", overloadId);
213+
}
214+
215+
/** Helper class for storing information about a single overloadable strict function. */
216+
@AutoValue
217+
abstract static class OverloadInfo {
218+
/** The overload ID in question. */
219+
abstract String overloadId();
220+
221+
/** Java classes of the expected arguments. */
222+
abstract ImmutableList<Class<?>> argumentTypes();
223+
224+
/** True if the function is context-independent. */
225+
abstract boolean contextIndependent();
226+
227+
/** The function that is bound to the overload ID. */
228+
abstract StrictFunction function();
229+
230+
static OverloadInfo of(
231+
String overloadId,
232+
List<Class<?>> argumentTypes,
233+
boolean contextIndependent,
234+
StrictFunction function) {
235+
return new AutoValue_AsyncDispatcherBase_OverloadInfo(
236+
overloadId, ImmutableList.copyOf(argumentTypes), contextIndependent, function);
237+
}
238+
239+
/** Determines whether this overload can handle a call with the given actual arguments. */
240+
boolean canHandle(List<Object> arguments) {
241+
int arity = argumentTypes().size();
242+
if (arity != arguments.size()) {
243+
return false;
244+
}
245+
for (int i = 0; i < arity; ++i) {
246+
if (!argMatchesType(arguments.get(i), argumentTypes().get(i))) {
247+
return false;
248+
}
249+
}
250+
return true;
251+
}
252+
253+
/** Helper for determining runtime argument type matches. */
254+
private static boolean argMatchesType(Object argument, Class<?> parameterType) {
255+
if (argument != null) {
256+
return parameterType.isAssignableFrom(argument.getClass());
257+
}
258+
// null can be assigned to messages, maps, and objects
259+
return parameterType == Object.class
260+
|| MessageLite.class.isAssignableFrom(parameterType)
261+
|| Map.class.isAssignableFrom(parameterType);
262+
}
263+
}
264+
}

0 commit comments

Comments
 (0)