CommonNumericExpressionFactory.java

/*******************************************************************************
 * Copyright (c) 2013 Stephen F. Siegel, University of Delaware.
 * 
 * This file is part of SARL.
 * 
 * SARL is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * SARL is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with SARL. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package edu.udel.cis.vsl.sarl.expr.common;

import java.util.Comparator;

import edu.udel.cis.vsl.sarl.IF.SARLInternalException;
import edu.udel.cis.vsl.sarl.IF.expr.BooleanExpression;
import edu.udel.cis.vsl.sarl.IF.expr.NumericExpression;
import edu.udel.cis.vsl.sarl.IF.expr.NumericSymbolicConstant;
import edu.udel.cis.vsl.sarl.IF.expr.SymbolicExpression.SymbolicOperator;
import edu.udel.cis.vsl.sarl.IF.number.Number;
import edu.udel.cis.vsl.sarl.IF.number.NumberFactory;
import edu.udel.cis.vsl.sarl.IF.number.RationalNumber;
import edu.udel.cis.vsl.sarl.IF.object.NumberObject;
import edu.udel.cis.vsl.sarl.IF.object.StringObject;
import edu.udel.cis.vsl.sarl.IF.object.SymbolicObject;
import edu.udel.cis.vsl.sarl.IF.type.SymbolicIntegerType;
import edu.udel.cis.vsl.sarl.IF.type.SymbolicRealType;
import edu.udel.cis.vsl.sarl.IF.type.SymbolicType;
import edu.udel.cis.vsl.sarl.expr.IF.BooleanExpressionFactory;
import edu.udel.cis.vsl.sarl.expr.IF.NumericExpressionFactory;
import edu.udel.cis.vsl.sarl.object.IF.ObjectFactory;
import edu.udel.cis.vsl.sarl.type.IF.SymbolicTypeFactory;

/**
 * Given a numeric factory that deals with expressions of ideal type, and a
 * numeric factory that deals with expressions of herbrand type, this factory
 * puts them together to work on any numeric type.
 * 
 * @author siegel
 * 
 */
