/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.compiler.flow.plan;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.utils.collections.Maps;
import com.asakusafw.utils.collections.Sets;
import com.asakusafw.vocabulary.flow.graph.FlowElement;
import com.asakusafw.vocabulary.flow.graph.FlowElementInput;
import com.asakusafw.vocabulary.flow.graph.FlowElementKind;
import com.asakusafw.vocabulary.flow.graph.FlowElementOutput;
import com.asakusafw.vocabulary.flow.graph.FlowGraph;
import com.asakusafw.vocabulary.flow.graph.PortConnection;
/**
* A sub-graph of {@link FlowGraph}.
*/
public class FlowBlock {
static final Logger LOG = LoggerFactory.getLogger(FlowBlock.class);
private final int serialNumber;
private final FlowGraph source;
private final List<FlowBlock.Input> blockInputs;
private final List<FlowBlock.Output> blockOutputs;
private Set<FlowElement> elements;
private boolean detached;
/**
* Creates a new instance from boundary ports.
* Note that, the created instance will not be {@link #detach() detached}.
* @param serialNumber the serial number
* @param source the original flow graph
* @param inputs the input ports of this block
* @param outputs the output ports of this block
* @param elements the elements of this block
* @return the created instance
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public static FlowBlock fromPorts(
int serialNumber,
FlowGraph source,
List<FlowElementInput> inputs,
List<FlowElementOutput> outputs,
Set<FlowElement> elements) {
List<PortConnection> toInput = new ArrayList<>();
List<PortConnection> fromOutput = new ArrayList<>();
for (FlowElementInput in : inputs) {
toInput.addAll(in.getConnected());
}
for (FlowElementOutput out : outputs) {
fromOutput.addAll(out.getConnected());
}
return new FlowBlock(serialNumber, source, toInput, fromOutput, elements);
}
/**
* Creates a merged flow blocks from other blocks.
* @param blocks original blocks
* @param inputMapping input mapping (created -> original)
* @param outputMapping output mapping (created -> original)
* @return the merged block
* @throws IllegalArgumentException if some parameters were {@code null}
*/
public static FlowBlock fromBlocks(
Collection<FlowBlock> blocks,
Map<FlowBlock.Input, Set<FlowBlock.Input>> inputMapping,
Map<FlowBlock.Output, Set<FlowBlock.Output>> outputMapping) {
Precondition.checkMustNotBeNull(blocks, "blocks"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(inputMapping, "inputMapping"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(outputMapping, "outputMapping"); //$NON-NLS-1$
FlowGraph graph = null;
int minSerialNumber = Integer.MAX_VALUE;
int reduces = 0;
for (FlowBlock block : blocks) {
if (block.detached == false) {
throw new IllegalArgumentException();
}
graph = graph == null ? block.source : graph;
minSerialNumber = Math.min(minSerialNumber, block.serialNumber);
reduces += block.isReduceBlock() ? 1 : 0;
}
if (reduces != 0 && reduces != blocks.size()) {
throw new IllegalArgumentException("Cannot merge map blocks and reduce blocks"); //$NON-NLS-1$
}
FlowBlock result = new FlowBlock(minSerialNumber, graph);
for (FlowBlock block : blocks) {
result.elements.addAll(block.elements);
for (FlowBlock.Input origin : block.getBlockInputs()) {
FlowBlock.Input mapped = result.new Input(origin.getElementPort(), Collections.emptySet());
result.blockInputs.add(mapped);
Maps.addToSet(inputMapping, origin, mapped);
}
for (FlowBlock.Output origin : block.getBlockOutputs()) {
FlowBlock.Output mapped = result.new Output(origin.getElementPort(), Collections.emptySet());
result.blockOutputs.add(mapped);
Maps.addToSet(outputMapping, origin, mapped);
}
}
return result;
}
/**
* Creates a new instance.
* Note that, the created instance will not be {@link #detach() detached}.
* @param serialNumber the serial number
* @param source the original flow graph
* @param inputs the input connections of this block
* @param outputs the output connections of this block
* @param elements the elements of this block
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public FlowBlock(
int serialNumber,
FlowGraph source,
List<PortConnection> inputs,
List<PortConnection> outputs,
Set<FlowElement> elements) {
Precondition.checkMustNotBeNull(source, "source"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(inputs, "inputs"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(outputs, "outputs"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(elements, "elements"); //$NON-NLS-1$
int shuffles = countShuffleBoundary(inputs);
if (shuffles != 0 && shuffles != inputs.size()) {
throw new IllegalArgumentException("inputs must not be shuffle bounds partially"); //$NON-NLS-1$
}
this.serialNumber = serialNumber;
this.source = source;
this.blockInputs = toBlockInputs(inputs);
this.blockOutputs = toBlockOutputs(outputs);
this.elements = Sets.from(elements);
this.detached = false;
}
private FlowBlock(int serialNumber, FlowGraph source) {
Precondition.checkMustNotBeNull(source, "source"); //$NON-NLS-1$
this.serialNumber = serialNumber;
this.source = source;
this.blockInputs = new ArrayList<>();
this.blockOutputs = new ArrayList<>();
this.elements = new HashSet<>();
this.detached = false;
}
private int countShuffleBoundary(List<PortConnection> inputs) {
assert inputs != null;
int result = 0;
for (PortConnection input : inputs) {
if (FlowGraphUtil.isShuffleBoundary(input.getDownstream().getOwner())) {
result++;
}
}
return result;
}
private List<FlowBlock.Input> toBlockInputs(List<PortConnection> inputs) {
assert inputs != null;
Map<FlowElementInput, Set<PortConnection>> map = new LinkedHashMap<>();
for (PortConnection input : inputs) {
FlowElementInput port = input.getDownstream();
Maps.addToSet(map, port, input);
}
List<FlowBlock.Input> results = new ArrayList<>();
for (Map.Entry<FlowElementInput, Set<PortConnection>> entry : map.entrySet()) {
results.add(new FlowBlock.Input(entry.getKey(), entry.getValue()));
}
return results;
}
private List<FlowBlock.Output> toBlockOutputs(List<PortConnection> outputs) {
assert outputs != null;
Map<FlowElementOutput, Set<PortConnection>> map = new LinkedHashMap<>();
for (PortConnection output : outputs) {
FlowElementOutput port = output.getUpstream();
Maps.addToSet(map, port, output);
}
List<FlowBlock.Output> results = new ArrayList<>();
for (Map.Entry<FlowElementOutput, Set<PortConnection>> entry : map.entrySet()) {
results.add(new FlowBlock.Output(entry.getKey(), entry.getValue()));
}
return results;
}
/**
* Returns the original flow graph.
* @return the original flow graph
*/
public FlowGraph getSource() {
return source;
}
/**
* Returns the serial number of this block.
* @return the serial number
*/
public int getSerialNumber() {
return serialNumber;
}
/**
* Returns the input ports of this block.
* @return the input ports
*/
public List<FlowBlock.Input> getBlockInputs() {
return blockInputs;
}
/**
* Returns the output ports of this block.
* @return the output ports
*/
public List<FlowBlock.Output> getBlockOutputs() {
return blockOutputs;
}
/**
* Returns the elements of this block.
* @return the elements
*/
public Set<FlowElement> getElements() {
return elements;
}
/**
* Returns whether the target input and output are connected or not.
* @param upstream the upstream output
* @param downstream the downstream input
* @return {@code true} if the target inputs and output are connected, otherwise {@code false}
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public static boolean isConnected(FlowBlock.Output upstream, FlowBlock.Input downstream) {
Precondition.checkMustNotBeNull(upstream, "upstream"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(downstream, "downstream"); //$NON-NLS-1$
for (FlowBlock.Connection conn : upstream.getConnections()) {
if (conn.getDownstream().equals(downstream)) {
return true;
}
}
return false;
}
/**
* Connects between the target upstream output and downstream input.
* @param upstream the upstream output
* @param downstream the downstream input
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public static void connect(
FlowBlock.Output upstream,
FlowBlock.Input downstream) {
Precondition.checkMustNotBeNull(upstream, "upstream"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(downstream, "downstream"); //$NON-NLS-1$
if (upstream.isConnected(downstream)) {
return;
}
FlowBlock.Connection conn = new FlowBlock.Connection(upstream, downstream);
upstream.addConnection(conn);
downstream.addConnection(conn);
assert upstream.getElementPort().getDescription().getDataType().equals(
downstream.getElementPort().getDescription().getDataType());
}
/**
* Returns whether this block is empty or not.
* @return {@code true} if this block is empty, otherwise {@code false}
*/
public boolean isEmpty() {
return blockInputs.isEmpty() && blockOutputs.isEmpty();
}
/**
* Returns whether this block represents a reduce block or not.
* @return {@code true} if this block represents a reduce block, otherwise {@code false}
*/
public boolean isReduceBlock() {
if (blockInputs.isEmpty()) {
return false;
}
FlowBlock.Input first = blockInputs.get(0);
return FlowGraphUtil.isShuffleBoundary(first.getElementPort().getOwner());
}
/**
* Returns whether the succeeding blocks of this are reduce block or not.
* Note that this always returns {@code true} if there are no succeeding blocks.
* This method requires that this block has been {@link #detach() detached}.
* @return {@code true} if the succeeding blocks of this are reduce block, otherwise {@code false}
* @throws IllegalStateException if this block has not been {@link #detach() detached}
*/
public boolean isSucceedingReduceBlock() {
if (detached == false) {
throw new IllegalStateException(MessageFormat.format(
"{0} was not detached", //$NON-NLS-1$
this));
}
for (FlowBlock.Output output : blockOutputs) {
for (FlowBlock.Connection conn : output.getConnections()) {
FlowBlock successor = conn.getDownstream().getOwner();
return successor.isReduceBlock();
}
}
return false;
}
/**
* Detaches this block from the original flow graph.
* This operation will create a copy of this block, and the copy will be disconnected from other blocks.
*/
public void detach() {
if (detached) {
return;
}
LOG.debug("detaching from {}: {}", getSource(), this); //$NON-NLS-1$
Map<FlowElement, FlowElement> elementMapping = new HashMap<>();
Map<FlowElementInput, FlowElementInput> inputMapping = new HashMap<>();
Map<FlowElementOutput, FlowElementOutput> outputMapping = new HashMap<>();
FlowGraphUtil.deepCopy(elements, elementMapping, inputMapping, outputMapping);
this.elements = Sets.from(elementMapping.values());
reconnectBlockInOut(inputMapping, outputMapping);
detached = true;
}
private void reconnectBlockInOut(
Map<FlowElementInput, FlowElementInput> inputMapping,
Map<FlowElementOutput, FlowElementOutput> outputMapping) {
assert inputMapping != null;
assert outputMapping != null;
for (FlowBlock.Input bound : blockInputs) {
FlowElementInput port = inputMapping.get(bound.getElementPort());
assert port != null;
bound.setElementPort(port);
}
for (FlowBlock.Output bound : blockOutputs) {
FlowElementOutput port = outputMapping.get(bound.getElementPort());
assert port != null;
bound.setElementPort(port);
}
}
/**
* Unifies elements.
* @since 0.4.0
*/
public void unify() {
if (detached == false) {
throw new IllegalStateException();
}
Map<FlowElement, FlowElement> elementMapping = new HashMap<>();
Map<FlowElementInput, FlowElementInput> inputMapping = new HashMap<>();
Map<FlowElementOutput, FlowElementOutput> outputMapping = new HashMap<>();
FlowGraphUtil.deepCopy(elements, elementMapping, inputMapping, outputMapping);
unifyElements(elementMapping, inputMapping, outputMapping);
unifyInputs(elementMapping, inputMapping, outputMapping);
unifyOutputs(elementMapping, inputMapping, outputMapping);
}
private void unifyElements(
Map<FlowElement, FlowElement> elementMapping,
Map<FlowElementInput, FlowElementInput> inputMapping,
Map<FlowElementOutput, FlowElementOutput> outputMapping) {
assert elementMapping != null;
assert inputMapping != null;
assert outputMapping != null;
LOG.debug("Unifying elements: {}", this); //$NON-NLS-1$
Map<Object, FlowElement> unifier = new HashMap<>();
Map<FlowElement, FlowElement> unifiedElements = new HashMap<>();
Map<FlowElementInput, FlowElementInput> unifiedInputs = new HashMap<>();
Map<FlowElementOutput, FlowElementOutput> unifiedOutputs = new HashMap<>();
// find originals
for (Map.Entry<FlowElement, FlowElement> entry : elementMapping.entrySet()) {
FlowElement orig = entry.getKey();
FlowElement dest = entry.getValue();
assert orig.getIdentity().equals(orig.getIdentity());
FlowElement unified;
if (unifier.containsKey(orig.getIdentity()) == false) {
unified = dest;
unifier.put(orig.getIdentity(), unified);
} else {
unified = unifier.get(orig.getIdentity());
LOG.debug("Unify {} -> {}", dest, unified); //$NON-NLS-1$
}
unifiedElements.put(dest, unified);
List<FlowElementInput> srcInput = orig.getInputPorts();
List<FlowElementInput> dstInput = dest.getInputPorts();
List<FlowElementInput> uniInput = unified.getInputPorts();
assert srcInput.size() == uniInput.size();
for (int i = 0, n = srcInput.size(); i < n; i++) {
if (inputMapping.containsKey(srcInput.get(i))) {
inputMapping.put(srcInput.get(i), uniInput.get(i));
unifiedInputs.put(dstInput.get(i), uniInput.get(i));
}
}
List<FlowElementOutput> srcOutput = orig.getOutputPorts();
List<FlowElementOutput> dstOutput = dest.getOutputPorts();
List<FlowElementOutput> uniOutput = unified.getOutputPorts();
assert srcOutput.size() == uniOutput.size();
for (int i = 0, n = srcOutput.size(); i < n; i++) {
if (outputMapping.containsKey(srcOutput.get(i))) {
outputMapping.put(srcOutput.get(i), uniOutput.get(i));
unifiedOutputs.put(dstOutput.get(i), uniOutput.get(i));
}
}
}
// reconnect inputs
for (Map.Entry<FlowElement, FlowElement> entry : elementMapping.entrySet()) {
FlowElement elem = entry.getValue();
FlowElement unified = unifiedElements.get(elem);
assert unified != null;
if (elem != unified) {
List<FlowElementInput> srcInput = elem.getInputPorts();
List<FlowElementInput> uniInput = unified.getInputPorts();
assert srcInput.size() == uniInput.size();
for (int i = 0, n = srcInput.size(); i < n; i++) {
FlowElementInput srcPort = srcInput.get(i);
FlowElementInput uniPort = uniInput.get(i);
for (PortConnection conn : srcPort.getConnected()) {
FlowElementOutput opposite = unifiedOutputs.get(conn.getUpstream());
assert opposite != null;
PortConnection.connect(opposite, uniPort);
}
srcPort.disconnectAll();
}
}
}
// reconnect outputs
for (FlowElement elem : elementMapping.values()) {
FlowElement unified = unifiedElements.get(elem);
assert unified != null;
if (elem != unified) {
List<FlowElementOutput> srcOutput = elem.getOutputPorts();
List<FlowElementOutput> uniOutput = unified.getOutputPorts();
assert srcOutput.size() == uniOutput.size();
for (int i = 0, n = srcOutput.size(); i < n; i++) {
FlowElementOutput srcPort = srcOutput.get(i);
FlowElementOutput uniPort = uniOutput.get(i);
for (PortConnection conn : srcPort.getConnected()) {
FlowElementInput opposite = unifiedInputs.get(conn.getDownstream());
assert opposite != null;
PortConnection.connect(uniPort, opposite);
}
srcPort.disconnectAll();
}
}
}
// delete unified
for (Map.Entry<FlowElement, FlowElement> entry : elementMapping.entrySet()) {
FlowElement elem = entry.getValue();
FlowElement unified = unifiedElements.get(elem);
assert unified != null;
entry.setValue(unified);
}
this.elements = Sets.from(elementMapping.values());
}
private void unifyInputs(
Map<FlowElement, FlowElement> elementMapping,
Map<FlowElementInput, FlowElementInput> inputMapping,
Map<FlowElementOutput, FlowElementOutput> outputMapping) {
assert elementMapping != null;
assert inputMapping != null;
assert outputMapping != null;
Map<FlowElementInput, FlowBlock.Input> map = new HashMap<>();
for (Iterator<FlowBlock.Input> iter = blockInputs.iterator(); iter.hasNext();) {
FlowBlock.Input blockPort = iter.next();
FlowElementInput elementPort = inputMapping.get(blockPort.getElementPort());
assert elementPort != null;
FlowBlock.Input unified = map.get(elementPort);
if (unified == null) {
map.put(elementPort, blockPort);
blockPort.setElementPort(elementPort);
} else {
LOG.debug("Input port {} will be unified", blockPort); //$NON-NLS-1$
iter.remove();
for (FlowBlock.Connection conn : blockPort.getConnections()) {
FlowBlock.Output opposite = conn.getUpstream();
FlowBlock.connect(opposite, unified);
}
blockPort.disconnect();
}
}
}
private void unifyOutputs(
Map<FlowElement, FlowElement> elementMapping,
Map<FlowElementInput, FlowElementInput> inputMapping,
Map<FlowElementOutput, FlowElementOutput> outputMapping) {
assert elementMapping != null;
assert outputMapping != null;
assert inputMapping != null;
Map<FlowElementOutput, FlowBlock.Output> map = new HashMap<>();
for (Iterator<FlowBlock.Output> iter = blockOutputs.iterator(); iter.hasNext();) {
FlowBlock.Output blockPort = iter.next();
FlowElementOutput elementPort = outputMapping.get(blockPort.getElementPort());
assert elementPort != null;
FlowBlock.Output unified = map.get(elementPort);
if (unified == null) {
map.put(elementPort, blockPort);
blockPort.setElementPort(elementPort);
} else {
LOG.debug("Output port {} will be unified", blockPort); //$NON-NLS-1$
iter.remove();
for (FlowBlock.Connection conn : blockPort.getConnections()) {
FlowBlock.Input opposite = conn.getDownstream();
FlowBlock.connect(unified, opposite);
}
blockPort.disconnect();
}
}
}
/**
* Removes unnecessary elements from this block.
* This method requires that this block has been {@link #detach() detached}.
* @return {@code true} if this block was modified, or otherwise {@code false}
* @throws IllegalStateException this block has not been {@link #detach() detached}
*/
public boolean compaction() {
if (detached == false) {
throw new IllegalStateException(MessageFormat.format(
"{0} was not detached", //$NON-NLS-1$
this));
}
LOG.debug("Applying compaction: {}", this); //$NON-NLS-1$
boolean changed = false;
boolean localChanged;
do {
localChanged = false;
changed |= mergeSameBlockEdges();
changed |= trimDisconnectedBlockEdges();
changed |= trimDeadElements();
changed |= trimDeadBlockEdges();
localChanged |= mergeIdentity();
changed |= localChanged;
} while (localChanged);
if (changed) {
collectGarbages();
}
return changed;
}
private boolean mergeSameBlockEdges() {
boolean changed = false;
LOG.debug("Merging same block edges: {}", this); //$NON-NLS-1$
Map<FlowElementInput, FlowBlock.Input> inputMapping = new HashMap<>();
for (FlowBlock.Input port : blockInputs) {
FlowBlock.Input prime = inputMapping.get(port.getElementPort());
if (prime != null) {
LOG.debug("Merging block input: {} on {}", port, this); //$NON-NLS-1$
for (FlowBlock.Connection conn : port.getConnections()) {
FlowBlock.connect(conn.getUpstream(), prime);
}
port.disconnect();
changed = true;
} else {
inputMapping.put(port.getElementPort(), port);
}
}
Map<FlowElementOutput, FlowBlock.Output> outputMapping = new HashMap<>();
for (FlowBlock.Output port : blockOutputs) {
FlowBlock.Output prime = outputMapping.get(port.getElementPort());
if (prime != null) {
LOG.debug("Merging block output: {} on {}", port, this); //$NON-NLS-1$
for (FlowBlock.Connection conn : port.getConnections()) {
FlowBlock.connect(prime, conn.getDownstream());
}
port.disconnect();
changed = true;
} else {
outputMapping.put(port.getElementPort(), port);
}
}
return changed;
}
private boolean trimDisconnectedBlockEdges() {
boolean changed = false;
LOG.debug("Searching for disconnected block edges: {}", this); //$NON-NLS-1$
// remove orphaned inputs
Iterator<FlowBlock.Input> inputs = blockInputs.iterator();
while (inputs.hasNext()) {
FlowBlock.Input port = inputs.next();
if (port.getConnections().isEmpty()) {
LOG.debug("Deleting unnecessary block edge: {} on {}", port, this); //$NON-NLS-1$
inputs.remove();
changed = true;
}
}
// remove orphaned outputs
Iterator<FlowBlock.Output> outputs = blockOutputs.iterator();
while (outputs.hasNext()) {
FlowBlock.Output port = outputs.next();
if (port.getConnections().isEmpty()) {
LOG.debug("Deleting dead block edge: {} on {}", port, this); //$NON-NLS-1$
outputs.remove();
changed = true;
}
}
return changed;
}
private boolean trimDeadElements() {
boolean changed = false;
LOG.debug("Searching for unnecessary operators: {}", this); //$NON-NLS-1$
Set<FlowElement> blockEdge = collectBlockEdges();
Set<FlowElement> removed = new HashSet<>();
LinkedList<FlowElement> work = new LinkedList<>();
work.addAll(elements);
while (work.isEmpty() == false) {
FlowElement element = work.removeFirst();
// ignore already removed in this round
if (removed.contains(element)) {
continue;
}
// ignore edge elements
if (blockEdge.contains(element)) {
continue;
}
if (FlowGraphUtil.isAlwaysEmpty(element)) {
LOG.debug("Deleting operator without input: {} on {}", element, this); //$NON-NLS-1$
work.addAll(FlowGraphUtil.getSuccessors(element));
remove(element);
removed.add(element);
changed = true;
} else if (FlowGraphUtil.isAlwaysStop(element)
&& FlowGraphUtil.hasMandatorySideEffect(element) == false) {
LOG.debug("Deleting operator without output: {} on {}", element, this); //$NON-NLS-1$
work.addAll(FlowGraphUtil.getPredecessors(element));
remove(element);
removed.add(element);
changed = true;
}
}
return changed;
}
private boolean trimDeadBlockEdges() {
boolean changed = false;
LOG.debug("Searching for unnecessary block edges: {}", this); //$NON-NLS-1$
Set<FlowElement> inputElements = new HashSet<>();
Set<FlowElement> outputElements = new HashSet<>();
for (FlowBlock.Output output : blockOutputs) {
outputElements.add(output.getElementPort().getOwner());
}
// remove unused inputs
Iterator<FlowBlock.Input> inputs = blockInputs.iterator();
while (inputs.hasNext()) {
FlowBlock.Input port = inputs.next();
FlowElement element = port.getElementPort().getOwner();
if (FlowGraphUtil.hasSuccessors(element) == false
&& FlowGraphUtil.hasMandatorySideEffect(element) == false
&& outputElements.contains(element) == false) {
LOG.debug("Deleting unnecessary input: {} on {}", port, this); //$NON-NLS-1$
port.disconnect();
inputs.remove();
changed = true;
} else {
inputElements.add(element);
}
}
// remove unused outputs
Iterator<FlowBlock.Output> outputs = blockOutputs.iterator();
while (outputs.hasNext()) {
FlowBlock.Output port = outputs.next();
FlowElement element = port.getElementPort().getOwner();
if (FlowGraphUtil.hasPredecessors(element) == false
&& inputElements.contains(element) == false) {
LOG.debug("Deleting unnecessary output: {} on {}", port, this); //$NON-NLS-1$
port.disconnect();
outputs.remove();
changed = true;
}
}
return changed;
}
private boolean mergeIdentity() {
boolean changed = false;
boolean foundTarget = false;
Map<FlowBlock.Input, List<FlowBlock.Output>> targets = new HashMap<>();
for (FlowBlock.Output output : blockOutputs) {
FlowElement element = output.getElementPort().getOwner();
if (element.getDescription().getKind() != FlowElementKind.PSEUD) {
continue;
}
if (output.getConnections().size() != 1) {
continue;
}
FlowBlock.Input opposite = output.getConnections().get(0).getDownstream();
List<FlowBlock.Output> list = targets.get(opposite);
if (list == null) {
list = new ArrayList<>();
targets.put(opposite, list);
} else {
foundTarget = true;
}
list.add(output);
}
if (foundTarget == false) {
return changed;
}
Map<FlowElementInput, FlowBlock.Input> inputs = new HashMap<>();
for (FlowBlock.Input input : blockInputs) {
FlowElementInput elementInput = input.getElementPort();
assert inputs.containsKey(elementInput) == false;
inputs.put(elementInput, input);
}
for (Map.Entry<FlowBlock.Input, List<FlowBlock.Output>> entry : targets.entrySet()) {
List<FlowBlock.Output> upstream = entry.getValue();
if (upstream.size() == 1) {
continue;
}
FlowElement primaryElement = upstream.get(0).getElementPort().getOwner();
assert primaryElement.getDescription().getKind() == FlowElementKind.PSEUD;
assert primaryElement.getInputPorts().size() == 1;
FlowElementInput primaryInput = primaryElement.getInputPorts().get(0);
FlowBlock.Input primarySource = inputs.get(primaryInput);
assert primarySource != null;
for (int i = 1, n = upstream.size(); i < n; i++) {
FlowBlock.Output otherTarget = upstream.get(i);
FlowElement otherElement = otherTarget.getElementPort().getOwner();
LOG.debug("Unifying pseud element: {} -> {}", otherElement, primaryElement); //$NON-NLS-1$
assert otherElement.getDescription().getKind() == FlowElementKind.PSEUD;
assert otherElement.getInputPorts().size() == 1;
FlowElementInput otherInput = otherElement.getInputPorts().get(0);
FlowBlock.Input otherSource = inputs.get(otherInput);
assert otherSource != null;
for (FlowBlock.Connection conn : otherSource.getConnections()) {
FlowBlock.connect(conn.getUpstream(), primarySource);
}
otherSource.disconnect();
otherTarget.disconnect();
changed = true;
}
}
return changed;
}
/**
* Removes unused elements.
* @return {@code true} if this operation removed at least one element
*/
public boolean collectGarbages() {
LOG.debug("removing dead operators: {}", this); //$NON-NLS-1$
Set<FlowElement> blockEdge = collectBlockEdges();
boolean changed = false;
LOOP: for (Iterator<FlowElement> iter = elements.iterator(); iter.hasNext();) {
FlowElement element = iter.next();
if (blockEdge.contains(element)) {
continue;
}
for (FlowElementInput input : element.getInputPorts()) {
if (input.getConnected().isEmpty() == false) {
continue LOOP;
}
}
for (FlowElementOutput output : element.getOutputPorts()) {
if (output.getConnected().isEmpty() == false) {
continue LOOP;
}
}
iter.remove();
changed = true;
}
return changed;
}
private void remove(FlowElement element) {
assert element != null;
elements.remove(element);
FlowGraphUtil.disconnect(element);
}
private Set<FlowElement> collectBlockEdges() {
Set<FlowElement> blockEdge = new HashSet<>();
for (FlowBlock.Input input : getBlockInputs()) {
blockEdge.add(input.getElementPort().getOwner());
}
for (FlowBlock.Output output : getBlockOutputs()) {
blockEdge.add(output.getElementPort().getOwner());
}
return blockEdge;
}
@Override
public String toString() {
return MessageFormat.format(
"FlowBlock[{1}]({0}) - {2}..", //$NON-NLS-1$
String.valueOf(serialNumber),
isReduceBlock() ? "R" : "M", //$NON-NLS-1$ //$NON-NLS-2$
getBlockInputs().isEmpty() ? "?" : getBlockInputs().get(0)); //$NON-NLS-1$
}
/**
* Represents an input port of {@link FlowBlock}.
*/
public class Input {
private FlowElementInput input;
private final List<Connection> connections;
private Set<PortConnection> originalConnections;
/**
* Creates a new instance.
* @param input the corresponding element port
* @param originalConnections the original connections for this port
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public Input(FlowElementInput input, Set<PortConnection> originalConnections) {
Precondition.checkMustNotBeNull(input, "input"); //$NON-NLS-1$
this.input = input;
this.connections = new ArrayList<>();
this.originalConnections = originalConnections;
}
/**
* Returns the original connections for this port.
* @return the original connections for this port
*/
public Set<PortConnection> getOriginalConnections() {
return originalConnections;
}
/**
* Returns the owner of this port.
* @return the owner
*/
public FlowBlock getOwner() {
return FlowBlock.this;
}
/**
* Returns the corresponding element port.
* @return the corresponding element port
*/
public FlowElementInput getElementPort() {
return this.input;
}
/**
* Returns the connections for other ports.
* @return the connections
*/
public List<Connection> getConnections() {
return this.connections;
}
void setElementPort(FlowElementInput port) {
assert port != null;
this.input = port;
this.originalConnections = Collections.emptySet();
}
void addConnection(Connection conn) {
assert conn != null;
connections.add(conn);
}
void disconnect() {
for (Connection conn : Lists.from(connections)) {
conn.disconnect();
}
}
@Override
public String toString() {
return MessageFormat.format(
"{0}'{'owner=FlowBlock@{1}'}'", //$NON-NLS-1$
getElementPort(),
String.valueOf(FlowBlock.this.hashCode()));
}
}
/**
* Represents an output port of {@link FlowBlock}.
*/
public class Output {
private FlowElementOutput output;
private final List<Connection> connections;
private Set<PortConnection> originalConnections;
/**
* Creates a new instance.
* @param output the corresponding element port
* @param originalConnections the original connections for this port
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public Output(FlowElementOutput output, Set<PortConnection> originalConnections) {
Precondition.checkMustNotBeNull(output, "output"); //$NON-NLS-1$
this.output = output;
this.connections = new ArrayList<>();
this.originalConnections = originalConnections;
}
boolean isConnected(FlowBlock.Input downstream) {
for (Connection conn : connections) {
if (conn.getDownstream() == downstream) {
return true;
}
}
return false;
}
/**
* Returns the original connections for this port.
* @return the original connections for this port
*/
public Set<PortConnection> getOriginalConnections() {
return originalConnections;
}
/**
* Returns the owner of this port.
* @return the owner
*/
public FlowBlock getOwner() {
return FlowBlock.this;
}
/**
* Returns the corresponding element port.
* @return the corresponding element port
*/
public FlowElementOutput getElementPort() {
return this.output;
}
/**
* Returns the connections of this port.
* @return the connections of this port
*/
public List<Connection> getConnections() {
return this.connections;
}
void setElementPort(FlowElementOutput port) {
assert port != null;
this.output = port;
this.originalConnections = Collections.emptySet();
}
void addConnection(Connection conn) {
assert conn != null;
connections.add(conn);
}
void disconnect() {
for (Connection conn : Lists.from(connections)) {
conn.disconnect();
}
}
@Override
public String toString() {
return MessageFormat.format(
"{0}'{'owner=FlowBlock@{1}'}'", //$NON-NLS-1$
getElementPort(),
String.valueOf(FlowBlock.this.hashCode()));
}
}
/**
* Represents connections between {@link Input} and {@link Output}.
*/
public static class Connection {
private final FlowBlock.Output upstream;
private final FlowBlock.Input downstream;
/**
* Creates a new instance.
* @param upstream the upstream output port
* @param downstream the downstream input port
* @throws IllegalArgumentException if the parameters are {@code null}
*/
public Connection(Output upstream, Input downstream) {
Precondition.checkMustNotBeNull(upstream, "upstream"); //$NON-NLS-1$
Precondition.checkMustNotBeNull(downstream, "downstream"); //$NON-NLS-1$
this.upstream = upstream;
this.downstream = downstream;
}
/**
* Returns the upstream output port.
* @return the upstream output port
*/
public FlowBlock.Output getUpstream() {
return upstream;
}
/**
* Returns the downstream input port.
* @return the downstream input port
*/
public FlowBlock.Input getDownstream() {
return downstream;
}
/**
* Disposes this connection.
*/
public void disconnect() {
upstream.getConnections().remove(this);
downstream.getConnections().remove(this);
}
@Override
public String toString() {
return MessageFormat.format(
"{0} => {1}", //$NON-NLS-1$
getUpstream(),
getDownstream());
}
}
}