diff --git a/cola-components/cola-component-ruleengine/README.md b/cola-components/cola-component-ruleengine/README.md new file mode 100644 index 000000000..35a95bbbf --- /dev/null +++ b/cola-components/cola-component-ruleengine/README.md @@ -0,0 +1,20 @@ +## 介绍 +这是COLA规则引擎 + +## 使用 +hello world 案例: +```java + RuleEngine ruleEngine = new DefaultRuleEngine(); + Rule rule = new RuleBuilder() + .name("hello world rule") + .description("always say hello world") + .priority(1) + .when(facts -> true) + .then(facts -> System.out.println("hello world")) + .build(); + Rules rules = new Rules(); + rules.register(rule); + + ruleEngine.fire(rules, null); +``` + diff --git a/cola-components/cola-component-ruleengine/gitignore.txt b/cola-components/cola-component-ruleengine/gitignore.txt new file mode 100644 index 000000000..9a1655a05 --- /dev/null +++ b/cola-components/cola-component-ruleengine/gitignore.txt @@ -0,0 +1,26 @@ +target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +bin/ +doc/ +.DS_Store diff --git a/cola-components/cola-component-ruleengine/pom.xml b/cola-components/cola-component-ruleengine/pom.xml new file mode 100644 index 000000000..ff0028faf --- /dev/null +++ b/cola-components/cola-component-ruleengine/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + com.alibaba.cola + cola-components-parent + 4.4.0-SNAPSHOT + + + cola-component-ruleengine + jar + ${project.artifactId}:${project.version} + ${project.artifactId} + https://github.com/alibaba/COLA + + + + GNU Lesser General Public License v2.1 + https://github.com/alibaba/COLA/blob/master/LICENSE + repo + + + + scm:git:https://github.com/alibaba/COLA.git + scm:git:https://github.com/alibaba/COLA.git + https://github.com/alibaba/COLA + + + https://github.com/alibaba/COLA/issues + GitHub Issues + + + + significantfrank + Frank Zhang + 25216348(at)qq.com + + Developer + Architect + + +8 + https://github.com/significantfrank + + + oldratlee + Jerry Lee + oldratlee(at)gmail.com + + Developer + CI/SCM Engineer + + +8 + https://github.com/oldratlee + + + + + + org.slf4j + slf4j-api + + + + + org.slf4j + slf4j-simple + test + + + junit + junit + test + + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + + + diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Action.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Action.java new file mode 100644 index 000000000..9638c84ef --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Action.java @@ -0,0 +1,7 @@ +package com.alibaba.cola.ruleengine.api; + +@FunctionalInterface +public interface Action { + void execute(Facts facts); +} + diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Condition.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Condition.java new file mode 100644 index 000000000..f5d90a73f --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Condition.java @@ -0,0 +1,36 @@ +package com.alibaba.cola.ruleengine.api; + +import java.util.Objects; + +@FunctionalInterface +public interface Condition { + + boolean evaluate(Facts facts); + + //谓词and逻辑,参考Predicate + default Condition and(Condition other) { + Objects.requireNonNull(other); + return (facts) -> { + return this.evaluate(facts) && other.evaluate(facts); + }; + } + + //谓词or逻辑,参考Predicate + default Condition or(Condition other) { + Objects.requireNonNull(other); + return (facts) -> { + return this.evaluate(facts) || other.evaluate(facts); + }; + } + + /** + * A NoOp {@link Condition} that always returns false. + */ + Condition FALSE = facts -> false; + + /** + * A NoOp {@link Condition} that always returns true. + */ + Condition TRUE = facts -> true; +} + diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Fact.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Fact.java new file mode 100644 index 000000000..b4d4d358e --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Fact.java @@ -0,0 +1,63 @@ +package com.alibaba.cola.ruleengine.api; + +import java.util.Objects; + +public class Fact { + + private final String name; + private final T value; + + /** + * Create a new fact. + * @param name of the fact + * @param value of the fact + */ + public Fact(String name, T value) { + Objects.requireNonNull(name, "name must not be null"); + Objects.requireNonNull(value, "value must not be null"); + this.name = name; + this.value = value; + } + + /** + * Get the fact name. + * @return fact name + */ + public String getName() { + return name; + } + + /** + * Get the fact value. + * @return fact value + */ + public T getValue() { + return value; + } + + @Override + public String toString() { + return "Fact{" + + "name='" + name + '\'' + + ", value=" + value + + '}'; + } + + /* + * The Facts API represents a namespace for facts where each fact has a unique name. + * Hence, equals/hashcode are deliberately calculated only on the fact name. + */ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Fact fact = (Fact) o; + return name.equals(fact.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Facts.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Facts.java new file mode 100644 index 000000000..68b089554 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Facts.java @@ -0,0 +1,140 @@ +package com.alibaba.cola.ruleengine.api; + +import java.util.*; + +public class Facts implements Iterable> { + + private final Set> facts = new HashSet<>(); + + /** + * Add a fact, replacing any fact with the same name. + * + * @param name of the fact to add, must not be null + * @param value of the fact to add, must not be null + */ + public void put(String name, T value) { + Objects.requireNonNull(name, "fact name must not be null"); + Objects.requireNonNull(value, "fact value must not be null"); + Fact retrievedFact = getFact(name); + if (retrievedFact != null) { + remove(retrievedFact); + } + add(new Fact<>(name, value)); + } + + /** + * Add a fact, replacing any fact with the same name. + * + * @param fact to add, must not be null + */ + public void add(Fact fact) { + Objects.requireNonNull(fact, "fact must not be null"); + Fact retrievedFact = getFact(fact.getName()); + if (retrievedFact != null) { + remove(retrievedFact); + } + facts.add(fact); + } + + /** + * Remove a fact by name. + * + * @param factName name of the fact to remove, must not be null + */ + public void remove(String factName) { + Objects.requireNonNull(factName, "fact name must not be null"); + Fact fact = getFact(factName); + if (fact != null) { + remove(fact); + } + } + + /** + * Remove a fact. + * + * @param fact to remove, must not be null + */ + public void remove(Fact fact) { + Objects.requireNonNull(fact, "fact must not be null"); + facts.remove(fact); + } + + /** + * Get the value of a fact by its name. This is a convenience method provided + * as a short version of {@code getFact(factName).getValue()}. + * + * @param factName name of the fact, must not be null + * @param type of the fact's value + * @return the value of the fact having the given name, or null if there is + * no fact with the given name + */ + @SuppressWarnings("unchecked") + public T get(String factName) { + Objects.requireNonNull(factName, "fact name must not be null"); + Fact fact = getFact(factName); + if (fact != null) { + return (T) fact.getValue(); + } + return null; + } + + /** + * Get a fact by name. + * + * @param factName name of the fact, must not be null + * @return the fact having the given name, or null if there is no fact with the given name + */ + public Fact getFact(String factName) { + Objects.requireNonNull(factName, "fact name must not be null"); + return facts.stream() + .filter(fact -> fact.getName().equals(factName)) + .findFirst() + .orElse(null); + } + + /** + * Return a copy of the facts as a map. It is not intended to manipulate + * facts outside of the rules engine (aka other than manipulating them through rules). + * + * @return a copy of the current facts as a {@link HashMap} + */ + public Map asMap() { + Map map = new HashMap<>(); + for (Fact fact : facts) { + map.put(fact.getName(), fact.getValue()); + } + return map; + } + + /** + * Return an iterator on the set of facts. It is not intended to remove + * facts using this iterator outside of the rules engine (aka other than doing it through rules) + * + * @return an iterator on the set of facts + */ + @Override + public Iterator> iterator() { + return facts.iterator(); + } + + /** + * Clear facts. + */ + public void clear() { + facts.clear(); + } + + @Override + public String toString() { + Iterator> iterator = facts.iterator(); + StringBuilder stringBuilder = new StringBuilder("["); + while (iterator.hasNext()) { + stringBuilder.append(iterator.next().toString()); + if (iterator.hasNext()) { + stringBuilder.append(","); + } + } + stringBuilder.append("]"); + return stringBuilder.toString(); + } +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rule.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rule.java new file mode 100644 index 000000000..4a16db3e4 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rule.java @@ -0,0 +1,65 @@ +package com.alibaba.cola.ruleengine.api; + +public interface Rule extends Comparable { + + /** + * Default rule name. + */ + String DEFAULT_NAME = "rule"; + + /** + * Default rule description. + */ + String DEFAULT_DESCRIPTION = "description"; + + /** + * Default rule priority. + */ + int DEFAULT_PRIORITY = Integer.MAX_VALUE - 1; + + /** + * Getter for rule name. + * @return the rule name + */ + default String getName() { + return DEFAULT_NAME; + } + + /** + * Getter for rule description. + * @return rule description + */ + default String getDescription() { + return DEFAULT_DESCRIPTION; + } + + /** + * Getter for rule priority. + * @return rule priority + */ + default int getPriority() { + return DEFAULT_PRIORITY; + } + + /** + * This method implements the rule's condition(s). + * Implementations should handle any runtime exception and return true/false accordingly + * + * @return true if the rule should be applied given the provided facts, false otherwise + */ + boolean evaluate(Facts facts); + + /** + * This method implements the rule's action(s). + * @throws Exception thrown if an exception occurs when performing action(s) + */ + void execute(Facts facts); + + /** + * This method apply facts to the rule, which is the combination of evaluation and execution + * @param facts + * @return true if this rule is applied successfully, false otherwise + */ + boolean apply(Facts facts); + +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/RuleEngine.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/RuleEngine.java new file mode 100644 index 000000000..3f15c1228 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/RuleEngine.java @@ -0,0 +1,9 @@ +package com.alibaba.cola.ruleengine.api; + +public interface RuleEngine { + /** + * Fire all registered rules on given facts. + */ + void fire(Rules rules, Facts facts); + +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rules.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rules.java new file mode 100644 index 000000000..db28ca38f --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/api/Rules.java @@ -0,0 +1,77 @@ +package com.alibaba.cola.ruleengine.api; + +import java.util.*; + +public class Rules implements Iterable { + + private List rules = new ArrayList<>(); + + /** + * Create a new {@link Rules} object. + * + * @param rules to register + */ + public Rules(Rule... rules) { + Collections.addAll(this.rules, rules); + } + + /** + * Register one or more new rules. + * + * @param rules to register, must not be null + */ + public void register(Rule... rules) { + Objects.requireNonNull(rules); + for (Rule rule : rules) { + Objects.requireNonNull(rule); + this.rules.add(rule); + } + } + + /** + * Unregister one or more rules. + * + * @param rules to unregister, must not be null + */ + public void unregister(Rule... rules) { + Objects.requireNonNull(rules); + for (Rule rule : rules) { + Objects.requireNonNull(rule); + this.rules.remove(rule); + } + } + + public void sortRulesByPriority(){ + Collections.sort(rules); + } + + /** + * Check if the rule set is empty. + * + * @return true if the rule set is empty, false otherwise + */ + public boolean isEmpty() { + return rules.isEmpty(); + } + + /** + * Clear rules. + */ + public void clear() { + rules.clear(); + } + + /** + * Return how many rules are currently registered. + * + * @return the number of rules currently registered + */ + public int size() { + return rules.size(); + } + + @Override + public Iterator iterator() { + return rules.iterator(); + } +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/AbstractRule.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/AbstractRule.java new file mode 100644 index 000000000..58ec21faa --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/AbstractRule.java @@ -0,0 +1,169 @@ +/* + * 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 com.alibaba.cola.ruleengine.core; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rule; + +import java.util.Objects; + +/** + * Basic rule implementation class that provides common methods. + *

