/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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.Port; 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]; } } 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); fireUpdate(this); return subprocesses[index]; } protected ExecutionUnit createSubprocess(int index) { return new ExecutionUnit(this, "Subprocess"); } /** 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()"); //return operators.iterator(); } /** 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() { if (getParent() == null) { return super.isEnabled(); } else { return (super.isEnabled() && getParent().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) { String tree = super.createProcessTree(indent, selfPrefix, childPrefix, markOperator, mark); for (int i = 0; i < subprocesses.length; i++) { tree += Tools.getLineSeparator() + subprocesses[i].createProcessTree(indent, childPrefix + "+- ", childPrefix + ((i < subprocesses.length-1) ? "| " : " "), markOperator, mark); } return tree; } 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; } } public void walk(Visitor<Operator> visitor) { super.walk(visitor); for (ExecutionUnit unit : subprocesses) { for (Operator op: unit.getOperators()) { op.walk(visitor); } } } }