All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.deliveredtechnologies.rulebook.lang.RuleBuilder Maven / Gradle / Ivy

There is a newer version: 0.12
Show newest version
package com.deliveredtechnologies.rulebook.lang;

import com.deliveredtechnologies.rulebook.Result;
import com.deliveredtechnologies.rulebook.Fact;
import com.deliveredtechnologies.rulebook.NameValueReferable;
import com.deliveredtechnologies.rulebook.NameValueReferableMap;
import com.deliveredtechnologies.rulebook.NameValueReferableTypeConvertibleMap;
import com.deliveredtechnologies.rulebook.model.AuditableRule;
import com.deliveredtechnologies.rulebook.model.GoldenRule;
import com.deliveredtechnologies.rulebook.model.Rule;
import com.deliveredtechnologies.rulebook.model.RuleChainActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;

import static com.deliveredtechnologies.rulebook.model.RuleChainActionType.CONTINUE_ON_FAILURE;
import static com.deliveredtechnologies.rulebook.model.RuleChainActionType.STOP_ON_FAILURE;

/**
 * The initial builder used to build a Rule.
 * @param  the type of facts used in the Rule
 * @param  the Result type used in the Rule
 */
public class RuleBuilder implements TerminatingRuleBuilder {

  private static Logger LOGGER = LoggerFactory.getLogger(RuleBuilder.class);

  private Class _ruleClass;
  private Class _factType;
  private Class _resultType;
  private RuleChainActionType _actionType = CONTINUE_ON_FAILURE;
  private Optional _name = Optional.empty();

  /**
   * Returns a new RuleBuilder for the specified Rule class.
   * @param ruleClass   the class of Rule to build
   * @param actionType  if STOP_ON_FAILURE, stops the rule chain if RuleState is BREAK only if the rule fails
   *                    (for supported rule classes); default is CONTINUE_ON_FAILURE
   * @return            a new RuleBuilder
   */
  public static RuleBuilder create(Class ruleClass,
                                                   RuleChainActionType actionType) {
    return new RuleBuilder<>(ruleClass, actionType);
  }

  /**
   * Returns a new RuleBuilder for the specified Rule class.
   * @param ruleClass the class of Rule to build
   * @return          a new RuleBuilder
   */
  public static RuleBuilder create(Class ruleClass) {
    return new RuleBuilder<>(ruleClass);
  }

  /**
   * Returns a new RuleBuilder for the default Rule type.
   * @return  a new RuleBuilder
   */
  public static RuleBuilder create() {
    RuleBuilder rule = new RuleBuilder<>(GoldenRule.class);
    rule._factType = Object.class;
    return rule;
  }

  private RuleBuilder(Class ruleClass) {
    _ruleClass = ruleClass;
  }

  private RuleBuilder(Class ruleClass, RuleChainActionType actionType) {
    this(ruleClass);
    _actionType = actionType;
  }

  public RuleBuilder withName(String name) {
    _name = Optional.of(name);
    return this;
  }

  /**
   * Specifies the fact type for the Rule being built.
   * @param factType  the type of facts to be used in the Rule
   * @param        the type of facts
   * @return          a builder using the new fact type for building a Rule
   */
  public  RuleBuilder withFactType(Class factType) {
    RuleBuilder builder = new RuleBuilder<>(_ruleClass);
    builder._factType = factType;
    builder._resultType = _resultType;
    builder._actionType = _actionType;
    return builder;
  }

  /**
   * Specifies the Result type for the Rule being built.
   * @param resultType  the type of the Result to be used in the Rule
   * @param          the type of Result
   * @return            a builder using a new result type for building a Rule
   */
  public  RuleBuilder withResultType(Class resultType) {
    RuleBuilder builder = new RuleBuilder<>(_ruleClass);
    builder._factType = _factType;
    builder._resultType = resultType;
    builder._actionType = _actionType;
    return builder;
  }

  /**
   * Adds a fact to the Rule using a name value pair to specify a new fact.
   * @param name    the name of the fact
   * @param value   the value of the fact
   * @return        a builder for building rules after a 'given' statement
   */
  @SuppressWarnings("unchecked")
  public GivenRuleBuilder given(String name, T value) {
    Rule rule = _name.map(ruleName -> (Rule)new AuditableRule(newRule(), ruleName)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new GivenRuleBuilder(rule, new Fact(name, value));
  }

  /**
   * Adds one or more facts to the Rule.
   * @param facts the facts to be added to the Rule
   * @return  a builder for building rules after a 'given' statement
   */
  @SuppressWarnings("unchecked")
  @SafeVarargs
  public final GivenRuleBuilder given(NameValueReferable... facts) {
    Rule rule = _name.map(name -> (Rule)new AuditableRule(newRule(), name)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new GivenRuleBuilder(rule, facts);
  }

  /**
   * Adds facts to a Rule.
   * @param   facts the facts to be added to the Rule
   * @return  a builder for building rules after a 'given' statement
   */
  @SuppressWarnings("unchecked")
  public final GivenRuleBuilder given(NameValueReferableMap facts) {
    Rule rule = _name.map(name -> (Rule)new AuditableRule(newRule(), name)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new GivenRuleBuilder(rule, facts);
  }

  /**
   * Specifies the condition for the Rule.
   * @param condition the condition for the Rule
   * @return          a builder for building rules after a 'when' statement
   */
  @SuppressWarnings("unchecked")
  public WhenRuleBuilder when(Predicate> condition) {
    Rule rule = _name.map(name -> (Rule)new AuditableRule(newRule(), name)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new WhenRuleBuilder(rule, condition);
  }

  /**
   * Adds a using constraint in the Rule that restricts the facts supplied to the subsequent 'then' action.
   * @param factNames the fact names to be supplied to the subsequent 'then' action
   * @return          a builder the allows for the Rule to be built following the 'using' statement
   */
  @SuppressWarnings("unchecked")
  public UsingRuleBuilder using(String... factNames) {
    Rule rule = _name.map(name -> (Rule)new AuditableRule(newRule(), name)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new UsingRuleBuilder(rule, factNames);
  }

  /**
   * Adds an action as a Consumer to the Rule.
   * @param action  a Consumer action to be added to the Rule that accepts the facts specified for the Rule
   * @return        a builder for building rules after a 'then' action is specified
   */
  @SuppressWarnings("unchecked")
  public ThenRuleBuilder then(Consumer> action) {
    Rule rule = _name.map(name -> (Rule)new AuditableRule(newRule(), name)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new ThenRuleBuilder(rule, action);
  }

  /**
   * Adds an action as a Consumer to the Rule.
   * @param action  a Consumer action to be added to the Rule that accepts the facts and the Result specified
   *                for the Rule
   * @return        a builder for building rules after a 'then' action is specified
   */
  @SuppressWarnings("unchecked")
  public ThenRuleBuilder then(BiConsumer, Result> action) {
    Rule rule = _name.map(name -> (Rule)new AuditableRule(newRule(), name)).orElse(newRule());
    if (rule == null) {
      throw new IllegalStateException("No Rule is instantiated; An invalid Rule class may have been provided");
    }
    return new ThenRuleBuilder(rule, action);
  }

  /**
   * Builds the Rule.
   * @return  a Rule
   */
  @Override
  public Rule build() {
    return newRule();
  }

  private Rule newRule() {
    if (_actionType.equals(STOP_ON_FAILURE)) {
      try {
        Constructor constructor = _ruleClass.getConstructor(Class.class, RuleChainActionType.class);
        return (Rule)constructor.newInstance(new Object[]{_factType, _actionType});
      } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException ex) {
        LOGGER.error("Unable to create an instance of the specified Rule class '"
            + _ruleClass
            + "' with the specified fact type and 'stopOnRuleFailire' boolean parameter");
        return null;
      }
    }
    try {
      Constructor constructor = _ruleClass.getConstructor(Class.class, Class.class);
      return (Rule)constructor.newInstance(new Object[]{_factType, _resultType});
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException ex) {
      LOGGER.debug("Unable to create an instance of the specified Rule class '"
          + _ruleClass
          + "' with fact and result types specified");
    }
    try {
      Constructor constructor = _ruleClass.getConstructor(Class.class);
      return (Rule)constructor.newInstance(new Object[] {_factType});
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException ex) {
      LOGGER.debug("Attempt to use a single argument constructor for specifying the Fact type failed", ex);
    }
    try {
      Constructor constructor = _ruleClass.getConstructor(new Class[] {});
      return (Rule)constructor.newInstance(new Object[]{});
    } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException ex) {
      LOGGER.debug("Attempt to use the default constructor failed for " + _ruleClass, ex);
      LOGGER.error("Unable to create a Rule of type '" + _ruleClass.getName() + "'");
    }

    return null;
  }
}