All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.exp.Expression Maven / Gradle / Ivy

There is a newer version: 4.2.1
Show newest version
/*****************************************************************
 *   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.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.cayenne.exp.parser.ASTScalar;
import org.apache.cayenne.exp.parser.ExpressionParser;
import org.apache.cayenne.exp.parser.ExpressionParserTokenManager;
import org.apache.cayenne.exp.parser.JavaCharStream;
import org.apache.cayenne.exp.parser.ParseException;
import org.apache.cayenne.util.ConversionUtil;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.util.XMLEncoder;
import org.apache.cayenne.util.XMLSerializable;
import org.apache.commons.collections.Transformer;

/**
 * Superclass of Cayenne expressions that defines basic API for expressions use.
 */
public abstract class Expression implements Serializable, XMLSerializable {

    /**
     * A value that a Transformer might return to indicate that a node has to be pruned
     * from the expression during the transformation.
     * 
     * @since 1.2
     */
    public final static Object PRUNED_NODE = new Object();

    public static final int AND = 0;
    public static final int OR = 1;
    public static final int NOT = 2;
    public static final int EQUAL_TO = 3;
    public static final int NOT_EQUAL_TO = 4;
    public static final int LESS_THAN = 5;
    public static final int GREATER_THAN = 6;
    public static final int LESS_THAN_EQUAL_TO = 7;
    public static final int GREATER_THAN_EQUAL_TO = 8;
    public static final int BETWEEN = 9;
    public static final int IN = 10;
    public static final int LIKE = 11;
    public static final int LIKE_IGNORE_CASE = 12;
    public static final int ADD = 16;
    public static final int SUBTRACT = 17;
    public static final int MULTIPLY = 18;
    public static final int DIVIDE = 19;
    public static final int NEGATIVE = 20;
    public static final int TRUE = 21;
    public static final int FALSE = 22;

