/** * 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; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.rapidminer.Process; import com.rapidminer.operator.ports.InputPort; import com.rapidminer.operator.ports.InputPorts; import com.rapidminer.operator.ports.OutputPort; import com.rapidminer.operator.ports.OutputPorts; import com.rapidminer.operator.ports.Port; import com.rapidminer.operator.ports.PortOwner; import com.rapidminer.operator.ports.impl.InputPortsImpl; import com.rapidminer.operator.ports.impl.OutputPortsImpl; import com.rapidminer.operator.ports.metadata.MDTransformer; import com.rapidminer.operator.ports.metadata.Precondition; import com.rapidminer.tools.DelegatingObserver; import com.rapidminer.tools.Observer; import com.rapidminer.tools.Tools; import com.rapidminer.tools.patterns.Visitor; /** * An OperatorChain is an Operator that contains children which are again Operators and which it can * execute once ore several times during its own execution.<br/> * * As of RapidMiner 5.0, an OperatorChain does not directly contain nested Operators, but rather * nested {@link ExecutionUnit}s which in turn contain operators. * * Please refer to the RapidMiner tutorial for a description how to implement your own operator * chain. * * @author Simon Fischer, Ingo Mierswa */ public abstract class OperatorChain extends Operator { private ExecutionUnit[] subprocesses; private final Observer<ExecutionUnit> delegatingObserver = new DelegatingObserver<ExecutionUnit, Operator>(this, this); /** * Creates an empty operator chain. * * @deprecated Use OpertorChain(OperatorDescription, String...) to assign names to subprocesses. */ @Deprecated public OperatorChain(OperatorDescription description) { this(description, new String[0]); } public OperatorChain(OperatorDescription description, String... subprocessNames) { super(description); subprocesses = new ExecutionUnit[subprocessNames.length]; for (int i = 0; i < subprocesses.length; i++) { subprocesses[i] = new ExecutionUnit(this, subprocessNames[i]); subprocesses[i].addObserver(delegatingObserver, false); makeDirtyOnUpdate(subprocesses[i].getInnerSinks()); } } /** * Indicates whether or not the GUI may offer an option to dynamically add to the number of * subprocesses. The default implementation returns false. */ public boolean areSubprocessesExtendable() { return false; } public ExecutionUnit removeSubprocess(int index) { ExecutionUnit deleted = subprocesses[index]; ExecutionUnit[] copy = subprocesses; subprocesses = new ExecutionUnit[copy.length - 1]; int j = 0; for (int i = 0; i < copy.length; i++) { if (i != index) { subprocesses[j++] = copy[i]; } } deleted.removeObserver(delegatingObserver); fireUpdate(this); return deleted; } /** Creates a subprocess by making a callback to {@link createSubprocess(int)}. */ public ExecutionUnit addSubprocess(int index) { ExecutionUnit[] copy = subprocesses; subprocesses = new ExecutionUnit[copy.length + 1]; int j = 0; for (int i = 0; i < copy.length; i++) { if (i == index) { j++; } subprocesses[j++] = copy[i]; } subprocesses[index] = createSubprocess(index); subprocesses[index].addObserver(delegatingObserver, false); fireUpdate(this); return subprocesses[index]; } protected ExecutionUnit createSubprocess(int index) { return new ExecutionUnit(this, "Subprocess"); } /** * This method returns an arbitrary implementation of {@link InputPorts} for inner sink port * initialization. Useful for adding an arbitrary implementation (e.g. changing port creation & * (dis)connection behavior, optionally by customized {@link InputPort} instances) by overriding * this method. * * @param portOwner * The owner of the ports. * @return The {@link InputPorts} instance, never {@code null}. * @since 7.3.0 */ protected InputPorts createInnerSinks(PortOwner portOwner) { return new InputPortsImpl(portOwner); } /** * This method returns an arbitrary implementation of {@link OutputPorts} for inner source port * initialization. Useful for adding an arbitrary implementation (e.g. changing port creation & * (dis)connection behavior, optionally by customized {@link OutputPort} instances) by * overriding this method. * * @param portOwner * The owner of the ports. * @return The {@link OutputPorts} instance, never {@code null}. * @since 7.3.0 */ protected OutputPorts createInnerSources(PortOwner portOwner) { return new OutputPortsImpl(portOwner); } /** * Returns the maximum number of inner operators. * * @deprecated Use subprocesses instead. */ @Deprecated public int getMaxNumberOfInnerOperators() { return 0; } /** * Returns the minimum number of inner operators. * @deprecated Use subprocesses instead. */ @Deprecated public int getMinNumberOfInnerOperators() { return 0; } /** * Must return a condition of the IO behaviour of all desired inner operators. * * @deprecated specify input and output ports instead. */ @Deprecated public com.rapidminer.operator.condition.InnerOperatorCondition getInnerOperatorCondition() { return null; } /** * Performs a deep clone of this operator chain. Use this method only if you are sure what you * are doing. */ @Override public Operator cloneOperator(String name, boolean forParallelExcecution) { OperatorChain clone = (OperatorChain) super.cloneOperator(name, forParallelExcecution); if (areSubprocessesExtendable()) { while (clone.getNumberOfSubprocesses() < getNumberOfSubprocesses()) { clone.addSubprocess(clone.getNumberOfSubprocesses()); } } for (int i = 0; i < subprocesses.length; i++) { clone.subprocesses[i].cloneExecutionUnitFrom(this.subprocesses[i], forParallelExcecution); } return clone; } /** * This method checks if inner operators can handle their input and deliver the necessary * output. Depending on the return value of the method {@link #shouldReturnInnerOutput()} this * method returns * <ul> * <li>the result of <code>getDeliveredOutputClasses()</code> if the output of the inner * operators should not be returned.</li> * <li>the result of <code>getAllOutputClasses(Class[] innerOutput)</code> if the output of the * inner operators (innerOutput) should also be returned.</li> * </ul> * * @deprecated As of RM, checkIO is replaced by the {@link MDTransformer}. */ @Override @Deprecated public Class<?>[] checkIO(Class<?>[] input) throws IllegalInputException, WrongNumberOfInnerOperatorsException { getLogger().warning("As of RM 5.0, checkIO() is no longer necessary."); return input; } /** * Indicates if inner output should be delivered by this operator chain. Default is false. * Operators which want to change this default behaviour should override this method and should * return true. In this case the method checkIO would not longer return the result of * {@link #getDeliveredOutputClasses()} but of {@link #getAllOutputClasses(Class[])}. * * @deprecated As of 5.0, this method is no longer necessary. */ @Deprecated protected boolean shouldReturnInnerOutput() { return false; } protected boolean shouldAddNonConsumedInput() { return !shouldReturnInnerOutput(); } /** * Adds a new inner operator at the last position. The returned index is the position of the * added operator with respect to all operators (including the disabled operators). */ @Deprecated public final int addOperator(Operator o) { for (ExecutionUnit process : subprocesses) { if (process.getNumberOfOperators() == 0) { process.addOperator(o); getLogger().warning( "OperatorChain.addOperator() is deprecated! Use getSubprocess(int).addOperator(). I have added the operator to subprocess " + process.getName()); } } throw new UnsupportedOperationException( "addOperator() is no longer supported. Failed to guess which subprocess was intended. Try getSubprocess(int).addOperator()"); } /** * Adds the given operator at the given position. Please note that all operators (including the * disabled operators) are used for position calculations. */ public final int addOperator(Operator operator, int index) { if (index < subprocesses.length) { subprocesses[index].addOperator(operator); getLogger().warning( "OperatorChain.addOperator() is deprecated! Use getSubprocess(int).addOperator(). I have added the operator to subprocess " + subprocesses[index].getName()); return index; } else { throw new UnsupportedOperationException( "addOperator() is no longer supported. Try getSubprocess(int).addOperator()"); } } /** * Register this operator chain and all of its children in the given process. This might change * the name of the operator. */ @Override protected void registerOperator(Process process) { super.registerOperator(process); for (ExecutionUnit subprocess : subprocesses) { for (Operator child : subprocess.getOperators()) { child.registerOperator(process); } } } /** Unregisters this chain and all of its children from the given process. */ @Override protected void unregisterOperator(Process process) { super.unregisterOperator(process); for (ExecutionUnit subprocess : subprocesses) { for (Operator child : subprocess.getOperators()) { child.unregisterOperator(process); } } } /** * Removes the given operator from this operator chain. Do not use this method to actually * remove an operator from an operator chain. Use operator.remove() instead. This method will be * invoked by the remove() method (which also performs some other actions). */ @Deprecated protected final void removeOperator(Operator operator) { throw new UnsupportedOperationException("removeOperator is deprecated. Use getSubprocess(int).removeOperator()"); } /** Returns the i-th inner operator. */ @Deprecated public Operator getOperator(int i) { throw new UnsupportedOperationException("getOperator(int) is deprecated. Try getSubprocess(int)."); } /** Returns an iterator over all Operators. */ @Deprecated public Iterator<Operator> getOperators() { throw new UnsupportedOperationException( "OperatorChain.getNumberOfOperators() is deprecated. Try getSubprocesses(int).getOperators()"); } /** Returns all operators contained in the subprocesses of this chain (non-recursive). */ public List<Operator> getImmediateChildren() { List<Operator> children = new LinkedList<Operator>(); for (ExecutionUnit executionUnit : subprocesses) { children.addAll(executionUnit.getOperators()); } return children; } /** Returns recursively all child operators independently if they are activated or not. */ public List<Operator> getAllInnerOperators() { List<Operator> children = new LinkedList<Operator>(); for (ExecutionUnit executionUnit : subprocesses) { children.addAll(executionUnit.getAllInnerOperators()); } return children; } public List<Operator> getAllInnerOperatorsAndMe() { List<Operator> children = getAllInnerOperators(); children.add(this); return children; } /** * As a workaround, returns the number of subprocesses. * * @deprecated as of RM replaced by subprocesses. */ @Deprecated public int getNumberOfOperators() { return subprocesses.length; } /** * Returns the number of all inner operators (including the disabled operators). Mainly used for * GUI purposes. Operators should use {@link #getNumberOfOperators()}. * * @deprecated Try getSubprocess(int).getNumberOfOperators() */ @Deprecated public int getNumberOfAllOperators() { return subprocesses.length; } /** * Returns the i-th operator. In contrast to the method {@link #getOperator(int i)} this method * also uses disabled operators. Mainly used for GUI purposes. Other operators should use the * method {@link #getOperator(int i)} which only delivers enabled inner operators. */ @Deprecated public Operator getOperatorFromAll(int i) { throw new UnsupportedOperationException( "OperatorChain.getOperatorFromAll(int) is deprecated. Try getSubprocess(int).getOperators()"); } /** * Returns the index of the given operator in the list of children. If useDisabled is true, * disabled operators are also used for index calculations. */ @Deprecated public int getIndexOfOperator(Operator operator, boolean useDisabled) { throw new UnsupportedOperationException( "OperatorChain.getOperatorFromAll(int) is deprecated. Try getSubprocess(int).getOperators()"); } /** * Returns the result of the super method if this operator does not have a parent. Otherwise * this method returns true if it is enabled and the parent is also enabled. */ @Override public boolean isEnabled() { return super.isEnabled(); } /** Invokes the super method and the method for all children. */ @Override public void processStarts() throws OperatorException { super.processStarts(); for (ExecutionUnit unit : subprocesses) { unit.processStarts(); } } /** Invokes the super method and the method for all children. */ @Override public void processFinished() throws OperatorException { super.processFinished(); for (ExecutionUnit unit : subprocesses) { unit.processFinished(); } } // -------------------- implemented abstract methods /** * Clears all sinks of all inner processes */ protected void clearAllInnerSinks() { for (ExecutionUnit subprocess : subprocesses) { subprocess.getInnerSinks().clear(Port.CLEAR_DATA); } } @Override public void doWork() throws OperatorException { for (ExecutionUnit subprocess : subprocesses) { subprocess.execute(); } } @Override public void freeMemory() { super.freeMemory(); for (ExecutionUnit unit : subprocesses) { unit.freeMemory(); } } // -------------------------------------------------------------------------------- /** * This method invokes the additional check method for each child. Subclasses which override * this method to perform a check should also invoke super.performAdditionalChecks()! */ @Override protected void performAdditionalChecks() { super.performAdditionalChecks(); for (ExecutionUnit subprocess : subprocesses) { for (Operator o : subprocess.getOperators()) { if (o.isEnabled()) { o.performAdditionalChecks(); } } } } /** * Will throw an exception if a non optional property has no default value and is not defined by * user. */ @Override public int checkProperties() { int errorCount = super.checkProperties(); for (ExecutionUnit subprocess : subprocesses) { for (Operator o : subprocess.getOperators()) { if (o.isEnabled()) { errorCount += o.checkProperties(); } } } return errorCount; } /** * Will count an the number of deprecated operators, i.e. the operators which * {@link #getDeprecationInfo()} method does not return null. Returns the total number of * deprecations. */ @Override public int checkDeprecations() { int deprecationCount = super.checkDeprecations(); for (ExecutionUnit subprocess : subprocesses) { for (Operator o : subprocess.getOperators()) { deprecationCount += o.checkDeprecations(); } } return deprecationCount; } /** * Checks if the number of inner operators lies between MinInnerOps and MaxInnerOps. Performs * the check for all operator chains which are children of this operator chain. * * @deprecated As of RM, this is implicit in the subprocesses. */ @Deprecated public int checkNumberOfInnerOperators() { return 0; } /** * Returns this OperatorChain's name and class and the process trees of the inner operators. */ @Override protected String createProcessTree(int indent, String selfPrefix, String childPrefix, Operator markOperator, String mark) { StringBuilder treeBuilder = new StringBuilder(super.createProcessTree(indent, selfPrefix, childPrefix, markOperator, mark)); for (int i = 0; i < subprocesses.length; i++) { List<String> processTreeList = subprocesses[i].createProcessTreeList(indent, childPrefix + "+- ", childPrefix + (i < subprocesses.length - 1 ? "| " : " "), markOperator, mark); for (String entry : processTreeList) { treeBuilder.append(Tools.getLineSeparator()); treeBuilder.append(entry); } } return treeBuilder.toString(); } @Override public List<String> createProcessTreeList(int indent, String selfPrefix, String childPrefix, Operator markOperator, String mark) { List<String> treeList = super.createProcessTreeList(indent, selfPrefix, childPrefix, markOperator, mark); for (int i = 0; i < subprocesses.length; i++) { treeList.addAll(subprocesses[i].createProcessTreeList(indent, childPrefix + "+- ", childPrefix + (i < subprocesses.length - 1 ? "| " : " "), markOperator, mark)); } return treeList; } public ExecutionUnit getSubprocess(int index) { return subprocesses[index]; } public int getNumberOfSubprocesses() { return subprocesses.length; } /** Returns an immutable view of all subprocesses */ public List<ExecutionUnit> getSubprocesses() { return Arrays.asList(subprocesses); } @Override protected void collectErrors(List<ProcessSetupError> errors) { super.collectErrors(errors); for (ExecutionUnit executionUnit : subprocesses) { for (Operator op : executionUnit.getOperators()) { op.collectErrors(errors); } for (Port port : executionUnit.getInnerSinks().getAllPorts()) { errors.addAll(port.getErrors()); } for (Port port : executionUnit.getInnerSources().getAllPorts()) { errors.addAll(port.getErrors()); } } } @Override public void clear(int clearFlags) { super.clear(clearFlags); for (ExecutionUnit executionUnit : subprocesses) { executionUnit.clear(clearFlags); } } @Override public void assumePreconditionsSatisfied() { super.assumePreconditionsSatisfied(); for (ExecutionUnit executionUnit : subprocesses) { for (InputPort inputPort : executionUnit.getInnerSinks().getAllPorts()) { for (Precondition precondition : inputPort.getAllPreconditions()) { precondition.assumeSatisfied(); } } } } @Override public void notifyRenaming(String oldName, String newName) { for (ExecutionUnit subprocess : subprocesses) { for (Operator child : subprocess.getOperators()) { child.notifyRenaming(oldName, newName); } } getParameters().notifyRenaming(oldName, newName); } @Override protected void propagateDirtyness() { for (ExecutionUnit unit : subprocesses) { for (Operator op : unit.getOperators()) { op.propagateDirtyness(); } } } @Override public void updateExecutionOrder() { for (ExecutionUnit unit : subprocesses) { unit.updateExecutionOrder(); } } @Override protected Operator lookupOperator(String operatorName) { Operator result = super.lookupOperator(operatorName); if (result != null) { return result; } else { for (Operator child : getAllInnerOperators()) { if (child.getName().equals(operatorName)) { return child; } } return null; } } @Override public void walk(Visitor<Operator> visitor) { super.walk(visitor); for (ExecutionUnit unit : subprocesses) { for (Operator op : unit.getOperators()) { op.walk(visitor); } } } }