| *.iml | |||||
| .idea/ | |||||
| target/ |
| <?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> |
| 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"; | |||||
| } | |||||
| } | |||||
| } |
| 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"); | |||||
| } | |||||
| } |
| 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; | |||||
| } | |||||
| } |
| 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)); | |||||
| } | |||||
| } |
| 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; | |||||
| } | |||||
| } |
| 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); | |||||
| } | |||||
| } |
| 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); | |||||
| } | |||||
| } |