/** * 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.HashSet; import java.util.List; import java.util.Set; import com.asakusafw.compiler.common.Precondition; 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.FlowElementOutput; import com.asakusafw.vocabulary.flow.graph.FlowGraph; import com.asakusafw.vocabulary.flow.graph.PortConnection; /** * Represents element paths on flow graph. */ public class FlowPath { private final Direction direction; private final Set<FlowElement> startings; private final Set<FlowElement> passings; private final Set<FlowElement> arrivals; /** * Creates a new instance. * @param direction the path direction * @param startings the head elements * @param passings the body elements * @param arrivals the tail elements */ public FlowPath( Direction direction, Set<FlowElement> startings, Set<FlowElement> passings, Set<FlowElement> arrivals) { Precondition.checkMustNotBeNull(direction, "direction"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(startings, "startings"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(passings, "passings"); //$NON-NLS-1$ Precondition.checkMustNotBeNull(arrivals, "arrivals"); //$NON-NLS-1$ this.direction = direction; this.startings = Sets.freeze(startings); this.passings = Sets.freeze(passings); this.arrivals = Sets.freeze(arrivals); } /** * Returns the path direction. * @return the path direction */ public Direction getDirection() { return this.direction; } /** * Returns the head elements. * @return the head elements */ public Set<FlowElement> getStartings() { return this.startings; } /** * Returns the body elements (excludes head and tail elements). * @return the head elements */ public Set<FlowElement> getPassings() { return this.passings; } /** * Returns the tail elements. * @return the tail elements */ public Set<FlowElement> getArrivals() { return this.arrivals; } /** * Creates a new {@link FlowBlock} object from this path (must be a forward path). * @param graph the original flow graph * @param blockSequence the block serial number * @param includeStartings {@code true} to include the {@link #getStartings() head elements}, * otherwise {@code false} * @param includeArrivals {@code true} to include the {@link #getArrivals() tail elements}, * otherwise {@code false} * @return the created block * @throws IllegalStateException if this is not a {@link Direction#FORWARD forward} path * @throws IllegalArgumentException if the resulting block will be empty, or the parameters are {@code null} */ public FlowBlock createBlock( FlowGraph graph, int blockSequence, boolean includeStartings, boolean includeArrivals) { Precondition.checkMustNotBeNull(graph, "graph"); //$NON-NLS-1$ if (direction != Direction.FORWARD) { throw new IllegalStateException("direction must be FORWARD"); //$NON-NLS-1$ } if (includeStartings == false && includeArrivals == false && passings.isEmpty()) { throw new IllegalArgumentException(); } Set<FlowElement> elements = createBlockElements(includeStartings, includeArrivals); List<PortConnection> inputs = createBlockInputs(includeStartings); List<PortConnection> outputs = createBlockOutputs(includeArrivals); return new FlowBlock(blockSequence, graph, inputs, outputs, elements); } private List<PortConnection> createBlockInputs(boolean includeStartings) { List<PortConnection> results = new ArrayList<>(); if (includeStartings) { for (FlowElement element : startings) { for (FlowElementInput input : element.getInputPorts()) { results.addAll(input.getConnected()); } } } else { for (FlowElement element : startings) { for (FlowElementOutput output : element.getOutputPorts()) { for (PortConnection conn : output.getConnected()) { FlowElement target = conn.getDownstream().getOwner(); if (passings.contains(target) || arrivals.contains(target)) { results.add(conn); } } } } } return results; } private List<PortConnection> createBlockOutputs(boolean includeArrivals) { List<PortConnection> results = new ArrayList<>(); if (includeArrivals) { for (FlowElement element : arrivals) { for (FlowElementOutput output : element.getOutputPorts()) { results.addAll(output.getConnected()); } } } else { for (FlowElement element : arrivals) { for (FlowElementInput input : element.getInputPorts()) { for (PortConnection conn : input.getConnected()) { FlowElement target = conn.getUpstream().getOwner(); if (passings.contains(target) || startings.contains(target)) { results.add(conn); } } } } } return results; } private Set<FlowElement> createBlockElements(boolean includeStartings, boolean includeArrivals) { Set<FlowElement> elements = new HashSet<>(); elements.addAll(passings); if (includeStartings) { elements.addAll(startings); } if (includeArrivals) { elements.addAll(arrivals); } return elements; } /** * Returns a new union path consists of this and the specified path. * @param other the target path * @return the created path * @throws IllegalArgumentException if the two paths have the different directions, * or the parameter is {@code null} */ public FlowPath union(FlowPath other) { Precondition.checkMustNotBeNull(other, "other"); //$NON-NLS-1$ if (this.direction != other.direction) { throw new IllegalArgumentException("other must have same direction"); //$NON-NLS-1$ } Set<FlowElement> newStartings = Sets.from(startings); newStartings.addAll(other.startings); Set<FlowElement> newPassings = Sets.from(passings); newPassings.addAll(other.passings); Set<FlowElement> newArrivals = Sets.from(arrivals); newArrivals.addAll(other.arrivals); return new FlowPath( direction, newStartings, newPassings, newArrivals); } /** * Returns a new intersect path which consists of this path and the transposed specified path. * @param other the target path * @return the created path * @throws IllegalArgumentException if the two paths have the different directions, * or the parameter is {@code null} */ public FlowPath transposeIntersect(FlowPath other) { Precondition.checkMustNotBeNull(other, "other"); //$NON-NLS-1$ if (this.direction == other.direction) { throw new IllegalArgumentException("other must have different direction"); //$NON-NLS-1$ } Set<FlowElement> newStartings = Sets.from(startings); newStartings.retainAll(other.arrivals); Set<FlowElement> newPassings = Sets.from(passings); newPassings.retainAll(other.passings); Set<FlowElement> newArrivals = Sets.from(arrivals); newArrivals.retainAll(other.startings); return new FlowPath( direction, newStartings, newPassings, newArrivals); } @Override public String toString() { return MessageFormat.format( "{0}: {1}->{2}", //$NON-NLS-1$ direction, startings, arrivals); } /** * Represents directions of {@link FlowPath}. */ public enum Direction { /** * The forward direction. */ FORWARD, /** * The backward direction. */ BACKWORD, } }