/*
* 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.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import com.rapidminer.Process;
import com.rapidminer.operator.execution.UnitExecutionFactory;
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.PortException;
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.CompatibilityLevel;
import com.rapidminer.operator.ports.metadata.OperatorLoopError;
import com.rapidminer.tools.AbstractObservable;
import com.rapidminer.tools.DelegatingObserver;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.Observer;
import com.rapidminer.tools.Tools;
/** A process is a collection of operators whose ports can be wired. A process
* provides input and output ports to its contained operators. ExecutionUnit replaces
* the legacy OperatorChain.
* This class takes care of executing the operators in the correct order by sorting
* them topologically with respect to their dependencies.
*
* @author Simon Fischer
*/
public class ExecutionUnit extends AbstractObservable<ExecutionUnit> {
private final PortOwner portOwner = new PortOwner() {
@Override public OperatorChain getPortHandler() { return getEnclosingOperator(); }
@Override public String getName() { return ExecutionUnit.this.getName(); }
@Override public Operator getOperator() { return getEnclosingOperator(); }
@Override public ExecutionUnit getConnectionContext() { return ExecutionUnit.this; }
};
private String name;
private final OperatorChain enclosingOperator;
private final InputPorts innerInputPorts = new InputPortsImpl(portOwner);
private final OutputPorts innerOutputPorts = new OutputPortsImpl(portOwner);
private Vector<Operator> operators = new Vector<Operator>();
private Vector<Operator> executionOrder;
private final Observer<Port> delegatingPortObserver = new DelegatingObserver<Port,ExecutionUnit>(this, this);
private final Observer<Operator> delegatingOperatorObserver = new DelegatingObserver<Operator,ExecutionUnit>(this, this);
// private final Observer<Port> portObserver = new Observer<Port>() {
// @Override public void update(Observable<Port> observable, Port arg) { clearAndUpdateMetaData(); }
// };
// private final Observer<Operator> operatorObserver = new Observer<Operator>() {
// @Override public void update(Observable<Operator> observable, Operator arg) { clearAndUpdateMetaData(); }
// };
public ExecutionUnit(OperatorChain enclosingOperator, String name) {
this.name = name;
this.enclosingOperator = enclosingOperator;
innerInputPorts.addObserver(delegatingPortObserver, false);
innerOutputPorts.addObserver(delegatingPortObserver, false);
//innerInputPorts.addObserver(portObserver, false);
//innerOutputPorts.addObserver(portObserver, false);
int index = 0;
do {
char c = name.charAt(index);
if (!(Character.isUpperCase(c) || Character.isDigit(c))) {
LogService.getRoot().warning("Process name does not follow naming conventions: "+name+" (in "+enclosingOperator.getOperatorDescription().getName()+")");
}
index = name.indexOf(' ', index) + 1;
} while (index != 0);
}
// private void clearAndUpdateMetaData() {
// //transformMDNeighbourhood();
// if (getEnclosingOperator() != null) {
// getEnclosingOperator().getRoot().clear(Port.CLEAR_METADATA | Port.CLEAR_META_DATA_ERRORS);
// getEnclosingOperator().getRoot().transformMetaData();
// } else {
// clear(Port.CLEAR_METADATA | Port.CLEAR_META_DATA_ERRORS);
// transformMetaData();
// }
// }
public InputPorts getInnerSinks() {
return innerInputPorts;
}
public OutputPorts getInnerSources() {
return innerOutputPorts;
}
/** Same as {@link #addOperator(Operator, boolean)}.
*/
public int addOperator(Operator operator) {
return addOperator(operator, true);
}
/** Adds the operator to this execution unit.
*
* @param registerWithProcess Typically true. If false, the operator will not be registered with its parent process.
* @return the new index of the operator.
*/
public int addOperator(Operator operator, boolean registerWithProcess) {
if (operator == null) {
throw new NullPointerException("operator cannot be null!");
}
if (operator instanceof ProcessRootOperator) {
throw new IllegalArgumentException("'Process' operator cannot be added. It must always be the top-level operator!");
}
operators.add(operator);
registerOperator(operator, registerWithProcess);
return operators.size() - 1;
}
/**
* Adds the operator to this execution unit. The operator at this index and
* all subsequent operators are shifted to the right. The operator is registered automatically.
*/
public void addOperator(Operator operator, int index) {
if (operator == null) {
throw new NullPointerException("operator cannot be null!");
}
if (operator instanceof ProcessRootOperator) {
throw new IllegalArgumentException("'Process' operator cannot be added. It must always be the top-level operator!");
}
operators.add(index, operator);
registerOperator(operator, true);
}
public int getIndexOfOperator(Operator operator) {
return operators.indexOf(operator);
}
private void registerOperator(Operator operator, boolean registerWithProcess) {
operator.setEnclosingProcess(this);
Process process = getEnclosingOperator().getProcess();
if ((process != null) && registerWithProcess) {
operator.registerOperator(process);
}
fireUpdate(this);
operator.addObserver(delegatingOperatorObserver, false);
operator.clear(Port.CLEAR_ALL);
if (process != null) {
process.fireOperatorAdded(operator);
}
}
private void unregister(Operator operator) {
operator.removeObserver(delegatingOperatorObserver);
//operator.removeObserver(operatorObserver);
}
/** Removes the given operator. Don't call this method directly but call
* {@link Operator#remove()}. */
protected void removeOperator(Operator operator) {
if (!operators.contains(operator)) {
throw new NoSuchElementException("Operator "+operator.getName() + " not contained in " + getName() + "!");
}
int oldIndex = operators.indexOf(operator);
int oldIndexAmongEnabled = getEnabledOperators().indexOf(operator);
operators.remove(operator);
unregister(operator);
//operator.disconnectPorts();
//transformMDNeighbourhood();
Process process = getEnclosingOperator().getProcess();
if (process != null) {
process.fireOperatorRemoved(operator, oldIndex, oldIndexAmongEnabled);
}
operator.setEnclosingProcess(null);
fireUpdate(this);
}
public void clear(int clearFlags) {
for (Operator operator : operators) {
operator.clear(clearFlags);
}
getInnerSinks().clear(clearFlags);
getInnerSources().clear(clearFlags);
}
/** Helper class to count the number of dependencies of an operator. */
private static class EdgeCounter {
private final Map<Operator,Integer> numIncomingEdges = new LinkedHashMap<Operator,Integer>();
private EdgeCounter(Collection<Operator> operators) {
for (Operator op : operators) {
numIncomingEdges.put(op, 0);
}
}
private void incNumEdges(Operator op) {
Integer num = numIncomingEdges.get(op);
if (num == null) {
// this can only happen if we add edges to inner ports of the enclosing operator.
return;
}
num = num + 1;
numIncomingEdges.put(op, num);
}
private int decNumEdges(Operator op) {
Integer num = numIncomingEdges.get(op);
// this can only happen if we add edges to inner ports of the enclosing operator.
if (num == null) {
return -1;
}
num = num - 1;
assert(num >= 0);
numIncomingEdges.put(op, num);
return num;
}
private LinkedList<Operator> getIndependentOperators() {
LinkedList<Operator> independentOperators = new LinkedList<Operator>();
for (Map.Entry<Operator,Integer> entry : numIncomingEdges.entrySet()) {
if (entry.getValue() == null || entry.getValue() == 0) {
independentOperators.add(entry.getKey());
}
}
return independentOperators;
}
}
/** Sorts the operators topologically, i.e. such that operator <var>i</var>
* in the returned ordering has dependencies (i.e. connected {@link InputPort}s) only
* from operators <var>0..i-1</var>. */
public Vector<Operator> topologicalSort() {
final Map<Operator,Integer> originalIndices = new HashMap<Operator,Integer>();
for (int i = 0; i < operators.size(); i++) {
originalIndices.put(operators.get(i), i);
}
EdgeCounter counter = new EdgeCounter(operators);
for (Operator child : getOperators()) {
for (OutputPort out : child.getOutputPorts().getAllPorts()) {
InputPort dest = out.getDestination();
if (dest != null) {
counter.incNumEdges(dest.getPorts().getOwner().getOperator());
}
}
}
Vector<Operator> sorted = new Vector<Operator>();
PriorityQueue<Operator> independentOperators = new PriorityQueue<Operator>(Math.max(1, operators.size()), new Comparator<Operator>() {
@Override
public int compare(Operator o1, Operator o2) {
return originalIndices.get(o1) - originalIndices.get(o2);
}
});
independentOperators.addAll(counter.getIndependentOperators());
while (!independentOperators.isEmpty()) {
Operator first = independentOperators.poll();
sorted.add(first);
for (OutputPort out : first.getOutputPorts().getAllPorts()) {
InputPort dest = out.getDestination();
if (dest != null) {
Operator destOp = dest.getPorts().getOwner().getOperator();
if (counter.decNumEdges(destOp) == 0) {
//independentOperators.addFirst(destOp);
independentOperators.add(destOp);
}
}
}
}
return sorted;
}
protected void updateExecutionOrder() {
this.executionOrder = topologicalSort();
if (!this.executionOrder.equals(operators)) {
if (operators.size() != executionOrder.size()) {
// we have a circle. without a check, operator vanishes.
return;
}
this.operators = this.executionOrder;
getEnclosingOperator().getProcess().fireExecutionOrderChanged(this);
}
for (Operator operator : this.operators) {
operator.updateExecutionOrder();
}
}
public void transformMetaData() {
List<Operator> sorted = topologicalSort();
for (Operator op : sorted) {
op.transformMetaData();
}
if (sorted.size() != operators.size()) {
List<Operator> remainder = new LinkedList<Operator>(operators);
remainder.removeAll(sorted);
for (Operator nodeInCircle : remainder) {
for (OutputPort outputPort : nodeInCircle.getOutputPorts().getAllPorts()) {
InputPort destination = outputPort.getDestination();
if ((destination != null) &&
remainder.contains(destination.getPorts().getOwner().getOperator())) {
if (destination.getSource() != null) {
// (source can be null *during* a disconnect in which case
// both the source and the destination fire an update
// which leads to this inconsistent state)
destination.addError(new OperatorLoopError(destination));
}
outputPort.addError(new OperatorLoopError(outputPort));
}
}
}
}
getInnerSinks().checkPreconditions();
}
/** Returns an unmodifiable view of the operators contained in this process. */
public List<Operator> getOperators() {
return Collections.unmodifiableList(operators);
// return operators;
}
/** Use this method only in cases where you are sure that you don't want a ConcurrentModificationException to occur
* when the list of operators is modified. */
public Enumeration<Operator> getOperatorEnumeration() {
return operators.elements();
}
/** Returns an unmodifiable view of the operators contained in this process. */
public List<Operator> getEnabledOperators() {
return new EnabledOperatorView(operators);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/** Returns the operator that contains this process as a subprocess. */
public OperatorChain getEnclosingOperator() {
return enclosingOperator;
}
private void unwire(boolean recursive) {
getInnerSources().disconnectAll();
for (Operator op : getOperators()) {
unwire(op, recursive);
}
}
private void unwire(Operator op, boolean recursive) throws PortException {
op.getOutputPorts().disconnectAll();
if (recursive) {
if (op instanceof OperatorChain) {
for (ExecutionUnit subprocess : ((OperatorChain) op).getSubprocesses()) {
subprocess.unwire(recursive);
}
}
}
}
@SuppressWarnings("deprecation")
private void autoWire(CompatibilityLevel level, InputPorts inputPorts, LinkedList<OutputPort> readyOutputs) throws PortException {
boolean success = false;
do {
Set<InputPort> complete = new HashSet<InputPort>();
for (InputPort in : inputPorts.getAllPorts()) {
success = false;
if (!in.isConnected() && !complete.contains(in) && in.getPorts().getOwner().getOperator().shouldAutoConnect(in)) {
Iterator<OutputPort> outIterator;
// TODO: Simon: Does the same in both cases. Check again.
if (in.simulatesStack()) {
outIterator = readyOutputs.descendingIterator();
} else {
outIterator = readyOutputs.descendingIterator();
}
while (outIterator.hasNext()) {
OutputPort outCandidate = outIterator.next();
// TODO: Remove shouldAutoConnect() in later versions
Operator owner = outCandidate.getPorts().getOwner().getOperator();
if (owner.shouldAutoConnect(outCandidate)) {
if (outCandidate.getMetaData() != null) {
if (in.isInputCompatible(outCandidate.getMetaData(), level)) {
readyOutputs.remove(outCandidate);
outCandidate.connectTo(in);
// we cannot continue with the remaining input ports
// since connecting may have triggered the creation of new input ports
// which would result in undefined behavior and a ConcurrentModificationException
success = true;
break;
}
}
}
}
// no port found.
complete.add(in);
if (success) {
break;
}
}
}
} while (success);
}
/** Transforms the meta data of the enclosing operator. Required in
* {@link #autoWire()} after each Operator that has been wired. */
private void transformMDNeighbourhood() {
//getEnclosingOperator().getTransformer().transformMetaData();
getEnclosingOperator().transformMetaData();
}
/** Connects the ports automatically in a first-fit approach. Operators
* are connected in their ordering within the {@link #operators} list.
* Every input of every operator is connected to the first compatible output
* of an operator "left" of this operator. This corresponds to the way,
* IOObjects were consumed in the pre-5.0 version. Disabled operators
* are skipped.
*
* <br/>
* @param level If level is {@link CompatibilityLevel#VERSION_5}, an input
* is considered compatible only if it satisfies all meta data
* constraints. For {@link CompatibilityLevel#PRE_VERSION_5} we
* only consider the classes.
*
* @param keepConnections if true, don't unwire old connections before rewiring.
* */
public void autoWire(CompatibilityLevel level, boolean keepConnections, boolean recursive) throws PortException {
if (!keepConnections) {
unwire(recursive);
}
// store all outputs. Scan them to find matching inputs.
LinkedList<OutputPort> readyOutputs = new LinkedList<OutputPort>();
addReadyOutputs(readyOutputs, getInnerSources());
List<Operator> enabled = new LinkedList<Operator>();
for (Operator op : getOperators()) {
if (op.isEnabled()) {
enabled.add(op);
}
}
autoWire(level, enabled, readyOutputs, recursive, true);
}
/**
* @param wireNew If true, OutputPorts of operators will be added to readyOutputs once they are wired.
*/
private void autoWire(CompatibilityLevel level, List<Operator> operators, LinkedList<OutputPort> readyOutputs, boolean recursive, boolean wireNew) throws PortException {
transformMDNeighbourhood();
for (Operator op : operators) {
try {
readyOutputs = op.preAutoWire(readyOutputs);
} catch (OperatorException e) {
getEnclosingOperator().getLogger().log(Level.WARNING, "During auto-wiring: "+e, e);
}
autoWire(level, op.getInputPorts(), readyOutputs);
transformMDNeighbourhood();
if (recursive) {
if (op instanceof OperatorChain) {
for (ExecutionUnit subprocess : ((OperatorChain)op).getSubprocesses()) {
// we have already removed all connections, so keepConnections=true in recursive call
subprocess.autoWire(level, true, recursive);
}
}
}
if (wireNew) {
addReadyOutputs(readyOutputs, op.getOutputPorts());
}
}
autoWire(level, getInnerSinks(), readyOutputs);
transformMDNeighbourhood();
}
/** Automatically wires inputs and outputs of a single operator in this execution unit.
*
* @param inputs Wire inputs?
* @param outputs Wire outputs?*/
public void autoWireSingle(Operator operator, CompatibilityLevel level, boolean inputs, boolean outputs) {
// auto wire inputs
if (inputs) {
transformMDNeighbourhood();
// store all outputs. Scan them to find matching inputs.
LinkedList<OutputPort> readyOutputs = new LinkedList<OutputPort>();
// add the ports, oldest first. Simulate pre-5.0-like stack by taking
// the last out of this list when consuming input.
addReadyOutputs(readyOutputs, getInnerSources());
boolean found = false;
for (Operator other : operators) {
if (other == operator) {
found = true;
break;
} else {
addReadyOutputs(readyOutputs, other.getOutputPorts());
}
}
if (!found) {
throw new IllegalArgumentException("Operator "+operator.getName() + " does not belong to this subprocess "+getName()+".");
}
getEnclosingOperator().getLogger().fine("Wiring: "+operator + "." + operator.getInputPorts().getAllPorts() + " to " + readyOutputs);
autoWire(level, operator.getInputPorts(), readyOutputs);
}
// auto wire outputs
if (outputs) {
LinkedList<OutputPort> readyOutputs = new LinkedList<OutputPort>();
addReadyOutputs(readyOutputs, operator.getOutputPorts());
List<Operator> successors = new LinkedList<Operator>();
boolean foundMe = false;
for (Operator other : getOperators()) {
if (foundMe) {
successors.add(other);
} else if (other == operator) {
foundMe = true;
}
}
autoWire(level, successors, readyOutputs, false, false);
}
}
private void addReadyOutputs(LinkedList<OutputPort> readyOutputs, OutputPorts ports) {
// add the parameters in a stack-like fashion like in pre-5.0
//Iterator<OutputPort> i = ports.getAllPorts().iterator();
Iterator<OutputPort> i = new LinkedList<OutputPort>(ports.getAllPorts()).descendingIterator();
while (i.hasNext()) {
OutputPort port = i.next();
if (!port.isConnected() && port.shouldAutoConnect()) {
readyOutputs.addLast(port);
}
}
}
/** Returns a list of all available output ports within this process, including
* inner sources and output ports of enclosed operators. */
public Collection<OutputPort> getAllOutputPorts() {
Collection<OutputPort> outputPorts = new LinkedList<OutputPort>();
outputPorts.addAll(getInnerSources().getAllPorts());
for (Operator operator : operators) {
outputPorts.addAll(operator.getOutputPorts().getAllPorts());
}
return outputPorts;
}
public Operator getOperatorByName(String toOp) {
for (Operator op : operators) {
if (op.getName().equals(toOp)) {
return op;
}
}
return null;
}
public int getNumberOfOperators() {
return operators.size();
}
/** Clones operators contained in <code>original</code>, adds them
* to this execution unit and wires them as they were originally.
*
* @param forParallelExecution Indicates whether this clone is supposed
* to be executed in parallel. If yes, the clone will not be
* registered with the parent process and will share its
* {@link Operator#applyCount} with the original.
*/
public void cloneExecutionUnitFrom(ExecutionUnit original, boolean forParallelExecution) {
// Clone operators
Map<String,Operator> clonedOperatorsByName = new HashMap<String,Operator>();
for (Operator originalChild : original.operators) {
Operator clonedOperator = originalChild.cloneOperator(originalChild.getName(), forParallelExecution);
addOperator(clonedOperator, !forParallelExecution);
clonedOperatorsByName.put(originalChild.getName(), clonedOperator);
}
// Restore connections
cloneConnections(original.getInnerSources(), original, clonedOperatorsByName);
for (Operator op : original.operators) {
cloneConnections(op.getOutputPorts(), original, clonedOperatorsByName);
}
// Unlock
original.getInnerSources().unlockPortExtenders();
original.getInnerSinks().unlockPortExtenders();
for (Operator op : this.operators) {
op.getInputPorts().unlockPortExtenders();
op.getOutputPorts().unlockPortExtenders();
}
// Other:
this.expanded = original.expanded;
}
private void cloneConnections(OutputPorts originalPorts, ExecutionUnit originalExecutionUnit, Map<String,Operator> clonedOperatorsByName) {
for (OutputPort originalSource : originalPorts.getAllPorts()) {
if (originalSource.isConnected()) {
OutputPort mySource;
if (originalPorts.getOwner().getOperator() == originalExecutionUnit.getEnclosingOperator()) {
// this is an inner source
mySource = getInnerSources().getPortByName(originalSource.getName());
if (mySource== null) {
throw new RuntimeException("Error during clone: Corresponding source for "+originalSource+" not found (no such inner source).");
}
} else {
// this is an output port
Operator myOperator = clonedOperatorsByName.get(originalSource.getPorts().getOwner().getOperator().getName());
if (myOperator == null) {
throw new RuntimeException("Error during clone: Corresponding source for "+originalSource +" not found (no such operator).");
}
mySource = myOperator.getOutputPorts().getPortByName(originalSource.getName());
if (mySource == null) {
throw new RuntimeException("Error during clone: Corresponding source for "+originalSource+" not found (no such output port).");
}
}
InputPort originalDestination = originalSource.getDestination();
InputPort myDestination;
if (originalDestination.getPorts().getOwner().getOperator() == originalExecutionUnit.getEnclosingOperator()) {
// this is an inner sink
myDestination = getInnerSinks().getPortByName(originalDestination.getName());
if (myDestination == null) {
throw new RuntimeException("Error during clone: Corresponding destination for "+originalDestination+" not found (no such inner sink).");
}
} else {
// this is an input port
Operator myOperator = clonedOperatorsByName.get(originalDestination.getPorts().getOwner().getOperator().getName());
if (myOperator == null) {
throw new RuntimeException("Error during clone: Corresponding destination for "+originalDestination +" not found (no such operator).");
}
myDestination = myOperator.getInputPorts().getPortByName(originalDestination.getName());
if (myDestination == null) {
throw new RuntimeException("Error during clone: Corresponding destination for "+originalDestination+" not found (no such input port).");
}
}
mySource.connectTo(myDestination);
}
}
}
/** Returns all nested operators. */
public Collection<Operator> getChildOperators() {
List<Operator> children = new LinkedList<Operator>();
for (Operator operator : operators) {
children.add(operator);
}
return children;
}
/** Recursively returns all nested operators. */
public List<Operator> getAllInnerOperators() {
List<Operator> children = new LinkedList<Operator>();
for (Operator operator : operators) {
children.add(operator);
if (operator instanceof OperatorChain) {
children.addAll(((OperatorChain)operator).getAllInnerOperators());
}
}
return children;
}
protected String createProcessTree(int indent, String selfPrefix, String childPrefix, Operator markOperator, String mark) {
String tree = Tools.indent(indent) + " subprocess '"+getName()+"'";
Iterator<Operator> i = operators.iterator();
while (i.hasNext()) {
tree += Tools.getLineSeparator() + i.next().createProcessTree(indent, childPrefix + "+- ", childPrefix + (i.hasNext() ? "| " : " "), markOperator, mark);
}
return tree;
}
/** Executes the inner operators. */
public void execute() throws OperatorException {
// Logger logger = getEnclosingOperator().getLogger();
// if (logger.isLoggable(Level.FINE)) {
// getEnclosingOperator().getLogger().fine("Executing subprocess "+getEnclosingOperator().getName()+"."+getName()+". Execution order is: "+executionOrder);
// }
// for (Operator operator : executionOrder) {
// operator.execute();
// operator.freeMemory();
// }
UnitExecutionFactory.getInstance().getExecutor(this).execute(this);
}
/** Frees memory used by inner sinks. */
public void freeMemory() {
getInnerSources().freeMemory();
getInnerSinks().freeMemory();
}
private boolean expanded = true;
/** Sets the expansion mode which indicates if this operator is drawn expanded or not. */
public void setExpanded(boolean expanded) { this.expanded = expanded; }
/** Returns true if this operator should be painted expanded. */
public boolean isExpanded() { return expanded; }
public void processStarts() throws OperatorException {
for (Operator operator : operators) {
operator.processStarts();
}
executionOrder = topologicalSort();
}
public void processFinished() throws OperatorException {
for (Operator operator : operators) {
operator.processFinished();
}
}
/** Moves the operators from this process to another process, keeping all connections intact.
* TODO: Test more rigorously. Do we register/unregister everything correctly?
* @return the number of ports the connections of which could not be restored */
public int stealOperatorsFrom(ExecutionUnit otherUnit) {
int failedReconnects = 0;
// remember source and sink connections so we can reconnect them later.
Map<String,InputPort> sourceMap = new HashMap<String,InputPort>();
Map<String,OutputPort> sinkMap = new HashMap<String,OutputPort>();
for (OutputPort source : otherUnit.getInnerSources().getAllPorts()) {
if (source.isConnected()) {
sourceMap.put(source.getName(), source.getDestination());
}
}
otherUnit.getInnerSources().disconnectAll();
for (InputPort sink : otherUnit.getInnerSinks().getAllPorts()) {
if (sink.isConnected()) {
sinkMap.put(sink.getName(), sink.getSource());
}
}
otherUnit.getInnerSinks().disconnectAll();
// Move operators
Iterator<Operator> i = otherUnit.operators.iterator();
while (i.hasNext()) {
Operator operator = i.next();
i.remove();
otherUnit.unregister(operator);
Process otherProcess = operator.getProcess();
if (otherProcess != null) {
operator.unregisterOperator(otherProcess);
}
this.operators.add(operator);
operator.setEnclosingProcess(null);
//operator.unregisterOperator(operator.getProcess());
registerOperator(operator, true);
//operator.registerOperator(this.getEnclosingOperator().getProcess());
}
// Rewire sources and sinks
for (Map.Entry<String,InputPort> entry : sourceMap.entrySet()) {
OutputPort mySource = getInnerSources().getPortByName(entry.getKey());
if (mySource != null) {
mySource.connectTo(entry.getValue());
} else {
failedReconnects++;
}
}
getInnerSources().unlockPortExtenders();
for (Map.Entry<String,OutputPort> entry : sinkMap.entrySet()) {
InputPort mySink = getInnerSinks().getPortByName(entry.getKey());
if (mySink != null) {
entry.getValue().connectTo(mySink);
} else {
failedReconnects++;
}
}
getInnerSinks().unlockPortExtenders();
fireUpdate(this);
return failedReconnects;
}
/** Moves an operator to the given index. (If the old index is smaller
* than the new one, the new one will automatically be reduced by one.) */
public void moveToIndex(Operator op, int newIndex) {
int oldIndex = operators.indexOf(op);
Process process = getEnclosingOperator().getProcess();
if (oldIndex != -1) {
operators.remove(op);
if (process != null) {
int oldIndexAmongEnabled = getEnabledOperators().indexOf(op);
process.fireOperatorRemoved(op, oldIndex, oldIndexAmongEnabled);
}
if (oldIndex < newIndex) {
newIndex--;
}
operators.add(newIndex, op);
if (process != null) {
process.fireOperatorAdded(op);
}
fireUpdate();
updateExecutionOrder();
}
}
/** Re-arranges the execution order such that the specified operators immediately follow <code>insertAfter</code>. */
public void bringToFront(Collection<Operator> movedOperators, Operator insertAfter) {
this.operators.removeAll(movedOperators);
int index = this.operators.indexOf(insertAfter) + 1;
for (Operator op : movedOperators) {
this.operators.add(index++, op);
}
updateExecutionOrder();
fireUpdate();
}
}