
com.yahoo.document.select.rule.LogicNode Maven / Gradle / Ivy
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document.select.rule;
import com.yahoo.document.BucketIdFactory;
import com.yahoo.document.select.BucketSet;
import com.yahoo.document.select.Context;
import com.yahoo.document.select.ResultList;
import com.yahoo.document.select.Visitor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
/**
* This class defines a logical expression of nodes. This implementation uses a stack to evaluate its content as to
* avoid deep recursions when building the parse tree.
*
* @author Simon Thoresen Hult
*/
public class LogicNode implements ExpressionNode {
// A no-op value is defined for completeness.
public static final int NOP = 0;
// The OR operator has lower precedence than AND.
public static final int OR = 1;
// The AND operator has the highest precedence.
public static final int AND = 2;
// The items contained in this.
private final List items = new ArrayList<>();
/**
* Construct an empty logic expression.
*/
public LogicNode() {
// empty
}
public List getItems() {
return items;
}
/**
* Adds an (operator, node) pair to this expression.
*
* @param operator The operator that combines the previous with the node given.
* @param node The node to add to this.
* @return This, to allow chaining.
*/
public LogicNode add(String operator, ExpressionNode node) {
items.add(new LogicNode.NodeItem(stringToOperator(operator), node));
return this;
}
@Override
public BucketSet getBucketSet(BucketIdFactory factory) {
Deque buf = new ArrayDeque<>();
for (NodeItem item : items) {
if (!buf.isEmpty()) {
while (buf.peek().operator > item.operator) {
combineBuckets(buf);
}
}
buf.push(new BucketItem(item.operator, item.node.getBucketSet(factory)));
}
while (buf.size() > 1) {
combineBuckets(buf);
}
return buf.pop().buckets;
}
/**
* Combines the top two items of the given stack using the operator of the second.
*
* @param buf The stack of bucket items.
*/
private void combineBuckets(Deque buf) {
BucketItem rhs = buf.pop();
BucketItem lhs = buf.pop();
switch (rhs.operator) {
case AND -> {
if (lhs.buckets == null) {
lhs.buckets = rhs.buckets;
} else if (rhs.buckets == null) {
// empty
} else {
lhs.buckets = lhs.buckets.intersection(rhs.buckets);
}
}
case OR -> {
if (lhs.buckets == null) {
// empty
} else if (rhs.buckets == null) {
lhs.buckets = null;
} else {
lhs.buckets = lhs.buckets.union(rhs.buckets);
}
}
default -> throw new IllegalStateException("Arithmetic operator " + rhs.operator + " not supported.");
}
buf.push(lhs);
}
@Override
public Object evaluate(Context context) {
Deque buf = new ArrayDeque<>();
for (NodeItem item : items) {
if ( buf.size() > 1) {
while ((buf.peek().getOperator() >= item.operator)) {
combineValues(buf);
}
}
buf.push(new LazyValueItem(item, context));
}
while (buf.size() > 1) {
combineValues(buf);
}
return buf.pop().getResult();
}
/**
* Combines the top two items of the given stack using the operator of the second.
*
* @param buf The stack of values.
*/
private void combineValues(Deque buf) {
ValueItem rhs = buf.pop();
ValueItem lhs = buf.pop();
buf.push(new LazyCombinedItem(lhs, rhs));
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public String toString() {
StringBuilder ret = new StringBuilder();
for (LogicNode.NodeItem item : items) {
if (item.operator != NOP) {
ret.append(" ").append(operatorToString(item.operator)).append(" ");
}
ret.append(item.node);
}
return ret.toString();
}
/**
* Converts the given operator index to a string representation.
*
* @param operator The operator index to convert.
* @return The string representation.
*/
private String operatorToString(int operator) {
return switch (operator) {
case NOP -> null;
case OR -> "or";
case AND -> "and";
default -> throw new IllegalStateException("Logical operator " + operator + " not supported.");
};
}
/**
* Converts the given operator string to a corresponding operator index. This is necessary to perform a stack
* traversal of logic expression.
*
* @param operator The operator to convert.
* @return The corresponding index.
*/
private int stringToOperator(String operator) {
if (operator == null) {
return NOP;
} else if (operator.equalsIgnoreCase("or")) {
return OR;
} else if (operator.equalsIgnoreCase("and")) {
return AND;
} else {
throw new IllegalStateException("Logical operator '" + operator + "' not supported.");
}
}
/**
* Private class to store results in a stack.
*/
private static abstract class ValueItem implements ResultList.LazyResultList {
private final int operator;
ValueItem(int operator) {
this.operator = operator;
}
int getOperator() { return operator; }
}
private static final class LazyValueItem extends ValueItem {
private final NodeItem item;
private final Context context;
private ResultList lazyResult = null;
LazyValueItem(NodeItem item, Context context) {
super(item.operator);
this.item = item;
this.context = context;
}
@Override
public ResultList getResult() {
if (lazyResult == null) {
lazyResult = ResultList.toResultList(item.node.evaluate(context));
}
return lazyResult;
}
}
private static final class LazyCombinedItem extends ValueItem {
private final ValueItem lhs;
private final ValueItem rhs;
private ResultList lazyResult = null;
LazyCombinedItem(ValueItem lhs, ValueItem rhs) {
super(lhs.getOperator());
this.lhs = lhs;
this.rhs = rhs;
}
@Override
public ResultList getResult() {
if (lazyResult == null) {
switch (rhs.getOperator()) {
case AND -> lazyResult = lhs.getResult().combineAND(rhs);
case OR -> lazyResult = lhs.getResult().combineOR(rhs);
default ->
throw new IllegalStateException("Logical operator " + rhs.getOperator() + " not supported.");
}
}
return lazyResult;
}
}
/**
* Private class to store bucket sets in a stack.
*/
private static final class BucketItem {
final private int operator;
private BucketSet buckets;
BucketItem(int operator, BucketSet buckets) {
this.operator = operator;
this.buckets = buckets;
}
}
/**
* Private class to store expression nodes in a stack.
*/
public static final class NodeItem {
final private int operator;
final private ExpressionNode node;
NodeItem(int operator, ExpressionNode node) {
this.operator = operator;
this.node = node;
}
public int getOperator() {
return operator;
}
public ExpressionNode getNode() {
return node;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy