diff --git a/drools-compiler/src/main/java/org/drools/compiler/rule/builder/PatternBuilder.java b/drools-compiler/src/main/java/org/drools/compiler/rule/builder/PatternBuilder.java index c924584ae94..d76f82dc601 100644 --- a/drools-compiler/src/main/java/org/drools/compiler/rule/builder/PatternBuilder.java +++ b/drools-compiler/src/main/java/org/drools/compiler/rule/builder/PatternBuilder.java @@ -114,6 +114,8 @@ import static org.drools.compiler.rule.builder.util.PatternBuilderUtil.getNormalizeDate; import static org.drools.compiler.rule.builder.util.PatternBuilderUtil.normalizeEmptyKeyword; import static org.drools.compiler.rule.builder.util.PatternBuilderUtil.normalizeStringOperator; +import static org.drools.util.StringUtils.doesFirstPropHaveListMapAccessor; +import static org.drools.util.StringUtils.extractFirstIdentifier; import static org.drools.util.StringUtils.isIdentifier; /** @@ -1474,8 +1476,12 @@ protected void buildRuleBindings(RuleBuildContext context, declr.setReadAccessor(extractor); - if (!declr.isFromXpathChunk() && typeDeclaration != null && extractor instanceof FieldNameSupplier) { - addFieldToPatternWatchlist(pattern, typeDeclaration, ((FieldNameSupplier) extractor).getFieldName()); + if (!declr.isFromXpathChunk() && typeDeclaration != null) { + if (extractor instanceof FieldNameSupplier) { + addFieldToPatternWatchlist(pattern, typeDeclaration, ((FieldNameSupplier) extractor).getFieldName()); + } else if (doesFirstPropHaveListMapAccessor(fieldBindingDescr.getBindingField())) { + addFieldToPatternWatchlist(pattern, typeDeclaration, extractFirstIdentifier(fieldBindingDescr.getBindingField(), 0)); // extractor is MVELObjectClassFieldReader + } } } diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyReactivityTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyReactivityTest.java index 35ea079bf03..018773fa003 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyReactivityTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/PropertyReactivityTest.java @@ -1637,4 +1637,96 @@ private void testVariousCasePropFact(String modifyStatement, String... expectedR assertThat(results).containsExactly(expectedResults); } + + @Test + public void bindOnlyPropertyReacts() { + // DROOLS-7214 + final String str = + "import " + Person.class.getCanonicalName() + ";\n" + + "dialect \"mvel\"\n" + + "rule R when\n" + + " $p : Person( name == \"Mario\", $age : age )\n" + + "then\n" + + " modify($p) { age = $age + 1 };\n" + + "end\n"; + + KieSession ksession = getKieSession(str); + + Person p = new Person("Mario", 40); + ksession.insert(p); + int fired = ksession.fireAllRules(10); // intentional loop + + assertThat(fired).isEqualTo(10); + } + + @Test + public void bindOnlyMapPropertyReacts() { + // DROOLS-7214 + final String str = + "import " + Person.class.getCanonicalName() + ";\n" + + "dialect \"mvel\"\n" + + "rule R when\n" + + " $p : Person( name == \"Mario\", $map : itemsString )\n" + + "then\n" + + " $p.itemsString[\"B\"] = \"itemB\";\n" + + " modify($p) { itemsString = $p.itemsString };\n" + + "end\n"; + + KieSession ksession = getKieSession(str); + + Person p = new Person("Mario", 40); + p.getItemsString().put("A", "itemA"); + ksession.insert(p); + int fired = ksession.fireAllRules(10); // intentional loop + + assertThat(fired).isEqualTo(10); + } + + @Test + public void bindOnlyMapPropertyWithAccessOperatorReacts() { + // DROOLS-7214 + final String str = + "import " + Person.class.getCanonicalName() + ";\n" + + "dialect \"mvel\"\n" + + "rule R when\n" + + " $p : Person( name == \"Mario\", $mapDataA : itemsString[\"A\"] )\n" + + "then\n" + + " $p.itemsString[\"B\"] = \"itemB\";\n" + + " modify($p) { itemsString = $p.itemsString };\n" + + "end\n"; + + KieSession ksession = getKieSession(str); + + Person p = new Person("Mario", 40); + p.getItemsString().put("A", "itemA"); + ksession.insert(p); + int fired = ksession.fireAllRules(10); // intentional loop + + assertThat(fired).isEqualTo(10); + } + + @Test + public void bindOnlyListPropertyWithAccessOperatorReacts() { + // DROOLS-7214 + final String str = + "import " + Person.class.getCanonicalName() + ";\n" + + "import " + Address.class.getCanonicalName() + ";\n" + + "dialect \"mvel\"\n" + + "rule R when\n" + + " $p : Person( name == \"Mario\", $listData0 : addresses[0] )\n" + + "then\n" + + " $p.addresses.add(new Address(\"C\"));\n" + + " modify($p) { addresses = $p.addresses };\n" + + "end\n"; + + KieSession ksession = getKieSession(str); + + Person p = new Person("Mario", 40); + p.getAddresses().add(new Address("A")); + p.getAddresses().add(new Address("B")); + ksession.insert(p); + int fired = ksession.fireAllRules(10); // intentional loop + + assertThat(fired).isEqualTo(10); + } } diff --git a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraint.java b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraint.java index 373417669c5..39487277a2e 100644 --- a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraint.java +++ b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraint.java @@ -88,6 +88,7 @@ import static org.drools.util.StringUtils.codeAwareIndexOf; import static org.drools.util.StringUtils.equalsIgnoreSpaces; import static org.drools.util.StringUtils.extractFirstIdentifier; +import static org.drools.util.StringUtils.lookAheadIgnoringSpaces; import static org.drools.util.StringUtils.skipBlanks; public class MVELConstraint extends MutableTypeConstraint implements IndexableConstraint, AcceptsReadAccessor { @@ -533,8 +534,8 @@ private int nextPropertyName(String expression, List names, int cursor) } if (!isAccessor) { - String lookAhead = lookAheadIgnoringSpaces(expression, cursor); - boolean isMethodInvocation = lookAhead != null && lookAhead.equals("("); + Character lookAhead = lookAheadIgnoringSpaces(expression, cursor); + boolean isMethodInvocation = lookAhead != null && lookAhead.equals('('); if (isMethodInvocation) { return nextPropertyName(expression, names, cursor); } @@ -546,17 +547,6 @@ private int nextPropertyName(String expression, List names, int cursor) return skipOperator(expression, cursor); } - private String lookAheadIgnoringSpaces(String expression, int cursor) { - while (cursor < expression.length()) { - char c = expression.charAt(cursor); - if (!Character.isWhitespace(c)) { - return "" + c; - } - cursor++; - } - return null; - } - private int skipOperator(String expression, int cursor) { if (cursor < expression.length() && expression.charAt(cursor) == '.') { while (cursor < expression.length() && Character.isWhitespace(expression.charAt(++cursor))); diff --git a/drools-util/src/main/java/org/drools/util/StringUtils.java b/drools-util/src/main/java/org/drools/util/StringUtils.java index 40eb6135cb0..b861192d93e 100644 --- a/drools-util/src/main/java/org/drools/util/StringUtils.java +++ b/drools-util/src/main/java/org/drools/util/StringUtils.java @@ -1366,4 +1366,22 @@ public static boolean equalsIgnoreSpaces(String s1, String s2) { public static String uuid() { return "x" + UUID.randomUUID().toString().replace( '-', 'x' ); } + + public static boolean doesFirstPropHaveListMapAccessor(String expression) { + StringBuilder propertyNameBuilder = new StringBuilder(); + int cursor = extractFirstIdentifier(expression, propertyNameBuilder, 0); + Character nextChar = lookAheadIgnoringSpaces(expression, cursor); + return nextChar != null && nextChar.equals('['); + } + + public static Character lookAheadIgnoringSpaces(String expression, int cursor) { + while (cursor < expression.length()) { + char c = expression.charAt(cursor); + if (!Character.isWhitespace(c)) { + return c; + } + cursor++; + } + return null; + } }