Updated boolean expressions to really, actually, seriously work this time.

This commit is contained in:
Krazy
2014-09-16 00:49:44 +00:00
parent 1c9975bf2a
commit 37c1cc9c54
5 changed files with 244 additions and 235 deletions

View File

@@ -0,0 +1,170 @@
package forge.itemmanager;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import forge.card.CardRules;
import forge.card.CardRulesPredicates;
import forge.util.PredicateString.StringOp;
import java.util.*;
public class BooleanExpression {
private Stack<Operator> operators = new Stack<>();
private Stack<Predicate<CardRules>> operands = new Stack<>();
private StringTokenizer expression;
private boolean inName, inType, inText, inCost;
private static enum Operator {
AND("&", 0), OR("|", 0), NOT("!", 1), OPEN_PAREN("(", 2), CLOSE_PAREN(")", 2), ESCAPE("\\", -1);
private final String token;
private final int precedence;
private Operator(final String token, final int precedence) {
this.token = token;
this.precedence = precedence;
}
}
public BooleanExpression(final String expression, final boolean inName, final boolean inType, final boolean inText, final boolean inCost) {
this.expression = new StringTokenizer(expression);
this.inName = inName;
this.inType = inType;
this.inText = inText;
this.inCost = inCost;
}
public Predicate<CardRules> evaluate() {
String currentValue = "";
boolean escapeNext = false;
while (expression.hasNext()) {
String token = expression.next();
Operator operator = null;
if (token.equals(Operator.AND.token)) {
operator = Operator.AND;
} else if (token.equals(Operator.OR.token)) {
operator = Operator.OR;
} else if (token.equals(Operator.OPEN_PAREN.token)) {
operator = Operator.OPEN_PAREN;
} else if (token.equals(Operator.CLOSE_PAREN.token)) {
operator = Operator.CLOSE_PAREN;
} else if (token.equals(Operator.NOT.token)) {
operator = Operator.NOT;
} else if (token.equals(Operator.ESCAPE.token)) {
escapeNext = true;
continue;
}
if (operator == null) {
currentValue += token;
} else {
if (escapeNext) {
escapeNext = false;
currentValue += token;
continue;
}
if (!currentValue.trim().isEmpty()) {
operands.push(valueOf(currentValue.trim()));
}
currentValue = "";
if (!operators.isEmpty() && operator.precedence < operators.peek().precedence) {
resolve(true);
} else if (!operators.isEmpty() && operator == Operator.CLOSE_PAREN) {
while (!operators.isEmpty() && operators.peek() != Operator.OPEN_PAREN) {
resolve(true);
}
}
operators.push(operator);
}
}
if (!currentValue.trim().isEmpty()) {
operands.push(valueOf(currentValue.trim()));
}
while (operators.size() > 0) {
resolve(true);
}
return operands.get(0);
}
private void resolve(final boolean alwaysPopOperator) {
Predicate<CardRules> right;
Predicate<CardRules> left;
switch (operators.peek()) {
case AND:
operators.pop();
right = operands.pop();
left = operands.pop();
operands.push(Predicates.and(left, right));
break;
case OR:
operators.pop();
right = operands.pop();
left = operands.pop();
operands.push(Predicates.or(left, right));
break;
case NOT:
operators.pop();
left = operands.pop();
operands.push(Predicates.not(left));
break;
default:
if (alwaysPopOperator) {
operators.pop();
}
break;
}
}
private Predicate<CardRules> valueOf(final String value) {
List<Predicate<CardRules>> predicates = new ArrayList<>();
if (inName) {
predicates.add(CardRulesPredicates.name(StringOp.CONTAINS_IC, value));
}
if (inType) {
predicates.add(CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, value));
}
if (inText) {
predicates.add(CardRulesPredicates.rules(StringOp.CONTAINS_IC, value));
}
if (inCost) {
predicates.add(CardRulesPredicates.cost(StringOp.CONTAINS_IC, value));
}
if (predicates.size() > 0) {
return Predicates.or(predicates);
}
return Predicates.alwaysTrue();
}
public static boolean isExpression(final String string) {
return string.contains(Operator.AND.token) || string.contains(Operator.OR.token) || string.trim().startsWith(Operator.NOT.token);
}
}

View File

