/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools.expression.internal.function.date;
import java.util.Date;
import java.util.concurrent.Callable;
import com.rapidminer.tools.expression.DoubleCallable;
import com.rapidminer.tools.expression.ExpressionEvaluator;
import com.rapidminer.tools.expression.ExpressionParsingException;
import com.rapidminer.tools.expression.ExpressionType;
import com.rapidminer.tools.expression.FunctionInputException;
import com.rapidminer.tools.expression.internal.SimpleExpressionEvaluator;
import com.rapidminer.tools.expression.internal.function.AbstractFunction;
/**
* Abstract class for a {@link Function} that has a date, an integer and a unit as arguments and can
* have locale and returns a date. The function can has time zone and localization strings as
* optional parameters
*
* @author David Arnu
*
*/
public abstract class AbstractDateManipulationFunction extends AbstractFunction {
public AbstractDateManipulationFunction(String i18nKey, int numberOfArgumentsToCheck, int returnType) {
super(i18nKey, numberOfArgumentsToCheck, returnType);
}
@Override
public ExpressionEvaluator compute(ExpressionEvaluator... inputEvaluators) {
ExpressionType type = getResultType(inputEvaluators);
if (inputEvaluators.length == 3) {
ExpressionEvaluator date = inputEvaluators[0];
ExpressionEvaluator value = inputEvaluators[1];
ExpressionEvaluator unit = inputEvaluators[2];
return new SimpleExpressionEvaluator(type, makeDateCallable(date, value, unit, null, null),
isResultConstant(inputEvaluators));
} else {
ExpressionEvaluator date = inputEvaluators[0];
ExpressionEvaluator value = inputEvaluators[1];
ExpressionEvaluator unit = inputEvaluators[2];
ExpressionEvaluator locale = inputEvaluators[3];
ExpressionEvaluator timeZone = inputEvaluators[4];
return new SimpleExpressionEvaluator(type, makeDateCallable(date, value, unit, locale, timeZone),
isResultConstant(inputEvaluators));
}
}
private Callable<Date> makeDateCallable(ExpressionEvaluator date, ExpressionEvaluator value, ExpressionEvaluator unit,
ExpressionEvaluator locale, ExpressionEvaluator timeZone) {
final Callable<Date> funcDate = date.getDateFunction();
final DoubleCallable funcValue = value.getDoubleFunction();
final Callable<String> funcUnit = unit.getStringFunction();
final Callable<String> funcLocale;
final Callable<String> funcTimeZone;
if (locale != null) {
funcLocale = locale.getStringFunction();
} else {
// create an dummy ExpressionEvaluator for the missing locale argument
locale = new SimpleExpressionEvaluator("", ExpressionType.STRING);
funcLocale = new Callable<String>() {
@Override
public String call() throws Exception {
return null;
}
};
}
if (timeZone != null) {
funcTimeZone = timeZone.getStringFunction();
} else {
// create an dummy ExpressionEvaluator for the missing time zone argument
timeZone = new SimpleExpressionEvaluator("", ExpressionType.STRING);
funcTimeZone = new Callable<String>() {
@Override
public String call() throws Exception {
return null;
}
};
}
try {
final Date valueDate = date.isConstant() ? funcDate.call() : null;
final double valueValue = value.isConstant() ? funcValue.call() : Double.NaN;
final String valueUnit = unit.isConstant() ? funcUnit.call() : null;
final String valueLocale = locale.isConstant() ? funcLocale.call() : null;
final String valueTimezone = timeZone.isConstant() ? funcTimeZone.call() : null;
// only likely cases checked:
// all constant values
if (date.isConstant() && value.isConstant() && unit.isConstant() && locale.isConstant() && timeZone.isConstant()) {
final Date result = compute(valueDate, valueValue, valueUnit, valueLocale, valueTimezone);
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return result;
}
};
// constant date, value and unit are not constant
} else if (date.isConstant() && !value.isConstant() && !unit.isConstant()) {
// branch with constant locale and time zone data, probably both are constant or
// both are not
if (locale.isConstant() && timeZone.isConstant()) {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(valueDate, funcValue.call(), funcUnit.call(), valueLocale, valueTimezone);
}
};
} else {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(valueDate, funcValue.call(), funcUnit.call(), funcLocale.call(),
funcTimeZone.call());
}
};
}
// constant value, date and unit are not
} else if (!date.isConstant() && value.isConstant() && !unit.isConstant()) {
// branch with constant locale and time zone data, probably both are constant or
// both are not
if (locale.isConstant() && timeZone.isConstant()) {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), valueValue, funcUnit.call(), valueLocale, valueTimezone);
}
};
} else {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), valueValue, funcUnit.call(), funcLocale.call(),
funcTimeZone.call());
}
};
}
// constant unit, date and value are not
} else if (!date.isConstant() && !value.isConstant() && unit.isConstant()) {
// branch with constant locale and time zone data, probably both are constant or
// both are not
if (locale.isConstant() && timeZone.isConstant()) {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), funcValue.call(), valueUnit, valueLocale, valueTimezone);
}
};
} else {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), funcValue.call(), valueUnit, funcLocale.call(),
funcTimeZone.call());
}
};
}
// value and unit are constant, date is not
} else if (!date.isConstant() && value.isConstant() && unit.isConstant()) {
// branch with constant locale and time zone data, probably both are constant or
// both are not
if (locale.isConstant() && timeZone.isConstant()) {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), valueValue, valueUnit, valueLocale, valueTimezone);
}
};
} else {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), valueValue, valueUnit, funcLocale.call(), funcTimeZone.call());
}
};
}
// date, value and unit are variable
} else {
// branch with constant locale and time zone data, probably both are constant or
// both are not
if (locale.isConstant() && timeZone.isConstant()) {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), funcValue.call(), funcUnit.call(), valueLocale, valueTimezone);
}
};
} else {
return new Callable<Date>() {
@Override
public Date call() throws Exception {
return compute(funcDate.call(), funcValue.call(), funcUnit.call(), funcLocale.call(),
funcTimeZone.call());
}
};
}
}
} catch (ExpressionParsingException e) {
throw e;
} catch (Exception e) {
throw new ExpressionParsingException(e);
}
}
/**
* Computes the result for manipulating a date for a certain value on a given unit with
* additional locale and time zone arguments.
*
* @param date
* date to manipulate
* @param value
* the amount of which the date should change
* @param unit
* the unit constant which should be changed
* @param valueTimezone
* time zone string
* @return the result of the computation.
*/
protected abstract Date compute(Date date, double value, String unit, String valueLocale, String valueTimezone);
@Override
protected ExpressionType computeType(ExpressionType... inputTypes) {
if (inputTypes.length != 3 && inputTypes.length != 5) {
throw new FunctionInputException("expression_parser.function_wrong_input_two", getFunctionName(), "3", "5",
inputTypes.length);
}
ExpressionType firstType = inputTypes[0];
ExpressionType secondType = inputTypes[1];
ExpressionType thirdType = inputTypes[2];
if (firstType != ExpressionType.DATE) {
throw new FunctionInputException("expression_parser.function_wrong_type_at", getFunctionName(), "date", "first");
}
if (secondType != ExpressionType.INTEGER) {
throw new FunctionInputException("expression_parser.function_wrong_type_at", getFunctionName(), "integer",
"second");
}
if (thirdType != ExpressionType.STRING) {
throw new FunctionInputException("expression_parser.function_wrong_type_at", getFunctionName(), "string",
"third");
}
if (inputTypes.length == 5) {
if (inputTypes[3] != ExpressionType.STRING) {
throw new FunctionInputException("expression_parser.function_wrong_type_at", getFunctionName(), "string",
"fourth");
}
if (inputTypes[4] != ExpressionType.STRING) {
throw new FunctionInputException("expression_parser.function_wrong_type_at", getFunctionName(), "string",
"fifth");
}
}
return ExpressionType.DATE;
}
}