| @@ -0,0 +1,3 @@ | |||
| *.iml | |||
| .idea/ | |||
| target/ | |||
| @@ -0,0 +1,37 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>co.wuunder</groupId> | |||
| <artifactId>rule-engine</artifactId> | |||
| <version>1.0-SNAPSHOT</version> | |||
| <properties> | |||
| <maven.compiler.source>1.8</maven.compiler.source> | |||
| <maven.compiler.target>1.8</maven.compiler.target> | |||
| </properties> | |||
| <build> | |||
| <plugins> | |||
| <plugin> | |||
| <artifactId>maven-compiler-plugin</artifactId> | |||
| <version>3.5.1</version> | |||
| <configuration> | |||
| <source>1.8</source> | |||
| <target>1.8</target> | |||
| </configuration> | |||
| </plugin> | |||
| </plugins> | |||
| </build> | |||
| <dependencies> | |||
| <dependency> | |||
| <groupId>org.junit.jupiter</groupId> | |||
| <artifactId>junit-jupiter-engine</artifactId> | |||
| <version>5.0.3</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| </dependencies> | |||
| </project> | |||
| @@ -0,0 +1,19 @@ | |||
| package co.wuunder.rule_engine; | |||
| public class Address { | |||
| public final String country; | |||
| public final String business; | |||
| public Address(String country, String business) { | |||
| this.country = country; | |||
| this.business = business; | |||
| } | |||
| public String type() { | |||
| if(this.business == null || this.business.isEmpty()) { | |||
| return "consumer"; | |||
| } else { | |||
| return "business"; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| package co.wuunder.rule_engine; | |||
| import java.util.List; | |||
| import java.util.Random; | |||
| import java.util.stream.IntStream; | |||
| import static java.util.stream.Collectors.toList; | |||
| public class RandomRules { | |||
| private static final Random random = new Random(System.currentTimeMillis()); | |||
| public static List<RateRule> randomRules(int numberOfRules) { | |||
| return IntStream.range(0, numberOfRules) | |||
| .mapToObj(RandomRules::randomRule) | |||
| .collect(toList()); | |||
| } | |||
| private static RateRule randomRule(int _n) { | |||
| return new RateRule(random.nextInt(), random.nextInt(), random.nextInt(), random.nextInt() % 2 == 0, random.nextInt(), "NL", "consumer", "NL", "consumer"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| package co.wuunder.rule_engine; | |||
| public class RateRule { | |||
| public final int minWeight; | |||
| public final int maxWeight; | |||
| public final int maxLengthHeightWidth; | |||
| public final boolean isReturn; | |||
| public final int maxDimension; | |||
| public final String pickupCountry; | |||
| public final String pickupAddressType; | |||
| public final String deliveryCountry; | |||
| public final String deliveryAddressType; | |||
| public RateRule(int minWeight, int maxWeight, int maxLengthHeightWidth, boolean isReturn, | |||
| int maxDimension, String pickupCountry, String pickupAddressType, String deliveryCountry, | |||
| String deliveryAddressType) { | |||
| this.minWeight = minWeight; | |||
| this.maxWeight = maxWeight; | |||
| this.maxLengthHeightWidth = maxLengthHeightWidth; | |||
| this.isReturn = isReturn; | |||
| this.maxDimension = maxDimension; | |||
| this.pickupCountry = pickupCountry; | |||
| this.pickupAddressType = pickupAddressType; | |||
| this.deliveryCountry = deliveryCountry; | |||
| this.deliveryAddressType = deliveryAddressType; | |||
| } | |||
| @Override | |||
| public boolean equals(Object o) { | |||
| if (this == o) return true; | |||
| if (o == null || getClass() != o.getClass()) return false; | |||
| RateRule rateRule = (RateRule) o; | |||
| if (minWeight != rateRule.minWeight) return false; | |||
| if (maxWeight != rateRule.maxWeight) return false; | |||
| if (maxLengthHeightWidth != rateRule.maxLengthHeightWidth) return false; | |||
| if (isReturn != rateRule.isReturn) return false; | |||
| if (maxDimension != rateRule.maxDimension) return false; | |||
| if (pickupCountry != null ? !pickupCountry.equals(rateRule.pickupCountry) : rateRule.pickupCountry != null) | |||
| return false; | |||
| if (pickupAddressType != null ? !pickupAddressType.equals(rateRule.pickupAddressType) : rateRule.pickupAddressType != null) | |||
| return false; | |||
| if (deliveryCountry != null ? !deliveryCountry.equals(rateRule.deliveryCountry) : rateRule.deliveryCountry != null) | |||
| return false; | |||
| return deliveryAddressType != null ? deliveryAddressType.equals(rateRule.deliveryAddressType) : rateRule.deliveryAddressType == null; | |||
| } | |||
| @Override | |||
| public int hashCode() { | |||
| int result = minWeight; | |||
| result = 31 * result + maxWeight; | |||
| result = 31 * result + maxLengthHeightWidth; | |||
| result = 31 * result + (isReturn ? 1 : 0); | |||
| result = 31 * result + maxDimension; | |||
| result = 31 * result + (pickupCountry != null ? pickupCountry.hashCode() : 0); | |||
| result = 31 * result + (pickupAddressType != null ? pickupAddressType.hashCode() : 0); | |||
| result = 31 * result + (deliveryCountry != null ? deliveryCountry.hashCode() : 0); | |||
| result = 31 * result + (deliveryAddressType != null ? deliveryAddressType.hashCode() : 0); | |||
| return result; | |||
| } | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| package co.wuunder.rule_engine; | |||
| import java.util.Arrays; | |||
| import java.util.List; | |||
| import java.util.function.BiPredicate; | |||
| import static java.util.stream.Collectors.toList; | |||
| public class RuleEngine { | |||
| private final List<RateRule> rules; | |||
| private final List<BiPredicate<Shipment, RateRule>> functions; | |||
| public RuleEngine(List<RateRule> rules) { | |||
| this.rules = rules; | |||
| this.functions = Arrays.asList( | |||
| Rules::matchesHeight, | |||
| Rules::matchesLength, | |||
| Rules::matchesWeight, | |||
| Rules::matchesWidth, | |||
| Rules::matchesReturn, | |||
| Rules::matchesDimensions, | |||
| Rules::matchesPickupCountry, | |||
| Rules::matchesPickupAddressType, | |||
| Rules::matchesDeliveryCountry, | |||
| Rules::matchesDeliveryAddressType | |||
| ); | |||
| } | |||
| public List<RateRule> generateRules(Shipment shipment) { | |||
| return rules.stream().filter(rule -> matchesRule(shipment, rule)).collect(toList()); | |||
| } | |||
| private boolean matchesRule(Shipment shipment, RateRule rateRule) { | |||
| return functions.stream().allMatch(f -> f.test(shipment, rateRule)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| package co.wuunder.rule_engine; | |||
| public class Rules { | |||
| public static boolean matchesWeight(Shipment shipment, RateRule rateRule) { | |||
| return shipment.weight >= rateRule.minWeight && shipment.weight <= rateRule.maxWeight; | |||
| } | |||
| public static boolean matchesLength(Shipment shipment, RateRule rateRule) { | |||
| return shipment.length < rateRule.maxLengthHeightWidth; | |||
| } | |||
| public static boolean matchesWidth(Shipment shipment, RateRule rateRule) { | |||
| return shipment.width < rateRule.maxLengthHeightWidth; | |||
| } | |||
| public static boolean matchesHeight(Shipment shipment, RateRule rateRule) { | |||
| return shipment.height < rateRule.maxLengthHeightWidth; | |||
| } | |||
| public static boolean matchesDimensions(Shipment shipment, RateRule rateRule) { | |||
| return shipment.dimensions() <= rateRule.maxDimension; | |||
| } | |||
| public static boolean matchesPickupCountry(Shipment shipment, RateRule rateRule) { | |||
| return shipment.pickupCountry().equals(rateRule.pickupCountry); | |||
| } | |||
| public static boolean matchesPickupAddressType(Shipment shipment, RateRule rateRule) { | |||
| return shipment.pickupAddressType().equals(rateRule.pickupAddressType); | |||
| } | |||
| public static boolean matchesDeliveryCountry(Shipment shipment, RateRule rateRule) { | |||
| return shipment.deliveryCountry().equals(rateRule.deliveryCountry); | |||
| } | |||
| public static boolean matchesDeliveryAddressType(Shipment shipment, RateRule rateRule) { | |||
| return shipment.deliveryAddressType().equals(rateRule.deliveryAddressType); | |||
| } | |||
| public static boolean matchesReturn(Shipment shipment, RateRule rateRule) { | |||
| return shipment.isReturn == rateRule.isReturn; | |||
| } | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| package co.wuunder.rule_engine; | |||
| public class Shipment { | |||
| public final int weight; | |||
| public final int width; | |||
| public final int height; | |||
| public final int length; | |||
| public final boolean isReturn; | |||
| public final boolean dropoff; | |||
| public final Address pickupAddress; | |||
| public final Address deliveryAddress; | |||
| public Shipment(int weight, int width, int height, int length, boolean isReturn, boolean dropoff, | |||
| Address pickupAddress, Address deliveryAddress) { | |||
| this.weight = weight; | |||
| this.width = width; | |||
| this.height = height; | |||
| this.length = length; | |||
| this.isReturn = isReturn; | |||
| this.dropoff = dropoff; | |||
| this.pickupAddress = pickupAddress; | |||
| this.deliveryAddress = deliveryAddress; | |||
| } | |||
| public String deliveryCountry() { | |||
| return this.deliveryAddress.country; | |||
| } | |||
| public String deliveryAddressType() { | |||
| if(this.dropoff) { | |||
| return "parcelshop"; | |||
| } | |||
| return this.deliveryAddress.type(); | |||
| } | |||
| public String pickupCountry() { | |||
| return this.pickupAddress.country; | |||
| } | |||
| public String pickupAddressType() { | |||
| if(this.dropoff) { | |||
| return "parcelshop"; | |||
| } | |||
| return this.pickupAddress.type(); | |||
| } | |||
| public int dimensions() { | |||
| double dimensions = (2.0 * this.weight) + (2 * this.height) + this.length; | |||
| return (int)Math.ceil(dimensions); | |||
| } | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| import co.wuunder.rule_engine.Address; | |||
| import co.wuunder.rule_engine.RuleEngine; | |||
| import co.wuunder.rule_engine.Shipment; | |||
| import org.junit.jupiter.api.Test; | |||
| import java.util.Random; | |||
| import static co.wuunder.rule_engine.RandomRules.*; | |||
| public class PerfTest { | |||
| private final RuleEngine ruleEngine = new RuleEngine(randomRules(100_000)); | |||
| @Test | |||
| public void perf() { | |||
| long start = System.currentTimeMillis(); | |||
| int numberOfShipments = 50; | |||
| for(int i = 0 ; i < numberOfShipments; i++) { | |||
| ruleEngine.generateRules(randomShipment()); | |||
| } | |||
| long end = System.currentTimeMillis(); | |||
| System.out.println((end - start) / numberOfShipments); | |||
| } | |||
| private Shipment randomShipment() { | |||
| Random random = new Random(System.currentTimeMillis()); | |||
| Address pickupAddress = new Address("NL", ""); | |||
| Address deliveryAddress = new Address("NL", ""); | |||
| return new Shipment(random.nextInt(), random.nextInt(), random.nextInt(), random.nextInt(), random.nextInt() % 2 == 0, random.nextInt() % 2 == 0, pickupAddress, deliveryAddress); | |||
| } | |||
| } | |||