@@ -15,7 +15,10 @@ import forge.itemmanager.SItemManagerUtil.StatTypes;
import forge.util.BinaryUtil;
import forge.util.PredicateString.StringOp;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Static factory; holds blocks of form elements and predicates
@@ -38,10 +41,15 @@ public class SFilterUtil {
if (BooleanExpression.isExpression(text)) {
BooleanExpression expression = new BooleanExpression(text, inName, inType, inText, inCost);
Predicate<CardRules> filter = expression.evaluate();
if (filter != null) {
return Predicates.compose(invert ? Predicates.not(filter) : filter, PaperCard.FN_GET_RULES);
try {
Predicate<CardRules> filter = expression.evaluate();
if (filter != null) {
return Predicates.compose(invert ? Predicates.not(filter) : filter, PaperCard.FN_GET_RULES);
}
} catch (Exception ignored) {
ignored.printStackTrace();
//Continue with standard filtering if the expression is not valid.
}
}
@@ -64,236 +72,6 @@ public class SFilterUtil {
return Predicates.compose(textFilter, PaperCard.FN_GET_RULES);
}
private static class Tokenizer implements Iterator<String> {
private String string;
private int index = 0;
private Tokenizer(String string) {
this.string = string;
}
@Override
public boolean hasNext() {
return index < string.length();
}
@Override
public String next() {
return string.charAt(index++) + "";
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
public String lookAhead() {
if (hasNext()) {
return string.charAt(index) + "";
}
else {
return "";
}
}
}
private static class BooleanExpression {
private static enum Operation {
AND("&&"), OR("||"), NOT("!"), OPEN_PAREN("("), CLOSE_PAREN(")"), ESCAPE("\\");
private final String token;
private Operation(String token) {
this.token = token;
}
}
private String text;
private Stack<String> stack = new Stack<>();
private String currentValue = "";
private boolean inName = false;
private boolean inType = false;
private boolean inText = false;
private boolean inCost = false;
private BooleanExpression(String text, boolean inName, boolean inType, boolean inText, boolean inCost) {
this.text = text;
this.inName = inName;
this.inType = inType;
this.inText = inText;
this.inCost = inCost;
parse();
}
private static boolean isExpression(String text) {
return text.contains(Operation.AND.token) || text.contains(Operation.OR.token);
}
private void parse() {
Tokenizer tokenizer = new Tokenizer(text);
String currentChar;
boolean escapeNext = false;
while (tokenizer.hasNext()) {
currentChar = tokenizer.next();
if (escapeNext) {
currentValue += currentChar;
escapeNext = false;
}
else if (currentChar.equals(Operation.ESCAPE.token)) {
escapeNext = true;
}
else {
if ((currentChar + tokenizer.lookAhead()).equals(Operation.AND.token)) {
tokenizer.next();
pushTokenToStack(Operation.AND.token);
}
else if ((currentChar + tokenizer.lookAhead()).equals(Operation.OR.token)) {
tokenizer.next();
pushTokenToStack(Operation.OR.token);
}
else if (currentChar.equals(Operation.OPEN_PAREN.token)) {
pushTokenToStack(Operation.OPEN_PAREN.token);
}
else if (currentChar.equals(Operation.CLOSE_PAREN.token)) {
pushTokenToStack(Operation.CLOSE_PAREN.token);
}
else if (currentChar.equals(Operation.NOT.token)) {
pushTokenToStack(Operation.NOT.token);
}
else {
currentValue += currentChar;
}
}
}
if (!currentValue.trim().isEmpty()) {
stack.push(currentValue.trim());
}
}
private void pushTokenToStack(String token) {
currentValue = currentValue.trim();
if (!currentValue.isEmpty()) {
stack.push(currentValue);
currentValue = "";
}
stack.push(token);
}
private Predicate<CardRules> evaluate() {
//Collections.reverse(stack); //Reverse the stack so we're popping off the start
Predicate<CardRules> rules = null;
Stack<String> evaluationStack = new Stack<>();
while (stack.size() > 0) {
String stackItem = stack.pop();
if (stackItem.equals(Operation.CLOSE_PAREN.token)) {
rules = evaluateUntilToken(evaluationStack, rules, Operation.OPEN_PAREN.token);
}
else {
evaluationStack.push(stackItem);
}
}
return evaluateUntilToken(evaluationStack, rules, "");
}
private Predicate<CardRules> evaluateUntilToken(Stack<String> evaluationStack, Predicate<CardRules> rules, String token) {
Predicate<CardRules> outputRules = rules;
Operation currentOperation = null;
while (!evaluationStack.isEmpty()) {
String stackItem = evaluationStack.pop();
if (!token.isEmpty() && token.equals(stackItem)) {
break;
}
if (isOperation(stackItem)) {
if (stackItem.equals(Operation.AND.token)) {
currentOperation = Operation.AND;
}
else if (stackItem.equals(Operation.OR.token)) {
currentOperation = Operation.OR;
}
else if (stackItem.equals(Operation.NOT.token)) {
if (outputRules == null) {
return null;
}
outputRules = Predicates.not(outputRules);
}
}
else {
if (currentOperation == null) {
if (outputRules == null) {
outputRules = evaluateValue(stackItem);
}
}
else {
if (outputRules == null) {
return null;
}
switch (currentOperation) {
case AND:
outputRules = Predicates.and(outputRules, evaluateValue(stackItem));
break;
case OR:
outputRules = Predicates.or(outputRules, evaluateValue(stackItem));
break;
default:
break;
}
currentOperation = null;
}
}
}
return outputRules;
}
private Predicate<CardRules> evaluateValue(String value) {
List<Predicate<CardRules>> predicates = new ArrayList<>();
if (inName) {
predicates.add(CardRulesPredicates.name(StringOp.CONTAINS_IC, value));
}
if (inType) {
predicates.add(CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, value));
}
if (inText) {
predicates.add(CardRulesPredicates.rules(StringOp.CONTAINS_IC, value));
}
if (inCost) {
predicates.add(CardRulesPredicates.cost(StringOp.CONTAINS_IC, value));
}
if (predicates.size() > 0) {
return Predicates.or(predicates);
}
return Predicates.alwaysTrue();
}
private boolean isOperation(String token) {
for (Operation o : Operation.values()) {
if (token.equals(o.token)) {
return true;
}
}
return false;
}
}
public static <T extends InventoryItem> Predicate<T> buildItemTextFilter(String text) {
if (text.trim().isEmpty()) {
return Predicates.alwaysTrue();

View File

@@ -0,0 +1,29 @@
package forge.itemmanager;
import java.util.Iterator;
public class StringTokenizer implements Iterator<String> {
private String string;
private int index = 0;
public StringTokenizer(String string) {
this.string = string;
}
@Override
public boolean hasNext() {
return index < string.length();
}
@Override
public String next() {
return string.charAt(index++) + "";
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
}