/**
* 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;
import java.util.List;
import com.rapidminer.Process;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.ProcessRootOperator;
import com.rapidminer.operator.ProcessSetupError;
import com.rapidminer.operator.ports.Port;
import com.rapidminer.operator.ports.metadata.InputMissingMetaDataError;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeAttribute;
import com.rapidminer.tools.container.Pair;
/**
* This class contains utility methods related to {@link com.rapidminer.Process}es.
*
* @author Marco Boeck
* @since 6.5.0
*
*/
public final class ProcessTools {
/**
* Private constructor which throws if called.
*/
private ProcessTools() {
throw new UnsupportedOperationException("Static utility class");
}
/**
* Checks whether the given process has at least one connected result port, i.e. if the process
* would generate results in the result perspective.
*
* @param process
* the process in question
* @return {@code true} if the process has at least one connected result port; {@code false} if
* it does not
*/
public static boolean isProcessConnectedToResultPort(final Process process) {
if (process == null) {
throw new IllegalArgumentException("process must not be null!");
}
return process.getRootOperator().getSubprocess(0).getInnerSinks().getNumberOfConnectedPorts() > 0;
}
/**
* Extracts the last executed operator of the {@link ProcessRootOperator} of the provided
* process.
*
* @param process
* the process to extract the operator from
* @return the last executed child operator of the {@link ProcessRootOperator} or {@code null}
* if process contains no operators
*/
public static Operator getLastExecutedRootChild(final Process process) {
if (process == null) {
throw new IllegalArgumentException("process must not be null!");
}
List<Operator> enabledOps = process.getRootOperator().getSubprocess(0).getEnabledOperators();
return enabledOps.isEmpty() ? null : enabledOps.get(enabledOps.size() - 1);
}
/**
* Checks whether the given process contains at least one operator with a mandatory input port
* which is not connected. The port is then returned. If no such port can be found, returns
* {@code null}.
* <p>
* This method explicitly only checks for unconnected ports because metadata alone could lead to
* a false positive. That would prevent process execution, so a false positive has to be
* prevented under any circumstances.
* </p>
*
* @param process
* the process in question
* @return the first {@link Port} found if the process contains at least one operator with an
* input port which is not connected; {@code null} otherwise
*/
public static Port getPortWithoutMandatoryConnection(final Process process) {
if (process == null) {
throw new IllegalArgumentException("process must not be null!");
}
for (Operator op : process.getAllOperators()) {
// / if operator or one of its parents is disabled, we don't care
if (isSuperOperatorDisabled(op)) {
continue;
}
// look for matching errors. We can only identify this via metadata errors
for (ProcessSetupError error : op.getErrorList()) {
// the error list of an OperatorChain contains all errors of its children
// we only want errors for the current operator however, so skip otherwise
if (!op.equals(error.getOwner().getOperator())) {
continue;
}
if (error instanceof InputMissingMetaDataError) {
InputMissingMetaDataError err = (InputMissingMetaDataError) error;
// as we don't know what will be sent at runtime, we only look for unconnected
if (!err.getPort().isConnected()) {
return err.getPort();
}
}
}
}
// no port with missing input and no connection found
return null;
}
/**
* Checks whether the given operator or one of its suboperators has a mandatory input port which
* is not connected. The port is then returned. If no such port can be found, returns
* {@code null}.
* <p>
* This method explicitly only checks for unconnected ports because metadata alone could lead to
* a false positive.
* </p>
*
* @param operator
* the operator for which to check for unconnected mandatory ports
* @return the first {@link Port} found if the operator has at least one input port which is not
* connected; {@code null} otherwise
*/
public static Port getMissingPortConnection(Operator operator) {
// look for matching errors. We can only identify this via metadata errors
for (ProcessSetupError error : operator.getErrorList()) {
if (error instanceof InputMissingMetaDataError) {
InputMissingMetaDataError err = (InputMissingMetaDataError) error;
// as we don't know what will be sent at runtime, we only look for unconnected
if (!err.getPort().isConnected()) {
return err.getPort();
}
}
}
return null;
}
/**
* Checks whether the given process contains at least one operator with a mandatory parameter
* which has no value and no default value. Both the operator and the parameter are then
* returned. If no such operator can be found, returns {@code null}.
*
* @param process
* the process in question
* @return the first {@link Operator} found if the process contains at least one operator with a
* mandatory parameter which is neither set nor has a default value; {@code null}
* otherwise
*/
public static Pair<Operator, ParameterType> getOperatorWithoutMandatoryParameter(final Process process) {
if (process == null) {
throw new IllegalArgumentException("process must not be null!");
}
for (Operator op : process.getAllOperators()) {
// if operator or one of its parents is disabled, we don't care
if (isSuperOperatorDisabled(op)) {
continue;
}
// check all parameters and see if they have no value and are non optional
ParameterType param = getMissingMandatoryParameter(op);
if (param != null) {
return new Pair<>(op, param);
}
}
// no operator with missing mandatory parameter found
return null;
}
/**
* Checks whether the given operator or one of its sub-operators has a mandatory parameter which
* has no value and no default value. Both the operator and the parameter are then returned. If
* no such operator can be found, returns {@code null}.
*
* @param operator
* the operator in question
* @return the first {@link Operator} found if the operator or one of its sub-operators has a
* mandatory parameter which is neither set nor has a default value; {@code null}
* otherwise
*/
public static Pair<Operator, ParameterType> getOperatorWithoutMandatoryParameter(final Operator operator) {
if (operator == null) {
throw new IllegalArgumentException("operator must not be null!");
}
// check the operator first
ParameterType param = getMissingMandatoryParameter(operator);
if (param != null) {
return new Pair<>(operator, param);
}
// if it has children check them
if (operator instanceof OperatorChain) {
for (Operator op : ((OperatorChain) operator).getAllInnerOperators()) {
// if operator or one of its parents is disabled, we don't care
if (isSuperOperatorDisabled(op)) {
continue;
}
// check all parameters and see if they have no value and are non optional
param = getMissingMandatoryParameter(op);
if (param != null) {
return new Pair<>(op, param);
}
}
}
// no operator with missing mandatory parameter found
return null;
}
/**
* Checks whether the given operator has a mandatory parameter which has no value and no default
* value and returns the parameter. If no such parameter can be found, returns {@code null}.
*
* @param operator
* the operator in question
* @return the first mandatory parameter which is neither set nor has a default value;
* {@code null} otherwise
*/
private static ParameterType getMissingMandatoryParameter(Operator operator) {
for (String key : operator.getParameters().getKeys()) {
ParameterType param = operator.getParameterType(key);
if (!param.isOptional()) {
if (operator.getParameters().getParameterOrNull(key) == null) {
return param;
} else if (param instanceof ParameterTypeAttribute
&& "".equals(operator.getParameters().getParameterOrNull(key))) {
return param;
}
}
}
return null;
}
/**
* Recursively checks if the operator or one of its (grant) parents is disabled.
*
* @param operator
* the operator to check
* @return {@code true} if the operator or one of the operators it is contained in is disabled
*/
private static boolean isSuperOperatorDisabled(Operator operator) {
return !operator.isEnabled() || operator.getParent() != null && isSuperOperatorDisabled(operator.getParent());
}
}