+ * You can extend this class and override {@link AbstractRule#evaluate(Facts)} and {@link AbstractRule#execute(Facts)} to provide rule + * conditions and actions logic. + * + * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + */ +public abstract class AbstractRule implements Rule { + + /** + * Rule name. + */ + protected String name; + + /** + * Rule description. + */ + protected String description; + + /** + * Rule priority. + */ + protected int priority; + + /** + * Create a new {@link AbstractRule}. + */ + public AbstractRule() { + this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY); + } + + /** + * Create a new {@link AbstractRule}. + * + * @param name rule name + */ + public AbstractRule(final String name) { + this(name, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY); + } + + public AbstractRule(final int priority) { + this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, priority); + } + + /** + * Create a new {@link AbstractRule}. + * + * @param name rule name + * @param description rule description + */ + public AbstractRule(final String name, final String description) { + this(name, description, Rule.DEFAULT_PRIORITY); + } + + /** + * Create a new {@link AbstractRule}. + * + * @param name rule name + * @param description rule description + * @param priority rule priority + */ + public AbstractRule(final String name, final String description, final int priority) { + this.name = name; + this.description = description; + this.priority = priority; + } + + public abstract boolean evaluate(Facts facts); + + public abstract void execute(Facts facts); + + public abstract boolean apply(Facts facts); + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public int getPriority() { + return priority; + } + + public void setPriority(final int priority) { + this.priority = priority; + } + + /* + * Rules are unique according to their names within a rules engine registry. + */ + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + AbstractRule basicRule = (AbstractRule) o; + + if (priority != basicRule.priority) + return false; + if (!name.equals(basicRule.name)) + return false; + return Objects.equals(description, basicRule.description); + + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + priority; + return result; + } + + @Override + public String toString() { + return name + ":" + priority; + } + + @Override + public int compareTo(final Rule rule) { + if (getPriority() < rule.getPriority()) { + return -1; + } else if (getPriority() > rule.getPriority()) { + return 1; + } + return 0; + } + +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/CompositeRule.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/CompositeRule.java new file mode 100644 index 000000000..64c7b23c4 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/CompositeRule.java @@ -0,0 +1,118 @@ +package com.alibaba.cola.ruleengine.core; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CompositeRule extends AbstractRule { + private static final Logger LOGGER = LoggerFactory.getLogger(CompositeRule.class); + + private List rules = new ArrayList<>(); + + //对于CompositeRule组合规则,我们要定义rule和rule之间的关系,当前支持OR和AND两种关系。 + //or关系是只要有一条rule满足条件就执行,其余的忽略 + private boolean isOrRelation; + //and关系是要所有的rules都满足条件才全部执行 + private boolean isAndRelation; + + private boolean isSorted=false; + + public static CompositeRule anyOf(Rule... rules) { + CompositeRule instance = new CompositeRule(); + instance.isOrRelation = true; + Collections.addAll(instance.rules, rules); + return instance; + } + + public static CompositeRule allOf(Rule... rules) { + CompositeRule instance = new CompositeRule(); + instance.isAndRelation = true; + Collections.addAll(instance.rules, rules); + return instance; + } + + public CompositeRule priority(int priority) { + this.priority = priority; + return this; + } + + public CompositeRule name(String name){ + this.name = name; + return this; + } + + private CompositeRule() { + + } + + @Override + public boolean evaluate(Facts facts) { + if (isOrRelation) { + if (rules.stream().anyMatch(rule -> rule.evaluate(facts))) + return true; + } + if (isAndRelation) { + if (rules.stream().allMatch(rule -> rule.evaluate(facts))) + return true; + } + return false; + } + + /** + * CompositeRule的execute + * + * @param facts + */ + @Override + public void execute(Facts facts) { + //不支持OR relation + if (isOrRelation) { + throw new RuntimeException("execute not supported for OR relation composite"); + } + //对于AND 就是执行all + if (isAndRelation) { + for (Rule rule : rules) { + rule.execute(facts); + } + } + } + + @Override + public boolean apply(Facts facts) { + sort(); + if (isOrRelation) { + LOGGER.debug("start OR composite rule apply"); + for (Rule rule : rules) { + //短路操作,只要第一个rule成功执行,其它就不执行了 + if (rule.apply(facts)) { + return true; + } + } + } + if (isAndRelation) { + LOGGER.debug("start AND composite rule apply"); + if (evaluate(facts)) { + for (Rule rule : rules) { + //所有的rules都执行 + rule.execute(facts); + } + return true; + } + } + return false; + } + + private void sort(){ + if(!isSorted){ + LOGGER.debug(this.name+" before sort:" + rules); + Collections.sort(rules); + LOGGER.debug(this.name+" after sort:" + rules); + isSorted = true; + } + } +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRule.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRule.java new file mode 100644 index 000000000..981c603ea --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRule.java @@ -0,0 +1,78 @@ +/* + * 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 com.alibaba.cola.ruleengine.core; + +import com.alibaba.cola.ruleengine.api.Action; +import com.alibaba.cola.ruleengine.api.Condition; +import com.alibaba.cola.ruleengine.api.Facts; + +import java.util.ArrayList; +import java.util.List; + +public class DefaultRule extends AbstractRule { + + private final Condition condition; + private final List actions; + + public DefaultRule(Condition condition, Action action){ + this.condition = condition; + this.actions = new ArrayList<>(); + this.actions.add(action); + } + + public DefaultRule(Condition condition, List actions){ + this.condition = condition; + this.actions = actions; + } + + public DefaultRule(String name, String description, int priority, Condition condition, List actions) { + super(name, description, priority); + this.condition = condition; + this.actions = actions; + } + + @Override + public boolean evaluate(Facts facts) { + return condition.evaluate(facts); + } + + @Override + public void execute(Facts facts) { + for (Action action : actions) { + action.execute(facts); + } + } + + @Override + public boolean apply(Facts facts){ + if (condition.evaluate(facts)){ + for (Action action : actions) { + action.execute(facts); + } + return true; + } + return false; + } + +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRuleEngine.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRuleEngine.java new file mode 100644 index 000000000..b6acd6c9f --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/DefaultRuleEngine.java @@ -0,0 +1,29 @@ +package com.alibaba.cola.ruleengine.core; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rule; +import com.alibaba.cola.ruleengine.api.RuleEngine; +import com.alibaba.cola.ruleengine.api.Rules; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultRuleEngine implements RuleEngine { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRuleEngine.class); + private boolean isSorted = false; + + @Override + public void fire(Rules rules, Facts facts) { + if (rules.isEmpty()) { + LOGGER.warn("No rules registered! Nothing to apply"); + return; + } + if(!isSorted){ + //for performance concern: only sort at the first time + rules.sortRulesByPriority(); + isSorted = true; + } + for (Rule rule : rules) { + rule.apply(facts); + } + } +} diff --git a/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/RuleBuilder.java b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/RuleBuilder.java new file mode 100644 index 000000000..07f22d89b --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/main/java/com/alibaba/cola/ruleengine/core/RuleBuilder.java @@ -0,0 +1,82 @@ +package com.alibaba.cola.ruleengine.core; + +import com.alibaba.cola.ruleengine.api.Action; +import com.alibaba.cola.ruleengine.api.Condition; +import com.alibaba.cola.ruleengine.api.Rule; + +import java.util.ArrayList; +import java.util.List; + +public class RuleBuilder { + + private String name = Rule.DEFAULT_NAME; + private String description = Rule.DEFAULT_DESCRIPTION; + private int priority = Rule.DEFAULT_PRIORITY; + + private Condition condition = Condition.FALSE; + private final List actions = new ArrayList<>(); + + /** + * Set rule name. + * + * @param name of the rule + * @return the builder instance + */ + public RuleBuilder name(String name) { + this.name = name; + return this; + } + + /** + * Set rule description. + * + * @param description of the rule + * @return the builder instance + */ + public RuleBuilder description(String description) { + this.description = description; + return this; + } + + /** + * Set rule priority. + * + * @param priority of the rule + * @return the builder instance + */ + public RuleBuilder priority(int priority) { + this.priority = priority; + return this; + } + + /** + * Set rule condition. + * + * @param condition of the rule + * @return the builder instance + */ + public RuleBuilder when(Condition condition) { + this.condition = condition; + return this; + } + + /** + * Add an action to the rule. + * + * @param action to add + * @return the builder instance + */ + public RuleBuilder then(Action action) { + this.actions.add(action); + return this; + } + + /** + * Create a new {@link Rule}. + * + * @return a new rule instance + */ + public Rule build() { + return new DefaultRule(name, description, priority, condition, actions); + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/CompositeRuleTest.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/CompositeRuleTest.java new file mode 100644 index 000000000..17bac9f5e --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/CompositeRuleTest.java @@ -0,0 +1,97 @@ +package com.alibaba.cola.ruleengine; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rule; +import com.alibaba.cola.ruleengine.api.RuleEngine; +import com.alibaba.cola.ruleengine.api.Rules; +import com.alibaba.cola.ruleengine.core.CompositeRule; +import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; +import com.alibaba.cola.ruleengine.core.RuleBuilder; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CompositeRuleTest { + + RuleEngine fizzBuzzEngine; + + @Before + public void setUp(){ + fizzBuzzEngine = new DefaultRuleEngine(); + } + + @Test + public void test_fizz_first(){ + Facts facts = new Facts(); + facts.put("number", 15); + + Rules rules = assembleRules(1,2,3); + + fizzBuzzEngine.fire(rules, facts); + assertThat(facts.getFact("fizz").getValue()).isEqualTo("Fizz"); + } + + @Test + public void test_buzz_first(){ + Facts facts = new Facts(); + facts.put("number", 15); + + Rules rules = assembleRules(2,1,3); + + fizzBuzzEngine.fire(rules, facts); + assertThat(facts.getFact("buzz").getValue()).isEqualTo("Buzz"); + } + + @Test + public void test_fizzBuzz_first(){ + Facts facts = new Facts(); + facts.put("number", 15); + + Rules rules = assembleRules(2,3,1); + + fizzBuzzEngine.fire(rules, facts); + assertThat(facts.getFact("fizz").getValue()).isEqualTo("Fizz"); + assertThat(facts.getFact("buzz").getValue()).isEqualTo("Buzz"); + } + + + private Rules assembleRules(int fizzPriority, int BuzzPriority, int FizzBuzzPriority){ + // create rules + Rules rules = new Rules(); + Rule fizzRule = new RuleBuilder() + .name("fizzRule") + .description("fizz rule when input times 3, output is Fizz") + .priority(fizzPriority) + .when(facts -> (int) facts.get("number") % 3 == 0) + .then(facts -> facts.put("fizz","Fizz")) + .build(); + + Rule buzzRule = new RuleBuilder() + .name("buzzRule") + .description("buzz rule when input times 5, output is buzz") + .priority(BuzzPriority) + .when(facts -> (int) facts.get("number") % 5 == 0) + .then(facts -> facts.put("buzz","Buzz")) + .build(); + + + Rule fizzBuzzRule = CompositeRule.allOf(fizzRule, buzzRule) + .name("fizzBuzzRule") + .priority(FizzBuzzPriority); + + Rule defaultRule = new RuleBuilder() + .name("defaultRule") + .description("default rule, output number") + .priority(40) + .when(facts -> true) + .then(facts -> System.out.print((int) facts.get("number"))) + .build(); + + Rule rule = CompositeRule.anyOf(fizzBuzzRule, fizzRule, buzzRule, defaultRule) + .name("anyRule"); + + rules.register(rule); + return rules; + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/FactsTest.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/FactsTest.java new file mode 100644 index 000000000..751f4a4c2 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/FactsTest.java @@ -0,0 +1,99 @@ +package com.alibaba.cola.ruleengine; + +import com.alibaba.cola.ruleengine.api.Fact; +import com.alibaba.cola.ruleengine.api.Facts; + +import java.util.Map; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class FactsTest { + + private final Facts facts = new Facts(); + + @Test + public void factsMustHaveUniqueName() { + facts.add(new Fact<>("foo", 1)); + facts.add(new Fact<>("foo", 2)); + + assertThat(facts).hasSize(1); + Fact fact = facts.getFact("foo"); + assertThat(fact.getValue()).isEqualTo(2); + } + + @Test + public void testAdd() { + Fact fact1 = new Fact<>("foo", 1); + Fact fact2 = new Fact<>("bar", 2); + facts.add(fact1); + facts.add(fact2); + + assertThat(facts).contains(fact1); + assertThat(facts).contains(fact2); + } + + @Test + public void testPut() { + facts.put("foo", 1); + facts.put("bar", 2); + + assertThat(facts).contains(new Fact<>("foo", 1)); + assertThat(facts).contains(new Fact<>("bar", 2)); + } + + @Test + public void testRemove() { + Fact foo = new Fact<>("foo", 1); + facts.add(foo); + facts.remove(foo); + + assertThat(facts).isEmpty(); + } + + @Test + public void testRemoveByName() { + Fact foo = new Fact<>("foo", 1); + facts.add(foo); + facts.remove("foo"); + + assertThat(facts).isEmpty(); + } + + @Test + public void testGet() { + Fact fact = new Fact<>("foo", 1); + facts.add(fact); + Integer value = facts.get("foo"); + assertThat(value).isEqualTo(1); + } + + @Test + public void testGetFact() { + Fact fact = new Fact<>("foo", 1); + facts.add(fact); + Fact retrievedFact = facts.getFact("foo"); + assertThat(retrievedFact).isEqualTo(fact); + } + + @Test + public void testAsMap() { + Fact fact1 = new Fact<>("foo", 1); + Fact fact2 = new Fact<>("bar", 2); + facts.add(fact1); + facts.add(fact2); + Map map = facts.asMap(); + assertThat(map).containsKeys("foo", "bar"); + assertThat(map).containsValues(1, 2); + } + + @Test + public void testClear() { + Facts facts = new Facts(); + facts.add(new Fact<>("foo", 1)); + facts.clear(); + assertThat(facts).isEmpty(); + } + +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/HelloWorld.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/HelloWorld.java new file mode 100644 index 000000000..6844fd546 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/HelloWorld.java @@ -0,0 +1,24 @@ +package com.alibaba.cola.ruleengine; + +import com.alibaba.cola.ruleengine.api.Rule; +import com.alibaba.cola.ruleengine.api.RuleEngine; +import com.alibaba.cola.ruleengine.api.Rules; +import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; +import com.alibaba.cola.ruleengine.core.RuleBuilder; + +public class HelloWorld { + public static void main(String[] args) { + RuleEngine ruleEngine = new DefaultRuleEngine(); + Rule rule = new RuleBuilder() + .name("hello world rule") + .description("always say hello world") + .priority(1) + .when(facts -> true) + .then(facts -> System.out.println("hello world")) + .build(); + Rules rules = new Rules(); + rules.register(rule); + + ruleEngine.fire(rules, null); + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/PriorityTest.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/PriorityTest.java new file mode 100644 index 000000000..f9e66d0ce --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/PriorityTest.java @@ -0,0 +1,67 @@ +package com.alibaba.cola.ruleengine; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rules; +import com.alibaba.cola.ruleengine.core.AbstractRule; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PriorityTest { + + @Test + public void testNoPriority() { + DummyRule r1 = new DummyRule(); + DummyRule r2 = new DummyRule(); + DummyRule r3 = new DummyRule(); + + Rules rules = new Rules(); + rules.register(r1, r2, r3); + rules.sortRulesByPriority(); + + assertThat(rules).startsWith(r1).endsWith(r3); + } + + @Test + public void testPriority(){ + DummyRule r1 = new DummyRule(10); + DummyRule r2 = new DummyRule(3); + DummyRule r3 = new DummyRule(1); + + Rules rules = new Rules(); + rules.register(r1, r2, r3); + rules.sortRulesByPriority(); + + assertThat(rules).startsWith(r3).endsWith(r1); + } + + + + + static class DummyRule extends AbstractRule { + + public DummyRule(){ + + } + + public DummyRule(int priority){ + super(priority); + } + + @Override + public boolean evaluate(Facts facts) { + return false; + } + + @Override + public void execute(Facts facts) { + + } + + @Override + public boolean apply(Facts facts) { + return false; + } + } +} + diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleBuilderTest.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleBuilderTest.java new file mode 100644 index 000000000..69ed8f81a --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleBuilderTest.java @@ -0,0 +1,61 @@ +package com.alibaba.cola.ruleengine; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rule; +import com.alibaba.cola.ruleengine.api.RuleEngine; +import com.alibaba.cola.ruleengine.api.Rules; +import com.alibaba.cola.ruleengine.core.CompositeRule; +import com.alibaba.cola.ruleengine.core.DefaultRule; +import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; +import com.alibaba.cola.ruleengine.core.RuleBuilder; +import org.junit.Test; + +public class RuleBuilderTest { + + @Test + public void testFizzBuzz() { + RuleEngine fizzBuzzEngine = new DefaultRuleEngine(); + + // create rules + Rules rules = new Rules(); + Rule fizzRule = new RuleBuilder() + .name("fizzRule") + .description("fizz rule when input times 3, output is Fizz") + .priority(10) + .when(facts -> (int) facts.get("number") % 3 == 0) + .then(facts -> System.out.print("Fizz")) + .build(); + + Rule buzzRule = new RuleBuilder() + .name("buzzRule") + .description("buzz rule when input times 5, output is buzz") + .priority(1) + .when(facts -> (int) facts.get("number") % 5 == 0) + .then(facts -> System.out.print("Buzz")) + .build(); + + + Rule fizzBuzzRule = CompositeRule.allOf(fizzRule, buzzRule) + .name("fizzBuzzRule") + .priority(30); + + Rule defaultRule = new RuleBuilder() + .name("defaultRule") + .description("default rule, output number") + .priority(40) + .when(facts -> true) + .then(facts -> System.out.print((int) facts.get("number"))) + .build(); + + Rule rule = CompositeRule.anyOf(fizzBuzzRule, fizzRule, buzzRule, defaultRule) + .name("anyRule"); + + rules.register(rule); + + // fire rules + Facts facts = new Facts(); + facts.put("number", 15); + fizzBuzzEngine.fire(rules, facts); + } + +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleEngineTest.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleEngineTest.java new file mode 100644 index 000000000..7192d2d5b --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/RuleEngineTest.java @@ -0,0 +1,45 @@ +package com.alibaba.cola.ruleengine; + +import com.alibaba.cola.ruleengine.api.Facts; +import com.alibaba.cola.ruleengine.api.Rule; +import com.alibaba.cola.ruleengine.api.RuleEngine; +import com.alibaba.cola.ruleengine.api.Rules; +import com.alibaba.cola.ruleengine.core.CompositeRule; +import com.alibaba.cola.ruleengine.core.DefaultRule; +import com.alibaba.cola.ruleengine.core.DefaultRuleEngine; +import org.junit.Test; + +public class RuleEngineTest { + + @Test + public void testRuleEngine() { + RuleEngine ruleEngine = new DefaultRuleEngine(); + Rules rules = new Rules(); + ruleEngine.fire(rules, null); + } + + @Test + public void testFizzBuzz() { + RuleEngine fizzBuzzEngine = new DefaultRuleEngine(); + + // create rules + Rules rules = new Rules(); + Rule fizzRule = new DefaultRule(facts -> (int) facts.get("number") % 3 == 0, facts -> System.out.print("Fizz")); + Rule buzzRule = new DefaultRule(facts -> (int) facts.get("number") % 5 == 0, facts -> System.out.print("Buzz")); + Rule fizzBuzzRule = CompositeRule.allOf(fizzRule, buzzRule); + Rule NonFizzBuzzRule = new DefaultRule(facts -> true, facts -> System.out.print((int) facts.get("number"))); + Rule rule = CompositeRule.anyOf(fizzBuzzRule, fizzRule, buzzRule, NonFizzBuzzRule); + rules.register(rule); + + // fire rules + Facts facts = new Facts(); + facts.put("number", 15); + fizzBuzzEngine.fire(rules, facts); +// for (int i = 1; i <= 10; i++) { +// facts.put("number", i); +// fizzBuzzEngine.fire(rules, facts); +// System.out.println(); +// } + } + +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/FizzBuzzTest.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/FizzBuzzTest.java new file mode 100644 index 000000000..25f36c858 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/FizzBuzzTest.java @@ -0,0 +1,50 @@ +package com.alibaba.cola.ruleengine.fizzbuzz; + +//import com.alibaba.cola.ruleengine.fizzbuzz.v1.FizzBuzz; +import com.alibaba.cola.ruleengine.fizzbuzz.v2.FizzBuzz; +import org.assertj.core.api.Assertions; +import org.junit.Test; + +public class FizzBuzzTest { + + @Test + public void num_given_1() { + //given + int input = 1; + //when + String result = FizzBuzz.count(input); + //then + Assertions.assertThat(result).isEqualTo("1"); + } + + @Test + public void fizz_given_3() { + //given + int input = 3; + //when + String result = FizzBuzz.count(input); + //then + Assertions.assertThat(result).isEqualTo("Fizz"); + + } + + @Test + public void buzz_given_5() { + //given + int input = 5; + //when + String result = FizzBuzz.count(input); + //then + Assertions.assertThat(result).isEqualTo("Buzz"); + } + + @Test + public void fizz_buzz_given_15() { + //given + int input = 15; + //when + String result = FizzBuzz.count(input); + //then + Assertions.assertThat(result).isEqualTo("FizzBuzz"); + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v1/FizzBuzz.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v1/FizzBuzz.java new file mode 100644 index 000000000..64984d966 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v1/FizzBuzz.java @@ -0,0 +1,13 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v1; + +public class FizzBuzz { + public static String count(int n){ + if (((n % 3) == 0) && ((n % 5) == 0)) + return "FizzBuzz"; + if ((n % 3) == 0) + return "Fizz"; + if ((n % 5) == 0) + return "Buzz"; + return String.valueOf(n); + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Action.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Action.java new file mode 100644 index 000000000..424dfb3e6 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Action.java @@ -0,0 +1,7 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v2; + +@FunctionalInterface +public interface Action { + String execute(int n); +} + diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Condition.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Condition.java new file mode 100644 index 000000000..c1dd65f09 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Condition.java @@ -0,0 +1,25 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v2; + +import java.util.Objects; + +@FunctionalInterface +public interface Condition { + + boolean evaluate(int n); + + //谓词and逻辑,参考Predicate + default Condition and(Condition other) { + Objects.requireNonNull(other); + return (n) -> { + return this.evaluate(n) && other.evaluate(n); + }; + } + + //谓词or逻辑,参考Predicate + default Condition or(Condition other) { + Objects.requireNonNull(other); + return (n) -> { + return this.evaluate(n) || other.evaluate(n); + }; + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/FizzBuzz.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/FizzBuzz.java new file mode 100644 index 000000000..e4c1a934a --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/FizzBuzz.java @@ -0,0 +1,21 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v2; + +import static com.alibaba.cola.ruleengine.fizzbuzz.v2.SimpleRuleEngine.*; +import static com.alibaba.cola.ruleengine.fizzbuzz.v2.TimesCondition.times; + +/** + * 用简易规则引擎重构后的FizzBuzz实现 + */ +public class FizzBuzz { + public static String count(int i){ + //Composite condition + Rule fizzBuzzRule = atom(times(3).and(times(5)), n -> "FizzBuzz"); + Rule fizzRule = atom(times(3) , n -> "Fizz"); + Rule buzzRule = atom(times(5), n -> "Buzz"); + //Composite rule + Rule compositeFizzBuzzRule = allOf(fizzRule, buzzRule); + Rule defaultRule = atom(n -> true, n -> String.valueOf(n)); + Rule rule = anyOf(compositeFizzBuzzRule, fizzRule, buzzRule, defaultRule); + return rule.apply(i); + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Rule.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Rule.java new file mode 100644 index 000000000..dd05e2d0f --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/Rule.java @@ -0,0 +1,6 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v2; + +@FunctionalInterface +public interface Rule { + String apply(int n); +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/SimpleRuleEngine.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/SimpleRuleEngine.java new file mode 100644 index 000000000..b97491ac6 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/SimpleRuleEngine.java @@ -0,0 +1,29 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v2; + +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 通过原子atom rule,以及atom rule之间的组合解决FizzBuzz问题 + * 这里为了简单使用Rule的组合模式代替了RuleEngine实体 + * 注意:这个SimpleRuleEngine只能解决输入为n,输出为String的FizzBuzz问题 + * 完全不具备通用性 + */ +public class SimpleRuleEngine { + public static Rule atom(Condition condition, Action action){ + return n -> condition.evaluate(n) ? action.execute(n) : ""; + } + + public static Rule anyOf(Rule... rules){ + return n -> stringStream(n, rules).filter(s -> !s.isEmpty()).findFirst().get(); + } + + public static Rule allOf(Rule... rules){ + return n -> stringStream(n, rules).collect(Collectors.joining()); + } + + public static Stream stringStream(int n, Rule[] rules){ + return Arrays.stream(rules).map(r -> r.apply(n)); + } +} diff --git a/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/TimesCondition.java b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/TimesCondition.java new file mode 100644 index 000000000..d706ca0e5 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/java/com/alibaba/cola/ruleengine/fizzbuzz/v2/TimesCondition.java @@ -0,0 +1,11 @@ +package com.alibaba.cola.ruleengine.fizzbuzz.v2; + +/** + * 计算倍数关系的谓词逻辑 + */ +public class TimesCondition { + public static Condition times(int i){ + return n -> n % i == 0; + } +} + diff --git a/cola-components/cola-component-ruleengine/src/test/resources/logback-test.xml b/cola-components/cola-component-ruleengine/src/test/resources/logback-test.xml new file mode 100644 index 000000000..3a9e27707 --- /dev/null +++ b/cola-components/cola-component-ruleengine/src/test/resources/logback-test.xml @@ -0,0 +1,21 @@ + + + + + + %-4relative [%thread] %-5level %logger{35} - %msg%n + utf8 + + + + + + + + + + + + + + diff --git a/cola-components/pom.xml b/cola-components/pom.xml index 5c2e0929f..d5dae877d 100644 --- a/cola-components/pom.xml +++ b/cola-components/pom.xml @@ -1,437 +1,831 @@ - - 4.0.0 - - com.alibaba.cola - cola-components-parent - 4.4.0-SNAPSHOT - pom - ${project.artifactId} - ${project.artifactId} + + + 4.0.0 + + com.alibaba.cola + + cola-components-parent + + 4.4.0-SNAPSHOT + + pom + + ${project.artifactId} + + ${project.artifactId} + + https://github.com/alibaba/COLA + + + + + + GNU Lesser General Public License v2.1 + + https://github.com/alibaba/COLA/blob/master/LICENSE + + repo + + + + + + + + scm:git:https://github.com/alibaba/COLA.git + + scm:git:https://github.com/alibaba/COLA.git + https://github.com/alibaba/COLA - - - - GNU Lesser General Public License v2.1 - https://github.com/alibaba/COLA/blob/master/LICENSE - repo - - - - scm:git:https://github.com/alibaba/COLA.git - scm:git:https://github.com/alibaba/COLA.git - https://github.com/alibaba/COLA - - - https://github.com/alibaba/COLA/issues - GitHub Issues - - - - significantfrank - Frank Zhang - 25216348(at)qq.com - - Developer - Architect - - +8 - https://github.com/significantfrank - - - oldratlee - Jerry Lee - oldratlee(at)gmail.com - - Developer - CI/SCM Engineer - - +8 - https://github.com/oldratlee - - - - - cola-component-dto - cola-component-exception - cola-component-statemachine - cola-component-domain-starter - cola-component-extension-starter - cola-component-catchlog-starter - cola-component-test-container - cola-components-bom - - - - 1.8 - ${maven.compiler.source} - UTF-8 - - 2.7.5 - - + + + + + + https://github.com/alibaba/COLA/issues + + GitHub Issues + + + + + + + + significantfrank + + Frank Zhang + + 25216348(at)qq.com + + + + Developer + + Architect + + + + +8 + + https://github.com/significantfrank + + + + + + oldratlee + + Jerry Lee + + oldratlee(at)gmail.com + + + + Developer + + CI/SCM Engineer + + + + +8 + + https://github.com/oldratlee + + + + + + + + cola-component-dto + + cola-component-exception + + cola-component-statemachine + + cola-component-domain-starter + + cola-component-extension-starter + + cola-component-catchlog-starter + + cola-component-test-container + + cola-components-bom + + cola-component-ruleengine + + + + + + 1.8 + + ${maven.compiler.source} + + UTF-8 + + 2.7.5 + + + + + + + + org.projectlombok + + lombok + + provided + + + + + + + + junit + + junit + + test + + + + + + + - - org.projectlombok - lombok - provided - - - - - junit - junit - test - + + + + org.springframework.boot + + spring-boot-dependencies + + ${spring.boot.version} + + pom + + import + + + + + + + + com.alibaba + + fastjson + + 1.2.83 + + provided + + + + + + commons-cli + + commons-cli + + 1.5.0 + + + - - - - - org.springframework.boot - spring-boot-dependencies - ${spring.boot.version} - pom - import - - - - - com.alibaba - fastjson - 1.2.83 - provided - - - commons-cli - commons-cli - 1.5.0 - - - - - - - - - maven-enforcer-plugin - - - - enforce - - - - - 3.3.9 - - - - - - - - - - - maven-resources-plugin - 3.3.1 - - - maven-compiler-plugin - 3.11.0 - - - maven-source-plugin - 3.2.1 - - - maven-javadoc-plugin - 3.6.0 - - - maven-gpg-plugin - 3.1.0 - - - maven-enforcer-plugin - 3.4.1 - - - maven-deploy-plugin - 3.1.1 - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 - - - org.jacoco - jacoco-maven-plugin - 0.8.10 - - - pl.project13.maven - git-commit-id-plugin - 4.9.10 - - - - org.codehaus.mojo - versions-maven-plugin - 2.16.0 - - file://${maven.multiModuleProjectDirectory}/cola-component-dto/src/versions-rules.xml - false - - - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - - - - gen-java-src - - - performRelease - true - - - - - - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - gen-java-doc - - - performRelease - true - - - - - - maven-javadoc-plugin - - - attach-javadoc - - jar - - - - - 8 - protected - UTF-8 - UTF-8 - UTF-8 - - --no-module-directories - -quiet - -J-Duser.language=en - -J-Duser.country=US - -Xdoclint:none - - - - - - - - gen-code-cov - - - env.TRAVIS - true - - - - - - - org.jacoco - jacoco-maven-plugin - - - - prepare-agent - - - - report - test - - report - - - - - - - - - gen-sign - - - performRelease - true - - - - - - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - - - - gen-git-properties - - - performRelease - true - - - - - - - pl.project13.maven - git-commit-id-plugin - - - get-the-git-infos - - revision - - - - validate-the-git-infos - - validateRevision - - - - - - - - validating git dirty - ${git.dirty} - false - - - true - + + + + pl.project13.maven + + git-commit-id-plugin + + + + + + get-the-git-infos + + + + revision + + + + + + + + validate-the-git-infos + + + + validateRevision + + + + + + + + + + + + + + + + validating git dirty + + ${git.dirty} + + false + + + + + + true + + ${project.build.outputDirectory}/META-INF/scm/${project.groupId}/${project.artifactId}/git.properties - - - - - - - force-jdk11-when-release - - - performRelease - true - - - - - - - maven-enforcer-plugin - - - - enforce - - - - - 11 - - - - - - - - - - - deploy-settings - - - performRelease - true - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - true - - ossrh - https://oss.sonatype.org/ - true - - - - - - + + + + maven-enforcer-plugin + + + + + + + + enforce + + + + + + + + + + 11 + + + + + + + + + + + + + + + + + + + + + + deploy-settings + + + + + + performRelease + + true + + + + + + + + + + + + org.sonatype.plugins + + nexus-staging-maven-plugin + + true + + + + ossrh + + https://oss.sonatype.org/ + + true + + + + + + + + + + + + +