/**
* 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.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
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.TreeMap;
import java.util.Vector;
import java.util.logging.Level;
import com.rapidminer.Process;
import com.rapidminer.operator.execution.UnitExecutionFactory;
import com.rapidminer.operator.execution.UnitExecutor;
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.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;
private final OutputPorts innerOutputPorts;
private Vector<Operator> operators = new Vector<Operator>();
private Vector<Operator> executionOrder;
/**
* Container for user data.
*/
private Map<String, UserData<Object>> userData;
/**
* Lock object for user data container.
*/
private final Object userDataLock = new Object();
private final Observer<Port> delegatingPortObserver = new DelegatingObserver<Port, ExecutionUnit>(this, this);
private final Observer<Operator> delegatingOperatorObserver = new DelegatingObserver<Operator, ExecutionUnit>(this,
this);
public ExecutionUnit(OperatorChain enclosingOperator, String name) {
this.name = name;
innerInputPorts = enclosingOperator.createInnerSinks(portOwner);
innerOutputPorts = enclosingOperator.createInnerSources(portOwner);
this.enclosingOperator = enclosingOperator;
innerInputPorts.addObserver(delegatingPortObserver, false);
innerOutputPorts.addObserver(delegatingPortObserver, 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()+")");
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.operator.ExecutionUnit.process_name_does_not_follow_name_conventions",
new Object[] { name, enclosingOperator.getOperatorDescription().getName() });
}
index = name.indexOf(' ', index) + 1;
} while (index != 0);
}
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);
}
/**
* Looks up {@link UserData} entries. Returns null if key is unknown.
*
* @param The
* key of the user data.
* @return The user data.
*/
public UserData<Object> getUserData(String key) {
synchronized (this.userDataLock) {
if (this.userData == null) {
return null;
} else {
return this.userData.get(key);
}
}
}
/**
* Stores arbitrary {@link UserData}.
*
* @param key
* The key to used to identify the data.
* @param data
* The user data.
*/
public void setUserData(String key, UserData<Object> data) {
synchronized (this.userDataLock) {
if (this.userData == null) {
this.userData = new TreeMap<>();
}
this.userData.put(key, data);
}
}
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);
}
/**
* 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);
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(new ArrayList<>(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().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 = 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();
}
// copy user data entries
if (original.userData != null) {
for (String key : original.userData.keySet()) {
UserData<Object> data = original.userData.get(key);
if (data != null) {
setUserData(key, data.copyUserData(this));
}
}
}
// 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 List<String> createProcessTreeList(int indent, String selfPrefix, String childPrefix, Operator markOperator,
String mark) {
List<String> treeList = new LinkedList<>();
treeList.add(Tools.indent(indent) + " subprocess '" + getName() + "'");
Iterator<Operator> i = operators.iterator();
while (i.hasNext()) {
treeList.addAll(i.next().createProcessTreeList(indent, childPrefix + "+- ",
childPrefix + (i.hasNext() ? "| " : " "), markOperator, mark));
}
return treeList;
}
/** Executes the inner operators. */
public void execute() throws OperatorException {
UnitExecutor executor = UnitExecutionFactory.getInstance().getExecutor(this);
// check only the callstack of nested operators, otherwise execution units of
// unsigned extensions might not be able to execute trusted operators
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws OperatorException {
executor.execute(ExecutionUnit.this);
return null;
}
});
} catch (PrivilegedActionException e) {
// e.getException() can either be an instance of OperatorException,
// or an unsafe Exception,
// as only checked exceptions will be wrapped in a
// PrivilegedActionException.
if (e.getException() instanceof OperatorException) {
throw (OperatorException) e.getException();
} else {
// Wrap unsafe Exceptions
throw new OperatorException(e.getException().getMessage(), e.getException());
}
}
}
/** 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();
}
updateExecutionOrder();
}
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);
registerOperator(operator, true);
}
// 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();
}
}