diff --git a/easy-rules-core/pom.xml b/easy-rules-core/pom.xml index b3a0faf5..1b88cdf6 100644 --- a/easy-rules-core/pom.xml +++ b/easy-rules-core/pom.xml @@ -77,6 +77,11 @@ mockito-core test + + com.google.code.findbugs + jsr305 + test + diff --git a/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleDefinitionValidator.java b/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleDefinitionValidator.java index 7511879c..fe34b899 100644 --- a/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleDefinitionValidator.java +++ b/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleDefinitionValidator.java @@ -30,7 +30,12 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; + import org.jeasy.rules.annotation.Action; import org.jeasy.rules.annotation.Condition; import org.jeasy.rules.annotation.Fact; @@ -117,20 +122,24 @@ private boolean isConditionMethodWellDefined(final Method method) { && validParameters(method); } + private static final Set OTHER_ANNOTATIONS = Collections.singleton("javax.annotation.Nullable"); + private boolean validParameters(final Method method) { int notAnnotatedParameterCount = 0; Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (Annotation[] annotations : parameterAnnotations) { - if (annotations.length == 0) { - notAnnotatedParameterCount += 1; - } else { - //Annotation types has to be Fact - for (Annotation annotation : annotations) { - if (!annotation.annotationType().equals(Fact.class)) { - return false; - } + boolean annotatedAsFact = false; + for (Annotation annotation : annotations) { + //Annotation types has to be Fact or another accepted annotation + if (annotation.annotationType().equals(Fact.class)) { + annotatedAsFact = true; + } else if (!OTHER_ANNOTATIONS.contains(annotation.annotationType().getCanonicalName())) { + return false; } } + if (!annotatedAsFact) { + notAnnotatedParameterCount += 1; + } } if (notAnnotatedParameterCount > 1) { return false; @@ -147,6 +156,15 @@ private boolean validParameters(final Method method) { private Parameter getNotAnnotatedParameter(Method method) { Parameter[] parameters = method.getParameters(); for (Parameter parameter : parameters) { + boolean hasAnnotation = false; + for (Annotation annotation : parameter.getAnnotations()) { + if (annotation.annotationType().equals(Fact.class)) { + hasAnnotation = true; + } + } + if (!hasAnnotation) { + return parameter; + } if (parameter.getAnnotations().length == 0) { return parameter; } diff --git a/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleProxy.java b/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleProxy.java index 399ef8ff..437620e0 100644 --- a/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleProxy.java +++ b/easy-rules-core/src/main/java/org/jeasy/rules/core/RuleProxy.java @@ -163,15 +163,23 @@ private List getActualParameters(Method method, Facts facts) { List actualParameters = new ArrayList<>(); Annotation[][] parameterAnnotations = method.getParameterAnnotations(); for (Annotation[] annotations : parameterAnnotations) { - if (annotations.length == 1) { - String factName = ((Fact) (annotations[0])).value(); //validated upfront. - Object fact = facts.get(factName); - if (fact == null && !facts.asMap().containsKey(factName)) { + String factName = null; + boolean annotatedAsNullable = false; + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(Fact.class)) { + factName = ((Fact) annotation).value(); + } else if ("javax.annotation.Nullable".equals(annotation.annotationType().getCanonicalName())) { + annotatedAsNullable = true; + } + } + if (factName != null) { + Object fact = facts.get(factName); //validated upfront. + if (fact == null && !facts.asMap().containsKey(factName) && !annotatedAsNullable) { throw new NoSuchFactException(format("No fact named '%s' found in known facts: %n%s", factName, facts), factName); } actualParameters.add(fact); } else { - actualParameters.add(facts); //validated upfront, there may be only one parameter not annotated and which is of type Facts.class + actualParameters.add(facts); //validated upfront, there may be only one parameter not annotated as Fact and which is of type Facts.class } } return actualParameters; diff --git a/easy-rules-core/src/test/java/org/jeasy/rules/core/OptionalFactAnnotationParameterTest.java b/easy-rules-core/src/test/java/org/jeasy/rules/core/OptionalFactAnnotationParameterTest.java new file mode 100644 index 00000000..7fd171d8 --- /dev/null +++ b/easy-rules-core/src/test/java/org/jeasy/rules/core/OptionalFactAnnotationParameterTest.java @@ -0,0 +1,71 @@ +/* + * The MIT License + * + * Copyright (c) 2021, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jeasy.rules.core; + +import org.jeasy.rules.annotation.Action; +import org.jeasy.rules.annotation.Condition; +import org.jeasy.rules.annotation.Fact; +import org.jeasy.rules.annotation.Rule; +import org.jeasy.rules.api.Facts; +import org.jeasy.rules.api.Rules; +import org.junit.Assert; +import org.junit.Test; + +import javax.annotation.Nullable; +import java.util.Map; + +/** + * Null facts are not accepted by design, a declared fact can be missing though. + */ +public class OptionalFactAnnotationParameterTest extends AbstractTest { + + @Test + public void testMissingFact() { + Rules rules = new Rules(); + rules.register(new AnnotatedParametersRule()); + + Facts facts = new Facts(); + facts.put("fact1", new Object()); + + Map results = rulesEngine.check(rules, facts); + + for (boolean b : results.values()) { + Assert.assertTrue(b); + } + } + + @Rule + public static class AnnotatedParametersRule { + + @Condition + public boolean when(@Fact("fact1") Object fact1, @Nullable @Fact("fact2") Object fact2) { + return fact1 != null && fact2 == null; + } + + @Action + public void then(@Fact("fact1") Object fact1, @Nullable @Fact("fact2") Object fact2) { + } + + } +} diff --git a/licence-header-template.txt b/licence-header-template.txt index dc24825c..e0431cfc 100644 --- a/licence-header-template.txt +++ b/licence-header-template.txt @@ -1,6 +1,6 @@ The MIT License - Copyright (c) ${currentYear}, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + Copyright (c) 2021, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pom.xml b/pom.xml index 426ac55c..469216ea 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ 1.1.1 1.7.30 2.11.3 + 3.0.2 2.5.3 3.8.1 2.22.2 @@ -112,6 +113,12 @@ ${mockito.version} test + + com.google.code.findbugs + jsr305 + ${jsr305.version} + test +