From 3c1f54cd23e345f1f22448267051483fe2795b3c Mon Sep 17 00:00:00 2001 From: Toshiya Kobayashi Date: Thu, 4 Aug 2022 16:17:00 +0900 Subject: [PATCH] =?UTF-8?q?[DROOLS-7088]=20Property=20reactivity=20discrep?= =?UTF-8?q?ancy=20between=20executable=20mode=E2=80=A6=20(#4585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [DROOLS-7088] Property reactivity discrepancy between executable model and non executable model with a capitalized property - unit tests * - fixed non-exec-model - added more tests - added import --- .../modelcompiler/PropertyReactivityTest.java | 66 ++++++++++++++++++ .../domain/VariousCasePropFact.java | 68 +++++++++++++++++++ .../mvel/builder/MVELConsequenceBuilder.java | 14 +++- 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/domain/VariousCasePropFact.java diff --git a/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/PropertyReactivityTest.java b/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/PropertyReactivityTest.java index f7937a24276..0fcd38931a6 100644 --- a/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/PropertyReactivityTest.java +++ b/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/PropertyReactivityTest.java @@ -27,6 +27,7 @@ import org.drools.modelcompiler.domain.Person; import org.drools.modelcompiler.domain.Pet; import org.drools.modelcompiler.domain.Result; +import org.drools.modelcompiler.domain.VariousCasePropFact; import org.junit.Test; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.Message.Level; @@ -34,6 +35,7 @@ import org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.FactHandle; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -1573,4 +1575,68 @@ public void testMvelModifyAfterSingleQuote() { assertEquals(41, p.getAge()); assertEquals(1, ksession.getObjects((Object object) -> object.equals("ok")).size()); } + + @Test + public void testOnlyFirstLetterIsUpperCaseProperty() { + // See JavaBeans 1.01 spec : 8.8 Capitalization of inferred names + // “FooBah” becomes “fooBah” + testVariousCasePropFact("modify($f) { MyTarget = \"123\" };", "R1", "R2"); // Actually, this modifies "myTarget" property (backed by private "MyTarget" field). This shouldn't react R1 + } + + @Test + public void testTwoFirstLettersAreUpperCaseProperty() { + // See JavaBeans 1.01 spec : 8.8 Capitalization of inferred names + // “URL” becomes “URL” + testVariousCasePropFact("modify($f) { URL = \"123\" };", "R1", "R2"); // This shouldn't react R1 + } + + @Test + public void testFirstLetterIsMultibyteProperty() { + // Multibyte is not mentioned in JavaBeans spec + testVariousCasePropFact("modify($f) { 名前 = \"123\" };", "R1", "R2"); // This shouldn't react R1 + } + + @Test + public void testOnlyFirstLetterIsUpperCaseAndMultibyteProperty() { + // Multibyte is not mentioned in JavaBeans spec + testVariousCasePropFact("modify($f) { My名前 = \"123\" };", "R1", "R2"); // Actually, this modifies "my名前" property (backed by private "My名前" field). This shouldn't react R1 + } + + @Test + public void testOnlyFirstLetterIsUpperCasePublicFieldProperty() { + testVariousCasePropFact("modify($f) { MyPublicTarget = \"123\" };", "R1", "R2"); // this modifies "MyPublicTarget" public field directly. This shouldn't react R1 + } + + private void testVariousCasePropFact(String modifyStatement, String... expectedResults) { + final String str = + "import " + VariousCasePropFact.class.getCanonicalName() + ";\n" + + "dialect \"mvel\"\n" + + "global java.util.List results;\n" + + "rule R1\n" + + "salience 100\n" + + "when\n" + + " $f : VariousCasePropFact( value == \"A\" )\n" + + "then\n" + + " results.add(\"R1\")\n" + + "end\n" + + "rule R2\n" + + "no-loop\n" + + "when\n" + + " $f : VariousCasePropFact( value == \"A\" )\n" + + "then\n" + + " results.add(\"R2\");\n" + + modifyStatement + "\n" + + "end\n"; + + KieSession ksession = getKieSession(str); + List results = new ArrayList<>(); + ksession.setGlobal("results", results); + + VariousCasePropFact fact = new VariousCasePropFact(); + fact.setValue("A"); + ksession.insert(fact); + ksession.fireAllRules(); + + assertThat(results).containsExactly(expectedResults); + } } diff --git a/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/domain/VariousCasePropFact.java b/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/domain/VariousCasePropFact.java new file mode 100644 index 00000000000..fdd827f3836 --- /dev/null +++ b/drools-model/drools-model-compiler/src/test/java/org/drools/modelcompiler/domain/VariousCasePropFact.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.drools.modelcompiler.domain; + +public class VariousCasePropFact { + + private String value; // lower only + private String MyTarget; // upper + lower + private String URL; // upper + upper + private String 名前; // multibyte + private String My名前; // upper + lower + multibyte + public String MyPublicTarget; // public field : upper + lower + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getMyTarget() { + return MyTarget; + } + + public void setMyTarget(String myTarget) { + MyTarget = myTarget; + } + + public String getURL() { + return URL; + } + + public void setURL(String uRL) { + URL = uRL; + } + + public String get名前() { + return 名前; + } + + public void set名前(String 名前) { + this.名前 = 名前; + } + + public String getMy名前() { + return My名前; + } + + public void setMy名前(String my名前) { + My名前 = my名前; + } + +} diff --git a/drools-mvel/src/main/java/org/drools/mvel/builder/MVELConsequenceBuilder.java b/drools-mvel/src/main/java/org/drools/mvel/builder/MVELConsequenceBuilder.java index f03bdc8261a..d316495911e 100644 --- a/drools-mvel/src/main/java/org/drools/mvel/builder/MVELConsequenceBuilder.java +++ b/drools-mvel/src/main/java/org/drools/mvel/builder/MVELConsequenceBuilder.java @@ -14,6 +14,7 @@ package org.drools.mvel.builder; +import java.beans.Introspector; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -269,6 +270,7 @@ public static String rewriteUpdates( Function> classResolver, F continue; } BitMask modificationMask = getEmptyPropertyReactiveMask(settableProperties.size()); + boolean directAccess = false; for (String expr : splitStatements(text)) { if (expr.startsWith( identifier + "." )) { @@ -285,10 +287,12 @@ public static String rewriteUpdates( Function> classResolver, F if (expr.length() > endMethodArgs+1 && expr.substring(endMethodArgs+1).trim().startsWith(".")) { propertyName = Character.toLowerCase(propertyName.charAt(3)) + propertyName.substring(4); } + } else { + directAccess = true; } } - int index = settableProperties.indexOf(propertyName); + int index = findPropertyIndex(settableProperties, propertyName, directAccess); if (index >= 0) { modificationMask = setPropertyOnMask(modificationMask, index); } else { @@ -307,6 +311,14 @@ public static String rewriteUpdates( Function> classResolver, F return text; } + private static int findPropertyIndex(List settableProperties, String propertyName, boolean directAccess) { + int index = settableProperties.indexOf(propertyName); + if (index < 0 && directAccess) { + index = settableProperties.indexOf(Introspector.decapitalize(propertyName)); // e.g. "MyTarget" in mvel can be a property "myTarget" + } + return index; + } + public static String processMacros(String consequence) { MacroProcessor macroProcessor = new MacroProcessor(); macroProcessor.setMacros( macros );