/** * 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.logical; import java.util.Date; import java.util.concurrent.Callable; import com.rapidminer.tools.Ontology; import com.rapidminer.tools.Tools; 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.FunctionDescription; import com.rapidminer.tools.expression.FunctionInputException; import com.rapidminer.tools.expression.internal.SimpleExpressionEvaluator; import com.rapidminer.tools.expression.internal.function.AbstractFunction; /** * Class for the IF function that has one logical (numerical, true or false) input and two arbitrary * inputs * * @author Sabrina Kirstein */ public class If extends AbstractFunction { /** * Constructs an IF Function with 3 parameters with {@link FunctionDescription} */ public If() { super("logical.if", 3, Ontology.ATTRIBUTE_VALUE); } @Override public ExpressionEvaluator compute(ExpressionEvaluator... inputEvaluators) { if (inputEvaluators.length != 3) { throw new FunctionInputException("expression_parser.function_wrong_input", getFunctionName(), 3, inputEvaluators.length); } ExpressionEvaluator condition = inputEvaluators[0]; ExpressionEvaluator ifCase = inputEvaluators[1]; ExpressionEvaluator elseCase = inputEvaluators[2]; ExpressionType type = null; // if we know that the condition is constant, check the type based on the condition if (condition.isConstant()) { if (condition.getType() != ExpressionType.INTEGER && condition.getType() != ExpressionType.DOUBLE && condition.getType() != ExpressionType.BOOLEAN) { throw new FunctionInputException("expression_parser.function_wrong_type.argument_two", 1, getFunctionName(), "boolean", "numerical"); } Boolean cond = getCondition(condition); if (cond != null) { type = computeType(cond, ifCase.getType(), elseCase.getType()); } } // if the condition is not constant, make the best guess given the types of the second and // their ExpressionEvaluator if (type == null) { type = getResultType(inputEvaluators); } // return the callables based on the return type that was computed before switch (type) { case DOUBLE: case INTEGER: DoubleCallable doubleCallable = makeDoubleCallable(condition, ifCase, elseCase); return new SimpleExpressionEvaluator(doubleCallable, type, isResultConstant(inputEvaluators)); case BOOLEAN: Callable<Boolean> booleanCallable = makeBooleanCallable(condition, ifCase, elseCase); return new SimpleExpressionEvaluator(booleanCallable, isResultConstant(inputEvaluators), type); case DATE: Callable<Date> dateCallable = makeDateCallable(condition, ifCase, elseCase); return new SimpleExpressionEvaluator(type, dateCallable, isResultConstant(inputEvaluators)); case STRING: default: Callable<String> stringCallable = makeStringCallable(condition, ifCase, elseCase); return new SimpleExpressionEvaluator(stringCallable, type, isResultConstant(inputEvaluators)); } } /** * Builds a boolean callable from the given evaluators * * @param condition * evaluator * @param ifBlock * evaluator * @param elseBlock * evaluator * @return the resulting boolean callable */ protected Callable<Boolean> makeBooleanCallable(final ExpressionEvaluator condition, final ExpressionEvaluator ifBlock, final ExpressionEvaluator elseBlock) { // if we know that the condition is constant, make the callable based on the selected case if (condition.isConstant()) { // check the condition value Boolean cond = getCondition(condition); if (cond != null) { // check the condition value if (cond) { // return Callable<Boolean> from the ifCase // if this block is constant, just return the value if (ifBlock.isConstant()) { try { final Boolean value = ifBlock.getBooleanFunction().call(); return new Callable<Boolean>() { @Override public Boolean call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new Callable<Boolean>() { @Override public Boolean call() throws Exception { return ifBlock.getBooleanFunction().call(); } }; } } else { // return Callable<Boolean> from the elseCase // if this block is constant, just return the value if (elseBlock.isConstant()) { try { final Boolean value = elseBlock.getBooleanFunction().call(); return new Callable<Boolean>() { @Override public Boolean call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new Callable<Boolean>() { @Override public Boolean call() throws Exception { return elseBlock.getBooleanFunction().call(); } }; } } } else { return new Callable<Boolean>() { @Override public Boolean call() throws Exception { return null; } }; } } else { // create a Callable<Boolean> that checks whether the condition is given and calls // the if or else part return new Callable<Boolean>() { @Override public Boolean call() throws Exception { Boolean cond = getCondition(condition); if (cond == null) { return null; } if (cond) { return ifBlock.getBooleanFunction().call(); } else { return elseBlock.getBooleanFunction().call(); } } }; } } /** * Builds a String callable from the given evaluators * * @param condition * evaluator * @param ifBlock * evaluator * @param elseBlock * evaluator * @return the resulting String callable */ protected Callable<String> makeStringCallable(final ExpressionEvaluator condition, final ExpressionEvaluator ifBlock, final ExpressionEvaluator elseBlock) { // if we know that the condition is constant, make the callable based on the selected case if (condition.isConstant()) { // check the condition value Boolean cond = getCondition(condition); if (cond != null) { if (cond) { // return Callable<String> from the ifCase // if this block is constant, just return the value if (ifBlock.isConstant()) { try { final String value = ifBlock.getStringFunction().call(); return new Callable<String>() { @Override public String call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new Callable<String>() { @Override public String call() throws Exception { return ifBlock.getStringFunction().call(); } }; } } else { // return Callable<String> from the elseCase // if this block is constant, just return the value if (elseBlock.isConstant()) { try { final String value = elseBlock.getStringFunction().call(); return new Callable<String>() { @Override public String call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new Callable<String>() { @Override public String call() throws Exception { return elseBlock.getStringFunction().call(); } }; } } } else { return new Callable<String>() { @Override public String call() throws Exception { return null; } }; } } else { // create a Callable<String> that checks whether the condition is given and calls the // if or else part return new Callable<String>() { @Override public String call() throws Exception { Boolean cond = getCondition(condition); if (cond == null) { return null; } if (cond) { switch (ifBlock.getType()) { case BOOLEAN: return convertToString(ifBlock.getBooleanFunction().call()); case INTEGER: return convertToString(ifBlock.getDoubleFunction().call(), true); case DOUBLE: return convertToString(ifBlock.getDoubleFunction().call(), false); case DATE: return convertToString(ifBlock.getDateFunction().call()); case STRING: default: return ifBlock.getStringFunction().call(); } } else { switch (elseBlock.getType()) { case BOOLEAN: return convertToString(elseBlock.getBooleanFunction().call()); case INTEGER: return convertToString(elseBlock.getDoubleFunction().call(), true); case DOUBLE: return convertToString(elseBlock.getDoubleFunction().call(), false); case DATE: return convertToString(elseBlock.getDateFunction().call()); case STRING: default: return elseBlock.getStringFunction().call(); } } } }; } } /** * Converts the object into a String, return {@code null} if the object is {@code null}. */ private String convertToString(Object object) { if (object == null) { return null; } return object.toString(); } /** * Converts the value into a string, returning {@code null} if the value is Double.NaN, * formatting infinity via a symbol and casting to int if possible and the value is supposed to * represent an integer. * * @param value * @param isInteger * whether the double value represents an integer * @return the value converted to an integer */ private String convertToString(double value, boolean isInteger) { if (Double.isNaN(value)) { return null; } else if (Double.isInfinite(value)) { return Tools.formatNumber(value); } else if (isInteger && value == (int) value) { return Integer.toString((int) value); } else { return Double.toString(value); } } /** * Builds a Date callable from the given evaluators * * @param condition * evaluator * @param ifBlock * evaluator * @param elseBlock * evaluator * @return the resulting Date callable */ protected Callable<Date> makeDateCallable(final ExpressionEvaluator condition, final ExpressionEvaluator ifBlock, final ExpressionEvaluator elseBlock) { // if we know that the condition is constant, make the callable based on the selected case if (condition.isConstant()) { // check the condition value Boolean cond = getCondition(condition); if (cond != null) { if (cond) { // return Callable<Date> from the ifCase // if this block is constant, just return the value if (ifBlock.isConstant()) { try { final Date value = ifBlock.getDateFunction().call(); return new Callable<Date>() { @Override public Date call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new Callable<Date>() { @Override public Date call() throws Exception { return ifBlock.getDateFunction().call(); } }; } } else { // return Callable<Date> from the elseCase // if this block is constant, just return the value if (elseBlock.isConstant()) { try { final Date value = elseBlock.getDateFunction().call(); return new Callable<Date>() { @Override public Date call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new Callable<Date>() { @Override public Date call() throws Exception { return elseBlock.getDateFunction().call(); } }; } } } else { return new Callable<Date>() { @Override public Date call() throws Exception { return null; } }; } } else { // create a Callable<Date> that checks whether the condition is given and calls the if // or else part return new Callable<Date>() { @Override public Date call() throws Exception { Boolean cond = getCondition(condition); if (cond == null) { return null; } if (cond) { return ifBlock.getDateFunction().call(); } else { return elseBlock.getDateFunction().call(); } } }; } } /** * Builds a Double callable from the given evaluators * * @param condition * evaluator * @param ifBlock * evaluator * @param elseBlock * evaluator * @return the resulting Double callable */ protected DoubleCallable makeDoubleCallable(final ExpressionEvaluator condition, final ExpressionEvaluator ifBlock, final ExpressionEvaluator elseBlock) { // if we know that the condition is constant, make the callable based on the selected case if (condition.isConstant()) { // check the condition value Boolean cond = getCondition(condition); if (cond != null) { if (cond) { // return DoubleCallable from the ifCase // if this block is constant, just return the value if (ifBlock.isConstant()) { try { final double value = ifBlock.getDoubleFunction().call(); return new DoubleCallable() { @Override public double call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new DoubleCallable() { @Override public double call() throws Exception { return ifBlock.getDoubleFunction().call(); } }; } } else { // return DoubleCallable from the elseCase // if this block is constant, just return the value if (elseBlock.isConstant()) { try { final double value = elseBlock.getDoubleFunction().call(); return new DoubleCallable() { @Override public double call() throws Exception { return value; } }; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else { // if it isnt constant, return the function result return new DoubleCallable() { @Override public double call() throws Exception { return elseBlock.getDoubleFunction().call(); } }; } } } else { return new DoubleCallable() { @Override public double call() throws Exception { return Double.NaN; } }; } } else { // create a DoubleCallable that checks whether the condition is given and calls the // if or else part return new DoubleCallable() { @Override public double call() throws Exception { Boolean cond = getCondition(condition); if (cond == null) { return Double.NaN; } if (cond) { return ifBlock.getDoubleFunction().call(); } else { return elseBlock.getDoubleFunction().call(); } } }; } } @Override protected ExpressionType computeType(ExpressionType... inputTypes) { // check whether there are 3 arguments if (inputTypes.length != 3) { throw new FunctionInputException("expression_parser.function_wrong_input", getFunctionName(), "3", inputTypes.length); } // check that the first input is logical (boolean or double) if (inputTypes[0] != ExpressionType.INTEGER && inputTypes[0] != ExpressionType.DOUBLE && inputTypes[0] != ExpressionType.BOOLEAN) { throw new FunctionInputException("expression_parser.function_wrong_type.argument_two", 1, getFunctionName(), "boolean", "numerical"); } ExpressionType ifCase = inputTypes[1]; ExpressionType elseCase = inputTypes[2]; // if both cases have the same type, the type is clear if (ifCase.equals(elseCase)) { return ifCase; } else if (ifCase.equals(ExpressionType.INTEGER) && elseCase.equals(ExpressionType.DOUBLE)) { return ExpressionType.DOUBLE; } else if (ifCase.equals(ExpressionType.DOUBLE) && elseCase.equals(ExpressionType.INTEGER)) { return ExpressionType.DOUBLE; } else { return ExpressionType.STRING; } } /** * Returns the {@link ExpressionType} based on the condition value * * @param conditionTrue * @param ifcase * {@link ExpressionType} of the if case * @param elsecase * {@link ExpressionType} of the else case * @return the {@link ExpressionType} of the case, which is given by the parameter conditionTrue */ private ExpressionType computeType(Boolean conditionTrue, ExpressionType ifcase, ExpressionType elsecase) { if (conditionTrue) { return ifcase; } else { return elsecase; } } /** * Returns the condition of the given {@link ExpressionEvaluator} if this one is constant * * @param condition * {@link ExpressionEvaluator} * @return the condition of the {@link ExpressionEvaluator} */ private Boolean getCondition(ExpressionEvaluator condition) { Boolean cond = false; if (condition.getType() == ExpressionType.BOOLEAN) { final Callable<Boolean> funcCond = condition.getBooleanFunction(); try { cond = funcCond.call(); } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } else if (condition.getType() == ExpressionType.DOUBLE || condition.getType() == ExpressionType.INTEGER) { final DoubleCallable funcCond = condition.getDoubleFunction(); try { final double condValue = funcCond.call(); if (Double.isNaN(condValue)) { return null; } cond = Math.abs(condValue) < Double.MIN_VALUE * 2 ? false : true; } catch (ExpressionParsingException e) { throw e; } catch (Exception e) { throw new ExpressionParsingException(e); } } return cond; } }