org.objectstyle.cayenne.exp.Expression.java Source code

Java tutorial

Introduction

Here is the source code for org.objectstyle.cayenne.exp.Expression.java

Source

/* ====================================================================
 * 
 * The ObjectStyle Group Software License, version 1.1
 * ObjectStyle Group - http://objectstyle.org/
 * 
 * Copyright (c) 2002-2004, Andrei (Andrus) Adamchik and individual authors
 * of the software. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 
 * 3. The end-user documentation included with the redistribution, if any,
 *    must include the following acknowlegement:
 *    "This product includes software developed by independent contributors
 *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 * 
 * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
 *    or promote products derived from this software without prior written
 *    permission. For written permission, email
 *    "andrus at objectstyle dot org".
 * 
 * 5. Products derived from this software may not be called "ObjectStyle"
 *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
 *    names without prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 * 
 * This software consists of voluntary contributions made by many
 * individuals and hosted on ObjectStyle Group web site.  For more
 * information on the ObjectStyle Group, please see
 * <http://objectstyle.org/>.
 */
package org.objectstyle.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.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.log4j.Logger;
import org.objectstyle.cayenne.exp.parser.ExpressionParser;
import org.objectstyle.cayenne.exp.parser.ParseException;
import org.objectstyle.cayenne.util.ConversionUtil;
import org.objectstyle.cayenne.util.Util;
import org.objectstyle.cayenne.util.XMLEncoder;
import org.objectstyle.cayenne.util.XMLSerializable;

/** 
 * Superclass of Cayenne expressions that defines basic
 * API for expressions use.
 */
public abstract class Expression implements Serializable, XMLSerializable {
    private final static Logger logObj = Logger.getLogger(Expression.class);

    private final static Object nullValue = 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 EXISTS = 15;
    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 POSITIVE = 21;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int ALL = 22;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int SOME = 23;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int ANY = 24;

    /** 
     * Expression interpreted as raw SQL. 
     * No translations will be done for this kind of expressions. 
     */
    public static final int RAW_SQL = 25;

    /** 
     * 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:
     * <ul>
     *    <li><i>An attribute of root ObjEntity.</i>
     *    For entity Gallery OBJ_PATH expression "galleryName" will point to ObjAttribute "galleryName" 
     *    <li><i>Another ObjEntity related to root ObjEntity via a chain of relationships.</i>
     *    For entity Gallery OBJ_PATH expression "paintingArray.toArtist" will point to ObjEntity "Artist" 
     *    <li><i>ObjAttribute of another ObjEntity related to root ObjEntity via a chain of relationships.</i>
     *    For entity Gallery OBJ_PATH expression "paintingArray.toArtist.artistName" will point to ObjAttribute "artistName" 
     * </ul>
     */
    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:
     * <ul>
     *    <li><i>An attribute of root DbEntity.</i>
     *    For entity GALLERY, DB_PATH expression "GALLERY_NAME" will point 
     *    to a DbAttribute "GALLERY_NAME".
     *      </li>
     * 
     *    <li><i>Another DbEntity related to root DbEntity via a chain of relationships.</i>
     *    For entity GALLERY DB_PATH expression "paintingArray.toArtist" will point to 
     *    DbEntity "ARTIST".
     *    </li>
     * 
     *    <li><i>DbAttribute of another ObjEntity related to root DbEntity via a chain 
     *    of relationships.</i>
     *    For entity GALLERY DB_PATH expression "paintingArray.toArtist.ARTIST_NAME" will point 
     *    to DbAttribute "ARTIST_NAME".
     *    </li>
     * </ul>
     */
    public static final int DB_PATH = 27;

    /** 
     * Interpreted as a comma-separated list of literals. 
     */
    public static final int LIST = 28;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int SUBQUERY = 29;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int COUNT = 30;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int AVG = 31;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int SUM = 32;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int MAX = 33;

