package org.infinispan.objectfilter.impl.syntax.parser; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import org.infinispan.objectfilter.impl.logging.Log; import org.infinispan.objectfilter.impl.ql.PropertyPath; import org.infinispan.objectfilter.impl.ql.QueryRendererDelegate; import org.infinispan.objectfilter.impl.syntax.AggregationExpr; import org.infinispan.objectfilter.impl.syntax.AndExpr; import org.infinispan.objectfilter.impl.syntax.BetweenExpr; import org.infinispan.objectfilter.impl.syntax.BooleanExpr; import org.infinispan.objectfilter.impl.syntax.ComparisonExpr; import org.infinispan.objectfilter.impl.syntax.ConstantBooleanExpr; import org.infinispan.objectfilter.impl.syntax.ConstantValueExpr; import org.infinispan.objectfilter.impl.syntax.FullTextBoostExpr; import org.infinispan.objectfilter.impl.syntax.FullTextOccurExpr; import org.infinispan.objectfilter.impl.syntax.FullTextRangeExpr; import org.infinispan.objectfilter.impl.syntax.FullTextRegexpExpr; import org.infinispan.objectfilter.impl.syntax.FullTextTermExpr; import org.infinispan.objectfilter.impl.syntax.IsNullExpr; import org.infinispan.objectfilter.impl.syntax.LikeExpr; import org.infinispan.objectfilter.impl.syntax.NotExpr; import org.infinispan.objectfilter.impl.syntax.OrExpr; import org.infinispan.objectfilter.impl.syntax.PropertyValueExpr; import org.jboss.logging.Logger; /** * Builder for the creation of WHERE/HAVING clause filters targeting a single entity. * <p/> * Implemented as a stack of {@link LazyBooleanExpr}s which allows to add elements to the constructed query in a * uniform manner while traversing through the original query parse tree. * * @author anistor@redhat.com * @since 9.0 */ final class ExpressionBuilder<TypeMetadata> { private static final Log log = Logger.getMessageLogger(Log.class, ExpressionBuilder.class.getName()); private final ObjectPropertyHelper<TypeMetadata> propertyHelper; private TypeMetadata entityType; /** * Keep track of all the parent expressions ({@code AND}, {@code OR}, {@code NOT}) of the WHERE/HAVING clause of the * built query. */ private final Deque<LazyBooleanExpr> stack = new ArrayDeque<>(); ExpressionBuilder(ObjectPropertyHelper<TypeMetadata> propertyHelper) { this.propertyHelper = propertyHelper; } public void setEntityType(TypeMetadata entityType) { this.entityType = entityType; stack.push(new LazyRootBooleanExpr()); } public void addFullTextTerm(PropertyPath<?> propertyPath, String value, Integer fuzzySlop) { push(new FullTextTermExpr(makePropertyValueExpr(propertyPath), value, fuzzySlop)); } public void addFullTextRegexp(PropertyPath<?> propertyPath, String regexp) { push(new FullTextRegexpExpr(makePropertyValueExpr(propertyPath), regexp)); } public void addFullTextRange(PropertyPath<?> propertyPath, boolean includeLower, Object lower, Object upper, boolean includeUpper) { push(new FullTextRangeExpr(makePropertyValueExpr(propertyPath), includeLower, lower, upper, includeUpper)); } public void addComparison(PropertyPath<?> propertyPath, ComparisonExpr.Type comparisonType, Object value) { Comparable typedValue = (Comparable) propertyHelper.convertToBackendType(entityType, propertyPath.asArrayPath(), value); push(new ComparisonExpr(makePropertyValueExpr(propertyPath), new ConstantValueExpr(typedValue), comparisonType)); } public void addRange(PropertyPath<?> propertyPath, Object lower, Object upper) { Comparable lowerValue = (Comparable) propertyHelper.convertToBackendType(entityType, propertyPath.asArrayPath(), lower); Comparable upperValue = (Comparable) propertyHelper.convertToBackendType(entityType, propertyPath.asArrayPath(), upper); push(new BetweenExpr(makePropertyValueExpr(propertyPath), new ConstantValueExpr(lowerValue), new ConstantValueExpr(upperValue))); } public void addIn(PropertyPath<?> propertyPath, List<Object> values) { PropertyValueExpr valueExpr = makePropertyValueExpr(propertyPath); List<BooleanExpr> children = new ArrayList<>(values.size()); for (Object element : values) { //todo [anistor] need more efficient implementation Comparable typedValue = (Comparable) propertyHelper.convertToBackendType(entityType, propertyPath.asArrayPath(), element); ComparisonExpr booleanExpr = new ComparisonExpr(valueExpr, new ConstantValueExpr(typedValue), ComparisonExpr.Type.EQUAL); children.add(booleanExpr); } push(new OrExpr(children)); } public void addLike(PropertyPath<?> propertyPath, Object patternValue, Character escapeCharacter) { // TODO [anistor] escapeCharacter is ignored for now push(new LikeExpr(makePropertyValueExpr(propertyPath), patternValue)); } public void addIsNull(PropertyPath<?> propertyPath) { push(new IsNullExpr(makePropertyValueExpr(propertyPath))); } public void pushAnd() { push(new LazyAndExpr()); } public void pushOr() { push(new LazyOrExpr()); } public void pushNot() { push(new LazyNegationExpr()); } public void pushFullTextBoost(float boost) { push(new LazyFTBoostExpr(boost)); } public void pushFullTextOccur(QueryRendererDelegate.Occur occur) { push(new LazyFTOccurExpr(occur)); } private void push(BooleanExpr expr) { push(new LazyLeafBooleanExpr(expr)); } private void push(LazyBooleanExpr expr) { // add as sub-expression to the current top expression stack.peek().addChild(expr); // push to expression stack if required if (expr.isParent()) { stack.push(expr); } } public void pop() { stack.pop(); } public BooleanExpr build() { return stack.getFirst().get(); } private PropertyValueExpr makePropertyValueExpr(PropertyPath<?> propertyPath) { //todo [anistor] 2 calls to propertyHelper .. very inefficient boolean isRepeated = propertyHelper.isRepeatedProperty(entityType, propertyPath.asArrayPath()); Class<?> primitiveType = propertyHelper.getPrimitivePropertyType(entityType, propertyPath.asArrayPath()); if (propertyPath instanceof AggregationPropertyPath) { return new AggregationExpr((AggregationPropertyPath) propertyPath, isRepeated, primitiveType); } else { return new PropertyValueExpr(propertyPath, isRepeated, primitiveType); } } public void addConstantBoolean(boolean booleanConstant) { push(ConstantBooleanExpr.forBoolean(booleanConstant)); } /** * Delays construction until {@link #get} is called. */ abstract static class LazyBooleanExpr { /** * Indicates whether this expression can have sub-expressions or not. */ public boolean isParent() { return true; } /** * Adds the given lazy expression to this. * * @param child the child to add */ public abstract void addChild(LazyBooleanExpr child); /** * Returns the query represented by this lazy expression. Contains the all sub-expressions if this is a parent. * * @return the expression represented by this lazy expression */ public abstract BooleanExpr get(); } private static final class LazyLeafBooleanExpr extends LazyBooleanExpr { private final BooleanExpr booleanExpr; LazyLeafBooleanExpr(BooleanExpr booleanExpr) { this.booleanExpr = booleanExpr; } @Override public boolean isParent() { return false; } @Override public void addChild(LazyBooleanExpr child) { throw new UnsupportedOperationException("Adding a sub-expression to a non-parent expression is illegal"); } @Override public BooleanExpr get() { return booleanExpr; } } private static final class LazyAndExpr extends LazyBooleanExpr { private final List<LazyBooleanExpr> children = new ArrayList<>(3); @Override public void addChild(LazyBooleanExpr child) { children.add(child); } @Override public BooleanExpr get() { if (children.isEmpty()) { throw new IllegalStateException("A conjunction must have at least one child"); } BooleanExpr firstChild = children.get(0).get(); if (children.size() == 1) { return firstChild; } AndExpr andExpr = new AndExpr(firstChild); for (int i = 1; i < children.size(); i++) { BooleanExpr child = children.get(i).get(); andExpr.getChildren().add(child); } return andExpr; } } private static final class LazyOrExpr extends LazyBooleanExpr { private final List<LazyBooleanExpr> children = new ArrayList<>(3); @Override public void addChild(LazyBooleanExpr child) { children.add(child); } @Override public BooleanExpr get() { if (children.isEmpty()) { throw new IllegalStateException("A disjunction must have at least one child"); } BooleanExpr firstChild = children.get(0).get(); if (children.size() == 1) { return firstChild; } OrExpr orExpr = new OrExpr(firstChild); for (int i = 1; i < children.size(); i++) { BooleanExpr child = children.get(i).get(); orExpr.getChildren().add(child); } return orExpr; } } private static final class LazyNegationExpr extends LazyBooleanExpr { private LazyBooleanExpr child; @Override public void addChild(LazyBooleanExpr child) { if (this.child != null) { throw log.getNotMoreThanOnePredicateInNegationAllowedException(child); } this.child = child; } public LazyBooleanExpr getChild() { return child; } @Override public BooleanExpr get() { return new NotExpr(getChild().get()); } } private static final class LazyFTBoostExpr extends LazyBooleanExpr { private LazyBooleanExpr child; private final float boost; LazyFTBoostExpr(float boost) { this.boost = boost; } @Override public void addChild(LazyBooleanExpr child) { if (this.child != null) { throw log.getNotMoreThanOnePredicateInNegationAllowedException(child); } this.child = child; } public LazyBooleanExpr getChild() { return child; } @Override public BooleanExpr get() { return new FullTextBoostExpr(getChild().get(), boost); } } private static final class LazyFTOccurExpr extends LazyBooleanExpr { private LazyBooleanExpr child; private final QueryRendererDelegate.Occur occur; LazyFTOccurExpr(QueryRendererDelegate.Occur occur) { this.occur = occur; } @Override public void addChild(LazyBooleanExpr child) { if (this.child != null) { throw log.getNotMoreThanOnePredicateInNegationAllowedException(child); } this.child = child; } public LazyBooleanExpr getChild() { return child; } @Override public BooleanExpr get() { return new FullTextOccurExpr(getChild().get(), occur); } } private static final class LazyRootBooleanExpr extends LazyBooleanExpr { private LazyBooleanExpr child; @Override public void addChild(LazyBooleanExpr child) { if (this.child != null) { throw log.getNotMoreThanOnePredicateInRootOfWhereClauseAllowedException(child); } this.child = child; } @Override public BooleanExpr get() { return child == null ? null : child.get(); } } }