package org.infinispan.objectfilter.impl.syntax; import java.util.ArrayList; import java.util.List; /** * Applies some optimisations to a boolean expression. Most notably, it brings it to NNF (Negation normal form, see * http://en.wikipedia.org/wiki/Negation_normal_form). Moves negation directly near the variable by repeatedly applying * the De Morgan's laws (see http://en.wikipedia.org/wiki/De_Morgan%27s_laws). Eliminates double negation. Normalizes * comparison operators by replacing 'greater' with 'less'. Detects sub-expressions that are boolean constants. * Simplifies boolean constants by applying boolean short-circuiting. Eliminates resulting trivial conjunctions or * disjunctions that have only one child. Ensures all paths from root to leafs contain an alternation of conjunction and * disjunction. This is achieved by absorbing the children whenever a boolean sub-expression if of the same kind as the * parent. * * @author anistor@redhat.com * @since 7.0 */ public final class BooleanFilterNormalizer { /** * A visitor that removes constant boolean expressions, absorbs sub-expressions (removes needless parentheses) and * swaps comparison operand sides to ensure the constant is always on the right side. */ private final ExprVisitor simplifierVisitor = new ExprVisitor() { @Override public BooleanExpr visit(NotExpr notExpr) { // push the negation down the tree until it reaches a PrimaryPredicateExpr return notExpr.getChild().acceptVisitor(deMorganVisitor); } @Override public BooleanExpr visit(OrExpr orExpr) { List<BooleanExpr> children = new ArrayList<>(orExpr.getChildren().size()); for (BooleanExpr child : orExpr.getChildren()) { child = child.acceptVisitor(this); if (child instanceof ConstantBooleanExpr) { // remove boolean constants or shortcircuit entirely if (((ConstantBooleanExpr) child).getValue()) { return ConstantBooleanExpr.TRUE; } } else if (child instanceof OrExpr) { // absorb sub-expressions of the same kind children.addAll(((OrExpr) child).getChildren()); } else { children.add(child); } } PredicateOptimisations.optimizePredicates(children, false); // simplify trivial expressions if (children.size() == 1) { return children.get(0); } return new OrExpr(children); } @Override public BooleanExpr visit(AndExpr andExpr) { List<BooleanExpr> children = new ArrayList<>(andExpr.getChildren().size()); for (BooleanExpr child : andExpr.getChildren()) { child = child.acceptVisitor(this); if (child instanceof ConstantBooleanExpr) { // remove boolean constants or shortcircuit entirely if (!((ConstantBooleanExpr) child).getValue()) { return ConstantBooleanExpr.FALSE; } } else if (child instanceof AndExpr) { // absorb sub-expressions of the same kind children.addAll(((AndExpr) child).getChildren()); } else { children.add(child); } } PredicateOptimisations.optimizePredicates(children, true); // simplify trivial expressions if (children.size() == 1) { return children.get(0); } return new AndExpr(children); } @Override public BooleanExpr visit(ComparisonExpr comparisonExpr) { // start moving the constant to the right side of the comparison if it's not already there ValueExpr leftChild = comparisonExpr.getLeftChild(); leftChild = leftChild.acceptVisitor(this); ValueExpr rightChild = comparisonExpr.getRightChild(); rightChild = rightChild.acceptVisitor(this); ComparisonExpr.Type comparisonType = comparisonExpr.getComparisonType(); // handle constant expressions if (leftChild instanceof ConstantValueExpr) { if (rightChild instanceof ConstantValueExpr) { // replace the comparison of the two constants with the actual result ConstantValueExpr leftConstant = (ConstantValueExpr) leftChild; ConstantValueExpr rightConstant = (ConstantValueExpr) rightChild; Comparable leftValue = leftConstant.getConstantValue(); Comparable rightValue = rightConstant.getConstantValue(); int compRes = leftValue.compareTo(rightValue); switch (comparisonType) { case LESS: return ConstantBooleanExpr.forBoolean(compRes < 0); case LESS_OR_EQUAL: return ConstantBooleanExpr.forBoolean(compRes <= 0); case EQUAL: return ConstantBooleanExpr.forBoolean(compRes == 0); case NOT_EQUAL: return ConstantBooleanExpr.forBoolean(compRes != 0); case GREATER_OR_EQUAL: return ConstantBooleanExpr.forBoolean(compRes >= 0); case GREATER: return ConstantBooleanExpr.forBoolean(compRes > 0); default: throw new IllegalStateException("Unexpected comparison type: " + comparisonType); } } // swap operand sides to ensure the constant is always on the right side ValueExpr temp = rightChild; rightChild = leftChild; leftChild = temp; // now reverse the operator too to restore the semantics comparisonType = comparisonType.reverse(); } // comparison operators are never negated using NotExpr return new ComparisonExpr(leftChild, rightChild, comparisonType); } @Override public BooleanExpr visit(BetweenExpr betweenExpr) { return new AndExpr( new ComparisonExpr(betweenExpr.getLeftChild(), betweenExpr.getFromChild(), ComparisonExpr.Type.GREATER_OR_EQUAL), new ComparisonExpr(betweenExpr.getLeftChild(), betweenExpr.getToChild(), ComparisonExpr.Type.LESS_OR_EQUAL) ); } }; /** * Handles negation by applying De Morgan laws. */ private final ExprVisitor deMorganVisitor = new ExprVisitor() { @Override public BooleanExpr visit(ConstantBooleanExpr constantBooleanExpr) { // negated constants are simplified immediately return constantBooleanExpr.negate(); } @Override public BooleanExpr visit(NotExpr notExpr) { // double negation is eliminated, child is simplified return notExpr.getChild().acceptVisitor(simplifierVisitor); } @Override public BooleanExpr visit(OrExpr orExpr) { List<BooleanExpr> children = new ArrayList<>(orExpr.getChildren().size()); for (BooleanExpr child : orExpr.getChildren()) { child = child.acceptVisitor(this); if (child instanceof ConstantBooleanExpr) { // remove boolean constants if (!((ConstantBooleanExpr) child).getValue()) { return ConstantBooleanExpr.FALSE; } } else if (child instanceof AndExpr) { // absorb sub-expressions of the same kind children.addAll(((AndExpr) child).getChildren()); } else { children.add(child); } } // simplify trivial expressions if (children.size() == 1) { return children.get(0); } return new AndExpr(children); } @Override public BooleanExpr visit(AndExpr andExpr) { List<BooleanExpr> children = new ArrayList<>(andExpr.getChildren().size()); for (BooleanExpr child : andExpr.getChildren()) { child = child.acceptVisitor(this); if (child instanceof ConstantBooleanExpr) { // remove boolean constants if (((ConstantBooleanExpr) child).getValue()) { return ConstantBooleanExpr.TRUE; } } else if (child instanceof OrExpr) { // absorb sub-expressions of the same kind children.addAll(((OrExpr) child).getChildren()); } else { children.add(child); } } // simplify trivial expressions if (children.size() == 1) { return children.get(0); } return new OrExpr(children); } @Override public BooleanExpr visit(ComparisonExpr comparisonExpr) { BooleanExpr booleanExpr = comparisonExpr.acceptVisitor(simplifierVisitor); // simplify negated constants immediately if (booleanExpr instanceof ConstantBooleanExpr) { return ((ConstantBooleanExpr) booleanExpr).negate(); } // eliminate double negation if (booleanExpr instanceof NotExpr) { return ((NotExpr) booleanExpr).getChild(); } // interval predicates are never negated, they are converted instead into the opposite interval if (booleanExpr instanceof ComparisonExpr) { ComparisonExpr c = (ComparisonExpr) booleanExpr; return new ComparisonExpr(c.getLeftChild(), c.getRightChild(), c.getComparisonType().negate()); } return new NotExpr(booleanExpr); } @Override public BooleanExpr visit(BetweenExpr betweenExpr) { return new OrExpr( new ComparisonExpr(betweenExpr.getLeftChild(), betweenExpr.getFromChild(), ComparisonExpr.Type.LESS), new ComparisonExpr(betweenExpr.getLeftChild(), betweenExpr.getToChild(), ComparisonExpr.Type.GREATER) ); } @Override public BooleanExpr visit(IsNullExpr isNullExpr) { return new NotExpr(isNullExpr); } @Override public BooleanExpr visit(LikeExpr likeExpr) { return new NotExpr(likeExpr); } }; public BooleanExpr normalize(BooleanExpr booleanExpr) { return booleanExpr == null ? null : booleanExpr.acceptVisitor(simplifierVisitor); } }