Allow rules to have optional facts

pull/391/head
Nicolas Vervelle 3 years ago
parent d445083153
commit e066ed25ce

@ -77,6 +77,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

@ -30,7 +30,12 @@ import java.lang.reflect.Method;
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 @@ class RuleDefinitionValidator {
&& validParameters(method);
}
private static final Set<String> 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 @@ class RuleDefinitionValidator {
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;
}

@ -163,15 +163,23 @@ public class RuleProxy implements InvocationHandler {
List<Object> 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;

@ -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<org.jeasy.rules.api.Rule, Boolean> 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) {
}
}
}

@ -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

@ -37,6 +37,7 @@
<system-lambda.version>1.1.1</system-lambda.version>
<slf4j.version>1.7.30</slf4j.version>
<jackson.version>2.11.3</jackson.version>
<jsr305.version>3.0.2</jsr305.version>
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
@ -112,6 +113,12 @@
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

Loading…
Cancel
Save