/**
* 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.operator.meta.branch;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.rapidminer.generator.GenerationException;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.OperatorVersion;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.InputPorts;
import com.rapidminer.operator.ports.MultiInputPortPairExtender;
import com.rapidminer.operator.ports.MultiOutputPortPairExtender;
import com.rapidminer.operator.ports.OutputPort;
import com.rapidminer.operator.ports.OutputPorts;
import com.rapidminer.operator.ports.metadata.SubprocessTransformRule;
import com.rapidminer.operator.preprocessing.filter.ExampleFilter;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.parameter.ParameterTypeCategory;
import com.rapidminer.parameter.ParameterTypeExpression;
import com.rapidminer.parameter.ParameterTypeString;
import com.rapidminer.parameter.ParameterTypeStringCategory;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.parameter.conditions.AboveOperatorVersionCondition;
import com.rapidminer.parameter.conditions.AndParameterCondition;
import com.rapidminer.parameter.conditions.BelowOrEqualOperatorVersionCondition;
import com.rapidminer.parameter.conditions.EqualStringCondition;
import com.rapidminer.parameter.conditions.NonEqualStringCondition;
import com.rapidminer.parameter.conditions.OrParameterCondition;
import com.rapidminer.tools.OperatorService;
import com.rapidminer.tools.Tools;
import com.rapidminer.tools.expression.ExpressionParserBuilder;
import com.rapidminer.tools.expression.internal.ExpressionParserUtils;
/**
* <p>
* This operator provides a conditional execution of parts of processes. It has to have two
* OperatorChains as children. The first chain is processed if the specified condition is true, the
* second one is processed if it is false (if-then-else). The second chain may be omitted (if-then).
* In this case, this operator has only one inner operator.
* </p>
*
* <p>
* If the condition "attribute_value_filter" is used, the same attribute value conditions
* already known from the {@link ExampleFilter} operator can be used. In addition to the known
* attribute value relation format (e.g. "att1>=0.7"), this operator expects an
* additional definition for the used example which cam be added in "[" and "]"
* after the attribute value condition. The following values are possible:
* <ul>
* <li>a fixed number, e.g. "att1>0.7 [7]" meaning that the value for attribute
* "att1" for the example 7 must be greater than 0.7</li>
* <li>the wildcard "*" meaning that the attribute value condition must be fulfilled for
* all examples, e.g. "att4<=5 [*]"</li>
* <li>no example definition, meaning the same as the wildcard definition [*]</li>
* </ul>
* </p>
*
* @author Sebastian Land, Ingo Mierswa
*/
public class ProcessBranch extends OperatorChain {
public static final String PARAMETER_CONDITION_TYPE = "condition_type";
public static final String PARAMETER_CONDITION_VALUE = "condition_value";
public static final String PARAMETER_EXPRESSION_VALUE = "expression";
public static final String PARAMETER_RETURN_INNER_OUTPUT = "return_inner_output";
public static final String PARAMETER_IO_OBJECT = "io_object";
public static final String CONDITION_EXPRESSION = "expression";
// CHECK INDEX OF INPUT_EXISTS BELOW
public static final String[] CONDITION_NAMES = { "attribute_value_filter", "attribute_available", "min_examples",
"max_examples", "min_attributes", "max_attributes", "min_fitness", "max_fitness", "min_performance_value",
"max_performance_value", "file_exists", "input_exists", // <== THIS NEEDS TO BE THE
// 11'th ENTRY IN THE LIST!!!
"macro_defined", CONDITION_EXPRESSION };
// ONLY TRUE IF INPUT EXISTS IS 11'th ENTRY ABOVE
public static final String CONDITION_INPUT_EXISTS = CONDITION_NAMES[11];
public static final Class<?>[] CONDITION_CLASSES = { DataValueCondition.class, AttributeAvailableCondition.class,
MinNumberOfExamplesCondition.class, MaxNumberOfExamplesCondition.class, MinNumberOfAttributesCondition.class,
MaxNumberOfAttributesCondition.class, MinFitnessCondition.class, MaxFitnessCondition.class,
MinPerformanceValueCondition.class, MaxPerformanceValueCondition.class, FileExistsCondition.class,
InputExistsCondition.class, MacroDefinedCondition.class, ExpressionCondition.class };
private String[] objectArray = null;
private final InputPort conditionInput = getInputPorts().createPort("condition");
private final OutputPort conditionInnerSourceThen = getSubprocess(0).getInnerSources().createPort("condition");
private final OutputPort conditionInnerSourceElse = getSubprocess(1).getInnerSources().createPort("condition");
private final MultiOutputPortPairExtender inputExtender = new MultiOutputPortPairExtender("input", getInputPorts(),
new OutputPorts[] { getSubprocess(0).getInnerSources(), getSubprocess(1).getInnerSources() });
private final MultiInputPortPairExtender outputExtender = new MultiInputPortPairExtender("input", getOutputPorts(),
new InputPorts[] { getSubprocess(0).getInnerSinks(), getSubprocess(1).getInnerSinks() });
public ProcessBranch(OperatorDescription description) {
super(description, "Then", "Else");
inputExtender.start();
getTransformer().addPassThroughRule(conditionInput, conditionInnerSourceThen);
getTransformer().addPassThroughRule(conditionInput, conditionInnerSourceElse);
getTransformer().addRule(inputExtender.makePassThroughRule());
getTransformer().addRule(new SubprocessTransformRule(getSubprocess(0)));
getTransformer().addRule(new SubprocessTransformRule(getSubprocess(1)));
getTransformer().addRule(outputExtender.makePassThroughRule());
outputExtender.start();
}
@Override
public void doWork() throws OperatorException {
// creating condition
Class<?> conditionClass = null;
String selectedConditionName = "";
selectedConditionName = getParameterAsString(PARAMETER_CONDITION_TYPE);
for (int i = 0; i < CONDITION_NAMES.length; i++) {
if (selectedConditionName.toLowerCase().equals(CONDITION_NAMES[i].toLowerCase())) {
conditionClass = CONDITION_CLASSES[i];
break;
}
}
if (conditionClass == null) {
try {
conditionClass = Tools.classForName(selectedConditionName);
} catch (ClassNotFoundException e) {
throw new UserError(this, e, 904, new Object[] { selectedConditionName, e });
}
}
ProcessBranchCondition condition = null;
try {
condition = (ProcessBranchCondition) conditionClass.newInstance();
} catch (InstantiationException e) {
throw new UserError(this, e, 904, new Object[] { selectedConditionName, e });
} catch (IllegalAccessException e) {
throw new UserError(this, e, 904, new Object[] { selectedConditionName, e });
}
clearAllInnerSinks();
if (condition != null) {
// checking condition
String conditionValue;
if (CONDITION_INPUT_EXISTS.equals(selectedConditionName)) {
Class<? extends IOObject> selectedConditionClass = getSelectedClass();
if (selectedConditionClass == null) {
throw new UserError(this, 904, "'" + getParameter(PARAMETER_IO_OBJECT) + "'", "Class does not exist.");
}
conditionValue = null;
} else {
if (!getCompatibilityLevel().isAtMost(ExpressionParserBuilder.OLD_EXPRESSION_PARSER_FUNCTIONS)
&& CONDITION_EXPRESSION.equals(selectedConditionName)) {
conditionValue = getParameter(PARAMETER_EXPRESSION_VALUE);
} else {
conditionValue = getParameterAsString(PARAMETER_CONDITION_VALUE);
}
}
try {
boolean conditionState = condition.check(this, conditionValue);
// execute
if (conditionState) {
conditionInnerSourceThen.deliver(conditionInput.getDataOrNull(IOObject.class));
} else {
conditionInnerSourceElse.deliver(conditionInput.getDataOrNull(IOObject.class));
}
int chosenProcess = conditionState ? 0 : 1;
inputExtender.passDataThrough(chosenProcess);
getSubprocess(chosenProcess).execute();
outputExtender.passDataThrough(chosenProcess);
} catch (GenerationException e) {
throw new UserError(this, e, "cannot_parse_expression",
new Object[] { conditionValue.replace("<", "<"), e.getMessage().replace("<", "<") });
}
} else {
outputExtender.passDataThrough(0);
}
}
@Override
public boolean getAddOnlyAdditionalOutput() {
return getParameterAsBoolean(PARAMETER_RETURN_INNER_OUTPUT);
}
public <T extends IOObject> T getConditionInput(Class<T> cls) throws UserError {
return conditionInput.<T> getData(cls);
}
public <T extends IOObject> T getConditionInputOrNull(Class<T> cls) throws UserError {
return conditionInput.<T> getDataOrNull(cls);
}
public Class<? extends IOObject> getSelectedClass() throws UndefinedParameterError {
int ioType = getParameterAsInt(PARAMETER_IO_OBJECT);
if (objectArray == null) {
return null;
} else {
if (ioType >= 0 && ioType < objectArray.length) {
return OperatorService.getIOObjectClass(objectArray[ioType]);
} else {
return null;
}
}
}
@Override
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
ParameterType type = new ParameterTypeStringCategory(PARAMETER_CONDITION_TYPE,
"The condition which is used for the condition check.", CONDITION_NAMES);
type.setExpert(false);
types.add(type);
// add default condition parameter
type = new ParameterTypeString(PARAMETER_CONDITION_VALUE,
"A condition parameter which might be desired for some condition checks.", true);
type.setExpert(false);
type.registerDependencyCondition(new NonEqualStringCondition(this, PARAMETER_CONDITION_TYPE, true,
CONDITION_INPUT_EXISTS));
// Only show condition parameter in for CONDITION_EXPRESSION in case version is at most 6.4
// (version <= 6.4 && type == expression) || ( type != expression)
AndParameterCondition belowNewExpressionParserAndExpression = new AndParameterCondition(this, false,
new BelowOrEqualOperatorVersionCondition(this, ExpressionParserBuilder.OLD_EXPRESSION_PARSER_FUNCTIONS),
new EqualStringCondition(this, PARAMETER_CONDITION_TYPE, true, CONDITION_EXPRESSION));
OrParameterCondition expressionCondition = new OrParameterCondition(this, false,
belowNewExpressionParserAndExpression, new NonEqualStringCondition(this, PARAMETER_CONDITION_TYPE, true,
CONDITION_EXPRESSION));
type.registerDependencyCondition(expressionCondition);
type.registerDependencyCondition(new NonEqualStringCondition(this, PARAMETER_CONDITION_TYPE, true,
CONDITION_INPUT_EXISTS));
types.add(type);
// add expression type
type = new ParameterTypeExpression(PARAMETER_EXPRESSION_VALUE,
"The expression parameter which allows to specify an expression for condition type 'expression'.",
conditionInput, true);
type.setExpert(false);
// only show the parameter above version 6.4.0 and if condition type "expression" is
// selected
type.registerDependencyCondition(new AboveOperatorVersionCondition(this,
ExpressionParserBuilder.OLD_EXPRESSION_PARSER_FUNCTIONS));
type.registerDependencyCondition(new EqualStringCondition(this, PARAMETER_CONDITION_TYPE, true, CONDITION_EXPRESSION));
types.add(type);
Set<String> ioObjects = OperatorService.getIOObjectsNames();
this.objectArray = new String[ioObjects.size()];
Iterator<String> i = ioObjects.iterator();
int index = 0;
while (i.hasNext()) {
objectArray[index++] = i.next();
}
type = new ParameterTypeCategory(PARAMETER_IO_OBJECT,
"The class of the object(s) which should be checked for existance.", objectArray, 0);
type.registerDependencyCondition(new EqualStringCondition(this, PARAMETER_CONDITION_TYPE, true,
CONDITION_INPUT_EXISTS));
type.setExpert(false);
types.add(type);
types.add(new ParameterTypeBoolean(PARAMETER_RETURN_INNER_OUTPUT,
"Indicates if the output of the inner operators should be delivered.", true));
return types;
}
@Override
public OperatorVersion[] getIncompatibleVersionChanges() {
// Add compatibility level for new expression parser
// Will be used by ExpressionCondition
return ExpressionParserUtils.addIncompatibleExpressionParserChange(super.getIncompatibleVersionChanges());
}
}