    /**
     * Expression describes a path relative to an ObjEntity. OBJ_PATH expression is
     * resolved relative to some root ObjEntity. Path expression components are separated
     * by "." (dot). Path can point to either one of these:
     * 
    *
  • An attribute of root ObjEntity. For entity Gallery OBJ_PATH expression * "galleryName" will point to ObjAttribute "galleryName" *
  • Another ObjEntity related to root ObjEntity via a chain of * relationships. For entity Gallery OBJ_PATH expression "paintingArray.toArtist" * will point to ObjEntity "Artist" *
  • ObjAttribute of another ObjEntity related to root ObjEntity via a chain of * relationships. For entity Gallery OBJ_PATH expression * "paintingArray.toArtist.artistName" will point to ObjAttribute "artistName" *
*/ public static final int OBJ_PATH = 26; /** * Expression describes a path relative to a DbEntity. DB_PATH expression is resolved * relative to some root DbEntity. Path expression components are separated by "." * (dot). Path can point to either one of these: *
    *
  • An attribute of root DbEntity. For entity GALLERY, DB_PATH expression * "GALLERY_NAME" will point to a DbAttribute "GALLERY_NAME".
  • *
  • Another DbEntity related to root DbEntity via a chain of relationships. * For entity GALLERY DB_PATH expression "paintingArray.toArtist" will point to * DbEntity "ARTIST".
  • *
  • DbAttribute of another ObjEntity related to root DbEntity via a chain of * relationships. For entity GALLERY DB_PATH expression * "paintingArray.toArtist.ARTIST_NAME" will point to DbAttribute "ARTIST_NAME".
  • *
*/ public static final int DB_PATH = 27; /** * Interpreted as a comma-separated list of literals. */ public static final int LIST = 28; public static final int NOT_BETWEEN = 35; public static final int NOT_IN = 36; public static final int NOT_LIKE = 37; public static final int NOT_LIKE_IGNORE_CASE = 38; /** * @since 3.1 */ public static final int BITWISE_NOT = 39; /** * @since 3.1 */ public static final int BITWISE_AND = 40; /** * @since 3.1 */ public static final int BITWISE_OR = 41; /** * @since 3.1 */ public static final int BITWISE_XOR = 42; private static final int PARSE_BUFFER_MAX_SIZE = 4096; protected int type; /** * Parses string, converting it to Expression. If string does not represent a * semantically correct expression, an ExpressionException is thrown. * * @since 1.1 */ // TODO: cache expression strings, since this operation is pretty slow public static Expression fromString(String expressionString) { if (expressionString == null) { throw new NullPointerException("Null expression string."); } // optimizing parser buffers per CAY-1667... // adding 1 extra char to the buffer size above the String length, as otherwise // resizing still occurs at the end of the stream int bufferSize = expressionString.length() > PARSE_BUFFER_MAX_SIZE ? PARSE_BUFFER_MAX_SIZE : expressionString.length() + 1; Reader reader = new StringReader(expressionString); JavaCharStream stream = new JavaCharStream(reader, 1, 1, bufferSize); ExpressionParserTokenManager tm = new ExpressionParserTokenManager(stream); ExpressionParser parser = new ExpressionParser(tm); try { return parser.expression(); } catch (ParseException ex) { // can be null String message = ex.getMessage(); throw new ExpressionException(message != null ? message : "", ex); } catch (Throwable th) { // can be null String message = th.getMessage(); // another common error is TokenManagerError throw new ExpressionException(message != null ? message : "", th); } } /** * Returns a map of path aliases for this expression. It returns a non-empty map only * if this is a path expression and the aliases are known at the expression creation * time. Otherwise an empty map is returned. * * @since 3.0 */ public abstract Map getPathAliases(); /** * Returns String label for this expression. Used for debugging. */ public String expName() { switch (type) { case AND: return "AND"; case OR: return "OR"; case NOT: return "NOT"; case EQUAL_TO: return "="; case NOT_EQUAL_TO: return "<>"; case LESS_THAN: return "<"; case LESS_THAN_EQUAL_TO: return "<="; case GREATER_THAN: return ">"; case GREATER_THAN_EQUAL_TO: return ">="; case BETWEEN: return "BETWEEN"; case IN: return "IN"; case LIKE: return "LIKE"; case LIKE_IGNORE_CASE: return "LIKE_IGNORE_CASE"; case OBJ_PATH: return "OBJ_PATH"; case DB_PATH: return "DB_PATH"; case LIST: return "LIST"; case NOT_BETWEEN: return "NOT BETWEEN"; case NOT_IN: return "NOT IN"; case NOT_LIKE: return "NOT LIKE"; case NOT_LIKE_IGNORE_CASE: return "NOT LIKE IGNORE CASE"; default: return "other"; } } @Override public boolean equals(Object object) { if (!(object instanceof Expression)) { return false; } Expression e = (Expression) object; if (e.getType() != getType() || e.getOperandCount() != getOperandCount()) { return false; } // compare operands int len = e.getOperandCount(); for (int i = 0; i < len; i++) { if (!Util.nullSafeEquals(e.getOperand(i), getOperand(i))) { return false; } } return true; } /** * Returns a type of expression. Most common types are defined as public static fields * of this interface. */ public int getType() { return type; } public void setType(int type) { this.type = type; } /** * A shortcut for expWithParams(params, true). */ public Expression expWithParameters(Map parameters) { return expWithParameters(parameters, true); } /** * Creates and returns a new Expression instance using this expression as a prototype. * All ExpressionParam operands are substituted with the values in the * params map. *

* Null values in the params map should be explicitly created in the * map for the corresponding key. *

* * @param parameters a map of parameters, with each key being a string name of an * expression parameter, and value being the value that should be used in * the final expression. * @param pruneMissing If true, subexpressions that rely on missing * parameters will be pruned from the resulting tree. If false * , any missing values will generate an exception. * @return Expression resulting from the substitution of parameters with real values, * or null if the whole expression was pruned, due to the missing parameters. */ public Expression expWithParameters( final Map parameters, final boolean pruneMissing) { // create transformer for named parameters Transformer transformer = new Transformer() { public Object transform(Object object) { if (!(object instanceof ExpressionParameter)) { // mainly for the ASTList array child... if (object instanceof Object[]) { Object[] source = (Object[]) object; int len = source.length; Object[] target = new Object[len]; for (int i = 0; i < len; i++) { target[i] = transform(source[i]); } return target; } return object; } String name = ((ExpressionParameter) object).getName(); if (!parameters.containsKey(name)) { if (pruneMissing) { return PRUNED_NODE; } else { throw new ExpressionException("Missing required parameter: $" + name); } } else { Object value = parameters.get(name); // wrap lists (for now); also support null parameters // TODO: andrus 8/14/2007 - shouldn't we also wrap non-null object // values in ASTScalars? return (value != null) ? ExpressionFactory.wrapPathOperand(value) : new ASTScalar(null); } } }; return transform(transformer); } /** * Creates a new expression that joins this object with another expression, using * specified join type. It is very useful for incrementally building chained * expressions, like long AND or OR statements. */ public Expression joinExp(int type, Expression exp) { Expression join = ExpressionFactory.expressionOfType(type); join.setOperand(0, this); join.setOperand(1, exp); join.flattenTree(); return join; } /** * Chains this expression with another expression using "and". */ public Expression andExp(Expression exp) { return joinExp(Expression.AND, exp); } /** * Chains this expression with another expression using "or". */ public Expression orExp(Expression exp) { return joinExp(Expression.OR, exp); } /** * Returns a logical NOT of current expression. * * @since 1.0.6 */ public abstract Expression notExp(); /** * Returns a count of operands of this expression. In real life there are unary (count * == 1), binary (count == 2) and ternary (count == 3) expressions. */ public abstract int getOperandCount(); /** * Returns a value of operand at index. Operand indexing starts at 0. */ public abstract Object getOperand(int index); /** * Sets a value of operand at index. Operand indexing starts at 0. */ public abstract void setOperand(int index, Object value); /** * Calculates expression value with object as a context for path expressions. * * @since 1.1 */ public abstract Object evaluate(Object o); /** * Calculates expression boolean value with object as a context for path expressions. * * @since 1.1 */ public boolean match(Object o) { return ConversionUtil.toBoolean(evaluate(o)); } /** * Returns the first object in the list that matches the expression. * * @since 3.1 */ public T first(List objects) { for (T o : objects) { if (match(o)) { return o; } } return null; } /** * Returns a list of objects that match the expression. */ public List filterObjects(List objects) { if (objects == null || objects.size() == 0) { return Collections.EMPTY_LIST; } return (List) filter(objects, new LinkedList()); } /** * Adds objects matching this expression from the source collection to the target * collection. * * @since 1.1 */ public Collection filter(Collection source, Collection target) { for (T o : source) { if (match(o)) { target.add(o); } } return target; } /** * Clones this expression. * * @since 1.1 */ public Expression deepCopy() { return transform(null); } /** * Creates a copy of this expression node, without copying children. * * @since 1.1 */ public abstract Expression shallowCopy(); /** * Returns true if this node should be pruned from expression tree in the event a * child is removed. * * @since 1.1 */ protected abstract boolean pruneNodeForPrunedChild(Object prunedChild); /** * Restructures expression to make sure that there are no children of the same type as * this expression. * * @since 1.1 */ protected abstract void flattenTree(); /** * Traverses itself and child expressions, notifying visitor via callback methods as * it goes. This is an Expression-specific implementation of the "Visitor" design * pattern. * * @since 1.1 */ public void traverse(TraversalHandler visitor) { if (visitor == null) { throw new NullPointerException("Null Visitor."); } traverse(null, visitor); } /** * Traverses itself and child expressions, notifying visitor via callback methods as * it goes. * * @since 1.1 */ protected void traverse(Expression parentExp, TraversalHandler visitor) { visitor.startNode(this, parentExp); // recursively traverse each child int count = getOperandCount(); for (int i = 0; i < count; i++) { Object child = getOperand(i); if (child instanceof Expression) { Expression childExp = (Expression) child; childExp.traverse(this, visitor); } else { visitor.objectNode(child, this); } visitor.finishedChild(this, i, i < count - 1); } visitor.endNode(this, parentExp); } /** * Creates a transformed copy of this expression, applying transformation provided by * Transformer to all its nodes. Null transformer will result in an identical deep * copy of this expression. *

* To force a node and its children to be pruned from the copy, Transformer should * return Expression.PRUNED_NODE. Otherwise an expectation is that if a node is an * Expression it must be transformed to null or another Expression. Any other object * type would result in a ExpressionException. * * @since 1.1 */ public Expression transform(Transformer transformer) { Object transformed = transformExpression(transformer); if (transformed == PRUNED_NODE || transformed == null) { return null; } else if (transformed instanceof Expression) { return (Expression) transformed; } throw new ExpressionException("Invalid transformed expression: " + transformed); } /** * A recursive method called from "transform" to do the actual transformation. * * @return null, Expression.PRUNED_NODE or transformed expression. * @since 1.2 */ protected Object transformExpression(Transformer transformer) { Expression copy = shallowCopy(); int count = getOperandCount(); for (int i = 0, j = 0; i < count; i++) { Object operand = getOperand(i); Object transformedChild; if (operand instanceof Expression) { transformedChild = ((Expression) operand) .transformExpression(transformer); } else if (transformer != null) { transformedChild = transformer.transform(operand); } else { transformedChild = operand; } // prune null children only if there is a transformer and it indicated so boolean prune = transformer != null && transformedChild == PRUNED_NODE; if (!prune) { copy.setOperand(j, transformedChild); j++; } if (prune && pruneNodeForPrunedChild(operand)) { // bail out early... return PRUNED_NODE; } } // all the children are processed, only now transform this copy return (transformer != null) ? (Expression) transformer.transform(copy) : copy; } /** * Encodes itself, wrapping the string into XML CDATA section. * * @since 1.1 */ public void encodeAsXML(XMLEncoder encoder) { encoder.print(""); } /** * Stores a String representation of Expression using a provided PrintWriter. * * @since 1.1 */ public abstract void encodeAsString(PrintWriter pw); /** * Stores a String representation of Expression as EJBQL using a provided PrintWriter. * DB path expressions produce non-standard EJBQL path expressions. * * @since 3.0 */ public abstract void encodeAsEJBQL(PrintWriter pw, String rootId); @Override public String toString() { StringWriter buffer = new StringWriter(); PrintWriter pw = new PrintWriter(buffer); encodeAsString(pw); pw.close(); buffer.flush(); return buffer.toString(); } /** * @since 3.0 */ public String toEJBQL(String rootId) { StringWriter buffer = new StringWriter(); PrintWriter pw = new PrintWriter(buffer); encodeAsEJBQL(pw, rootId); pw.close(); buffer.flush(); return buffer.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy