// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.util; import java.util.ArrayList; import java.util.List; /** * One-class rule engine for includes/excludes logic. It can be used when * set of objects has to filtered using includes and excludes rules. * For example, when filtering files by file name etc. * <p> * Rule engine works in one of two modes: * <ul> * <li><i>blacklist</i> - when any input is allowed by default and when you specify * explicit excludes. * </li> * <li><i>whitelist</i> - when any input is disabled by default and when you specify * explicit includes. * </li> * </ul> * <p> * The logic of this rule engine depends on the current mode. In both cases, * always the inverse rules are considered first. For example, for <i>blacklist</i> * mode, engine first examine excludes, and then includes. This way you can * set any filter combination. * <p> * All Jodd classes that filters something uses this class to unify the * behavior across the Jodd library. */ public class InExRules<T, R> implements InExRuleMatcher<T, R> { protected List<Rule<R>> rules; protected final InExRuleMatcher<T, R> inExRuleMatcher; protected int includesCount; protected int excludesCount; protected boolean blacklist = true; /** * Creates default instance. */ public InExRules() { this.inExRuleMatcher = this; } /** * Creates instance that uses provided matcher. */ public InExRules(InExRuleMatcher<T, R> inExRuleMatcher) { this.inExRuleMatcher = inExRuleMatcher; } /** * Returns total number of all rules. */ public int totalRules() { if (rules == null) { return 0; } return rules.size(); } /** * Returns total number of include rules. */ public int totalIncludeRules() { return includesCount; } /** * Returns total number of exclude rules. */ public int totalExcludeRules() { return excludesCount; } /** * Returns <code>true</code> if rule engine has at least one rule set. */ public boolean hasRules() { if (rules == null) { return false; } return !rules.isEmpty(); } /** * Rule definition. */ public static class Rule<R> { public final R value; public final boolean include; public Rule(R value, boolean include) { this.value = value; this.include = include; } @Override public String toString() { return (include ? "+" : "-") + value.toString(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Rule rule = (Rule) o; if (include != rule.include) { return false; } if (!value.equals(rule.value)) { return false; } return true; } @Override public int hashCode() { int result = value.hashCode(); result = 31 * result + (include ? 1 : 0); return result; } } /** * Returns rule's value on given index. */ public R getRule(int index) { return rules.get(index).value; } /** * Resets all the rules in this rule engine. */ public void reset() { if (rules != null) { rules.clear(); } includesCount = excludesCount = 0; blacklist = true; } /** * Enables <i>blacklist</i> mode - everything is <b>included</b> by default, * and user sets explicit excludes. */ public void blacklist() { blacklist = true; } /** * Returns <code>true</code> if blacklist mode is set. */ public boolean isBlacklist() { return blacklist; } /** * Enables <i>whitelist</i> mode - everything is <b>excluded</b> by default, * and user set explicit includes. */ public void whitelist() { blacklist = false; } /** * Returns <code>true</code> if whitelist mode is set. */ public boolean isWhitelist() { return !blacklist; } /** * Sets blacklist or whitelist mode depending on rules. Smart mode * determines the following: * <ul> * <li>If there are only include rules, then the {@link #whitelist() whitelist} mode is set.</li> * <li>If there are only excluded rules, then the {@link #blacklist() blacklist} mode is set.</li> * <li>In any other case (both type of rules exist or no rules are set), then mode is not changed.</li> * </ul> * Should be called <b>after</b> all the rules are set, before matching starts. */ public void smartMode() { if (excludesCount == 0 && includesCount > 0) { whitelist(); } else if (excludesCount > 0 && includesCount == 0) { blacklist(); } } /** * Adds include rule. */ public void include(R rule) { addRule(rule, true); } /** * Adds exclude rule. */ public void exclude(R rule) { addRule(rule, false); } /** * Adds a rule. Duplicates are not allowed and will be ignored. */ protected void addRule(R rule, boolean include) { if (rules == null) { rules = new ArrayList<>(); } if (include) { includesCount++; } else { excludesCount++; } Rule<R> newRule = new Rule<>(rule, include); if (rules.contains(newRule)) { return; } rules.add(newRule); } /** * Matches value against the set of rules using current white/black list mode. */ public boolean match(T value) { return match(value, blacklist); } /** * Matches value against the set of rules using provided white/black list mode. */ public boolean match(T value, boolean blacklist) { if (rules == null) { return blacklist; } boolean include = blacklist; if (include) { include = processExcludes(value, true); include = processIncludes(value, include); } else { include = processIncludes(value, false); include = processExcludes(value, include); } return include; } /** * Applies rules on given flag using current black/white list mode. * @see #apply(Object, boolean, boolean) */ public boolean apply(T value, boolean flag) { return apply(value, blacklist, flag); } /** * Applies rules on given flag. Flag is only changed if at least one rule * matched. Otherwise, the same value is returned. This way you can * chain several rules and have the rule engine change the flag * only when a rule is matched. */ public boolean apply(T value, final boolean blacklist, boolean flag) { if (rules == null) { return flag; } if (blacklist) { flag = processExcludes(value, flag); flag = processIncludes(value, flag); } else { flag = processIncludes(value, flag); flag = processExcludes(value, flag); } return flag; } /** * Process includes rules. */ protected boolean processIncludes(T value, boolean include) { if (includesCount > 0) { if (!include) { for (Rule<R> rule : rules) { if (!rule.include) { continue; } if (inExRuleMatcher.accept(value, rule.value, true)) { include = true; break; } } } } return include; } /** * Process excludes rules. */ protected boolean processExcludes(T value, boolean include) { if (excludesCount > 0) { if (include) { for (Rule<R> rule : rules) { if (rule.include) { continue; } if (inExRuleMatcher.accept(value, rule.value, false)) { include = false; break; } } } } return include; } /** * Matches value against single rule. By default performs <code>equals</code> on value * against the rule. */ public boolean accept(T value, R rule, boolean include) { return value.equals(rule); } }