    /**
     * Currently not supported in Cayenne.
     */
    public static final int MIN = 34;

    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;

    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.");
        }

        Reader reader = new StringReader(expressionString);
        try {
            return new ExpressionParser(reader).expression();
        } catch (ParseException ex) {
            throw new ExpressionException(ex.getMessage(), ex);
        } catch (Throwable th) {
            // another common error is TokenManagerError
            throw new ExpressionException(th.getMessage(), th);
        }
    }

    /**
     * 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 EXISTS:
            return "EXISTS";
        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";
        }
    }

    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 <code>expWithParams(params, true)</code>.
     */
    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 <code>params</code> map. 
     * 
     * <p><i>Null values in the <code>params</code> map should be
     * explicitly created in the map for the corresponding key.
     * </i></p>
     * 
     * @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 <code>true</code>, subexpressions that rely
     * on missing parameters will be pruned from the resulting tree. If
     * <code>false</code>, 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)) {
                    return object;
                }

                String name = ((ExpressionParameter) object).getName();
                if (!parameters.containsKey(name)) {
                    if (pruneMissing) {
                        return null;
                    } else {
                        throw new ExpressionException("Missing required parameter: $" + name);
                    }
                } else {
                    Object value = parameters.get(name);

                    // wrap lists (for now); also support null parameters
                    return (value != null) ? ExpressionFactory.wrapPathOperand(value) : nullValue;
                }
            }
        };

        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 Expression notExp() {
        Expression exp = ExpressionFactory.expressionOfType(Expression.NOT);
        exp.setOperand(0, this);
        return exp;
    }

    /** 
     * 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 <code>index</code>. 
     * Operand indexing starts at 0. 
     */
    public abstract Object getOperand(int index);

    /** 
     * Sets a value of operand at <code>index</code>. 
     * Operand indexing starts at 0.
     */
    public abstract void setOperand(int index, Object value);

    /** 
     * Method for in-memory evaluation of expressions. 
     * 
     * @deprecated Since 1.1 use {@link #evaluate(Object)} or {@link #match(Object)}.
     */
    public boolean eval(Object o) {
        return match(o);
    }

    /**
     * Calculates expression value with object as a context for 
     * path expressions.
     * 
     * @since 1.1
     */
    public Object evaluate(Object o) {
        return ASTCompiler.compile(this).evaluateASTChain(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 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) {
        Iterator it = source.iterator();
        while (it.hasNext()) {
            Object o = it.next();
            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.
     * 
     * <p>To force a node and its children to be pruned from the 
     * copy, Transformer should return null for a given node.
     * 
     * <p>There is one limitation on what Transformer is expected to do: 
     * if a node is an Expression it must be transformed to null
     * or another Expression. Any other object type would result in an 
     * exception. 
     * 
     *
     * @since 1.1
     */
    public Expression transform(Transformer transformer) {

        Expression copy = shallowCopy();
        int count = getOperandCount();
        for (int i = 0, j = 0; i < count; i++) {
            Object operand = getOperand(i);
            Object transformedChild = operand;

            if (operand instanceof Expression) {
                transformedChild = ((Expression) operand).transform(transformer);
            } else if (transformer != null) {
                transformedChild = transformer.transform(operand);
            }

            if (transformedChild != null) {
                Object value = (transformedChild != nullValue) ? transformedChild : null;
                copy.setOperand(j, value);
                j++;
            } else if (pruneNodeForPrunedChild(operand)) {
                // bail out early...
                return null;
            }
        }

        // 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("<![CDATA[");
        encodeAsString(encoder.getPrintWriter());
        encoder.print("]]>");
    }

    /**
     * Stores a String representation of Expression using a provided
     * PrintWriter.
     * 
     * @since 1.1
     */
    public abstract void encodeAsString(PrintWriter pw);

    /**
     * Convenience method to log nested expressions. Used mainly for debugging.
     * Called from "toString".
     * 
     * @deprecated Since 1.1 <code>encode</code> is used to recursively
     * print expressions.
     */
    protected void toStringBuffer(StringBuffer buf) {
        for (int i = 0; i < getOperandCount(); i++) {
            if (i > 0 || getOperandCount() == 1) {
                buf.append(" ").append(expName()).append(" ");
            }

            Object op = getOperand(i);
            if (op == null) {
                buf.append("<null>");
            } else if (op instanceof String) {
                buf.append("'").append(op).append("'");
            } else if (op instanceof Expression) {
                buf.append('(');
                ((Expression) op).toStringBuffer(buf);
                buf.append(')');
            } else {
                buf.append(String.valueOf(op));
            }
        }

    }

    public String toString() {
        StringWriter buffer = new StringWriter();
        PrintWriter pw = new PrintWriter(buffer);
        encodeAsString(pw);
        pw.close();
        buffer.flush();
        return buffer.toString();
    }
}