From 785ef86a9a774db20f8610a1b73f4d5cdb635eb4 Mon Sep 17 00:00:00 2001 From: Mario Fusco Date: Wed, 15 May 2024 08:57:58 +0200 Subject: [PATCH 1/2] [DROOLS-7197] fix generics introspection on superclasses during exec model generation (#5944) --- .../drlxparse/SpecialComparisonCase.java | 13 +- .../expressiontyper/ExpressionTyper.java | 27 +-- .../model/codegen/execmodel/GenericsTest.java | 171 +++++++++++++++++- .../org/drools/mvelcompiler/RHSPhase.java | 12 +- .../ast/FieldToAccessorTExpr.java | 5 + .../mvelcompiler/ast/MethodCallExprT.java | 5 + .../mvelcompiler/ast/TypedExpression.java | 4 + .../mvel/integrationtests/GenericsTest.java | 101 +++++++++++ .../KnownExecModelDifferenceTest.java | 55 ------ .../main/java/org/drools/util/ClassUtils.java | 74 ++++++-- 10 files changed, 355 insertions(+), 112 deletions(-) create mode 100644 drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/GenericsTest.java diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SpecialComparisonCase.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SpecialComparisonCase.java index 7e935b37b12..2378816ac2c 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SpecialComparisonCase.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/drlxparse/SpecialComparisonCase.java @@ -53,9 +53,7 @@ String getMethodName(BinaryExpr.Operator operator) { static SpecialComparisonCase specialComparisonFactory(TypedExpression left, TypedExpression right) { if (isNumber(left) && !isObject(right.getRawClass()) || isNumber(right) && !isObject(left.getRawClass())) { // Don't coerce Object yet. EvaluationUtil will handle it dynamically later - Optional> leftCast = typeNeedsCast(left.getType()); - Optional> rightCast = typeNeedsCast(right.getType()); - if (leftCast.isPresent() || rightCast.isPresent()) { + if (typeNeedsCast(left.getType()) || typeNeedsCast(right.getType())) { return new ComparisonWithCast(true, left, right, of(Number.class), of(Number.class)); } else { return new NumberComparisonWithoutCast(left, right); @@ -67,13 +65,8 @@ static SpecialComparisonCase specialComparisonFactory(TypedExpression left, Type return new PlainEvaluation(left, right); } - private static Optional> typeNeedsCast(Type t) { - boolean needCast = isObject((Class)t) || isMap((Class) t) || isList((Class) t); - if (needCast) { - return of((Class) t); - } else { - return Optional.empty(); - } + private static boolean typeNeedsCast(Type t) { + return t instanceof Class && ( isObject((Class)t) || isMap((Class) t) || isList((Class) t) ); } private static boolean isList(Class t) { diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java index 928ca3f62ac..6cdc43d6d60 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/expressiontyper/ExpressionTyper.java @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; -import java.lang.reflect.TypeVariable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; @@ -94,6 +93,7 @@ import org.drools.mvelcompiler.CompiledExpressionResult; import org.drools.mvelcompiler.ConstraintCompiler; import org.drools.mvelcompiler.util.BigDecimalArgumentCoercion; +import org.drools.util.ClassUtils; import org.drools.util.MethodUtils; import org.drools.util.Pair; import org.drools.util.TypeResolver; @@ -125,6 +125,7 @@ import static org.drools.mvel.parser.MvelParser.parseType; import static org.drools.mvel.parser.printer.PrintUtil.printNode; import static org.drools.util.ClassUtils.extractGenericType; +import static org.drools.util.ClassUtils.actualTypeFromGenerics; import static org.drools.util.ClassUtils.getTypeArgument; import static org.drools.util.ClassUtils.getter2property; import static org.drools.util.ClassUtils.toRawClass; @@ -922,16 +923,7 @@ private TypedExpressionCursor parseMethodCallExpr(MethodCallExpr methodCallExpr, return new TypedExpressionCursor(methodCallExpr, ((ParameterizedType) originalTypeCursor).getActualTypeArguments()[0]); } - java.lang.reflect.Type genericReturnType = m.getGenericReturnType(); - if (genericReturnType instanceof TypeVariable) { - if (originalTypeCursor instanceof ParameterizedType) { - return new TypedExpressionCursor( methodCallExpr, getActualType( rawClassCursor, ( ParameterizedType ) originalTypeCursor, ( TypeVariable ) genericReturnType ) ); - } else { - return new TypedExpressionCursor(methodCallExpr, Object.class); - } - } else { - return new TypedExpressionCursor(methodCallExpr, genericReturnType); - } + return new TypedExpressionCursor(methodCallExpr, actualTypeFromGenerics(originalTypeCursor, m.getGenericReturnType(), rawClassCursor)); } private void promoteBigDecimalParameters(MethodCallExpr methodCallExpr, Class[] argsType, Class[] actualArgumentTypes) { @@ -967,17 +959,6 @@ private Optional checkStartsWithMVEL(MethodCallExpr metho } } - private java.lang.reflect.Type getActualType(Class rawClassCursor, ParameterizedType originalTypeCursor, TypeVariable genericReturnType) { - int genericPos = 0; - for (TypeVariable typeVar : rawClassCursor.getTypeParameters()) { - if (typeVar.equals( genericReturnType )) { - return originalTypeCursor.getActualTypeArguments()[genericPos]; - } - genericPos++; - } - throw new RuntimeException( "Unknonw generic type " + genericReturnType + " for type " + originalTypeCursor ); - } - private TypedExpressionCursor objectCreationExpr(ObjectCreationExpr objectCreationExpr) { parseNodeArguments( objectCreationExpr ); return new TypedExpressionCursor(objectCreationExpr, getClassFromType(ruleContext.getTypeResolver(), objectCreationExpr.getType())); @@ -1163,7 +1144,7 @@ private Optional drlNameExpr(Expression drlxExpr, DrlName return of(new TypedExpressionCursor(addCastToExpression(typeWithoutDollar, fieldAccessor, false), typeOfFirstAccessor ) ); } - return of(new TypedExpressionCursor(fieldAccessor, firstAccessor.getGenericReturnType() ) ); + return of( new TypedExpressionCursor(fieldAccessor, ClassUtils.actualTypeFromGenerics(originalTypeCursor, firstAccessor.getGenericReturnType()) ) ); } Field field = DrlxParseUtil.getField( classCursor, firstName ); diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java index d6358be006e..40ca7d24f61 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java @@ -20,10 +20,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import org.drools.model.codegen.execmodel.domain.Address; import org.drools.model.codegen.execmodel.domain.Person; - import org.junit.Test; import org.kie.api.runtime.KieSession; @@ -117,4 +117,173 @@ public void testClassWithGenericField() { ksession.insert(classWithGenericField); assertThat(ksession.fireAllRules()).isEqualTo(1); } + + @Test + public void testGenericsOnSuperclass() { + // KIE-DROOLS-5925 + String str = + "import " + DieselCar.class.getCanonicalName() + ";\n " + + "dialect \"mvel\"\n" + + "\n" + + "rule \"Diesel vehicles with more than 95 kW use high-octane fuel (diesel has no octane, this is a test)\"\n" + + " when\n" + + " $v: DieselCar(motor.kw > 95, score<=0, !motor.highOctane)\n" + + " then\n" + + " System.out.println(\"Diesel vehicle with more than 95 kW: \" + $v+\", score=\"+$v.score);\n" + + " $v.engine.highOctane = true;\n" + + " update($v);\n" + + "end\n" + + "\n" + + "rule \"High-octane fuel engines newer serial numbers have slightly higher score\"\n" + + " when\n" + + " $v: DieselCar(engine.highOctane, score<=1, motor.serialNumber > 50000)\n" + + " then\n" + + " System.out.println(\"High octane engine vehicle with newer serial number: \" + $v.motor.serialNumber);\n" + + " $v.score = $v.score + 1;\n" + + " update($v);\n" + + "end"; + + KieSession ksession = getKieSession(str); + + DieselCar vehicle1 = new DieselCar("Volkswagen", "Passat", 100); + vehicle1.setFrameMaxTorque(500); + vehicle1.getEngine().setMaxTorque(350); + vehicle1.getEngine().setSerialNumber(75_000); + vehicle1.setScore(0); + + DieselCar vehicle2 = new DieselCar("Peugeot", "208", 50); + vehicle2.setFrameMaxTorque(100); + vehicle2.getEngine().setMaxTorque(200); + vehicle2.setScore(0); + + ksession.insert(vehicle1); + ksession.insert(vehicle2); + assertThat(ksession.fireAllRules()).isEqualTo(3); + } + + public static abstract class Vehicle { + + private final String maker; + private final String model; + + private int score; + + public Vehicle(String maker, String model) { + this.maker = Objects.requireNonNull(maker); + this.model = Objects.requireNonNull(model); + } + + public String getMaker() { + return maker; + } + + public String getModel() { + return model; + } + + public abstract TEngine getEngine(); + + public TEngine getMotor() { + return getEngine(); + } + + public int getScore() { + return score; + } + + public void setScore(int score) { + this.score = score; + } + + @Override + public String toString() { + return "Vehicle{" + "maker='" + maker + '\'' + ", model='" + model + '\'' + '}'; + } + } + + public static abstract class Engine { + + private final int kw; + + public Engine(int kw) { + this.kw = kw; + } + + public int getKw() { + return kw; + } + + public abstract boolean isZeroEmissions(); + + } + + public static class DieselEngine extends Engine { + + // diesel has no octanes... but let's pretend it does + private boolean highOctane; + + private int maxTorque; + + private long serialNumber; + + public DieselEngine(int kw) { + super(kw); + } + + @Override + public boolean isZeroEmissions() { + return false; + } + + public boolean isHighOctane() { + return highOctane; + } + + public void setHighOctane(boolean highOctane) { + this.highOctane = highOctane; + } + + public int getMaxTorque() { + return maxTorque; + } + + public void setMaxTorque(int maxTorque) { + this.maxTorque = maxTorque; + } + + public void setSerialNumber(long serialNumber) { + this.serialNumber = serialNumber; + } + + public long getSerialNumber() { + return serialNumber; + } + + } + + public static class DieselCar extends Vehicle { + private final DieselEngine engine; + + private long frameMaxTorque; + + + + public DieselCar(String maker, String model, int kw) { + super(maker, model); + this.engine = new DieselEngine(kw); + } + + @Override + public DieselEngine getEngine() { + return engine; + } + + public long getFrameMaxTorque() { + return frameMaxTorque; + } + + public void setFrameMaxTorque(long frameMaxTorque) { + this.frameMaxTorque = frameMaxTorque; + } + } } \ No newline at end of file diff --git a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/RHSPhase.java b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/RHSPhase.java index 6751147ef9e..ef026350544 100644 --- a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/RHSPhase.java +++ b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/RHSPhase.java @@ -216,12 +216,14 @@ private Optional asEnum(SimpleName n) { private Optional asPropertyAccessor(SimpleName n, VisitorContext arg) { Optional lastTypedExpression = arg.getScope(); - Optional scopeType = lastTypedExpression.filter(ListAccessExprT.class::isInstance) - .map(ListAccessExprT.class::cast) - .map(expr -> expr.getElementType()) - .orElse(arg.getScopeType()); + Optional propertyType = lastTypedExpression.filter(ListAccessExprT.class::isInstance) + .map(ListAccessExprT.class::cast) + .map(expr -> expr.getElementType()) + .orElse(arg.getScopeType()); - Optional optAccessor = scopeType.flatMap(t -> ofNullable(getAccessor(classFromType(t), n.asString()))); + Optional scopeType = lastTypedExpression.flatMap(TypedExpression::getScopeType); + + Optional optAccessor = propertyType.flatMap(t -> ofNullable(getAccessor(classFromType(t, scopeType.orElse(null)), n.asString()))); return map2(lastTypedExpression, optAccessor, FieldToAccessorTExpr::new); } diff --git a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/FieldToAccessorTExpr.java b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/FieldToAccessorTExpr.java index fb4047e7804..e96e3143890 100644 --- a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/FieldToAccessorTExpr.java +++ b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/FieldToAccessorTExpr.java @@ -68,6 +68,11 @@ public Optional getType() { return Optional.of(type); } + @Override + public Optional getScopeType() { + return scope.getType(); + } + @Override public Node toJavaExpression() { List expressionArguments = this.arguments.stream() diff --git a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/MethodCallExprT.java b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/MethodCallExprT.java index 5d3337edc50..d2d93edce41 100644 --- a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/MethodCallExprT.java +++ b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/MethodCallExprT.java @@ -57,6 +57,11 @@ public Optional getType() { return type; } + @Override + public Optional getScopeType() { + return scope.flatMap(TypedExpression::getScopeType); + } + @Override public Node toJavaExpression() { Node scopeE = scope.map(TypedExpression::toJavaExpression).orElse(null); diff --git a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/TypedExpression.java b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/TypedExpression.java index d83d9a2a078..9f4ee5dd767 100644 --- a/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/TypedExpression.java +++ b/drools-model/drools-mvel-compiler/src/main/java/org/drools/mvelcompiler/ast/TypedExpression.java @@ -28,5 +28,9 @@ public interface TypedExpression { Optional getType(); Node toJavaExpression(); + + default Optional getScopeType() { + return Optional.empty(); + } } diff --git a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/GenericsTest.java b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/GenericsTest.java new file mode 100644 index 00000000000..cb5a94aa2cf --- /dev/null +++ b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/GenericsTest.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mvel.integrationtests; + +import java.util.Collection; + +import org.drools.mvel.integrationtests.facts.vehicles.DieselCar; +import org.drools.mvel.integrationtests.facts.vehicles.ElectricCar; +import org.drools.testcoverage.common.util.KieBaseTestConfiguration; +import org.drools.testcoverage.common.util.KieBaseUtil; +import org.drools.testcoverage.common.util.TestParametersUtil; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.kie.api.KieBase; +import org.kie.api.runtime.KieSession; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This is a place where known behavior differences between exec-model and non-exec-model. + * They are not treated as a bug and should be documented in "Migration from non-executable model to executable model" section + */ +@RunWith(Parameterized.class) +public class GenericsTest { + + private final KieBaseTestConfiguration kieBaseTestConfiguration; + + public GenericsTest(final KieBaseTestConfiguration kieBaseTestConfiguration) { + this.kieBaseTestConfiguration = kieBaseTestConfiguration; + } + + @Parameterized.Parameters(name = "KieBase type={0}") + public static Collection getParameters() { + return TestParametersUtil.getKieBaseCloudConfigurations(true); + } + + @Test + public void property_subClassMethod_genericsReturnType() { + // DROOLS-7197 + String str = "package com.example.reproducer\n" + + "import " + DieselCar.class.getCanonicalName() + ";\n" + + "rule R\n" + + "dialect \"mvel\"\n" + + "when\n" + + " $v : DieselCar(motor.adBlueRequired == true)\n" + + "then\n" + + " $v.score = 5;\n" + + "end"; + + KieBase kbase = KieBaseUtil.getKieBaseFromKieModuleFromDrl("test", kieBaseTestConfiguration, str); + KieSession ksession = kbase.newKieSession(); + + DieselCar dieselCar = new DieselCar("ABC", "Model 1.6", 85, true); + + ksession.insert(dieselCar); + ksession.fireAllRules(); + + assertThat(dieselCar.getScore()).isEqualTo(5); + } + + @Test + public void property_subClassMethod_explicitReturnType() { + // DROOLS-7197 + String str = "package com.example.reproducer\n" + + "import " + ElectricCar.class.getCanonicalName() + ";\n" + + "rule R\n" + + "dialect \"mvel\"\n" + + "when\n" + + " $v : ElectricCar(engine.batterySize > 70)\n" + + "then\n" + + " $v.score = 5;\n" + + "end"; + + KieBase kbase = KieBaseUtil.getKieBaseFromKieModuleFromDrl("test", kieBaseTestConfiguration, str); + KieSession ksession = kbase.newKieSession(); + + ElectricCar electricCar = new ElectricCar("XYZ", "Model 3", 200, 90); + + ksession.insert(electricCar); + ksession.fireAllRules(); + + assertThat(electricCar.getScore()).isEqualTo(5); // works for both cases + } +} diff --git a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KnownExecModelDifferenceTest.java b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KnownExecModelDifferenceTest.java index 5282f09451c..f42c3ab4cce 100644 --- a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KnownExecModelDifferenceTest.java +++ b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/integrationtests/KnownExecModelDifferenceTest.java @@ -25,8 +25,6 @@ import org.drools.mvel.compiler.Person; import org.drools.mvel.integrationtests.facts.VarargsFact; -import org.drools.mvel.integrationtests.facts.vehicles.DieselCar; -import org.drools.mvel.integrationtests.facts.vehicles.ElectricCar; import org.drools.testcoverage.common.util.KieBaseTestConfiguration; import org.drools.testcoverage.common.util.KieBaseUtil; import org.drools.testcoverage.common.util.KieUtil; @@ -164,59 +162,6 @@ public void setter_intToPrimitiveLongCoercionVarargs() { assertThat(fact.getValueList()).containsExactly(10L, 20L); // Coerced with both cases } - @Test - public void property_subClassMethod_genericsReturnType() { - // DROOLS-7197 - String str = "package com.example.reproducer\n" + - "import " + DieselCar.class.getCanonicalName() + ";\n" + - "rule R\n" + - "dialect \"mvel\"\n" + - "when\n" + - " $v : DieselCar(motor.adBlueRequired == true)\n" + - "then\n" + - " $v.score = 5;\n" + - "end"; - - if (kieBaseTestConfiguration.isExecutableModel()) { - KieBuilder kieBuilder = KieUtil.getKieBuilderFromDrls(kieBaseTestConfiguration, false, str); - assertThat(kieBuilder.getResults().hasMessages(Level.ERROR)).isTrue(); // Fail with exec-model - } else { - KieBase kbase = KieBaseUtil.getKieBaseFromKieModuleFromDrl("test", kieBaseTestConfiguration, str); - KieSession ksession = kbase.newKieSession(); - - DieselCar dieselCar = new DieselCar("ABC", "Model 1.6", 85, true); - - ksession.insert(dieselCar); - ksession.fireAllRules(); - - assertThat(dieselCar.getScore()).isEqualTo(5); - } - } - - @Test - public void property_subClassMethod_explicitReturnType() { - // DROOLS-7197 - String str = "package com.example.reproducer\n" + - "import " + ElectricCar.class.getCanonicalName() + ";\n" + - "rule R\n" + - "dialect \"mvel\"\n" + - "when\n" + - " $v : ElectricCar(engine.batterySize > 70)\n" + - "then\n" + - " $v.score = 5;\n" + - "end"; - - KieBase kbase = KieBaseUtil.getKieBaseFromKieModuleFromDrl("test", kieBaseTestConfiguration, str); - KieSession ksession = kbase.newKieSession(); - - ElectricCar electricCar = new ElectricCar("XYZ", "Model 3", 200, 90); - - ksession.insert(electricCar); - ksession.fireAllRules(); - - assertThat(electricCar.getScore()).isEqualTo(5); // works for both cases - } - @Test public void invalid_cast_intToString() { // DROOLS-7198 diff --git a/drools-util/src/main/java/org/drools/util/ClassUtils.java b/drools-util/src/main/java/org/drools/util/ClassUtils.java index 90ac3252286..b9db8dd786f 100644 --- a/drools-util/src/main/java/org/drools/util/ClassUtils.java +++ b/drools-util/src/main/java/org/drools/util/ClassUtils.java @@ -104,8 +104,8 @@ public static Class toNonPrimitiveType(Class c) { if (c == boolean.class) return Boolean.class; return c; } - - + + /** * Please do not use - internal * org/my/Class.xxx -> org.my.Class @@ -315,11 +315,11 @@ public static boolean isMatched(Map patterns, // see http://download.oracle.com/javase/6/docs/api/java/lang/Class.html#getName%28%29 String qualifiedNamespace = className; String name = className; - if( className.indexOf('.') > 0 ) { + if( className.indexOf('.') > 0 ) { qualifiedNamespace = className.substring( 0, className.lastIndexOf( '.' ) ).trim(); name = className.substring( className.lastIndexOf( '.' ) + 1 ).trim(); } - else if( className.indexOf('[') == 0 ) { + else if( className.indexOf('[') == 0 ) { qualifiedNamespace = className.substring(0, className.lastIndexOf('[') ); } Object object = patterns.get( qualifiedNamespace ); @@ -351,7 +351,7 @@ public static String getPackage(Class cls) { } else { dotPos = cls.getName().lastIndexOf( '.' ); } - + if ( dotPos > 0 ) { return cls.getName().substring( 0, dotPos ); @@ -556,17 +556,17 @@ public static Class extractGenericType(Class clazz, final String methodName) } throw new RuntimeException("No generic type"); } - + public static Type getTypeArgument(Type genericType, int index) { return genericType instanceof ParameterizedType ? (( ParameterizedType ) genericType).getActualTypeArguments()[index] : Object.class; } - + public static boolean isAssignableFrom(Type from, Type to) { Class fromClass = toRawClass( from ); Class toClass = toRawClass( to ); return fromClass.isAssignableFrom(toClass) || MethodUtils.areBoxingCompatible(fromClass, toClass); - } + } public static boolean isCollection(Type t) { @@ -577,15 +577,20 @@ public static boolean isCollection(Type t) { } public static Class classFromType(Type t) { - Class clazz; - if(t instanceof Class) { - clazz = (Class) t; - } else if(t instanceof ParameterizedType) { - clazz = (Class) ((ParameterizedType)t).getRawType(); - } else { - throw new UnsupportedOperationException("Unable to parse type"); + return classFromType(t, null); + } + + public static Class classFromType(Type t, Type scope) { + if (t instanceof Class) { + return (Class) t; } - return clazz; + if (t instanceof ParameterizedType) { + return (Class) ((ParameterizedType)t).getRawType(); + } + if (t instanceof TypeVariable && scope != null) { + return classFromType( actualTypeFromGenerics(scope, t) ); + } + throw new UnsupportedOperationException("Unable to parse type"); } public static Class toRawClass(Type type) { @@ -603,7 +608,7 @@ public static Class toRawClass(Type type) { } throw new UnsupportedOperationException( "Unknown type " + type ); } - + public static Class rawType(Type type) { if (type == null) { return null; @@ -670,7 +675,7 @@ public static boolean isConvertible( Class srcPrimitive, Class tgtPrimitiv public static boolean isFinal(Class clazz) { return Modifier.isFinal( clazz.getModifiers() ); } - + public static boolean isInterface(Class clazz) { return Modifier.isInterface( clazz.getModifiers() ); } @@ -914,4 +919,37 @@ public static ClassLoader findParentClassLoader(Class invokingClass) { public static boolean isNumericClass(Class clazz) { return numericClasses.contains(clazz); } + + public static Type actualTypeFromGenerics(Type scope, Type genericType) { + return actualTypeFromGenerics(scope, genericType, toRawClass(scope)); + } + + public static Type actualTypeFromGenerics(Type scope, Type genericType, Class rawClassCursor) { + if (genericType instanceof Class || genericType instanceof ParameterizedType) { + return genericType; + } + if (genericType instanceof TypeVariable typeVar) { + if (scope instanceof ParameterizedType paramType) { + return actualTypeFromGenerics(rawClassCursor, paramType, typeVar); + } + if (scope instanceof Class classCursor && classCursor.getSuperclass() != null && classCursor.getSuperclass() != Object.class) { + return actualTypeFromGenerics(classCursor.getGenericSuperclass(), genericType, classCursor.getSuperclass()); + } + if (typeVar.getBounds().length == 1 && typeVar.getBounds()[0] instanceof Class) { + return typeVar.getBounds()[0]; + } + } + return Object.class; + } + + private static Type actualTypeFromGenerics(Class rawClassCursor, ParameterizedType originalTypeCursor, TypeVariable genericType) { + int genericPos = 0; + for (TypeVariable typeVar : rawClassCursor.getTypeParameters()) { + if (typeVar.equals( genericType )) { + return originalTypeCursor.getActualTypeArguments()[genericPos]; + } + genericPos++; + } + throw new RuntimeException( "Unknonw generic type " + genericType + " for type " + originalTypeCursor ); + } } From 664511036b3562fa11f333810990570705cb52dd Mon Sep 17 00:00:00 2001 From: Mario Fusco Date: Thu, 16 May 2024 12:12:13 +0200 Subject: [PATCH 2/2] [KIE-DROOLS-5943] fix property reactivity in mvel constraint when using redundant pattern binding in constraint (#5953) --- .../base/rule/constraint/Constraint.java | 4 -- .../org/drools/core/reteoo/AlphaNode.java | 6 ++- .../drools/core/reteoo/EntryPointNode.java | 3 +- .../java/org/drools/core/reteoo/FromNode.java | 3 +- .../org/drools/core/reteoo/ObjectSource.java | 4 +- .../drools/core/reteoo/ObjectTypeNode.java | 3 +- .../java/org/drools/core/reteoo/Rete.java | 3 +- .../core/reteoo/RightInputAdapterNode.java | 3 +- .../org/drools/core/reteoo/WindowNode.java | 3 +- .../drools/core/reteoo/MockObjectSink.java | 3 +- .../drools/core/reteoo/MockObjectSource.java | 3 +- .../execmodel/generator/DrlxParseUtil.java | 3 +- .../model/codegen/execmodel/GenericsTest.java | 32 ++++++++++++++ .../execmodel/PropertyReactivityTest.java | 22 ++++++++++ .../java/org/drools/mvel/MVELConstraint.java | 44 ++++++++++--------- .../drools/mvel/model/MockObjectSource.java | 11 ++--- .../traits/core/reteoo/TraitAlphaNode.java | 6 ++- 17 files changed, 112 insertions(+), 44 deletions(-) diff --git a/drools-base/src/main/java/org/drools/base/rule/constraint/Constraint.java b/drools-base/src/main/java/org/drools/base/rule/constraint/Constraint.java index 55975e16d52..c07bf57cdea 100644 --- a/drools-base/src/main/java/org/drools/base/rule/constraint/Constraint.java +++ b/drools-base/src/main/java/org/drools/base/rule/constraint/Constraint.java @@ -83,10 +83,6 @@ void replaceDeclaration(Declaration oldDecl, */ boolean isTemporal(); - default BitMask getListenedPropertyMask(ObjectType objectType, List settableProperties ) { - return getListenedPropertyMask(Optional.empty(), objectType, settableProperties); - } - /** * Returns property reactivity BitMask of this constraint. * diff --git a/drools-core/src/main/java/org/drools/core/reteoo/AlphaNode.java b/drools-core/src/main/java/org/drools/core/reteoo/AlphaNode.java index c618e96d827..9214e71e3bf 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/AlphaNode.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/AlphaNode.java @@ -19,12 +19,14 @@ package org.drools.core.reteoo; import java.util.List; +import java.util.Optional; import org.drools.base.base.ObjectType; import org.drools.base.common.NetworkNode; import org.drools.base.common.RuleBasePartitionId; import org.drools.base.reteoo.BaseTerminalNode; import org.drools.base.reteoo.NodeTypeEnums; +import org.drools.base.rule.Pattern; import org.drools.base.rule.constraint.AlphaNodeFieldConstraint; import org.drools.core.common.InternalFactHandle; import org.drools.core.common.InternalWorkingMemory; @@ -309,8 +311,8 @@ public NetworkNode[] getSinks() { } } - public BitMask calculateDeclaredMask(ObjectType objectType, List settableProperties) { - return constraint.getListenedPropertyMask(objectType, settableProperties); + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType objectType, List settableProperties) { + return constraint.getListenedPropertyMask(Optional.ofNullable(pattern), objectType, settableProperties); } @Override diff --git a/drools-core/src/main/java/org/drools/core/reteoo/EntryPointNode.java b/drools-core/src/main/java/org/drools/core/reteoo/EntryPointNode.java index 3dabbbce2f2..91f4855fb8b 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/EntryPointNode.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/EntryPointNode.java @@ -29,6 +29,7 @@ import org.drools.base.common.RuleBasePartitionId; import org.drools.base.reteoo.NodeTypeEnums; import org.drools.base.rule.EntryPointId; +import org.drools.base.rule.Pattern; import org.drools.core.WorkingMemoryEntryPoint; import org.drools.core.common.BaseNode; import org.drools.core.common.DefaultEventHandle; @@ -418,7 +419,7 @@ public void byPassModifyToBetaNode(InternalFactHandle factHandle, } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { throw new UnsupportedOperationException(); } diff --git a/drools-core/src/main/java/org/drools/core/reteoo/FromNode.java b/drools-core/src/main/java/org/drools/core/reteoo/FromNode.java index e973ae82a9b..d0b4cffc483 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/FromNode.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/FromNode.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.drools.base.base.ObjectType; import org.drools.base.common.NetworkNode; @@ -182,7 +183,7 @@ protected Pattern getLeftInputPattern( BuildContext context ) { @Override protected BitMask setNodeConstraintsPropertyReactiveMask( BitMask mask, ObjectType objectType, List accessibleProperties) { for (int i = 0; i < alphaConstraints.length; i++) { - mask = mask.setAll(alphaConstraints[i].getListenedPropertyMask(objectType, accessibleProperties)); + mask = mask.setAll(alphaConstraints[i].getListenedPropertyMask(Optional.empty(), objectType, accessibleProperties)); } return mask; } diff --git a/drools-core/src/main/java/org/drools/core/reteoo/ObjectSource.java b/drools-core/src/main/java/org/drools/core/reteoo/ObjectSource.java index 15d79645037..faad3ecd97d 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/ObjectSource.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/ObjectSource.java @@ -121,14 +121,14 @@ public void initDeclaredMask(BuildContext context) { if ( isPropertyReactive(context.getRuleBase(), objectType) ) { List settableProperties = getAccessibleProperties( context.getRuleBase(), objectType ); - declaredMask = calculateDeclaredMask(objectType, settableProperties); + declaredMask = calculateDeclaredMask(pattern, objectType, settableProperties); } else { // if property specific is not on, then accept all modification propagations declaredMask = AllSetBitMask.get(); } } - public abstract BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties); + public abstract BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties); public void resetInferredMask() { this.inferredMask = EmptyBitMask.get(); diff --git a/drools-core/src/main/java/org/drools/core/reteoo/ObjectTypeNode.java b/drools-core/src/main/java/org/drools/core/reteoo/ObjectTypeNode.java index c507358b217..dcbac740338 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/ObjectTypeNode.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/ObjectTypeNode.java @@ -34,6 +34,7 @@ import org.drools.base.common.RuleBasePartitionId; import org.drools.base.reteoo.NodeTypeEnums; import org.drools.base.rule.EntryPointId; +import org.drools.base.rule.Pattern; import org.drools.base.time.JobHandle; import org.drools.core.common.DefaultFactHandle; import org.drools.core.common.InternalFactHandle; @@ -169,7 +170,7 @@ public RuleBasePartitionId getPartitionId() { } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { return EmptyBitMask.get(); } diff --git a/drools-core/src/main/java/org/drools/core/reteoo/Rete.java b/drools-core/src/main/java/org/drools/core/reteoo/Rete.java index 64382068e8c..aa4236a891d 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/Rete.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/Rete.java @@ -28,6 +28,7 @@ import org.drools.base.common.RuleBasePartitionId; import org.drools.base.reteoo.NodeTypeEnums; import org.drools.base.rule.EntryPointId; +import org.drools.base.rule.Pattern; import org.drools.core.common.InternalFactHandle; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.common.PropagationContext; @@ -215,7 +216,7 @@ public void byPassModifyToBetaNode(InternalFactHandle factHandle, } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { throw new UnsupportedOperationException(); } } diff --git a/drools-core/src/main/java/org/drools/core/reteoo/RightInputAdapterNode.java b/drools-core/src/main/java/org/drools/core/reteoo/RightInputAdapterNode.java index a2652269f3d..60e1320f88e 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/RightInputAdapterNode.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/RightInputAdapterNode.java @@ -27,6 +27,7 @@ import org.drools.base.common.NetworkNode; import org.drools.base.definitions.rule.impl.RuleImpl; import org.drools.base.reteoo.NodeTypeEnums; +import org.drools.base.rule.Pattern; import org.drools.core.RuleBaseConfiguration; import org.drools.core.common.ActivationsManager; import org.drools.core.common.InternalFactHandle; @@ -290,7 +291,7 @@ public void setLeftInputOtnId(ObjectTypeNodeId leftInputOtnId) { } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { throw new UnsupportedOperationException(); } diff --git a/drools-core/src/main/java/org/drools/core/reteoo/WindowNode.java b/drools-core/src/main/java/org/drools/core/reteoo/WindowNode.java index 84cc481f4d1..d4edd37b8fa 100644 --- a/drools-core/src/main/java/org/drools/core/reteoo/WindowNode.java +++ b/drools-core/src/main/java/org/drools/core/reteoo/WindowNode.java @@ -26,6 +26,7 @@ import org.drools.base.base.ObjectType; import org.drools.base.reteoo.NodeTypeEnums; import org.drools.base.rule.EntryPointId; +import org.drools.base.rule.Pattern; import org.drools.base.rule.constraint.AlphaNodeFieldConstraint; import org.drools.core.RuleBaseConfiguration; import org.drools.core.common.DefaultEventHandle; @@ -300,7 +301,7 @@ public EntryPointId getEntryPoint() { } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { throw new UnsupportedOperationException(); } diff --git a/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSink.java b/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSink.java index cc24330f5f9..54f375829f4 100644 --- a/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSink.java +++ b/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSink.java @@ -26,6 +26,7 @@ import org.drools.base.common.RuleBasePartitionId; import org.drools.base.reteoo.BaseTerminalNode; import org.drools.base.reteoo.NodeTypeEnums; +import org.drools.base.rule.Pattern; import org.drools.core.common.InternalFactHandle; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.common.PropagationContext; @@ -58,7 +59,7 @@ public void retractRightTuple(final TupleImpl rightTuple, } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { return null; } diff --git a/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSource.java b/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSource.java index dae9591c91a..79ecbed0542 100644 --- a/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSource.java +++ b/drools-core/src/test/java/org/drools/core/reteoo/MockObjectSource.java @@ -24,6 +24,7 @@ import org.drools.base.base.ObjectType; import org.drools.base.common.RuleBasePartitionId; +import org.drools.base.rule.Pattern; import org.drools.core.common.InternalFactHandle; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.common.PropagationContext; @@ -89,7 +90,7 @@ public int getType() { } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { throw new UnsupportedOperationException(); } } diff --git a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/DrlxParseUtil.java b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/DrlxParseUtil.java index 2f19929d3c7..7e90c86b7d9 100644 --- a/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/DrlxParseUtil.java +++ b/drools-model/drools-model-codegen/src/main/java/org/drools/model/codegen/execmodel/generator/DrlxParseUtil.java @@ -117,6 +117,7 @@ import static org.drools.model.codegen.execmodel.generator.DslMethodNames.PATTERN_CALL; import static org.drools.model.codegen.execmodel.generator.DslMethodNames.isDslTopLevelNamespace; import static org.drools.model.codegen.execmodel.generator.expressiontyper.ExpressionTyper.findLeftLeafOfNameExprTraversingParent; +import static org.drools.util.ClassUtils.actualTypeFromGenerics; import static org.drools.util.ClassUtils.toRawClass; import static org.drools.util.MethodUtils.findMethod; @@ -161,7 +162,7 @@ public static TypedExpression nameExprToMethodCallExpr(String name, java.lang.re Method accessor = getAccessor(clazz, name, context); if (accessor != null) { MethodCallExpr body = new MethodCallExpr( scope, accessor.getName() ); - return new TypedExpression( body, accessor.getGenericReturnType() ); + return new TypedExpression( body, actualTypeFromGenerics( type, accessor.getGenericReturnType() ) ); } else { // try parse it as inner class for (Class declaredClass : clazz.getClasses()) { diff --git a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java index 40ca7d24f61..c08544f01e9 100644 --- a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java +++ b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/GenericsTest.java @@ -143,6 +143,38 @@ public void testGenericsOnSuperclass() { " update($v);\n" + "end"; + runTestWithGenerics(str); + } + + @Test + public void testGenericsOnSuperclassWithRedundantVariableDeclaration() { + // KIE-DROOLS-5925 + String str = + "import " + DieselCar.class.getCanonicalName() + ";\n " + + "dialect \"mvel\"\n" + + "\n" + + "rule \"Diesel vehicles with more than 95 kW use high-octane fuel (diesel has no octane, this is a test)\"\n" + + " when\n" + + " $v: DieselCar($v.motor.kw > 95, score<=0, !$v.motor.highOctane)\n" + + " then\n" + + " System.out.println(\"Diesel vehicle with more than 95 kW: \" + $v+\", score=\"+$v.score);\n" + + " $v.engine.highOctane = true;\n" + + " update($v);\n" + + "end\n" + + "\n" + + "rule \"High-octane fuel engines newer serial numbers have slightly higher score\"\n" + + " when\n" + + " $v: DieselCar($v.engine.highOctane, $v.score<=1, $v.motor.serialNumber > 50000)\n" + + " then\n" + + " System.out.println(\"High octane engine vehicle with newer serial number: \" + $v.motor.serialNumber);\n" + + " $v.score = $v.score + 1;\n" + + " update($v);\n" + + "end"; + + runTestWithGenerics(str); + } + + private void runTestWithGenerics(String str) { KieSession ksession = getKieSession(str); DieselCar vehicle1 = new DieselCar("Volkswagen", "Passat", 100); 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 54e0672de07..45569cdf540 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 @@ -1858,4 +1858,26 @@ public void bindOnlyListPropertyWithAccessOperatorReacts() { assertThat(fired).isEqualTo(10); } + + @Test + public void testPropertyReactivityWithRedundantVariableDeclaration() { + // KIE-DROOLS-5943 + final String str = + "import " + Person.class.getCanonicalName() + ";\n" + + "\n" + + "rule R when\n" + + " $p : Person( $p.name == \"Mario\" )\n" + + "then\n" + + " $p.setAge( $p.getAge()+1 );\n" + + " update($p);\n" + + "end\n"; + + KieSession ksession = getKieSession( str ); + + Person p = new Person("Mario", 40); + ksession.insert( p ); + ksession.fireAllRules(3); + + assertThat(p.getAge()).isEqualTo(41); + } } 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 e493281d7e5..515d3f13021 100644 --- a/drools-mvel/src/main/java/org/drools/mvel/MVELConstraint.java +++ b/drools-mvel/src/main/java/org/drools/mvel/MVELConstraint.java @@ -18,6 +18,25 @@ */ package org.drools.mvel; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + import org.drools.base.RuleBase; import org.drools.base.RuleBuildContext; import org.drools.base.base.DroolsQuery; @@ -43,7 +62,6 @@ import org.drools.base.util.index.ConstraintTypeOperator; import org.drools.compiler.rule.builder.EvaluatorWrapper; import org.drools.core.impl.KnowledgeBaseImpl; -import org.drools.util.bitmask.BitMask; import org.drools.kiesession.rulebase.InternalKnowledgeBase; import org.drools.mvel.ConditionAnalyzer.CombinedCondition; import org.drools.mvel.ConditionAnalyzer.Condition; @@ -56,6 +74,7 @@ import org.drools.mvel.accessors.ClassFieldReader; import org.drools.mvel.expr.MVELCompilationUnit; import org.drools.mvel.extractors.MVELObjectClassFieldReader; +import org.drools.util.bitmask.BitMask; import org.drools.wiring.api.classloader.ProjectClassLoader; import org.kie.api.KieBaseConfiguration; import org.kie.api.runtime.rule.FactHandle; @@ -68,25 +87,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Externalizable; -import java.io.IOException; -import java.io.ObjectInput; -import java.io.ObjectOutput; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicInteger; - import static org.drools.base.reteoo.PropertySpecificUtil.allSetBitMask; import static org.drools.base.reteoo.PropertySpecificUtil.allSetButTraitBitMask; import static org.drools.base.reteoo.PropertySpecificUtil.getEmptyPropertyReactiveMask; @@ -476,6 +476,10 @@ private BitMask calculateMaskFromExpression(Optional pattern, List p.getDeclaration() != null && originalPropertyName.equals(p.getDeclaration().getBindingName())).orElse(false)) { + // drop first property part if it is (redundantly) equal to the declared pattern binding name + continue; + } if (propertyName == null || propertyName.equals("this") || propertyName.length() == 0) { return allSetButTraitBitMask(); } diff --git a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/model/MockObjectSource.java b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/model/MockObjectSource.java index 24e290d6cad..be2736c340a 100644 --- a/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/model/MockObjectSource.java +++ b/drools-test-coverage/test-compiler-integration/src/test/java/org/drools/mvel/model/MockObjectSource.java @@ -18,8 +18,13 @@ */ package org.drools.mvel.model; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + import org.drools.base.base.ObjectType; import org.drools.base.common.RuleBasePartitionId; +import org.drools.base.rule.Pattern; import org.drools.core.common.InternalFactHandle; import org.drools.core.common.InternalWorkingMemory; import org.drools.core.common.PropagationContext; @@ -28,10 +33,6 @@ import org.drools.core.reteoo.builder.BuildContext; import org.drools.util.bitmask.BitMask; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - public class MockObjectSource extends ObjectSource { private static final long serialVersionUID = 510l; @@ -90,7 +91,7 @@ public int getType() { } @Override - public BitMask calculateDeclaredMask(ObjectType modifiedType, List settableProperties) { + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType modifiedType, List settableProperties) { throw new UnsupportedOperationException(); } } diff --git a/drools-traits/src/main/java/org/drools/traits/core/reteoo/TraitAlphaNode.java b/drools-traits/src/main/java/org/drools/traits/core/reteoo/TraitAlphaNode.java index 068e690da07..202434ea324 100644 --- a/drools-traits/src/main/java/org/drools/traits/core/reteoo/TraitAlphaNode.java +++ b/drools-traits/src/main/java/org/drools/traits/core/reteoo/TraitAlphaNode.java @@ -19,8 +19,10 @@ package org.drools.traits.core.reteoo; import java.util.List; +import java.util.Optional; import org.drools.base.base.ObjectType; +import org.drools.base.rule.Pattern; import org.drools.traits.core.base.evaluators.IsAEvaluatorDefinition; import org.drools.core.reteoo.AlphaNode; import org.drools.core.reteoo.ObjectSource; @@ -41,8 +43,8 @@ public TraitAlphaNode(int id, AlphaNodeFieldConstraint constraint, ObjectSource } @Override - public BitMask calculateDeclaredMask(ObjectType objectType, List settableProperties) { - BitMask mask = constraint.getListenedPropertyMask(objectType, settableProperties); + public BitMask calculateDeclaredMask(Pattern pattern, ObjectType objectType, List settableProperties) { + BitMask mask = constraint.getListenedPropertyMask(Optional.ofNullable(pattern), objectType, settableProperties); if (isTraitEvaluator()) { return mask.set(PropertySpecificUtil.TRAITABLE_BIT); }