/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsmodel.query.relational;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Stack;
import java.util.Vector;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.query.MWQuery;
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneListIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.expressions.ExpressionOperator;
import org.eclipse.persistence.internal.expressions.FunctionExpression;
import org.eclipse.persistence.internal.expressions.LogicalExpression;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping;
/**
* This class contains a list of expressions and an operator used on those expressions
* The list of expressions can contain MWCompoundExpressions, MWBasicExpressions, or MWUnaryExpressions
*
*
* An example TopLink expressions which corresponds to a MWCompoundExpression is:
* get("firstName").equals("Karen").and(get("lastName").lessThan(getParameter("lastName")));
*
* expressions =
* -BinaryExpression(operator = EQUAL, get("firstName"), "Karen")
* -BinaryExpression(operator = LESS_THAN, get("lastName"), getParameter("lastName"))
* operatorType = AND
*
* This class also takes care of morphing a child expression between unary and binary
* if the user changes the child expression's operatorType
*
*/
public final class MWCompoundExpression extends MWExpression {
//order is important
private List expressions;
// property change
public final static String EXPRESSIONS_LIST = "expressions";
//Logical Operators
public final static String AND = "AND";
public final static String OR = "OR";
public final static String NAND = "NAND";
public final static String NOR = "NOR";
private Stack changes;
//Change tracking keys
public final static String ADD_EXPRESSION = "addExpression";
public final static String REMOVE_EXPRESSION = "removeExpression";
public final static String CLEARED_EXPRESSIONS = "clearedExpressions";
/**
* This class holds on to the information necessary to undo a change
*
* propertyChangeName -> The string used for a property change
* container -> The parent object on which the change is occuring
* oldValue -> the value before the property was changed
* newValue -> the value that the property was changed to
*
* When restoring a change, you will want to reset it to the oldValue
*/
private class PropertyChangeHolder
{
private String propertyChangeName;
private Undoable container;
private Object oldValue;
private Object newValue;
private PropertyChangeHolder(Undoable container, String propertyName, Object oldValue, Object newValue)
{
this.container = container;
this.propertyChangeName = propertyName;
this.oldValue = oldValue;
this.newValue = newValue;
}
private PropertyChangeHolder(Undoable container, String propertyName, Object newValue)
{
this(container, propertyName, null, newValue);
}
protected void undoChange()
{
this.container.undoChange(this.propertyChangeName, this.oldValue, this.newValue);
}
}
/**
* Default constructor - for TopLink use only.
*/
private MWCompoundExpression()
{
super();
}
//The parent will either be a BldrExpressionQueryFormat or a BldrCompoundExpression
MWCompoundExpression(MWModel parent)
{
super(parent, AND);
}
protected void addChildrenTo(List children)
{
super.addChildrenTo(children);
synchronized (this.expressions) { children.addAll(this.expressions); }
}
protected void initialize(Node parent)
{
super.initialize(parent);
this.expressions = new Vector();
}
protected void initialize()
{
super.initialize();
this.changes = new Stack();
}
private void addExpression(MWExpression expression)
{
this.expressions.add(expression);
fireItemAdded(EXPRESSIONS_LIST, this.expressions.lastIndexOf(expression), expression);
getRootCompoundExpression().propertyChanged(this, ADD_EXPRESSION, expression);
}
//When a compound expression is created a basic expression is automatically added to it.
//If the user has a basic expression chosen upon pressing the add button, then the
//parent CompoundExpression is asked to addBasicExpression
public MWBasicExpression addBasicExpression()
{
MWBasicExpression expression = new MWBasicExpression(this, MWBasicExpression.EQUAL);
addExpression(expression);
return expression;
}
public MWCompoundExpression addSubCompoundExpression()
{
// MWRelationalDescriptor owningDescriptor = getParentQuery().getQueryManager().getOwningDescriptor();
MWCompoundExpression expression = new MWCompoundExpression(this);
addExpression(expression);
expression.addBasicExpression();
return expression;
}
public int expressionsSize()
{
return this.expressions.size();
}
public ListIterator expressions()
{
return new CloneListIterator(this.expressions);
}
public MWExpression getExpression(int index)
{
return (MWExpression) this.expressions.get(index);
}
//Used to determine the line number of the expression in the ExpressionTree (1, 2, 2.1, 2.1.1, etc)
public String getIndex()
{
if (getParentCompoundExpression() == null)
{
return "";
}
else
{
return getParentCompoundExpression().getIndex() + Integer.toString(getParentCompoundExpression().getIndexOf(this)) + ".";
}
}
//this will return an index of 1-n
//this is only meant to be used by the ExpressionTree
int getIndexOf(MWExpression expression)
{
Iterator expressions = expressions();
int index = 1;
while (expressions.hasNext())
{
if( expressions.next() == expression)
return index;
index++;
}
return -1;
}
public void clearExpressions() {
for (int i =0; i < this.expressions.size(); i++) {
removeExpression((MWExpression) this.expressions.get(i));
}
}
public void removeExpression(MWExpression expression) {
int oldIndex = this.expressions.lastIndexOf(expression);
expression.clearExpressions();
this.expressions.remove(expression);
fireItemRemoved(EXPRESSIONS_LIST, oldIndex, expression);
getRootCompoundExpression().propertyChanged(this, REMOVE_EXPRESSION, expression);
}
public String displayString() {
return getOperatorType();
}
public MWCompoundExpression getParentCompoundExpression() {
if (this.getParent().getClass().isAssignableFrom(MWExpressionQueryFormat.class))
return null;
else
return (MWCompoundExpression) getParent();
}
public MWQuery getParentQuery() {
if (this.getParent().getClass().isAssignableFrom(MWExpressionQueryFormat.class))
return ((MWQueryFormat) this.getParent()).getQuery();
else //the parent is another MWCompoundExpression
return ((MWCompoundExpression)getParent()).getParentQuery();
}
public MWCompoundExpression getRootCompoundExpression() {
if (this.getParent().getClass().isAssignableFrom(MWExpressionQueryFormat.class))
return this;
else
return ((MWCompoundExpression)getParent()).getRootCompoundExpression();
}
void recalculateQueryables() {
Iterator expressions = expressions();
while (expressions.hasNext())
((MWExpression) expressions.next()).recalculateQueryables();
}
public void clearChanges() {
this.changes.clear();
}
/**
* @see org.eclipse.persistence.tools.workbench.mappingsmodel.query.MWExpression#restoreChanges(String, Object, Object)
*/
public void undoChange(String propertyName, Object oldValue, Object newValue) {
super.undoChange(propertyName, oldValue, newValue);
if (propertyName == ADD_EXPRESSION) {
removeExpression((MWExpression) newValue);
}
else if (propertyName == REMOVE_EXPRESSION) {
addExpression((MWExpression) newValue);
}
else if (propertyName == CLEARED_EXPRESSIONS) {
//do something here, or remove the Expressions_cleared options
}
}
void propertyChanged(Undoable container, String propertyName, Object oldValue, Object newValue) {
this.changes.push(new PropertyChangeHolder(container, propertyName, oldValue, newValue));
}
void propertyChanged(Undoable container, String propertyName, Object newValue) {
this.changes.push(new PropertyChangeHolder(container, propertyName, newValue));
}
public void restoreChanges() {
Stack allChanges = new Stack();
allChanges.addAll(this.changes);
while (!allChanges.isEmpty()) {
PropertyChangeHolder changeObject = (PropertyChangeHolder) allChanges.pop();
changeObject.undoChange();
}
clearChanges();
}
public void toString(StringBuffer sb) {
super.toString(sb);
sb.append("operator = " );
sb.append(getOperatorType());
sb.append(", expressions = ");
sb.append('(');
for (Iterator stream = this.expressions(); stream.hasNext(); ) {
MWExpression expression = (MWExpression) stream.next();
sb.append(expression);
if (stream.hasNext()) {
sb.append(", ");
}
}
sb.append(')');
}
//Persistence
public static XMLDescriptor buildDescriptor() {
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWCompoundExpression.class);
//Inheritance Policy
descriptor.getInheritancePolicy().setParentClass(MWExpression.class);
// aggregate collection - expressions
XMLCompositeCollectionMapping expressionsMapping = new XMLCompositeCollectionMapping();
expressionsMapping.setAttributeName("expressions");
expressionsMapping.setReferenceClass(MWExpression.class);
expressionsMapping.setXPath("expression-list/expression");
descriptor.addMapping(expressionsMapping);
return descriptor;
}
//review this one again and make sure to unit test it well
//Conversion methods
Expression buildRuntimeExpression(ExpressionBuilder builder) {
Expression finalExpression = null;
int operator;
boolean useNot = false;
if (getOperatorType() == AND)
operator = ExpressionOperator.And;
else if (getOperatorType() == OR)
operator = ExpressionOperator.Or;
else if (getOperatorType() == NAND) {
operator = ExpressionOperator.And;
useNot = true;
}
else { //operator type == NOR
operator = ExpressionOperator.Or;
useNot = true;
}
if (expressionsSize() > 0){
finalExpression = builder.and(((MWExpression) this.expressions.get(0)).buildRuntimeExpression(builder));
for (int i = 0; i < expressionsSize() - 1; i++) {
Expression nextExpression = ((MWExpression) this.expressions.get(i+1)).buildRuntimeExpression(builder);
if (operator == ExpressionOperator.And)
finalExpression = builder.and(finalExpression).and(nextExpression);
else
finalExpression = builder.or(finalExpression).or(nextExpression);
}
}
if (useNot) {
return finalExpression.not();
}
return finalExpression;
}
static MWCompoundExpression convertFromRuntime(MWModel parent, Expression selectionCriteria) {
MWCompoundExpression bldrExpression = new MWCompoundExpression(parent);
if (selectionCriteria.isRelationExpression()) {
//this compound expression only contains one basic expression
//set the operator to be AND by default
bldrExpression.addExpression(MWBasicExpression.convertFromRuntime(bldrExpression, selectionCriteria));
bldrExpression.setOperatorType(MWCompoundExpression.AND);
}
else { //selectionCriteria is a LogicalExpresison or a FunctionExpression
ExpressionOperator runtimeOperator = selectionCriteria.getOperator();
boolean usesNot = false;
// if the user has chosen NAND or NOR .not() is called on the expression
// when converting to runtime, so it became a function expression
if (runtimeOperator == ExpressionOperator.getOperator(new Integer(ExpressionOperator.Not))) {
//if the operator is NOT, we know it is a function expression
selectionCriteria = ((FunctionExpression)selectionCriteria).getBaseExpression();
usesNot = true;
runtimeOperator = selectionCriteria.getOperator();
}
if (runtimeOperator == ExpressionOperator.getOperator(new Integer(ExpressionOperator.And))) {
if (!usesNot)
bldrExpression.setOperatorType(AND);
else
bldrExpression.setOperatorType(NAND);
}
else if (runtimeOperator == ExpressionOperator.getOperator(new Integer(ExpressionOperator.Or))) {
if (!usesNot)
bldrExpression.setOperatorType(OR);
else//is FunctionExpression
bldrExpression.setOperatorType(NOR);
}
//happens if a unary operator is used
else if (selectionCriteria.isFunctionExpression()) {
bldrExpression.addExpression(MWBasicExpression.convertFromRuntime(bldrExpression, selectionCriteria));
return bldrExpression;
}
//We will get here if the user has specified one basic expression and the operator NOR or NAND
//Because toplink does not store whether it is NOR or NAND, we will just default to NAND.
//The NOR case would not correctly convert.
else if (selectionCriteria.isRelationExpression()) {
bldrExpression.addExpression(MWBasicExpression.convertFromRuntime(bldrExpression, selectionCriteria));
if (!usesNot)
bldrExpression.setOperatorType(AND);
else
bldrExpression.setOperatorType(NAND);
return bldrExpression;
}
Expression firstChild = ((LogicalExpression)selectionCriteria).getFirstChild();
if (firstChild.isRelationExpression() || (firstChild.isFunctionExpression() && !(firstChild.getOperator() == ExpressionOperator.getOperator(new Integer(ExpressionOperator.Not)))))
bldrExpression.addExpression(MWBasicExpression.convertFromRuntime(bldrExpression, firstChild));
else
bldrExpression.addExpression(MWCompoundExpression.convertFromRuntime(bldrExpression, firstChild));
Expression secondChild = ((LogicalExpression)selectionCriteria).getSecondChild();
if (secondChild.isRelationExpression() || (secondChild.isFunctionExpression() && !(secondChild.getOperator() == ExpressionOperator.getOperator(new Integer(ExpressionOperator.Not)))))
bldrExpression.addExpression(MWBasicExpression.convertFromRuntime(bldrExpression,secondChild));
else
bldrExpression.addExpression(MWCompoundExpression.convertFromRuntime(bldrExpression, secondChild));
}
return bldrExpression;
}
}