diff --git a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java index 023584c32..7b84826f1 100644 --- a/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java +++ b/common/src/main/java/dev/cel/common/ast/CelMutableExpr.java @@ -171,6 +171,10 @@ public int hashCode() { return name.hashCode(); } + private CelMutableIdent deepCopy() { + return new CelMutableIdent(name); + } + private CelMutableIdent(String name) { this.name = checkNotNull(name); } @@ -206,6 +210,10 @@ public void setTestOnly(boolean testOnly) { this.testOnly = testOnly; } + private CelMutableSelect deepCopy() { + return create(newInstance(operand()), field, testOnly); + } + @Override public boolean equals(Object obj) { if (obj == this) { @@ -321,6 +329,13 @@ public int hashCode() { return h; } + private CelMutableCall deepCopy() { + List copiedArgs = deepCopyList(args); + return target().isPresent() + ? create(newInstance(target.get()), function, copiedArgs) + : create(function, copiedArgs); + } + public static CelMutableCall create(String function, CelMutableExpr... args) { return create(function, Arrays.asList(checkNotNull(args))); } @@ -399,6 +414,10 @@ public int hashCode() { return h; } + private CelMutableCreateList deepCopy() { + return create(deepCopyList(elements), optionalIndices); + } + public static CelMutableCreateList create(CelMutableExpr... elements) { return create(Arrays.asList(checkNotNull(elements))); } @@ -489,6 +508,10 @@ public void setOptionalEntry(boolean optionalEntry) { this.optionalEntry = optionalEntry; } + private Entry deepCopy() { + return create(id, fieldKey, newInstance(value), optionalEntry); + } + public static Entry create(long id, String fieldKey, CelMutableExpr value) { return create(id, fieldKey, value, false); } @@ -557,6 +580,15 @@ public int hashCode() { return h; } + private CelMutableCreateStruct deepCopy() { + ArrayList copiedEntries = new ArrayList<>(); + for (CelMutableCreateStruct.Entry entry : entries) { + copiedEntries.add(entry.deepCopy()); + } + + return create(messageName, copiedEntries); + } + public static CelMutableCreateStruct create( String messageName, List entries) { return new CelMutableCreateStruct(messageName, entries); @@ -657,6 +689,10 @@ public int hashCode() { return h; } + private Entry deepCopy() { + return create(id, newInstance(key), newInstance(value), optionalEntry); + } + public static Entry create(CelMutableExpr key, CelMutableExpr value) { return create(0, key, value, false); } @@ -698,6 +734,15 @@ public int hashCode() { return h; } + private CelMutableCreateMap deepCopy() { + ArrayList copiedEntries = new ArrayList<>(); + for (CelMutableCreateMap.Entry entry : entries) { + copiedEntries.add(entry.deepCopy()); + } + + return create(copiedEntries); + } + public static CelMutableCreateMap create(List entries) { return new CelMutableCreateMap(new ArrayList<>(entries)); } @@ -818,6 +863,17 @@ public int hashCode() { return h; } + private CelMutableComprehension deepCopy() { + return create( + iterVar, + newInstance(iterRange), + accuVar, + newInstance(accuInit), + newInstance(loopCondition), + newInstance(loopStep), + newInstance(result)); + } + public static CelMutableComprehension create( String iterVar, CelMutableExpr iterRange, @@ -917,6 +973,11 @@ public static CelMutableExpr ofComprehension( return new CelMutableExpr(id, mutableComprehension); } + /** Constructs a deep copy of the mutable expression. */ + public static CelMutableExpr newInstance(CelMutableExpr other) { + return new CelMutableExpr(other); + } + private CelMutableExpr(long id, CelConstant mutableConstant) { this.id = id; setConstant(mutableConstant); @@ -967,6 +1028,40 @@ private CelMutableExpr() { this.exprKind = ExprKind.Kind.NOT_SET; } + private CelMutableExpr(CelMutableExpr other) { + checkNotNull(other); + this.id = other.id; + this.exprKind = other.exprKind; + switch (other.getKind()) { + case CONSTANT: + this.exprValue = other.exprValue; // Constant is immutable. + break; + case IDENT: + this.exprValue = other.ident().deepCopy(); + break; + case SELECT: + this.exprValue = other.select().deepCopy(); + break; + case CALL: + this.exprValue = other.call().deepCopy(); + break; + case CREATE_LIST: + this.exprValue = other.createList().deepCopy(); + break; + case CREATE_STRUCT: + this.exprValue = other.createStruct().deepCopy(); + break; + case CREATE_MAP: + this.exprValue = other.createMap().deepCopy(); + break; + case COMPREHENSION: + this.exprValue = other.comprehension().deepCopy(); + break; + default: + throw new IllegalStateException("Unexpected expr kind: " + this.exprKind); + } + } + private Object exprValue() { switch (this.exprKind) { case NOT_SET: @@ -992,6 +1087,15 @@ private Object exprValue() { throw new IllegalStateException("Unexpected expr kind: " + this.exprKind); } + private static List deepCopyList(List elements) { + ArrayList copiedArgs = new ArrayList<>(); + for (CelMutableExpr arg : elements) { + copiedArgs.add(newInstance(arg)); + } + + return copiedArgs; + } + private void checkExprKind(ExprKind.Kind exprKind) { checkArgument(this.exprKind.equals(exprKind), "Invalid ExprKind: %s", exprKind); } diff --git a/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java b/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java index 41b2c44b9..90901fc05 100644 --- a/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java +++ b/common/src/test/java/dev/cel/common/ast/CelMutableExprTest.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.testing.EqualsTester; +import com.google.common.truth.Correspondence; import com.google.testing.junit.testparameterinjector.TestParameter; import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.ast.CelExpr.ExprKind.Kind; @@ -68,6 +69,19 @@ public void ofConstant_withId() { assertThat(mutableExpr.constant()).isEqualTo(CelConstant.ofValue(5L)); } + @Test + public void mutableConstant_deepCopy() { + CelMutableExpr mutableExpr = CelMutableExpr.ofConstant(1L, CelConstant.ofValue(5L)); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.constant()).isEqualTo(deepCopiedExpr.constant()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + // The stored constant itself is immutable, thus remain referentially equal when copied. + assertThat(mutableExpr.constant()).isSameInstanceAs(deepCopiedExpr.constant()); + } + @Test public void ofIdent() { CelMutableExpr mutableExpr = CelMutableExpr.ofIdent("x"); @@ -93,6 +107,18 @@ public void mutableIdent_setName() { assertThat(ident.name()).isEqualTo("y"); } + @Test + public void mutableIdent_deepCopy() { + CelMutableExpr mutableExpr = CelMutableExpr.ofIdent(1L, "x"); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.ident()).isEqualTo(deepCopiedExpr.ident()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.ident()).isNotSameInstanceAs(deepCopiedExpr.ident()); + } + @Test public void ofSelect() { CelMutableExpr mutableExpr = @@ -131,6 +157,21 @@ public void mutableSelect_setters() { assertThat(select.testOnly()).isFalse(); } + @Test + public void mutableSelect_deepCopy() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofSelect( + 1L, + CelMutableSelect.create(CelMutableExpr.ofIdent("x"), "field", /* testOnly= */ true)); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.select()).isEqualTo(deepCopiedExpr.select()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.select()).isNotSameInstanceAs(deepCopiedExpr.select()); + } + @Test public void ofCall() { CelMutableExpr mutableExpr = @@ -245,6 +286,24 @@ public void mutableCall_setTarget() { assertThat(call.target()).hasValue(CelMutableExpr.ofConstant(CelConstant.ofValue("hello"))); } + @Test + public void mutableCall_deepCopy() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofCall( + 1L, + CelMutableCall.create( + CelMutableExpr.ofConstant(CelConstant.ofValue("target")), + "function", + CelMutableExpr.ofConstant(CelConstant.ofValue("arg")))); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.call()).isEqualTo(deepCopiedExpr.call()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.call()).isNotSameInstanceAs(deepCopiedExpr.call()); + } + @Test public void mutableCall_setFunction() { CelMutableCall call = CelMutableCall.create("function"); @@ -303,6 +362,29 @@ public void mutableCreateList_setElementAtIndex() { assertThat(createList.elements()).isInstanceOf(ArrayList.class); } + @Test + @SuppressWarnings("ReferenceEquality") // test only on iterating through elements + public void mutableCreateList_deepCopy() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofCreateList( + CelMutableCreateList.create( + CelMutableExpr.ofConstant(CelConstant.ofValue("element1")), + CelMutableExpr.ofConstant(CelConstant.ofValue("element2")))); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.createList()).isEqualTo(deepCopiedExpr.createList()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.createList()).isNotSameInstanceAs(deepCopiedExpr.createList()); + assertThat(mutableExpr.createList().elements()) + .comparingElementsUsing( + Correspondence.from( + (e1, e2) -> e1 != e2 && e1.equals(e2), + "are only value equal and not referentially equal")) + .containsExactlyElementsIn(deepCopiedExpr.createList().elements()); + } + @Test public void ofCreateStruct() { CelMutableExpr mutableExpr = @@ -375,6 +457,41 @@ public void mutableCreateStructEntry_setters() { 2L, "field2", CelMutableExpr.ofConstant(CelConstant.ofValue("value2")), true)); } + @Test + @SuppressWarnings("ReferenceEquality") // test only on iterating through elements + public void mutableCreateStruct_deepCopy() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofCreateStruct( + 8L, + CelMutableCreateStruct.create( + "message", + ImmutableList.of( + CelMutableCreateStruct.Entry.create( + 8L, + "field", + CelMutableExpr.ofConstant(CelConstant.ofValue("value")), + /* optionalEntry= */ true)))); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.createStruct()).isEqualTo(deepCopiedExpr.createStruct()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.createStruct()).isNotSameInstanceAs(deepCopiedExpr.createStruct()); + assertThat(mutableExpr.createStruct().entries()) + .isNotSameInstanceAs(deepCopiedExpr.createStruct().entries()); + assertThat(mutableExpr.createStruct().entries()) + .comparingElementsUsing( + Correspondence.from( + (e1, e2) -> + e1 != e2 + && e1.equals(e2) + && e1.value() != e2.value() + && e1.value().equals(e2.value()), + "are only value equal and not referentially equal")) + .containsExactlyElementsIn(deepCopiedExpr.createStruct().entries()); + } + @Test public void ofCreateMap() { CelMutableExpr mutableExpr = @@ -450,6 +567,40 @@ public void mutableCreateMapEntry_setters() { true)); } + @Test + @SuppressWarnings("ReferenceEquality") // test only on iterating through elements + public void mutableCreateMap_deepCopy() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofCreateMap( + 9L, + CelMutableCreateMap.create( + ImmutableList.of( + CelMutableCreateMap.Entry.create( + 10L, + CelMutableExpr.ofConstant(CelConstant.ofValue("key")), + CelMutableExpr.ofConstant(CelConstant.ofValue("value")), + /* optionalEntry= */ true)))); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr.createMap()).isEqualTo(deepCopiedExpr.createMap()); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.createMap()).isNotSameInstanceAs(deepCopiedExpr.createMap()); + assertThat(mutableExpr.createMap().entries()) + .comparingElementsUsing( + Correspondence.from( + (e1, e2) -> + e1 != e2 + && e1.equals(e2) + && e1.key() != e2.key() + && e1.key().equals(e2.key()) + && e1.value() != e2.value() + && e1.value().equals(e2.value()), + "are only value equal and not referentially equal")) + .containsExactlyElementsIn(deepCopiedExpr.createMap().entries()); + } + @Test public void ofComprehension_withId() { CelMutableExpr mutableExpr = @@ -517,6 +668,50 @@ public void mutableComprehension_setters() { CelMutableExpr.ofIdent("__result__"))); } + @Test + public void mutableComprehension_deepCopy() { + CelMutableExpr mutableExpr = + CelMutableExpr.ofComprehension( + 10L, + CelMutableComprehension.create( + "iterVar", + CelMutableExpr.ofCreateList( + CelMutableCreateList.create( + CelMutableExpr.ofConstant(CelConstant.ofValue(true)))), + "accuVar", + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofConstant(CelConstant.ofValue(true)), + CelMutableExpr.ofIdent("__result__"))); + + CelMutableExpr deepCopiedExpr = CelMutableExpr.newInstance(mutableExpr); + + assertThat(mutableExpr).isEqualTo(deepCopiedExpr); + assertThat(mutableExpr).isNotSameInstanceAs(deepCopiedExpr); + assertThat(mutableExpr.comprehension()).isEqualTo(deepCopiedExpr.comprehension()); + assertThat(mutableExpr.comprehension()).isNotSameInstanceAs(deepCopiedExpr.comprehension()); + assertThat(mutableExpr.comprehension().accuInit()) + .isEqualTo(deepCopiedExpr.comprehension().accuInit()); + assertThat(mutableExpr.comprehension().accuInit()) + .isNotSameInstanceAs(deepCopiedExpr.comprehension().accuInit()); + assertThat(mutableExpr.comprehension().iterRange()) + .isEqualTo(deepCopiedExpr.comprehension().iterRange()); + assertThat(mutableExpr.comprehension().iterRange()) + .isNotSameInstanceAs(deepCopiedExpr.comprehension().iterRange()); + assertThat(mutableExpr.comprehension().loopCondition()) + .isEqualTo(deepCopiedExpr.comprehension().loopCondition()); + assertThat(mutableExpr.comprehension().loopCondition()) + .isNotSameInstanceAs(deepCopiedExpr.comprehension().loopCondition()); + assertThat(mutableExpr.comprehension().loopStep()) + .isEqualTo(deepCopiedExpr.comprehension().loopStep()); + assertThat(mutableExpr.comprehension().loopStep()) + .isNotSameInstanceAs(deepCopiedExpr.comprehension().loopStep()); + assertThat(mutableExpr.comprehension().result()) + .isEqualTo(deepCopiedExpr.comprehension().result()); + assertThat(mutableExpr.comprehension().result()) + .isNotSameInstanceAs(deepCopiedExpr.comprehension().result()); + } + @Test public void equalityTest() { new EqualsTester()