public class CommonNumericExpressionFactory
		implements NumericExpressionFactory {

	private NumericExpressionFactory idealFactory;

	private NumericExpressionFactory herbrandFactory;

	private SymbolicTypeFactory typeFactory;

	private SymbolicRealType herbrandRealType, idealRealType;

	private SymbolicIntegerType herbrandIntegerType, idealIntegerType;

	private BooleanExpressionFactory booleanFactory;

	private ObjectFactory objectFactory;

	private NumberFactory numberFactory;

	private Comparator<NumericExpression> comparator;

	/**
	 * Constructor that creates a CommonNumericExpressionFactory.
	 * 
	 * @param idealFactory
	 * @param herbrandFactory
	 * 
	 * @return CommonNumericExpressionFactory
	 */
	public CommonNumericExpressionFactory(NumericExpressionFactory idealFactory,
			NumericExpressionFactory herbrandFactory) {
		this.idealFactory = idealFactory;
		this.herbrandFactory = herbrandFactory;
		this.booleanFactory = idealFactory.booleanFactory();
		this.typeFactory = idealFactory.typeFactory();
		this.objectFactory = typeFactory.objectFactory();
		this.numberFactory = idealFactory.numberFactory();
		this.comparator = new CommonNumericComparator(idealFactory.comparator(),
				herbrandFactory.comparator());
	}

	// Helpers...

	/**
	 * Private method that casts NumericExpression to Ideal type.
	 * 
	 * @param arg
	 * 
	 * @return NumericExpression
	 */
	private NumericExpression castToIdeal(NumericExpression arg) {
		SymbolicType oldType = arg.type();

		if (oldType.isIdeal())
			return arg;
		else {
			if (arg.operator() == SymbolicOperator.CONCRETE) {
				SymbolicObject object = arg.argument(0);

				if (object instanceof NumberObject)
					return idealFactory.number((NumberObject) object);
			}
			return idealFactory.expression(SymbolicOperator.CAST,
					oldType.isInteger() ? idealIntegerType : idealRealType,
					arg);
		}
	}

	/**
	 * Private method that casts NumericExpression to Herbrand type.
	 * 
	 * @param arg
	 * 
	 * @return NumericExpression
	 */
	private NumericExpression castToHerbrand(NumericExpression arg) {
		SymbolicType oldType = arg.type();

		if (oldType.isHerbrand())
			return arg;
		else {
			if (arg.operator() == SymbolicOperator.CONCRETE) {
				SymbolicObject object = arg.argument(0);

				if (object instanceof NumberObject)
					return herbrandFactory.number((NumberObject) object);
			}
			return herbrandFactory
					.expression(
							SymbolicOperator.CAST, oldType.isInteger()
									? herbrandIntegerType : herbrandRealType,
							arg);
		}
	}

	// Exported methods....

	/**
	 * Getter method that returns idealFactory.
	 * 
	 * @return idealFactory
	 */
	public NumericExpressionFactory idealFactory() {
		return idealFactory;
	}

	/**
	 * Getter method that returns herbrandFactory.
	 * 
	 * @return herbrandFactory
	 */
	public NumericExpressionFactory herbrandFactory() {
		return herbrandFactory;
	}

	/**
	 * Method that initializes this CommonNumericExpressionFactory
	 * 
	 */
	@Override
	public void init() {
		idealFactory.init();
		herbrandFactory.init();
		this.herbrandRealType = typeFactory.herbrandRealType();
		this.herbrandIntegerType = typeFactory.herbrandIntegerType();
		this.idealRealType = typeFactory.realType();
		this.idealIntegerType = typeFactory.integerType();
	}

	/**
	 * Getter method that returns booleanFactory.
	 * 
	 * @return BooleanExpressionFactory
	 */
	@Override
	public BooleanExpressionFactory booleanFactory() {
		return booleanFactory;
	}

	/**
	 * Getter method that returns numberFactory.
	 * 
	 * @return NumberFactory
	 */
	@Override
	public NumberFactory numberFactory() {
		return numberFactory;
	}

	/**
	 * Getter method that returns objectFacotry.
	 * 
	 * @return ObjectFactory
	 */
	@Override
	public ObjectFactory objectFactory() {
		return objectFactory;
	}

	/**
	 * Getter method that returns typeFactory.
	 * 
	 * @return SymbolicTypeFactory
	 */
	@Override
	public SymbolicTypeFactory typeFactory() {
		return typeFactory;
	}

	/**
	 * Getter method that returns comparator.
	 * 
	 * @return Comparator<NumericExpression>
	 */
	@Override
	public Comparator<NumericExpression> comparator() {
		return comparator;
	}

	/**
	 * Returns the ideal number. If you want it to be Herbrand you need to cast
	 * it to the appropriate Herbrand type.
	 * 
	 * @param numberObject
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression number(NumberObject numberObject) {
		return idealFactory.number(numberObject);
	}

	/**
	 * Method that returns NumericSymbolicConstant.
	 * 
	 * @param name
	 * @param type
	 * 
	 * @return NumericSymbolicConstant
	 */
	@Override
	public NumericSymbolicConstant symbolicConstant(StringObject name,
			SymbolicType type) {
		if (type.isIdeal())
			return idealFactory.symbolicConstant(name, type);
		else
			return herbrandFactory.symbolicConstant(name, type);
	}

	// /**
	// * One of several methods that create a NumericExpression.
	// *
	// * @param operator
	// * @param numericType
	// * @param arguments
	// * arguments is a Collection<SymbolicObject>
	// *
	// * @return NumericExpression
	// */
	// @Override
	// public NumericExpression expression(SymbolicOperator operator,
	// SymbolicType numericType, Collection<SymbolicObject> arguments) {
	// if (numericType.isIdeal())
	// return idealFactory.expression(operator, numericType, arguments);
	// else
	// return herbrandFactory.expression(operator, numericType, arguments);
	// }

	/**
	 * One of several methods that create a NumericExpression.
	 * 
	 * @param operator
	 * @param numericType
	 * @param arguments
	 *            arguments is a SymbolicObject array
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression expression(SymbolicOperator operator,
			SymbolicType numericType, SymbolicObject... arguments) {
		if (numericType.isIdeal())
			return idealFactory.expression(operator, numericType, arguments);
		else
			return herbrandFactory.expression(operator, numericType, arguments);
	}

	// /**
	// * One of several methods that create a NumericExpression.
	// *
	// * @param operator
	// * @param numericType
	// * @param arg0
	// * arg0 is a SymbolicObject
	// *
	// * @return NumericExpression
	// */
	// @Override
	// public NumericExpression expression(SymbolicOperator operator,
	// SymbolicType numericType, SymbolicObject arg0) {
	// if (numericType.isIdeal())
	// return idealFactory.expression(operator, numericType, arg0);
	// else
	// return herbrandFactory.expression(operator, numericType, arg0);
	// }
	//
	// /**
	// * One of several methods that create a NumericExpression.
	// *
	// * @param operator
	// * @param numericType
	// * @param arg0
	// * arg0 is a SymbolicObject
	// * @param arg1
	// * arg1 is a SymbolicObject
	// *
	// * @return NumericExpression
	// */
	// @Override
	// public NumericExpression expression(SymbolicOperator operator,
	// SymbolicType numericType, SymbolicObject arg0, SymbolicObject arg1) {
	// if (numericType.isIdeal())
	// return idealFactory.expression(operator, numericType, arg0, arg1);
	// else
	// return herbrandFactory
	// .expression(operator, numericType, arg0, arg1);
	// }
	//
	// /**
	// * One of several methods that create a NumericExpression.
	// *
	// * @param operator
	// * @param numericType
	// * @param arg0
	// * arg0 is a SymbolicObject
	// * @param arg1
	// * arg1 is a SymbolicObject
	// * @param arg2
	// * arg2 is a SymbolicObject
	// *
	// * @return NumericExpression
	// */
	// @Override
	// public NumericExpression expression(SymbolicOperator operator,
	// SymbolicType numericType, SymbolicObject arg0, SymbolicObject arg1,
	// SymbolicObject arg2) {
	// if (numericType.isIdeal())
	// return idealFactory.expression(operator, numericType, arg0, arg1,
	// arg2);
	// else
	// return herbrandFactory.expression(operator, numericType, arg0,
	// arg1, arg2);
	// }

	/**
	 * Method that returns zeroInt NumericExpression.
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression zeroInt() {
		return idealFactory.zeroInt();
	}

	/**
	 * Method that returns zeroReal NumericExpression.
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression zeroReal() {
		return idealFactory.zeroReal();
	}

	/**
	 * Method that returns oneInt NumericExpression.
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression oneInt() {
		return idealFactory.oneInt();
	}

	/**
	 * Method that returns oneReal NumericExpression.
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression oneReal() {
		return idealFactory.oneReal();
	}

	/**
	 * Method that adds two NumericExpressions.
	 * 
	 * @param arg0
	 *            arg0 is a NumericExpression
	 * @param arg1
	 *            arg1 is a NumericExpression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression add(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.add(arg0, arg1);
		else
			return herbrandFactory.add(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that subtracts a NumericExpression from another NumericExpression.
	 * 
	 * @param arg0
	 *            arg0 is a NumericExpression
	 * @param arg1
	 *            arg1 is a NumericExpression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression subtract(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.subtract(arg0, arg1);
		else
			return herbrandFactory.subtract(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that multiplies two NumericExpressions.
	 * 
	 * @param arg0
	 *            arg0 is a NumericExpression
	 * @param arg1
	 *            arg1 is a NumericExpression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression multiply(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.multiply(arg0, arg1);
		else
			return herbrandFactory.multiply(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that divides one numeric expression by another.
	 * 
	 * @param arg0
	 *            arg0 is a NumericExpression
	 * @param arg1
	 *            arg1 is a NumericExpression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression divide(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.divide(arg0, arg1);
		else
			return herbrandFactory.divide(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that returns modulo arg1 of arg0.
	 * 
	 * @param arg0
	 *            arg0 is a NumericExpression
	 * @param arg1
	 *            is a NumericExpression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression modulo(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.modulo(arg0, arg1);
		else
			return herbrandFactory.modulo(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that returns a NumericExpression minus arg.
	 * 
	 * @param arg
	 *            arg is a numeric expression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression minus(NumericExpression arg) {
		SymbolicType type = arg.type();

		if (type.isIdeal())
			return idealFactory.minus(arg);
		else
			return herbrandFactory.minus(castToHerbrand(arg));
	}

	/**
	 * One of two methods that returns base raised to exponent.
	 * 
	 * @param base
	 * @param exponent
	 *            exponent is an {@link NumberObject}
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression power(NumericExpression base,
			NumberObject exponent) {
		SymbolicType type = base.type();

		if (type.isIdeal())
			return idealFactory.power(base, exponent);
		else
			return herbrandFactory.power(castToHerbrand(base), exponent);
	}

	/**
	 * One of two methods that returns base raised to exponent.
	 * 
	 * @param base
	 * @param exponent
	 *            exponent is a NumericExpression
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression power(NumericExpression base,
			NumericExpression exponent) {
		SymbolicType t1 = base.type(), t2 = exponent.type();

		if (t1.isIdeal() && t2.isIdeal())
			return idealFactory.power(base, exponent);
		else
			return herbrandFactory.power(castToHerbrand(base),
					castToHerbrand(exponent));
	}

	/**
	 * Method that casts expr to newType
	 * 
	 * @param expr
	 * @param newType
	 * 
	 * @return NumericExpression
	 */
	@Override
	public NumericExpression cast(NumericExpression expr,
			SymbolicType newType) {
		SymbolicType oldType = expr.type();

		if (oldType.equals(newType))
			return expr;

		SymbolicOperator op = expr.operator();

		if (op == SymbolicOperator.CONCRETE) {
			NumberObject numberObject = (NumberObject) expr.argument(0);
			Number number = numberObject.getNumber();

			if (oldType.isInteger() && newType.isReal()) {
				numberObject = objectFactory
						.numberObject(numberFactory.rational(number));
			} else if (oldType.isReal() && newType.isInteger()) {
				if (number.signum() >= 0)
					number = numberFactory.floor((RationalNumber) number);
				else
					number = numberFactory.ceil((RationalNumber) number);
				numberObject = objectFactory.numberObject(number);
			}
			if (newType.isIdeal())
				return idealFactory.number(numberObject);
			else if (newType.isHerbrand())
				return herbrandFactory.number(numberObject);
			else
				throw new SARLInternalException("Unknown type: " + newType);
		} else if (newType.isIdeal())
			return idealFactory.cast(castToIdeal(expr), newType);
		else if (newType.isHerbrand())
			return herbrandFactory.cast(castToHerbrand(expr), newType);
		else
			throw new SARLInternalException("Unknown type: " + newType);
	}

	/**
	 * Method that extracts Number from expression.
	 * 
	 * @param expression
	 * 
	 * @return Number
	 */
	@Override
	public Number extractNumber(NumericExpression expression) {
		if (expression.type().isHerbrand())
			return herbrandFactory.extractNumber(expression);
		else if (expression.type().isIdeal())
			return idealFactory.extractNumber(expression);
		return null;
	}

	/**
	 * Method that returns BooleanExpression of arg0 < arg1
	 * 
	 * @param arg0
	 * @param arg1
	 * 
	 * @return BooleanExpression
	 */
	@Override
	public BooleanExpression lessThan(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.lessThan(arg0, arg1);
		else
			return herbrandFactory.lessThan(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that returns BooleanExpression of arg0 <= arg1
	 * 
	 * @param arg0
	 * @param arg1
	 * 
	 * @return BooleanExpression
	 */
	@Override
	public BooleanExpression lessThanEquals(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.lessThanEquals(arg0, arg1);
		else
			return herbrandFactory.lessThanEquals(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that returns BooleanExpression of arg0 !< arg1
	 * 
	 * @param arg0
	 * @param arg1
	 * 
	 * @return BooleanExpression
	 */
	@Override
	public BooleanExpression notLessThan(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.notLessThan(arg0, arg1);
		else
			return herbrandFactory.notLessThan(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that returns BooleanExpression of arg0 !<= arg1
	 * 
	 * @param arg0
	 * @param arg1
	 * 
	 * @return BooleanExpression
	 */
	@Override
	public BooleanExpression notLessThanEquals(NumericExpression arg0,
			NumericExpression arg1) {
		SymbolicType t0 = arg0.type(), t1 = arg1.type();

		if (t0.isIdeal() && t1.isIdeal())
			return idealFactory.notLessThanEquals(arg0, arg1);
		else
			return herbrandFactory.notLessThanEquals(castToHerbrand(arg0),
					castToHerbrand(arg1));
	}

	/**
	 * Method that returns BooleanExpression of equals arg0 arg1
	 * 
	 * @param arg0
	 * @param arg1
	 * 
	 * @return BooleanExpression
	 */
	@Override
	public BooleanExpression equals(NumericExpression arg0,
			NumericExpression arg1) {
		return idealFactory.equals(castToIdeal(arg0), castToIdeal(arg1));
	}

	/**
	 * Method that returns BooleanExpression of not equals arg0 arg1
	 * 
	 * @param arg0
	 * @param arg1
	 * 
	 * @return BooleanExpression
	 */
	@Override
	public BooleanExpression neq(NumericExpression arg0,
			NumericExpression arg1) {
		return idealFactory.neq(castToIdeal(arg0), castToIdeal(arg1));
	}

	@Override
	public NumericExpression[] expand(NumericExpression expr) {
		return idealFactory.expand(expr);
	}

	@Override
	public NumericExpression floor(NumericExpression expr) {
		if (expr.type().isIdeal())
			return idealFactory.floor(expr);
		else
			return herbrandFactory.floor(expr);
	}

	@Override
	public NumericExpression ceil(NumericExpression expr) {
		if (expr.type().isIdeal())
			return idealFactory.ceil(expr);
		else
			return herbrandFactory.ceil(expr);
	}

	@Override
	public NumericExpression roundToZero(NumericExpression expr) {
		if (expr.type().isIdeal())
			return idealFactory.roundToZero(expr);
		else
			return herbrandFactory.roundToZero(expr);
	}
}

/**
 * 
 * CommonNumericComparator class implements Comparator
 * <NumericExpression> interface
 * 
 * @author siegel
 *
 */
class CommonNumericComparator implements Comparator<NumericExpression> {
	private Comparator<NumericExpression> idealComparator;
	private Comparator<NumericExpression> herbrandComparator;

	/**
	 * Constructor that creates a CommonNumericComparator
	 * 
	 * @param idealComparator
	 * @param herbrandComparator
	 * 
	 * @return CommonNumericComparator
	 */
	CommonNumericComparator(Comparator<NumericExpression> idealComparator,
			Comparator<NumericExpression> herbrandComparator) {
		this.idealComparator = idealComparator;
		this.herbrandComparator = herbrandComparator;
	}

	/**
	 * Compare method for NumericExpressions.
	 * 
	 * @param o1
	 *            o1 is a NumericExpression
	 * @param o2
	 *            o2 is a NumericExpression
	 * 
	 * @return int
	 */
	@Override
	public int compare(NumericExpression o1, NumericExpression o2) {
		if (o1.type().isHerbrand()) {
			if (o2.type().isHerbrand())
				return herbrandComparator.compare(o1, o2);
			return -1;
		} else {
			if (o2.type().isHerbrand())
				return 1;
			return idealComparator.compare(o1, o2);
		}
	}
}