/*
* 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.ports;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.ports.metadata.MDTransformationRule;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.SimplePrecondition;
import com.rapidminer.tools.Observable;
import com.rapidminer.tools.Observer;
/**
* This class observes a set of input and output ports and adds additional ports as needed. Operators probably want to
* connect these ports by a {@link com.rapidminer.operator.ports.metadata.ManyToManyPassThroughRule}. It guarantees that
* there is always exactly one pair of in and output pairs which is not connected.
*
* @see PortPairExtender
* @see MultiPortPairExtender
*
* @author Simon Fischer
*/
public class PortPairExtender implements PortExtender {
private final String name;
private final InputPorts inPorts;
private final OutputPorts outPorts;
private final List<PortPair> managedPairs = new LinkedList<PortPair>();
/** If non null, add this meta data as a SimplePrecondition to each generated input port. */
private final MetaData preconditionMetaData;
private boolean isChanging = false;
private int runningId = 0;
private final Observer<Port> observer = new Observer<Port>() {
@Override
public void update(Observable<Port> observable, Port arg) {
updatePorts();
}
};
private int minNumber = 0;
/** A pair of ports managed by a PortPairExtender. */
public static class PortPair {
private final InputPort inputPort;
private final OutputPort outputPort;
private PortPair(InputPort inputPort, OutputPort outputPort) {
this.inputPort = inputPort;
this.outputPort = outputPort;
}
public InputPort getInputPort() {
return inputPort;
}
public OutputPort getOutputPort() {
return outputPort;
}
}
public PortPairExtender(String name, InputPorts inPorts, OutputPorts outPorts) {
this(name, inPorts, outPorts, null);
}
/**
* Creates a new port pair extender
*
* @param name
* The name prefix for all generated ports.
* @param inPorts
* Add generated input ports to these InputPorts
* @param outPorts
* Add generated output ports to these OutputPorts
* @param preconditionMetaData
* If non-null, create a SimplePrecondition for each newly generated input port.
*/
public PortPairExtender(String name, InputPorts inPorts, OutputPorts outPorts, MetaData preconditionMetaData) {
this.name = name;
this.inPorts = inPorts;
this.outPorts = outPorts;
this.preconditionMetaData = preconditionMetaData;
inPorts.registerPortExtender(this);
outPorts.registerPortExtender(this);
}
private void updatePorts() {
if (!isChanging) {
isChanging = true;
boolean first = true;
PortPair foundDisconnected = null;
Iterator<PortPair> i = managedPairs.iterator();
while (i.hasNext()) {
PortPair pair = i.next();
if (!pair.inputPort.isConnected() && !pair.inputPort.isLocked() && !pair.outputPort.isConnected() && !pair.outputPort.isLocked()) {
// we don't remove the first disconnected port.
if (first) {
first = false;
foundDisconnected = pair;
} else {
if (minNumber == 0) {
deletePorts(pair);
i.remove();
}
}
}
}
if ((foundDisconnected == null) || (managedPairs.size() < minNumber)) {
do {
managedPairs.add(createPort());
} while (managedPairs.size() < minNumber);
} else {
if (minNumber == 0) {
managedPairs.remove(foundDisconnected);
managedPairs.add(foundDisconnected);
inPorts.pushDown(foundDisconnected.getInputPort());
outPorts.pushDown(foundDisconnected.getOutputPort());
}
}
fixNames();
isChanging = false;
}
}
/** Creates an initial port and starts to listen. */
public void start() {
managedPairs.add(createPort());
fixNames();
inPorts.addObserver(observer, false);
outPorts.addObserver(observer, false);
}
private PortPair createPort() {
runningId++;
InputPort in = inPorts.createPassThroughPort(name + " " + runningId);
if (preconditionMetaData != null) {
in.addPrecondition(new SimplePrecondition(in, preconditionMetaData, false));
}
OutputPort out = outPorts.createPassThroughPort(name + " " + runningId);
return new PortPair(in, out);
}
private void deletePorts(PortPair pair) {
if (pair.outputPort.isConnected()) {
pair.outputPort.disconnect();
}
inPorts.removePort(pair.inputPort);
outPorts.removePort(pair.outputPort);
}
private void fixNames() {
runningId = 0;
for (PortPair pair : managedPairs) {
runningId++;
inPorts.renamePort(pair.inputPort, name + "_tmp_" + runningId);
outPorts.renamePort(pair.outputPort, name + "_tmp_" + runningId);
}
runningId = 0;
for (PortPair pair : managedPairs) {
runningId++;
inPorts.renamePort(pair.inputPort, name + " " + runningId);
outPorts.renamePort(pair.outputPort, name + " " + runningId);
}
}
/** The generated rule copies all meta data from the generated input ports to all generated output ports. */
public MDTransformationRule makePassThroughRule() {
return new MDTransformationRule() {
@Override
public void transformMD() {
for (PortPair pair : managedPairs) {
MetaData inData = pair.inputPort.getMetaData();
if (inData != null) {
inData = transformMetaData(inData.clone());
inData.addToHistory(pair.getOutputPort());
pair.outputPort.deliverMD(inData);
} else {
pair.outputPort.deliverMD(null);
}
}
}
};
}
protected MetaData transformMetaData(MetaData md) {
return md;
}
/** Passes the actual data from the output ports to their connected input ports. */
public void passDataThrough() {
for (PortPair pair : managedPairs) {
IOObject data = pair.inputPort.getAnyDataOrNull();
pair.outputPort.deliver(data);
}
}
/** Does the same as {@link #passDataThrough()} but copies the IOObjects. */
public void passCloneThrough() {
for (PortPair pair : managedPairs) {
IOObject data = pair.inputPort.getAnyDataOrNull();
if (data != null) {
pair.outputPort.deliver(data.copy());
} else {
pair.outputPort.deliver(null);
}
}
}
/** Returns an unmodifiable view of all port pairs managed by this port extender. */
public List<PortPair> getManagedPairs() {
return Collections.unmodifiableList(managedPairs);
}
/**
* Returns a list of all non-null data delivered to the input ports created by this port extender.
* @throws UserError
*/
public <T extends IOObject> List<T> getData() throws UserError {
List<T> results = new LinkedList<T>();
for (PortPair pair : managedPairs) {
T data = pair.inputPort.<T> getDataOrNull();
if (data != null) {
results.add(data);
}
}
return results;
}
/**
* Returns a list of all non-null data delivered to the input ports created by this port extender.
* @throws UserError
*/
public <T extends IOObject> List<T> getOutputData() throws UserError {
List<T> results = new LinkedList<T>();
for (PortPair pair : managedPairs) {
T data = pair.outputPort.<T> getDataOrNull();
if (data != null) {
results.add(data);
}
}
return results;
}
/**
* This method is a convenient method for delivering several IOObjects. But keep in mind that you cannot deliver
* more IObjects than you received first hand. First objects in list will be delivered on the first port. If input
* ports are not connected or got not delivered an objects unequal null, the corresponding output port is skipped.
*/
public void deliver(List<? extends IOObject> ioObjectList) {
Iterator<PortPair> portIterator = getManagedPairs().iterator();
for (IOObject object : ioObjectList) {
PortPair pair = portIterator.next();
IOObject data = pair.inputPort.getAnyDataOrNull();
while (data == null)
data = portIterator.next().inputPort.getAnyDataOrNull();
pair.outputPort.deliver(object);
}
}
@Override
public String getNamePrefix() {
return name + " ";
}
@Override
public void ensureMinimumNumberOfPorts(int minNumber) {
this.minNumber = minNumber;
updatePorts();
}
}