Skip to content

Commit 66c85dc

Browse files
Cristina Borzacopybara-github
authored andcommitted
Add optimizer listener
PiperOrigin-RevId: 974424751
1 parent 9a97aec commit 66c85dc

7 files changed

Lines changed: 235 additions & 17 deletions

File tree

optimizer/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ java_library(
1515
exports = ["//optimizer/src/main/java/dev/cel/optimizer:optimizer_builder"],
1616
)
1717

18+
java_library(
19+
name = "optimizer_listener",
20+
exports = ["//optimizer/src/main/java/dev/cel/optimizer:optimizer_listener"],
21+
)
22+
1823
java_library(
1924
name = "ast_optimizer",
2025
exports = ["//optimizer/src/main/java/dev/cel/optimizer:ast_optimizer"],

optimizer/src/main/java/dev/cel/optimizer/BUILD.bazel

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ java_library(
2727
],
2828
)
2929

30+
java_library(
31+
name = "optimizer_listener",
32+
srcs = ["CelOptimizerListener.java"],
33+
tags = [
34+
],
35+
deps = [
36+
":ast_optimizer",
37+
"//common:cel_ast",
38+
"@maven//:com_google_code_findbugs_annotations",
39+
"@maven//:com_google_errorprone_error_prone_annotations",
40+
],
41+
)
42+
3043
java_library(
3144
name = "optimizer_builder",
3245
srcs = [
@@ -38,6 +51,7 @@ java_library(
3851
deps = [
3952
":ast_optimizer",
4053
":optimization_exception",
54+
":optimizer_listener",
4155
"//common:cel_ast",
4256
"@maven//:com_google_errorprone_error_prone_annotations",
4357
],
@@ -54,6 +68,7 @@ java_library(
5468
":ast_optimizer",
5569
":optimization_exception",
5670
":optimizer_builder",
71+
":optimizer_listener",
5772
"//bundle:cel",
5873
"//common:cel_ast",
5974
"//common:compiler_common",

optimizer/src/main/java/dev/cel/optimizer/CelOptimizerBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public interface CelOptimizerBuilder {
2828
@CanIgnoreReturnValue
2929
CelOptimizerBuilder addAstOptimizers(Iterable<CelAstOptimizer> astOptimizers);
3030

31+
/** Adds one or more listeners to observe optimization lifecycle. */
32+
@CanIgnoreReturnValue
33+
CelOptimizerBuilder addOptimizerListeners(CelOptimizerListener... listeners);
34+
35+
/** Adds one or more listeners to observe optimization lifecycle. */
36+
@CanIgnoreReturnValue
37+
CelOptimizerBuilder addOptimizerListeners(Iterable<CelOptimizerListener> listeners);
38+
3139
/** Build a new instance of the {@link CelOptimizer}. */
3240
@CheckReturnValue
3341
CelOptimizer build();

optimizer/src/main/java/dev/cel/optimizer/CelOptimizerImpl.java

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@
3232
final class CelOptimizerImpl implements CelOptimizer {
3333
private final Cel cel;
3434
private final ImmutableSet<CelAstOptimizer> astOptimizers;
35+
private final ImmutableSet<CelOptimizerListener> listeners;
3536

36-
CelOptimizerImpl(Cel cel, ImmutableSet<CelAstOptimizer> astOptimizers) {
37+
CelOptimizerImpl(
38+
Cel cel,
39+
ImmutableSet<CelAstOptimizer> astOptimizers,
40+
ImmutableSet<CelOptimizerListener> listeners) {
3741
this.cel = cel;
3842
this.astOptimizers = astOptimizers;
43+
this.listeners = listeners;
3944
}
4045

4146
@Override
@@ -44,27 +49,51 @@ public CelAbstractSyntaxTree optimize(CelAbstractSyntaxTree ast) throws CelOptim
4449
throw new IllegalArgumentException("AST must be type-checked.");
4550
}
4651

52+
listeners.forEach(listener -> listener.onOptimizationStart(ast));
53+
4754
Cel celOptimizerEnv = cel;
4855
CelAbstractSyntaxTree optimizedAst = ast;
56+
4957
try {
5058
for (CelAstOptimizer optimizer : astOptimizers) {
51-
OptimizationResult result = optimizer.optimize(optimizedAst, celOptimizerEnv);
52-
if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) {
53-
celOptimizerEnv =
54-
celOptimizerEnv
55-
.toCelBuilder()
56-
.addVarDeclarations(result.newVarDecls())
57-
.addFunctionDeclarations(result.newFunctionDecls())
58-
.build();
59+
CelAbstractSyntaxTree preAst = optimizedAst;
60+
try {
61+
for (CelOptimizerListener listener : listeners) {
62+
listener.onPassStart(optimizer, preAst);
63+
}
64+
65+
OptimizationResult result = optimizer.optimize(optimizedAst, celOptimizerEnv);
66+
67+
if (!result.newFunctionDecls().isEmpty() || !result.newVarDecls().isEmpty()) {
68+
celOptimizerEnv =
69+
celOptimizerEnv
70+
.toCelBuilder()
71+
.addVarDeclarations(result.newVarDecls())
72+
.addFunctionDeclarations(result.newFunctionDecls())
73+
.build();
74+
}
75+
optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst();
76+
assertAstIdCorrectness(optimizedAst);
77+
78+
for (CelOptimizerListener listener : listeners) {
79+
listener.onPassEnd(optimizer, preAst, optimizedAst);
80+
}
81+
} catch (CelValidationException e) {
82+
notifyPassFailure(optimizer, preAst, e);
83+
throw new CelOptimizationException(
84+
"Optimized AST failed to type-check: " + e.getMessage(), e);
85+
} catch (CelOptimizationException e) {
86+
notifyPassFailure(optimizer, preAst, e);
87+
throw e;
88+
} catch (RuntimeException e) {
89+
notifyPassFailure(optimizer, preAst, e);
90+
throw new CelOptimizationException("Optimization failure: " + e.getMessage(), e);
5991
}
60-
optimizedAst = celOptimizerEnv.check(result.optimizedAst()).getAst();
61-
assertAstIdCorrectness(optimizedAst);
6292
}
63-
} catch (CelValidationException e) {
64-
throw new CelOptimizationException(
65-
"Optimized AST failed to type-check: " + e.getMessage(), e);
66-
} catch (RuntimeException e) {
67-
throw new CelOptimizationException("Optimization failure: " + e.getMessage(), e);
93+
} finally {
94+
for (CelOptimizerListener listener : listeners) {
95+
listener.onOptimizationEnd(ast, optimizedAst);
96+
}
6897
}
6998

7099
return optimizedAst;
@@ -123,6 +152,13 @@ private static void assertAstIdCorrectness(CelAbstractSyntaxTree ast) {
123152
}
124153
}
125154

155+
private void notifyPassFailure(
156+
CelAstOptimizer optimizer, CelAbstractSyntaxTree ast, Exception failure) {
157+
for (CelOptimizerListener listener : listeners) {
158+
listener.onPassFailure(optimizer, ast, failure);
159+
}
160+
}
161+
126162
/** Create a new builder for constructing a {@link CelOptimizer} instance. */
127163
static CelOptimizerImpl.Builder newBuilder(Cel cel) {
128164
return new CelOptimizerImpl.Builder(cel);
@@ -132,10 +168,12 @@ static CelOptimizerImpl.Builder newBuilder(Cel cel) {
132168
static final class Builder implements CelOptimizerBuilder {
133169
private final Cel cel;
134170
private final ImmutableSet.Builder<CelAstOptimizer> astOptimizers;
171+
private final ImmutableSet.Builder<CelOptimizerListener> listeners;
135172

136173
private Builder(Cel cel) {
137174
this.cel = cel;
138175
this.astOptimizers = ImmutableSet.builder();
176+
this.listeners = ImmutableSet.builder();
139177
}
140178

141179
@Override
@@ -151,9 +189,22 @@ public CelOptimizerBuilder addAstOptimizers(Iterable<CelAstOptimizer> astOptimiz
151189
return this;
152190
}
153191

192+
@Override
193+
public CelOptimizerBuilder addOptimizerListeners(CelOptimizerListener... listeners) {
194+
checkNotNull(listeners);
195+
return addOptimizerListeners(Arrays.asList(listeners));
196+
}
197+
198+
@Override
199+
public CelOptimizerBuilder addOptimizerListeners(Iterable<CelOptimizerListener> listeners) {
200+
checkNotNull(listeners);
201+
this.listeners.addAll(listeners);
202+
return this;
203+
}
204+
154205
@Override
155206
public CelOptimizer build() {
156-
return new CelOptimizerImpl(cel, astOptimizers.build());
207+
return new CelOptimizerImpl(cel, astOptimizers.build(), listeners.build());
157208
}
158209
}
159210
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.optimizer;
16+
17+
import javax.annotation.concurrent.ThreadSafe;
18+
import dev.cel.common.CelAbstractSyntaxTree;
19+
20+
/**
21+
* Listener interface for observing the execution lifecycle of {@link CelOptimizer}.
22+
*
23+
* <p>Implementations must be thread-safe.
24+
*/
25+
@ThreadSafe
26+
public interface CelOptimizerListener {
27+
/**
28+
* Invoked before the optimization pipeline begins.
29+
*
30+
* @param ast the initial AST to be optimized.
31+
*/
32+
default void onOptimizationStart(CelAbstractSyntaxTree ast) {}
33+
34+
/**
35+
* Invoked before a specific {@link CelAstOptimizer} pass executes.
36+
*
37+
* @param optimizer the optimizer pass that is about to execute.
38+
* @param ast the initial AST that is about to be optimized.
39+
*/
40+
default void onPassStart(CelAstOptimizer optimizer, CelAbstractSyntaxTree ast) {}
41+
42+
/**
43+
* Invoked after a specific {@link CelAstOptimizer} pass completes successfully.
44+
*
45+
* @param optimizer the optimizer pass that just completed.
46+
* @param preAst the initial AST that was passed to the optimizer pass.
47+
* @param optimizedAst the AST after the optimizer pass completed.
48+
*/
49+
default void onPassEnd(
50+
CelAstOptimizer optimizer,
51+
CelAbstractSyntaxTree preAst,
52+
CelAbstractSyntaxTree optimizedAst) {}
53+
54+
/**
55+
* Invoked if an optimizer pass throws an unhandled exception.
56+
*
57+
* @param optimizer the optimizer pass that threw the exception.
58+
* @param ast the initial AST that was passed to the optimizer pass.
59+
* @param failure the exception that was thrown by the optimizer pass.
60+
*/
61+
default void onPassFailure(
62+
CelAstOptimizer optimizer, CelAbstractSyntaxTree ast, Exception failure) {}
63+
64+
/**
65+
* Invoked after all optimization passes and final type-checks complete.
66+
*
67+
* @param initialAst the initial AST that was passed to the optimizer.
68+
* @param finalAst the final AST after all optimization passes.
69+
*/
70+
default void onOptimizationEnd(
71+
CelAbstractSyntaxTree initialAst, CelAbstractSyntaxTree finalAst) {}
72+
}

optimizer/src/test/java/dev/cel/optimizer/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ java_library(
3232
"//optimizer:optimization_exception",
3333
"//optimizer:optimizer_builder",
3434
"//optimizer:optimizer_impl",
35+
"//optimizer:optimizer_listener",
3536
"//parser:macro",
3637
"//parser:parser_factory",
3738
"//parser:unparser",

optimizer/src/test/java/dev/cel/optimizer/CelOptimizerImplTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,41 @@ public class CelOptimizerImplTest {
4444
.setStandardMacros(CelStandardMacro.STANDARD_MACROS)
4545
.build();
4646

47+
private final List<String> events = new ArrayList<>();
48+
49+
private final CelOptimizerListener listener =
50+
new CelOptimizerListener() {
51+
@Override
52+
public void onOptimizationStart(CelAbstractSyntaxTree ast) {
53+
events.add("start");
54+
}
55+
56+
@Override
57+
public void onPassStart(CelAstOptimizer optimizer, CelAbstractSyntaxTree ast) {
58+
events.add("pass_start");
59+
}
60+
61+
@Override
62+
public void onPassEnd(
63+
CelAstOptimizer optimizer,
64+
CelAbstractSyntaxTree preAst,
65+
CelAbstractSyntaxTree optimizedAst) {
66+
events.add("pass_end");
67+
}
68+
69+
@Override
70+
public void onOptimizationEnd(
71+
CelAbstractSyntaxTree initialAst, CelAbstractSyntaxTree finalAst) {
72+
events.add("end");
73+
}
74+
75+
@Override
76+
public void onPassFailure(
77+
CelAstOptimizer optimizer, CelAbstractSyntaxTree ast, Exception failure) {
78+
events.add("pass_failure");
79+
}
80+
};
81+
4782
@Test
4883
public void constructCelOptimizer_success() {
4984
CelOptimizer celOptimizer =
@@ -313,4 +348,35 @@ public void optimize_validMacroCalls_success() throws Exception {
313348
assertThat(optimizedAst).isNotNull();
314349
assertThat(optimizedAst.getSource().getMacroCalls()).hasSize(1);
315350
}
351+
352+
@Test
353+
public void optimize_withListener_invokesListenerMethods() throws Exception {
354+
CelOptimizer celOptimizer =
355+
CelOptimizerImpl.newBuilder(CEL)
356+
.addAstOptimizers((navigableAst, cel) -> OptimizationResult.create(navigableAst))
357+
.addOptimizerListeners(listener)
358+
.build();
359+
360+
CelAbstractSyntaxTree ast = CEL.compile("'hello world'").getAst();
361+
CelAbstractSyntaxTree unused = celOptimizer.optimize(ast);
362+
363+
assertThat(events).containsExactly("start", "pass_start", "pass_end", "end").inOrder();
364+
}
365+
366+
@Test
367+
public void optimize_withListener_onPassFailure_invokesListenerMethods() throws Exception {
368+
CelOptimizer celOptimizer =
369+
CelOptimizerImpl.newBuilder(CEL)
370+
.addAstOptimizers(
371+
(navigableAst, cel) -> {
372+
throw new RuntimeException("Test failure");
373+
})
374+
.addOptimizerListeners(listener)
375+
.build();
376+
377+
CelAbstractSyntaxTree ast = CEL.compile("'hello world'").getAst();
378+
assertThrows(CelOptimizationException.class, () -> celOptimizer.optimize(ast));
379+
380+
assertThat(events).containsExactly("start", "pass_start", "pass_failure", "end").inOrder();
381+
}
316382
}

0 commit comments

Comments
 (0)