org.apache.cayenne.exp.ASTNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cayenne-client-nodeps
Show all versions of cayenne-client-nodeps
Cayenne Object Persistence Framework
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.exp;
import java.util.Collection;
import java.util.regex.Pattern;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.map.Entity;
import org.apache.cayenne.property.PropertyUtils;
import org.apache.cayenne.util.Util;
/**
* A node of the Abstract Syntax Tree (AST) for a compiled Cayenne expression. ASTNode is
* implemented as a linked list holding a reference to the next node in the chain. This
* way, a chain of AST nodes can be evaluated by calling a method on the starting node.
* ASTNodes also has abstract API for various optimizations based on conditional
* execution.
*
* Also serves as a factory for specialized nodes handling various operations.
*
*
* @since 1.0.6
* @author Andrus Adamchik
* @deprecated since 1.2
*/
abstract class ASTNode {
protected ASTNode nextNode;
static ASTNode buildObjectNode(Object object, ASTNode parent) {
ASTNode node = new PushNode(object);
return parent != null ? parent.wrapChildNode(node) : node;
}
static ASTNode buildExpressionNode(Expression expression, ASTNode parent) {
ASTNode node;
switch (expression.getType()) {
case Expression.OBJ_PATH:
case Expression.DB_PATH:
node = new PropertyNode(expression);
break;
case Expression.EQUAL_TO:
node = new EqualsNode();
break;
case Expression.OR:
node = new OrNode();
break;
case Expression.AND:
node = new AndNode();
break;
case Expression.NOT:
node = new NotNode();
break;
case Expression.LIST:
node = new ListNode(expression.getOperand(0));
break;
case Expression.NOT_EQUAL_TO:
node = new NotEqualsNode();
break;
case Expression.LESS_THAN:
node = new LessThanNode();
break;
case Expression.LESS_THAN_EQUAL_TO:
node = new LessThanEqualsToNode();
break;
case Expression.GREATER_THAN:
node = new GreaterThanNode();
break;
case Expression.GREATER_THAN_EQUAL_TO:
node = new GreaterThanEqualsToNode();
break;
case Expression.BETWEEN:
node = new BetweenNode(false);
break;
case Expression.NOT_BETWEEN:
node = new BetweenNode(true);
break;
case Expression.IN:
node = new InNode(false);
break;
case Expression.NOT_IN:
node = new InNode(true);
break;
case Expression.LIKE:
node = new LikeNode((String) expression.getOperand(1), false, false);
break;
case Expression.NOT_LIKE:
node = new LikeNode((String) expression.getOperand(1), true, false);
break;
case Expression.LIKE_IGNORE_CASE:
node = new LikeNode((String) expression.getOperand(1), false, true);
break;
case Expression.NOT_LIKE_IGNORE_CASE:
node = new LikeNode((String) expression.getOperand(1), true, true);
break;
default:
throw new ExpressionException("Unsupported expression type: "
+ expression.getType()
+ " ("
+ expression.expName()
+ ")");
}
return parent != null ? parent.wrapChildNode(node) : node;
}
private static boolean contains(Object[] objects, Object object) {
int size = objects.length;
for (int i = 0; i < size; i++) {
if (Util.nullSafeEquals(objects[i], object)) {
return true;
}
}
return false;
}
/**
* Optionally can wrap a child node with a reference wrapper. Default implementation
* simply returns childNode unchanged.
*/
ASTNode wrapChildNode(ASTNode childNode) {
return childNode;
}
ASTNode getNextNode() {
return nextNode;
}
void setNextNode(ASTNode nextNode) {
this.nextNode = nextNode;
}
abstract void appendString(StringBuffer buffer);
/**
* Evaluates a chain of ASTNodes, starting from this node, in the context of a
* JavaBean object that will provide property values for the path nodes. The result is
* converted to boolean.
*/
boolean evaluateBooleanASTChain(Object bean) throws ExpressionException {
return ASTStack.booleanFromObject(evaluateASTChain(bean));
}
/**
* Evaluates a chain of ASTNodes, starting from this node, in the context of a
* JavaBean object that will provide property values for the path nodes.
*/
Object evaluateASTChain(Object bean) throws ExpressionException {
ASTNode currentNode = this;
ASTStack stack = new ASTStack();
// wrap in try/catch to provide unified exception processing
try {
while ((currentNode = currentNode.evaluateWithObject(stack, bean)) != null) {
// empty loop, all the action happens in condition part
}
return stack.pop();
}
catch (Throwable th) {
if (th instanceof ExpressionException) {
throw (ExpressionException) th;
}
else {
throw new ExpressionException("Error evaluating expression.", this
.toString(), Util.unwindException(th));
}
}
}
/**
* Evaluates expression using stack for the current values and an object parameter as
* a context for evaluation. Returns the next ASTNode to evaluate or null if
* evaluation is complete.
*/
abstract ASTNode evaluateWithObject(ASTStack stack, Object bean);
public String toString() {
// appends to a buffer producing a reverse polish notation (RPN)
StringBuffer buffer = new StringBuffer();
for (ASTNode node = this; node != null; node = node.getNextNode()) {
node.appendString(buffer);
}
return buffer.toString();
}
// ***************** concrete subclasses
final static class PushNode extends ASTNode {
Object value;
PushNode(Object value) {
this.value = value;
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
stack.push(value);
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append("%@");
}
}
final static class ListNode extends ASTNode {
Object[] value;
ListNode(Object object) {
// TODO: maybe it makes sense to sort the list
// now, to speed up lookups later?
if (object instanceof Collection) {
value = ((Collection) object).toArray();
}
else if (object instanceof Object[]) {
value = (Object[]) object;
}
else {
// object is really not a collection... I guess it would make
// sense to wrap it in a list
value = new Object[] {
object
};
}
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
stack.push(value);
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append("(%@)");
}
}
final static class PropertyNode extends ASTNode {
Expression pathExp;
String propertyPath;
PropertyNode(Expression pathExp) {
this.pathExp = pathExp;
this.propertyPath = (String) pathExp.getOperand(0);
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// push property value on stack
try {
// for DataObjects it should be faster to read property via
// dataObject methods instead of reflection
// for entities the whole meaning is different - we should return
// an iterator over attributes/relationships...
stack.push((bean instanceof DataObject) ? ((DataObject) bean)
.readNestedProperty(propertyPath) : (bean instanceof Entity)
? ((Entity) bean).resolvePathComponents(pathExp)
: PropertyUtils.getProperty(bean, propertyPath));
}
catch (Exception ex) {
String beanClass = (bean != null) ? bean.getClass().getName() : "";
String msg = "Error reading property '"
+ beanClass
+ "."
+ propertyPath
+ "'.";
throw new ExpressionException(msg, ex);
}
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append("'").append(propertyPath).append("'");
}
}
final static class EqualsNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects at least two values on the stack
stack.push(Util.nullSafeEquals(stack.pop(), stack.pop()));
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" = ");
}
}
final static class AndNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects two booleans on the stack
stack.push(stack.popBoolean() && stack.popBoolean());
return nextNode;
}
ASTNode wrapChildNode(ASTNode childNode) {
return new AndOperandWrapper(childNode, this);
}
void appendString(StringBuffer buffer) {
buffer.append(" and ");
}
}
final static class OrNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects two booleans on the stack
stack.push(stack.popBoolean() || stack.popBoolean());
return nextNode;
}
ASTNode wrapChildNode(ASTNode childNode) {
return new OrOperandWrapper(childNode, this);
}
void appendString(StringBuffer buffer) {
buffer.append(" or ");
}
}
final static class NotNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects one boolean on the stack
stack.push(!stack.popBoolean());
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" not ");
}
}
final static class NotEqualsNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects at least two values on the stack
stack.push(!Util.nullSafeEquals(stack.pop(), stack.pop()));
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" != ");
}
}
final static class LessThanNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects at least two values on the stack
boolean result = false;
Object c1 = stack.pop();
Comparable c2 = stack.popComparable();
// can't compare nulls...be consistent with SQL
if (c1 != null && c2 != null) {
// values are popped in reverse order of insertion,
// so compare the one popped last...
result = c2.compareTo(c1) < 0;
}
stack.push(result);
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" < ");
}
}
final static class LessThanEqualsToNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects at least two values on the stack
boolean result = false;
Object c1 = stack.pop();
Comparable c2 = stack.popComparable();
// can't compare nulls...be consistent with SQL
if (c1 != null && c2 != null) {
// values are popped in reverse order of insertion,
// so compare the one popped last...
result = c2.compareTo(c1) <= 0;
}
stack.push(result);
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" <= ");
}
}
final static class GreaterThanNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects at least two values on the stack
boolean result = false;
Object c1 = stack.pop();
Comparable c2 = stack.popComparable();
// can't compare nulls...be consistent with SQL
if (c1 != null && c2 != null) {
// values are popped in reverse order of insertion,
// so compare the one popped last...
result = c2.compareTo(c1) > 0;
}
stack.push(result);
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" > ");
}
}
final static class GreaterThanEqualsToNode extends ASTNode {
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// expects at least two values on the stack
boolean result = false;
Object c1 = stack.pop();
Comparable c2 = stack.popComparable();
// can't compare nulls...be consistent with SQL
if (c1 != null && c2 != null) {
// values are popped in reverse order of insertion,
// so compare the one popped last...
result = c2.compareTo(c1) >= 0;
}
stack.push(result);
return nextNode;
}
void appendString(StringBuffer buffer) {
buffer.append(" >= ");
}
}
final static class BetweenNode extends ASTNode {
boolean negate;
BetweenNode(boolean negate) {
this.negate = negate;
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
boolean result = false;
// pop in reverse order - c3 must be BETWEEN c2 and c1
Comparable c1 = stack.popComparable();
Comparable c2 = stack.popComparable();
Object c3 = stack.pop();
// can't compare nulls...be consistent with SQL
if (c1 != null && c2 != null && c3 != null) {
// values are popped in reverse order of insertion,
// so compare the one popped last...
result = c2.compareTo(c3) <= 0 && c1.compareTo(c3) >= 0;
}
stack.push((negate) ? !result : result);
return nextNode;
}
void appendString(StringBuffer buffer) {
if (negate) {
buffer.append(" NOT");
}
buffer.append(" BETWEEN ");
}
}
final static class InNode extends ASTNode {
boolean negate;
InNode(boolean negate) {
this.negate = negate;
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// pop in reverse order - o2 must be IN o1 list
Object[] o1 = (Object[]) stack.pop();
Object o2 = stack.pop();
boolean result = contains(o1, o2);
stack.push((negate) ? !result : result);
return nextNode;
}
void appendString(StringBuffer buffer) {
if (negate) {
buffer.append(" NOT");
}
buffer.append(" IN ");
}
}
final static class LikeNode extends ASTNode {
Pattern regex;
boolean negate;
LikeNode(String pattern, boolean negate, boolean ignoreCase) {
this.regex = Util.sqlPatternToPattern(pattern, ignoreCase);
this.negate = negate;
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
// LIKE AST uses a single operand, since regex is precompiled
Object o = stack.pop();
String string = (o != null) ? o.toString() : null;
boolean match = regex.matcher(string).find();
stack.push((negate) ? !match : match);
return nextNode;
}
void appendString(StringBuffer buffer) {
if (negate) {
buffer.append(" NOT");
}
buffer.append(" LIKE ").append(regex);
}
}
// ***************** wrappers and other helpers
/**
* A wrapper for an ASTNode that allows to skip further peer nodes evaluation on a
* certain outcome. Useful for optimizing AND, OR operations, etc.
*/
abstract static class ConditionalJumpNode extends ASTNode {
ASTNode wrappedNode;
ASTNode altNode;
ConditionalJumpNode(ASTNode wrappedNode, ASTNode altNode) {
this.wrappedNode = wrappedNode;
this.altNode = altNode;
}
ASTNode evaluateWithObject(ASTStack stack, Object bean) {
ASTNode next = wrappedNode.evaluateWithObject(stack, bean);
// pick on stack state and decide whether to continue
// with the normal flow, or jump to alternative node
return jumpPastAltNode(stack) ? ((altNode != null)
? altNode.getNextNode()
: null) : next;
}
ASTNode getNextNode() {
return (wrappedNode != null) ? wrappedNode.getNextNode() : null;
}
void setNextNode(ASTNode nextNode) {
if (wrappedNode != null) {
wrappedNode.setNextNode(nextNode);
}
}
void appendString(StringBuffer buffer) {
buffer.append("(");
if (wrappedNode != null) {
wrappedNode.appendString(buffer);
}
else {
buffer.append("?");
}
buffer.append(")");
}
abstract boolean jumpPastAltNode(ASTStack stack);
}
final static class AndOperandWrapper extends ConditionalJumpNode {
AndOperandWrapper(ASTNode wrappedNode, ASTNode altNode) {
super(wrappedNode, altNode);
}
boolean jumpPastAltNode(ASTStack stack) {
return !stack.peekBoolean();
}
}
final static class OrOperandWrapper extends ConditionalJumpNode {
OrOperandWrapper(ASTNode wrappedNode, ASTNode altNode) {
super(wrappedNode, altNode);
}
boolean jumpPastAltNode(ASTStack stack) {
return stack.peekBoolean();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy