Skip to content

Commit

Permalink
RuleAdapter support for unnamed facts of wildcarded collection types (d…
Browse files Browse the repository at this point in the history
…eliveredtechnologies#173)

* RuleAdapter support for unnamed facts of wildcarded collection types

* fixed spelling pojoeWithWildCardCollections -> pojoWithWildCardCollections

* removed unused imports
  • Loading branch information
BrianDeacon authored and Clayton7510 committed Sep 25, 2019
1 parent 771032f commit 7838ecf
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.deliveredtechnologies.rulebook.model.runner;



import com.deliveredtechnologies.rulebook.NameValueReferable;
import com.deliveredtechnologies.rulebook.NameValueReferableMap;
import com.deliveredtechnologies.rulebook.Result;
Expand All @@ -13,6 +12,10 @@
import com.deliveredtechnologies.rulebook.model.Rule;
import com.deliveredtechnologies.rulebook.model.RuleChainActionType;
import com.deliveredtechnologies.rulebook.model.RuleException;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -223,6 +226,45 @@ public Optional<Result> getResult() {
return _rule.getResult();
}

private List<Class<?>> eligibleParameterTypes(Field field) {
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType)genericType;
Type[] typeArguments = parameterizedType.getActualTypeArguments();
if (typeArguments != null && typeArguments.length > 0) {
Type typeArgument = typeArguments[0];
if (typeArgument instanceof Class) {
return Collections.singletonList((Class<?>)typeArgument);
} else if (typeArgument instanceof WildcardType) {
WildcardType wildcardType = (WildcardType) typeArgument;
return Stream.of(wildcardType.getUpperBounds())
.filter(type -> type instanceof Class)
.map(type -> (Class<?>) type)
.collect(Collectors.toList());
} else if (typeArgument instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable)typeArgument;
return Stream.of(typeVariable.getBounds())
.filter(type -> type instanceof Class)
.map(type -> (Class<?>) type)
.collect(Collectors.toList());
}
}
}
return Collections.emptyList();
}

private boolean isCompatibleGenericType(Field field, Object factObject) {
final Object factValue;
if (factObject instanceof NameValueReferable) {
factValue = ((NameValueReferable) factObject).getValue();
} else {
factValue = null;
}
return factValue != null
&& eligibleParameterTypes(field).stream()
.anyMatch(type -> type.isAssignableFrom(factValue.getClass()));
}

/**
* Convert the Facts to properties with the @Given annotation in the class.
* If any matched properties are non-Facts, then the value of the associated Facts are mapped to those
Expand All @@ -247,16 +289,13 @@ private void mapFactsToProperties(NameValueReferableMap facts) {
} else if (Collection.class.isAssignableFrom(field.getType())) {
//set a Collection of Fact object values
Stream stream = facts.values().stream()
.filter(fact -> { //filter on only facts that contain objects matching the generic type
ParameterizedType paramType = (ParameterizedType)field.getGenericType();
Class<?> genericType = (Class<?>)paramType.getActualTypeArguments()[0];
return genericType.equals(((NameValueReferable) fact).getValue().getClass());
})
.filter(fact -> isCompatibleGenericType(field, fact))
.map(fact -> {
ParameterizedType paramType = (ParameterizedType)field.getGenericType();
Class<?> genericType = (Class<?>)paramType.getActualTypeArguments()[0];
return genericType.cast(((NameValueReferable)fact).getValue());
Object factValue = ((NameValueReferable) fact).getValue();
Class<?> targetType = eligibleParameterTypes(field).iterator().next();
return targetType.cast(factValue);
});

if (List.class == field.getType()) {
//map List of Fact values to field
field.set(_pojoRule, stream.collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@
import com.deliveredtechnologies.rulebook.model.GoldenRule;
import com.deliveredtechnologies.rulebook.model.Rule;
import com.deliveredtechnologies.rulebook.model.runner.rule.result.ResultRule;
import com.deliveredtechnologies.rulebook.runner.test.rulebooks.RuleWithWildcardCollections;
import com.deliveredtechnologies.rulebook.runner.test.rulebooks.SampleRuleWithoutAnnotations;
import com.deliveredtechnologies.rulebook.runner.test.rulebooks.SampleRuleWithResult;
import com.deliveredtechnologies.rulebook.runner.test.rulebooks.SampleRuleWithoutRuleAnnotation;
import com.deliveredtechnologies.rulebook.runner.test.rulebooks.SubRuleWithResult;
import com.deliveredtechnologies.rulebook.runner.test.rulebooks.SampleRuleWithoutResult;

import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -36,6 +38,7 @@
/**
* Tests for {@link RuleAdapter}.
*/

public class RuleAdapterTest {

private FactMap<Object> _factMap;
Expand Down Expand Up @@ -295,6 +298,26 @@ public void suppliedThenConsumerShouldTakePrecendenceOverPojo() throws InvalidCl
Assert.assertTrue(consumer == ((List<Object>)ruleAdapter.getActions()).get(0));
}

@Test
public void pojoWithWildCardCollections() throws InvalidClassException {
RuleWithWildcardCollections<Number> rule = new RuleWithWildcardCollections<>();
RuleAdapter ruleAdapter = new RuleAdapter(rule);
Fact<String> testStringFact = new Fact("stringFactName", "A string value");
Fact<String> testStringFact2 = new Fact("secondStringFactName", "A different string value");
Fact<Integer> testIntegerFact = new Fact("integerFactName", Integer.MAX_VALUE);
Fact<Long> testLongFact = new Fact("longFactName", Long.MAX_VALUE);
ruleAdapter.addFacts(testStringFact, testStringFact2, testIntegerFact, testLongFact);
ruleAdapter.invoke();
Set<CharSequence> stringResults = (Set<CharSequence>)ruleAdapter.getResult().get().getValue();
List<Number> numbers = rule._numberSet;
Assert.assertEquals(2, numbers.size());
Assert.assertTrue(numbers.contains(testIntegerFact.getValue()));
Assert.assertTrue(numbers.contains(testLongFact.getValue()));
Assert.assertEquals(2, stringResults.size());
Assert.assertTrue(stringResults.contains(testStringFact.getValue()));
Assert.assertTrue(stringResults.contains(testStringFact2.getValue()));
}

@Test
public void pojoWithNoThenAnnotationDefaultsToNext() throws InvalidClassException {
SampleRuleWithoutAnnotations sampleRule = new SampleRuleWithoutAnnotations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public void ruleBookRunnerShouldAddRuleClassesInPackage() {
RuleBookRunner ruleBookRunner = spy(new RuleBookRunner("com.deliveredtechnologies.rulebook.runner.test.rulebooks"));
ruleBookRunner.run();

verify(ruleBookRunner, times(4)).addRule(any(RuleAdapter.class));
verify(ruleBookRunner, times(5)).addRule(any(RuleAdapter.class));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.deliveredtechnologies.rulebook.runner.test.rulebooks;

import com.deliveredtechnologies.rulebook.RuleState;
import com.deliveredtechnologies.rulebook.annotation.Given;
import com.deliveredtechnologies.rulebook.annotation.Result;
import com.deliveredtechnologies.rulebook.annotation.Rule;
import com.deliveredtechnologies.rulebook.annotation.Then;
import java.util.List;
import java.util.Set;



@Rule
public class RuleWithWildcardCollections<T extends Number> {

@Given
public Set<? extends CharSequence> _charSequenceSet;

@Given
public List<T> _numberSet;

@Result
Set<? extends CharSequence> _result;

@Then
public RuleState then() {
_result = _charSequenceSet;
return RuleState.NEXT;
}
}

0 comments on commit 7838ecf

Please sign in to comment.