mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 10:49:34 +02:00
GP-4988 Created flow chart function graph layout
This commit is contained in:
parent
954ff4e124
commit
0a5a2ce9d8
60 changed files with 6570 additions and 1312 deletions
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -442,7 +442,8 @@ public class FunctionGraph extends GroupingVisualGraph<FGVertex, FGEdge> {
|
|||
|
||||
public void setRootVertex(FGVertex rootVertex) {
|
||||
if (this.rootVertex != null) {
|
||||
throw new IllegalStateException("Cannot set the root vertex more than once!");
|
||||
this.rootVertex = rootVertex;
|
||||
return;
|
||||
}
|
||||
|
||||
this.rootVertex = rootVertex;
|
||||
|
@ -586,6 +587,7 @@ public class FunctionGraph extends GroupingVisualGraph<FGVertex, FGEdge> {
|
|||
|
||||
FGLayout originalLayout = getLayout();
|
||||
FGLayout newLayout = originalLayout.cloneLayout(newGraph);
|
||||
newGraph.rootVertex = rootVertex;
|
||||
|
||||
// setSize() must be called after setGraphLayout() due to callbacks performed when
|
||||
// setSize() is called
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -34,7 +34,7 @@ import ghidra.program.model.listing.Function;
|
|||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.FlowType;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
|
@ -184,7 +184,7 @@ public class FunctionGraphFactory {
|
|||
private static String layoutGraph(Function function, FGController controller,
|
||||
FunctionGraph functionGraph, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
if (!performSwingThreadRequiredWork(functionGraph)) {
|
||||
if (!performSwingThreadRequiredWork(functionGraph, monitor)) {
|
||||
return null;// shouldn't happen
|
||||
}
|
||||
|
||||
|
@ -214,19 +214,21 @@ public class FunctionGraphFactory {
|
|||
"\" (try another layout)";
|
||||
}
|
||||
|
||||
private static boolean performSwingThreadRequiredWork(FunctionGraph functionGraph) {
|
||||
final Collection<FGVertex> vertices = functionGraph.getVertices();
|
||||
try {
|
||||
SystemUtilities.runSwingNow(() -> {
|
||||
for (FGVertex v : vertices) {
|
||||
v.getComponent();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
private static boolean performSwingThreadRequiredWork(FunctionGraph functionGraph,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Collection<FGVertex> vertices = functionGraph.getVertices();
|
||||
monitor.initialize(vertices.size(), "Building vertex components");
|
||||
for (FGVertex v : vertices) {
|
||||
monitor.increment();
|
||||
try {
|
||||
Swing.runNow(v::getComponent);
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isEntry(CodeBlock codeBlock) {
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.GIcon;
|
||||
|
||||
public abstract class ExperimentalLayoutProvider extends FGLayoutProviderExtensionPoint {
|
||||
|
||||
private static final Icon ICON = new GIcon("icon.plugin.functiongraph.layout.experimental");
|
||||
|
||||
@Override
|
||||
public Icon getActionIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriorityLevel() {
|
||||
return -100; // below the others
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.graph.*;
|
||||
import ghidra.graph.graphs.DefaultVisualGraph;
|
||||
import ghidra.graph.viewer.VisualEdge;
|
||||
import ghidra.graph.viewer.VisualVertex;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* Base class for graph layouts that layout a graph based on a tree structure with orthogonal edge
|
||||
* routing.
|
||||
* <P>
|
||||
* The basic algorithm is to convert the graph to a tree and then, working bottom up in the tree,
|
||||
* assign each vertex to a {@link GridLocationMap}. This is done by assigning each leaf
|
||||
* vertex in its own simple 1x1 grid and them merging grids for sibling children side by side as
|
||||
* you work up the tree. Merging sub-tree grids is done by shifting child grids to the right as
|
||||
* each child grid is merged into the first (left most) child grid. The amount to shift a grid
|
||||
* before it is merged is done by comparing the maximum column values for each row from the left
|
||||
* grid to the minimum column values for each row from the right grid and finding the minimum shift
|
||||
* need such that no rows overlap. This way, grids are stitched together sort of like a jig-saw
|
||||
* puzzle.
|
||||
* <P>
|
||||
* Once the vertices are place in the grid, edge articulations are computed so that edges are
|
||||
* routed orthogonally using an {@link OrthogonalEdgeRouter}.
|
||||
* <P>
|
||||
*
|
||||
* To position the vertices and edges in layout space, it uses an
|
||||
* {@link OrthogonalGridToLayoutMapper} to size the grid and map grid points to layout points.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
|
||||
public abstract class AbstractFlowChartLayout<V extends VisualVertex, E extends VisualEdge<V>>
|
||||
extends AbstractVisualGraphLayout<V, E> {
|
||||
protected Comparator<E> edgeComparator;
|
||||
protected boolean leftAligned;
|
||||
|
||||
protected AbstractFlowChartLayout(DefaultVisualGraph<V, E> graph,
|
||||
Comparator<E> edgeComparator, boolean leftAligned) {
|
||||
super(graph, "Flow Chart");
|
||||
this.edgeComparator = edgeComparator;
|
||||
this.leftAligned = leftAligned;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GridLocationMap<V, E> performInitialGridLayout(VisualGraph<V, E> g)
|
||||
throws CancelledException {
|
||||
|
||||
V root = getRoot(g);
|
||||
|
||||
GDirectedGraph<V, E> tree = GraphAlgorithms.toTree(g, root, edgeComparator);
|
||||
GridLocationMap<V, E> grid = computeGridLocationMap(g, tree, root);
|
||||
|
||||
OrthogonalEdgeRouter<V, E> router = new OrthogonalEdgeRouter<>(grid);
|
||||
router.setColumnExclusionFunction(e -> getExcludedCols(grid, tree, e));
|
||||
router.computeAndSetEdgeArticulations(g.getEdges());
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LayoutPositions<V, E> positionInLayoutSpaceFromGrid(VisualGraph<V, E> g,
|
||||
GridLocationMap<V, E> grid) throws CancelledException {
|
||||
|
||||
boolean isCondensed = isCondensedLayout();
|
||||
Function<V, Shape> transformer = new VisualGraphVertexShapeTransformer<>();
|
||||
|
||||
OrthogonalGridToLayoutMapper<V, E> layoutMap =
|
||||
new OrthogonalGridToLayoutMapper<V, E>(grid, transformer, isCondensed);
|
||||
|
||||
Map<V, Point2D> vertexMap = layoutMap.getVertexLocations();
|
||||
Map<E, List<Point2D>> edgeMap = layoutMap.getEdgeLocations(vertexMap);
|
||||
|
||||
LayoutPositions<V, E> positions = LayoutPositions.createNewPositions(vertexMap, edgeMap);
|
||||
|
||||
// DEGUG triggers grid lines to be printed; useful for debugging
|
||||
// VisualGraphRenderer.setGridPainter(new GridPainter(layoutMap.getGridCoordinates()));
|
||||
|
||||
layoutMap.dispose();
|
||||
return positions;
|
||||
}
|
||||
|
||||
protected abstract V getRoot(VisualGraph<V, E> g);
|
||||
|
||||
@Override
|
||||
public boolean usesEdgeArticulations() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getVertexLocation(V v, Column<V> col, Row<V> row,
|
||||
Rectangle bounds) {
|
||||
return getCenteredVertexLocation(v, col, row, bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GridLocationMap for the subtree rooted at the given vertex. It does this by
|
||||
* recursively getting grid maps for each of its children and then merging them together
|
||||
* side by side.
|
||||
* @param g the original graph
|
||||
* @param tree the graph after edge removal to convert it into a tree
|
||||
* @param v the root of the subtree to get a grid map for
|
||||
* @return a GridLocationMap with the given vertex and all of its children position in the
|
||||
* grid.
|
||||
*/
|
||||
private GridLocationMap<V, E> computeGridLocationMap(GDirectedGraph<V, E> g,
|
||||
GDirectedGraph<V, E> tree, V v) {
|
||||
|
||||
Collection<E> edges = tree.getOutEdges(v);
|
||||
|
||||
if (edges.isEmpty()) {
|
||||
GridLocationMap<V, E> grid = new GridLocationMap<>(v, 1, 1);
|
||||
return grid;
|
||||
}
|
||||
|
||||
// get all child grids and merge them side by side
|
||||
|
||||
List<E> sortedEdges = new ArrayList<>(edges);
|
||||
sortedEdges.sort(edgeComparator);
|
||||
E edge = sortedEdges.get(0);
|
||||
V child = edge.getEnd();
|
||||
int totalEdges = sortedEdges.size();
|
||||
|
||||
GridLocationMap<V, E> childGrid = computeGridLocationMap(g, tree, child);
|
||||
int leftChildRootCol = childGrid.getRootColumn();
|
||||
int rightChildRootCol = leftChildRootCol;
|
||||
for (int i = 1; i < totalEdges; i++) {
|
||||
edge = sortedEdges.get(i);
|
||||
child = edge.getEnd();
|
||||
GridLocationMap<V, E> nextGrid = computeGridLocationMap(g, tree, child);
|
||||
int shift = merge(childGrid, nextGrid, i, totalEdges);
|
||||
rightChildRootCol = nextGrid.getRootColumn() + shift;
|
||||
}
|
||||
int rootCol = (leftChildRootCol + rightChildRootCol) / 2;
|
||||
if (leftAligned) {
|
||||
rootCol = 1;
|
||||
}
|
||||
GridLocationMap<V, E> grid = new GridLocationMap<>(v, 1, rootCol);
|
||||
grid.add(childGrid, 2, 0); // move child grid down 2: 1 for new root, 1 for edge row
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private int merge(GridLocationMap<V, E> leftGrid, GridLocationMap<V, E> rightGrid, int i,
|
||||
int totalEdges) {
|
||||
|
||||
GridRange[] ranges = leftGrid.getVertexColumnRanges();
|
||||
GridRange[] otherRanges = rightGrid.getVertexColumnRanges();
|
||||
|
||||
int shift = computeShift(ranges, otherRanges);
|
||||
|
||||
leftGrid.add(rightGrid, 0, shift);
|
||||
return shift;
|
||||
|
||||
}
|
||||
|
||||
private int computeShift(GridRange[] ranges, GridRange[] otherRanges) {
|
||||
int shift = 0;
|
||||
int commonHeight = Math.min(ranges.length, otherRanges.length);
|
||||
for (int i = 0; i < commonHeight; i++) {
|
||||
GridRange range = ranges[i];
|
||||
GridRange otherRange = otherRanges[i];
|
||||
int myMax = range.max;
|
||||
int otherMin = otherRange.min;
|
||||
if (myMax >= otherMin - 1) {
|
||||
int diff = myMax - otherMin + 2; // we want 1 empty column between
|
||||
shift = Math.max(shift, diff);
|
||||
}
|
||||
}
|
||||
return shift;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public VisualGraph<V, E> getVisualGraph() {
|
||||
return (VisualGraph<V, E>) getGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a range of columns that we don't want to attempt to perform column routing through.
|
||||
* Specifically, this is for back edges where we don't want them to route through columns that
|
||||
* cut through any of its parents sub trees. This will force the routing algorithm to route
|
||||
* around a nodes containing sub-tree instead of through it.
|
||||
* @param grid the grid map that will be examined to find a routing column that doesn't
|
||||
* have any blocking vertices.
|
||||
* @param tree the tree version of the original graph
|
||||
* @param e the edge to examine to find its parent's subtree column bounds
|
||||
* @return a minimum and maximum column index through which the back edge should not be routed
|
||||
*/
|
||||
private GridRange getExcludedCols(GridLocationMap<V, E> grid, GDirectedGraph<V, E> tree, E e) {
|
||||
GridRange range = new GridRange();
|
||||
V v = e.getStart();
|
||||
V ancestor = e.getEnd();
|
||||
boolean isBackEdge = grid.row(v) >= grid.row(ancestor);
|
||||
if (!isBackEdge) {
|
||||
// no exclusions
|
||||
return new GridRange();
|
||||
}
|
||||
|
||||
V parent = getParent(tree, v);
|
||||
while (parent != null) {
|
||||
Collection<V> children = tree.getSuccessors(parent);
|
||||
for (V child : children) {
|
||||
range.add(grid.col(child));
|
||||
}
|
||||
parent = getParent(tree, parent);
|
||||
if (parent == ancestor) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
private V getParent(GDirectedGraph<V, E> tree, V v) {
|
||||
Collection<V> predecessors = tree.getPredecessors(v);
|
||||
if (predecessors == null || predecessors.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return predecessors.iterator().next();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.graph.viewer.layout.GridPoint;
|
||||
|
||||
/**
|
||||
* Maintains a collection of ColumnSegments for the same grid column.
|
||||
*
|
||||
* @param <E> edge type
|
||||
*/
|
||||
public class ColSegmentList<E> {
|
||||
private List<ColumnSegment<E>> edgeSegments = new ArrayList<>();
|
||||
private int col;
|
||||
private long minY = Integer.MAX_VALUE;
|
||||
private long maxY = Integer.MIN_VALUE;
|
||||
|
||||
public ColSegmentList(int col) {
|
||||
this.col = col;
|
||||
}
|
||||
|
||||
public int getCol() {
|
||||
return col;
|
||||
}
|
||||
|
||||
public ColSegmentList(ColumnSegment<E> segment) {
|
||||
super();
|
||||
addSegment(segment);
|
||||
this.col = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns offsets for overlapping column segments. Parallel overlapping edges must be offset
|
||||
* from each other when assigned to layout space to avoid drawing over each other. Each
|
||||
* edge offset represents 1/2 the edge spacing distance. The reason offsets are assigned 2
|
||||
* apart from each other is so that even numbers of columns can be centered. So for example,
|
||||
* a column with 3 parallel edges are assigned offsets of -2,0,2, but 4 edges would be assigned
|
||||
* -3,-1, 1, 3. (offset in each direction by 1/2 of an edge spacing)
|
||||
*/
|
||||
public void assignOffsets() {
|
||||
// First partition the column segments into non-overlapping groups. Since column segments
|
||||
// may attach to vertices, it is easier to center them on the vertices if not trying to
|
||||
// consider all the segments in a column at the same time.
|
||||
List<ColSegmentList<E>> groups = sortIntoNonOverlappingGroups(edgeSegments);
|
||||
for (ColSegmentList<E> group : groups) {
|
||||
assignOffsets(group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the the range of y values in a given column segment list intersects the
|
||||
* range of y values in this column segment.
|
||||
* @param other the column segment list to compare
|
||||
* @return true if they intersect ranges.
|
||||
*/
|
||||
boolean intersects(ColSegmentList<E> other) {
|
||||
if (minY > other.maxY) {
|
||||
return false;
|
||||
}
|
||||
if (other.minY > maxY) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ColumnSegment<E> getSegment(E edge, GridPoint startPoint) {
|
||||
for (ColumnSegment<E> edgeSegment : edgeSegments) {
|
||||
if (edgeSegment.edge.equals(edge) && edgeSegment.startsAt(startPoint)) {
|
||||
return edgeSegment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return edgeSegments.toString();
|
||||
}
|
||||
|
||||
int getMinOffset() {
|
||||
int minOffset = 0;
|
||||
for (EdgeSegment<E> edgeSegment : edgeSegments) {
|
||||
minOffset = Math.min(minOffset, edgeSegment.getOffset());
|
||||
}
|
||||
return minOffset;
|
||||
}
|
||||
|
||||
int getMaxOffset() {
|
||||
int maxOffset = 0;
|
||||
for (EdgeSegment<E> edgeSegment : edgeSegments) {
|
||||
maxOffset = Math.max(maxOffset, edgeSegment.getOffset());
|
||||
}
|
||||
return maxOffset;
|
||||
}
|
||||
|
||||
void addSegment(ColumnSegment<E> segment) {
|
||||
edgeSegments.add(segment);
|
||||
minY = Math.min(minY, segment.getVirtualMinY());
|
||||
maxY = Math.max(maxY, segment.getVirtualMaxY());
|
||||
}
|
||||
|
||||
private void assignOffsets(ColSegmentList<E> group) {
|
||||
// First sort the column segments in a left to right ordering.
|
||||
Collections.sort(group.edgeSegments);
|
||||
|
||||
// Column segments are extend both to the right and left of the grid line, so first
|
||||
// find a starting edge to assign to 0 and then work in both directions giving offsets
|
||||
// to columns to avoid overlaps.
|
||||
|
||||
// see if there is a natural center line (from a vertex to vertex in one straight line)
|
||||
int naturalCenter = findNaturalCenter(group);
|
||||
int centerIndex = naturalCenter >= 0 ? naturalCenter : group.edgeSegments.size() / 2;
|
||||
assignOffsets(group, centerIndex);
|
||||
|
||||
// if used an arbitrary center index, our edges might not be centered around
|
||||
// the grid line, so adjust the offsets so the are.
|
||||
if (naturalCenter < 0) {
|
||||
int bias = group.getMaxOffset() + group.getMinOffset();
|
||||
int adjustment = -bias / 2;
|
||||
for (EdgeSegment<E> segment : group.edgeSegments) {
|
||||
segment.setOffset(segment.getOffset() + adjustment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assignOffsets(ColSegmentList<E> group, int center) {
|
||||
List<ColSegmentList<E>> nonOverlappingSegments = new ArrayList<>();
|
||||
|
||||
// assign negative offsets to column segments to the left of the center segment.
|
||||
for (int i = center; i >= 0; i--) {
|
||||
ColumnSegment<E> segment = group.edgeSegments.get(i);
|
||||
assignOffsets(nonOverlappingSegments, segment, -2); // 2 to keep edges two offsets apart
|
||||
}
|
||||
|
||||
// remove all the previous results except for the columm as we still need to check
|
||||
// for overlap against columns that have been assigned offset 0
|
||||
for (int i = nonOverlappingSegments.size() - 1; i > 0; i--) {
|
||||
nonOverlappingSegments.remove(i);
|
||||
}
|
||||
|
||||
// assign positive offsets to column segments to the right of the center segment.
|
||||
for (int i = center + 1; i < group.edgeSegments.size(); i++) {
|
||||
ColumnSegment<E> segment = group.edgeSegments.get(i);
|
||||
assignOffsets(nonOverlappingSegments, segment, 2); // 2 to keep edges two offsets apart
|
||||
}
|
||||
}
|
||||
|
||||
private void assignOffsets(List<ColSegmentList<E>> nonOverlappingSegments,
|
||||
ColumnSegment<E> segment, int stepSize) {
|
||||
|
||||
// Find lowest offset group that the given segment can be added without an overlap.
|
||||
// Start looking at the group with highest offsets first and work towards the 0
|
||||
// offset group to ensure that overlapping segments don't lose the ordering that
|
||||
// has already been establish. In other words, for a segment to be allowed to be
|
||||
// given a 0 offset (because it doesn't overlap any segments in that group), it must
|
||||
// also not overlap any existing groups with higher offsets. (Otherwise the ordering
|
||||
// we created to minimize edge crossings will be lost)
|
||||
|
||||
int i = nonOverlappingSegments.size() - 1;
|
||||
for (; i >= 0; i--) {
|
||||
if (nonOverlappingSegments.get(i).hasOverlappingSegment(segment)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we either broke at a group we overlap or we are at -1. Either way, we get added
|
||||
// to the next offset group.
|
||||
i++;
|
||||
if (i >= nonOverlappingSegments.size()) {
|
||||
nonOverlappingSegments.add(new ColSegmentList<E>(i));
|
||||
}
|
||||
// if adjusting to the left, offsets are negative
|
||||
int offset = i * stepSize;
|
||||
segment.setOffset(offset);
|
||||
nonOverlappingSegments.get(i).addSegment(segment);
|
||||
}
|
||||
|
||||
private boolean hasOverlappingSegment(ColumnSegment<E> segment) {
|
||||
for (ColumnSegment<E> edgeSegment : edgeSegments) {
|
||||
if (segment.overlaps(edgeSegment)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private int findNaturalCenter(ColSegmentList<E> group) {
|
||||
for (int i = 0; i < group.edgeSegments.size(); i++) {
|
||||
ColumnSegment<E> edgeSegment = group.edgeSegments.get(i);
|
||||
if (edgeSegment.points.size() == 2) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private List<ColSegmentList<E>> sortIntoNonOverlappingGroups(List<ColumnSegment<E>> segments) {
|
||||
List<ColSegmentList<E>> groups = new ArrayList<>(segments.size());
|
||||
for (ColumnSegment<E> segment : segments) {
|
||||
groupSegment(groups, segment);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
private void groupSegment(List<ColSegmentList<E>> groups, ColumnSegment<E> segment) {
|
||||
ColSegmentList<E> newGroup = new ColSegmentList<E>(segment);
|
||||
for (int i = groups.size() - 1; i >= 0; i--) {
|
||||
if (newGroup.intersects(groups.get(i))) {
|
||||
newGroup.merge(groups.get(i));
|
||||
groups.remove(i);
|
||||
}
|
||||
}
|
||||
groups.add(newGroup);
|
||||
}
|
||||
|
||||
private void merge(ColSegmentList<E> segmentList) {
|
||||
edgeSegments.addAll(segmentList.edgeSegments);
|
||||
minY = Math.min(minY, segmentList.minY);
|
||||
maxY = Math.max(maxY, segmentList.maxY);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,346 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.graph.viewer.layout.GridPoint;
|
||||
|
||||
/**
|
||||
* Vertical edge segments of an edge with articulation points. Each pair of points in the list
|
||||
* of articulation points corresponds to either a column segment or a row segment. There is
|
||||
* a built-in assumption in the sorting algorithm that the list of articulation points always
|
||||
* start and end with a column segment. See {@link EdgeSegment} for more information.
|
||||
*
|
||||
* @param <E> The edge type
|
||||
*/
|
||||
public class ColumnSegment<E> extends EdgeSegment<E> implements Comparable<ColumnSegment<E>> {
|
||||
|
||||
// specifies the orientation of the row attached to this segment (either top or bottom)
|
||||
enum RowOrientation {
|
||||
LEFT, // the attached row extends to the left of this column
|
||||
TERMINAL, // there is no attached row since this segment ends at a vertex.
|
||||
RIGHT // the attached row extends to the right of this column
|
||||
}
|
||||
|
||||
private RowSegment<E> next;
|
||||
private RowSegment<E> previous;
|
||||
|
||||
/**
|
||||
* Constructs the first segment which is always a column segment. This constructor will
|
||||
* also create all the follow-on segments which can be retrieved via the {@link #nextSegment()}
|
||||
* method.
|
||||
* @param e the edge to create segments for
|
||||
* @param points the articulation points for the edge.
|
||||
*/
|
||||
public ColumnSegment(E e, List<GridPoint> points) {
|
||||
this(null, e, points, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Package method for creating the column segments at a specific pointIndex.
|
||||
* @param previous the row segment the precedes this column segment
|
||||
* @param e the edge this segment is for
|
||||
* @param points the list of articulation points for the edge
|
||||
* @param pointIndex the index into the points list that is the first point for this segment
|
||||
*/
|
||||
ColumnSegment(RowSegment<E> previous, E e, List<GridPoint> points, int pointIndex) {
|
||||
super(e, points, pointIndex);
|
||||
this.previous = previous;
|
||||
if (pointIndex < points.size() - 2) {
|
||||
next = new RowSegment<E>(this, e, points, pointIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grid column index this column segment.
|
||||
* @return the grid column index this column segment
|
||||
*/
|
||||
public int getCol() {
|
||||
return points.get(pointIndex).col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the row where this column segment starts. Note that this is different
|
||||
* from the top row. The start row is in the order of the articulation points whereas the top
|
||||
* row is always the spatially upper (lower row index) row of either the start row or end row.
|
||||
* @return the index of the grid row for the start point of this segment
|
||||
*/
|
||||
public int getStartRow() {
|
||||
return points.get(pointIndex).row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the row where this column segment ends. Note that this is different
|
||||
* from the bottom row. The end row is in the order of the articulation points. The bottom row
|
||||
* is always the spatially lower (higher row index) row of either the start row or end row.
|
||||
* @return the index of the grid row for the end point of this segment
|
||||
*/
|
||||
public int getEndRow() {
|
||||
return points.get(pointIndex + 1).row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ColumnSegment<E> other) {
|
||||
// To make the comparison reversible and transitive, it is important that we are
|
||||
// consistent in the order we compare segments. We arbitrarily chose to always compare
|
||||
// the order by following the shape of the top and only considering the shape on the bottom
|
||||
// if the tops are equal.
|
||||
//
|
||||
// NOTE: Segments are compared by following the next or previous segments until one of the
|
||||
// segments definitively determines the order. When comparing segments in a particular
|
||||
// direction, is is important not to directly call the compareTo methods as that could result
|
||||
// in an infinite loop. Instead, when comparing in a particular direction, just directly
|
||||
// use the appropriate direction comparison so that it will follow that direction until
|
||||
// it finds a difference or it simply returns 0, in which case the original to
|
||||
// compareTo can then try the other direction. As a consequence of this, the basic obvious
|
||||
// comparison of the grid columns first had to be moved into both the compareTops and the
|
||||
// compareBottoms.
|
||||
|
||||
int result = compareTops(other);
|
||||
if (result == 0) {
|
||||
result = compareBottoms(other);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares column segments strictly based on the relationship of the connected rows at the top
|
||||
* of the segments.
|
||||
* @param other the other column segment to compare
|
||||
* @return a negative integer, zero, or a positive integer as this object
|
||||
* is less than, equal to, or greater than the specified object.
|
||||
*/
|
||||
public int compareTops(ColumnSegment<E> other) {
|
||||
// first, just check the columns of this segment and the other, if the columns are
|
||||
// not the same, then one column clearly is to the left of the other.
|
||||
|
||||
int result = getCol() - other.getCol();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We are in the same grid column,so the order is determined by the segment at the
|
||||
// top of the column. There are 3 possible orientations for the top row. 1) it can
|
||||
// extend to the left of our column, it can be a terminal point, or it can extend to the
|
||||
// right. If our orientation is not the same as the other column segment, the order is
|
||||
// simply LEFT < TERMINAL < RIGHT in order to reduce edge crossings. Edges on the left
|
||||
// go left and edges on the right go right, so they won't cross each other.
|
||||
|
||||
RowOrientation myTopRowOrientation = getOrientationForTopRow();
|
||||
RowOrientation otherTopRowOrientation = other.getOrientationForTopRow();
|
||||
|
||||
// if they are not the same, then LEFT < TERMINAL < RIGHT
|
||||
if (myTopRowOrientation != otherTopRowOrientation) {
|
||||
return myTopRowOrientation.compareTo(otherTopRowOrientation);
|
||||
}
|
||||
|
||||
// Both this segment and the other segment have the same orientation. Compare the top
|
||||
// row segments and use those results to determine our ordering left to right. If the
|
||||
// top rows extend to the left, then which ever row is above (lower value), should have
|
||||
// its associated column be to the right (higher value). Keeping lower rows (higher value)
|
||||
// on the left allows their shape to avoid being crossed by the taller shape, which will be
|
||||
// on the right.
|
||||
//
|
||||
// And if the top rows extends the right, the inverse applies.
|
||||
|
||||
switch (myTopRowOrientation) {
|
||||
case LEFT:
|
||||
RowSegment<E> myTopRowSegment = getTopRowSegment();
|
||||
RowSegment<E> otherTopRowSegment = other.getTopRowSegment();
|
||||
return -myTopRowSegment.compareLefts(otherTopRowSegment);
|
||||
case RIGHT:
|
||||
myTopRowSegment = getTopRowSegment();
|
||||
otherTopRowSegment = other.getTopRowSegment();
|
||||
return myTopRowSegment.compareRights(otherTopRowSegment);
|
||||
case TERMINAL:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares column segments strictly based on the relationship of the connected rows at the
|
||||
* bottom of the segments.
|
||||
* @param other the other column segment to compare
|
||||
* @return a negative integer, zero, or a positive integer as this object
|
||||
* is less than, equal to, or greater than the specified object.
|
||||
*/
|
||||
public int compareBottoms(ColumnSegment<E> other) {
|
||||
// first, just check the columns of this segment and the other, if the columns are
|
||||
// not the same, then one column clearly is to the left of the other.
|
||||
int result = getCol() - other.getCol();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We are in the same grid column, the order is determined by the segment at the
|
||||
// bottom of the column (we already tried the top and they were equal). There are
|
||||
// 3 possible orientations for the botom row. 1) it can
|
||||
// extend to the left of our column, it can be a terminal point, or it can extend to the
|
||||
// right. If our orientation is not the same as the other column segment, the order is
|
||||
// simply LEFT < TERMINAL < RIGHT in order to reduce edge crossings. Edges on the left
|
||||
// go left and edges on the right go right, so they won't cross each other.
|
||||
|
||||
RowOrientation myBottomRowOrientation = getOrientationForBottomRow();
|
||||
RowOrientation otherTopRowOrientation = other.getOrientationForBottomRow();
|
||||
// if they are not the same, then LEFT < TERMINAL < RIGHT
|
||||
if (myBottomRowOrientation != otherTopRowOrientation) {
|
||||
return myBottomRowOrientation.compareTo(otherTopRowOrientation);
|
||||
}
|
||||
|
||||
// Both this segment and the other segment have the same orientation. Compare the bottom
|
||||
// row segments and use those results to determine our ordering left to right. If the
|
||||
// bottom rows extend to the left, then which ever row is above (lower value), should have
|
||||
// its associated column be to the left (lower value). Keeping lower rows (higher value)
|
||||
// on the right allows their shape to avoid being crossed by the taller shape, which will be
|
||||
// on the left.
|
||||
//
|
||||
// And if the top rows extends the right, the inverse applies.
|
||||
|
||||
switch (myBottomRowOrientation) {
|
||||
case LEFT:
|
||||
RowSegment<E> myBottomRowSegment = getBottomRowSegment();
|
||||
RowSegment<E> otherBottomRowSegment = other.getBottomRowSegment();
|
||||
return myBottomRowSegment.compareLefts(otherBottomRowSegment);
|
||||
|
||||
case RIGHT:
|
||||
myBottomRowSegment = getBottomRowSegment();
|
||||
otherBottomRowSegment = other.getBottomRowSegment();
|
||||
return -myBottomRowSegment.compareRights(otherBottomRowSegment);
|
||||
case TERMINAL:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given column segments overlaps vertically.
|
||||
* @param other the other column segment to compare
|
||||
* @return true if these would overlap if drawn with same x column coordinate
|
||||
*/
|
||||
public boolean overlaps(ColumnSegment<E> other) {
|
||||
if (getVirtualMinY() > other.getVirtualMaxY()) {
|
||||
return false;
|
||||
}
|
||||
if (getVirtualMaxY() < other.getVirtualMinY()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RowSegment<E> nextSegment() {
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RowSegment<E> previousSegment() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is the first segment in an edge articulation point list.
|
||||
* @return true if this is the first segment in an edge articulation point list
|
||||
*/
|
||||
public boolean isStartSegment() {
|
||||
return previous == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is the last segment in an edge articulation point list.
|
||||
* @return true if this is the last segment in an edge articulation point list
|
||||
*/
|
||||
public boolean isEndSegment() {
|
||||
return next == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a top y position assuming rows are one million pixels apart for comparison purposes.
|
||||
* It takes into account the assigned offsets of the attached rows. This method depends on
|
||||
* row offsets having already been assigned.
|
||||
* @return a virtual top y position only useful for comparison purposes.
|
||||
*/
|
||||
public int getVirtualMinY() {
|
||||
return Math.min(getVirtualStartY(), getVirtualEndY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bottom y position assuming rows are one million pixels apart for comparison
|
||||
* purposes. It takes into account the assigned offsets of the attached rows. This method
|
||||
* depends on row offsets having already been assigned.
|
||||
* @return a virtual bottom y position only useful for comparison purposes.
|
||||
*/
|
||||
public int getVirtualMaxY() {
|
||||
return Math.max(getVirtualStartY(), getVirtualEndY());
|
||||
}
|
||||
|
||||
private int getVirtualStartY() {
|
||||
// start segments are given a slight downward offset for comparison purposes to
|
||||
// avoid being overlapped by ends segments that end on the same vertex as we begin
|
||||
int offset = previous == null ? 1 : previous.getOffset();
|
||||
|
||||
return getStartRow() * 1000000 + offset;
|
||||
}
|
||||
|
||||
private int getVirtualEndY() {
|
||||
// end segments are given a slight upward offset for comparison purposes to
|
||||
// avoid being overlapped by ends segments that start on the same vertex as we end
|
||||
int offset = next == null ? -1 : next.getOffset();
|
||||
return getEndRow() * 1000000 + offset;
|
||||
}
|
||||
|
||||
private RowSegment<E> getTopRowSegment() {
|
||||
return flowsUp() ? next : previous;
|
||||
}
|
||||
|
||||
private RowSegment<E> getBottomRowSegment() {
|
||||
return flowsUp() ? previous : next;
|
||||
}
|
||||
|
||||
private RowOrientation getOrientationForTopRow() {
|
||||
if (isStartSegment()) {
|
||||
return RowOrientation.TERMINAL;
|
||||
}
|
||||
RowSegment<E> topRowSegment = getTopRowSegment();
|
||||
int topRowOtherCol = flowsUp() ? topRowSegment.getEndCol() : topRowSegment.getStartCol();
|
||||
return topRowOtherCol < getCol() ? RowOrientation.LEFT : RowOrientation.RIGHT;
|
||||
}
|
||||
|
||||
private RowOrientation getOrientationForBottomRow() {
|
||||
if (isEndSegment()) {
|
||||
return RowOrientation.TERMINAL;
|
||||
}
|
||||
RowSegment<E> bottomRowSegment = getBottomRowSegment();
|
||||
int bottomRowOtherCol =
|
||||
flowsUp() ? bottomRowSegment.getStartCol() : bottomRowSegment.getEndCol();
|
||||
return bottomRowOtherCol < getCol() ? RowOrientation.LEFT : RowOrientation.RIGHT;
|
||||
}
|
||||
|
||||
private boolean flowsUp() {
|
||||
return getStartRow() > getEndRow();
|
||||
}
|
||||
|
||||
public ColumnSegment<E> last() {
|
||||
if (isEndSegment()) {
|
||||
return this;
|
||||
}
|
||||
return next.last();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.graph.viewer.layout.GridPoint;
|
||||
|
||||
/**
|
||||
* Base class for edge segments that are part of an articulated edge. Basically, edge articulations
|
||||
* are stored as a list of {@link GridPoint}s while in grid space. Each pair of points in the list
|
||||
* of points represents either a row or column segment. These segments are useful for orthogonal
|
||||
* edge routing algorithms as they provide a higher level API instead of dealing directly with
|
||||
* the points list.
|
||||
* <P>
|
||||
* Each segment has its related edge object and the full list of articulation points so that can
|
||||
* also provide information on its connected segments. The point index is simply the index into
|
||||
* the points list of the first point in the segment that this segment object represents.
|
||||
* <P>
|
||||
* Segments also maintain a linked list to the other segments that make up the edge which can
|
||||
* be retrieved via the {@link #nextSegment()} and {@link #previousSegment()} methods respectively.
|
||||
*
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public abstract class EdgeSegment<E> {
|
||||
|
||||
protected E edge;
|
||||
protected List<GridPoint> points; // this is a list of all articulations points for the edge
|
||||
protected int pointIndex; // the index into the points of the first point in the segment
|
||||
private int offset; // holds any offset assigned to this segment by edge routing
|
||||
|
||||
public EdgeSegment(E e, List<GridPoint> points, int pointIndex) {
|
||||
this.edge = e;
|
||||
this.points = points;
|
||||
this.pointIndex = pointIndex;
|
||||
}
|
||||
|
||||
public E getEdge() {
|
||||
return edge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset from the grid line for this segment. Edge routing algorithms will set
|
||||
* this value to keep overlapping segments in the same row or column for being assigned to
|
||||
* the same exact layout space location.
|
||||
* @param offset the distance from the grid line to use when assigning to layout space
|
||||
*/
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of x or y space to use when assigning to layout space to prevent this segment
|
||||
* from overlapping segments from other edges.
|
||||
* @return the offset from the grid line.
|
||||
*/
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s, i = %d, offset = %s", edge, pointIndex, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this edge ends at or above its start row.
|
||||
* @return true if this edge ends at or above its start row
|
||||
*/
|
||||
public boolean isBackEdge() {
|
||||
GridPoint start = points.get(0);
|
||||
GridPoint end = points.get(points.size() - 1);
|
||||
return start.row >= end.row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this segment starts at the given point.
|
||||
* @param p the grid point to check
|
||||
* @return true if this segment starts at the given point
|
||||
*/
|
||||
public boolean startsAt(GridPoint p) {
|
||||
return points.get(pointIndex).equals(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next edge segment after this one or null if this is the last segment. If the
|
||||
* this segment is a RowSegment, the next segment will be a ColumnSegment and vise-versa.
|
||||
* @return the next edge segment.
|
||||
*/
|
||||
public abstract EdgeSegment<E> nextSegment();
|
||||
|
||||
/**
|
||||
* Returns the previous edge segment before this one or null if this is the first segment. If
|
||||
* the this segment is a RowSegment, the previous segment will be a ColumnSegment and
|
||||
* vise-versa.
|
||||
* @return the previous edge segment.
|
||||
*/
|
||||
public abstract EdgeSegment<E> previousSegment();
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.graph.viewer.layout.GridLocationMap;
|
||||
import ghidra.graph.viewer.layout.GridPoint;
|
||||
|
||||
/**
|
||||
* Organizes all edge segments from a {@link GridLocationMap} into rows and column objects and
|
||||
* then assigns offsets to overlapping segments within a row or column. Offsets are values that
|
||||
* represent either x or y distances that will later be added to a row segment's y coordinate or a
|
||||
* column segment's x coordinate to keep them from overlapping in layout space.
|
||||
* <P>
|
||||
* The offsets have to be computed before sizing the grid because the offsets affect
|
||||
* the size of the grid rows and columns.
|
||||
*
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class EdgeSegmentMap<E> {
|
||||
|
||||
private Map<Integer, RowSegmentList<E>> rowSegmentMap = new HashMap<>();
|
||||
private Map<Integer, ColSegmentList<E>> colSegmentMap = new HashMap<>();
|
||||
|
||||
public EdgeSegmentMap(GridLocationMap<?, E> grid) {
|
||||
createEdgeSegments(grid);
|
||||
assignEdgeSegmentOffsets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all edge row segment lists.
|
||||
* @return a collection of all edge row segment lists
|
||||
*/
|
||||
public Collection<RowSegmentList<E>> rowSegments() {
|
||||
return rowSegmentMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of all edge column segment lists.
|
||||
* @return a collection of all edge column segment lists
|
||||
*/
|
||||
public Collection<ColSegmentList<E>> colSegments() {
|
||||
return colSegmentMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the column segment object for the given edge and start point.
|
||||
* @param edge the edge for which to find its column segment object
|
||||
* @param gridPoint the start point for the desired segment object
|
||||
* @return the column segment object for the given edge and start point.
|
||||
*/
|
||||
public ColumnSegment<E> getColumnSegment(E edge, GridPoint gridPoint) {
|
||||
ColSegmentList<E> colSegments = colSegmentMap.get(gridPoint.col);
|
||||
return colSegments == null ? null : colSegments.getSegment(edge, gridPoint);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
rowSegmentMap.clear();
|
||||
colSegmentMap.clear();
|
||||
}
|
||||
|
||||
private void createEdgeSegments(GridLocationMap<?, E> grid) {
|
||||
for (E edge : grid.edges()) {
|
||||
|
||||
List<GridPoint> gridPoints = grid.getArticulations(edge);
|
||||
ColumnSegment<E> colSegment = new ColumnSegment<E>(edge, gridPoints);
|
||||
|
||||
addColSegment(colSegment);
|
||||
|
||||
// segments always start and end with a column segment, so any additional segments
|
||||
// will be in pairs of a row segment followed by a column segment
|
||||
while (!colSegment.isEndSegment()) {
|
||||
RowSegment<E> rowSegment = colSegment.nextSegment();
|
||||
addRowSegment(rowSegment);
|
||||
colSegment = rowSegment.nextSegment();
|
||||
addColSegment(colSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assignEdgeSegmentOffsets() {
|
||||
for (RowSegmentList<E> rowSegments : rowSegmentMap.values()) {
|
||||
rowSegments.assignOffsets();
|
||||
}
|
||||
for (ColSegmentList<E> colSegments : colSegmentMap.values()) {
|
||||
colSegments.assignOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
private void addRowSegment(RowSegment<E> rowSegment) {
|
||||
int row = rowSegment.getRow();
|
||||
RowSegmentList<E> edgeRow =
|
||||
rowSegmentMap.computeIfAbsent(row, k -> new RowSegmentList<E>(k));
|
||||
edgeRow.addSegment(rowSegment);
|
||||
}
|
||||
|
||||
private void addColSegment(ColumnSegment<E> colSegment) {
|
||||
int col = colSegment.getCol();
|
||||
ColSegmentList<E> edgeCol =
|
||||
colSegmentMap.computeIfAbsent(col, k -> new ColSegmentList<E>(k));
|
||||
edgeCol.addSegment(colSegment);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import edu.uci.ics.jung.visualization.renderers.BasicEdgeRenderer;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.FGEdgeRenderer;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.program.model.symbol.FlowType;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
|
||||
/**
|
||||
* Adapts the {@link AbstractFlowChartLayout} to work for {@link FunctionGraph}s.
|
||||
*/
|
||||
public class FGFlowChartLayout extends AbstractFlowChartLayout<FGVertex, FGEdge>
|
||||
implements FGLayout {
|
||||
|
||||
private FunctionGraphOptions options;
|
||||
|
||||
protected FGFlowChartLayout(FunctionGraph graph, boolean leftAligned) {
|
||||
super(graph, new FGEdgeComparator(), leftAligned);
|
||||
this.options = graph.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionGraph getVisualGraph() {
|
||||
return (FunctionGraph) super.getVisualGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedLayout(
|
||||
VisualGraph<FGVertex, FGEdge> newGraph) {
|
||||
return new FGFlowChartLayout((FunctionGraph) newGraph, leftAligned);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FGLayout cloneLayout(VisualGraph<FGVertex, FGEdge> newGraph) {
|
||||
VisualGraphLayout<FGVertex, FGEdge> clone = super.cloneLayout(newGraph);
|
||||
return (FGLayout) clone;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCondensedLayout() {
|
||||
return options.useCondensedLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicEdgeRenderer<FGVertex, FGEdge> getEdgeRenderer() {
|
||||
return new FGEdgeRenderer();
|
||||
}
|
||||
|
||||
private static class FGEdgeComparator implements Comparator<FGEdge> {
|
||||
@Override
|
||||
public int compare(FGEdge e1, FGEdge e2) {
|
||||
return priority(e1).compareTo(priority(e2));
|
||||
}
|
||||
|
||||
private Integer priority(FGEdge e) {
|
||||
FlowType type = e.getFlowType();
|
||||
// making fall through edges a higher priority, makes it more likely that vertices
|
||||
// with fall through connections will be direct descendants (closer) when the graph is
|
||||
// converted to a tree.
|
||||
if (type == RefType.FALL_THROUGH) {
|
||||
return 1; // lower is more preferred
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FGVertex getRoot(VisualGraph<FGVertex, FGEdge> g) {
|
||||
if (graph instanceof FunctionGraph fg) {
|
||||
return fg.getRootVertex();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProviderExtensionPoint;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Makes the Flow Chart Layout available for the function graph feature.
|
||||
*/
|
||||
public class FlowChartLayoutProvider extends FGLayoutProviderExtensionPoint {
|
||||
private static final Icon ICON = new GIcon("icon.plugin.functiongraph.layout.flowchart");
|
||||
|
||||
@Override
|
||||
public String getLayoutName() {
|
||||
return "Flow Chart";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getActionIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriorityLevel() {
|
||||
return 140;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
return new FGFlowChartLayout(graph, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProviderExtensionPoint;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Makes the Left Aligned Flow Chart Layout available for the function graph feature.
|
||||
*/
|
||||
public class LeftAlignedFlowChartLayoutProvider extends FGLayoutProviderExtensionPoint {
|
||||
private static final Icon ICON = new GIcon("icon.plugin.functiongraph.layout.flowchart.left");
|
||||
|
||||
@Override
|
||||
public String getLayoutName() {
|
||||
return "Flow Chart (Left)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getActionIcon() {
|
||||
return ICON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriorityLevel() {
|
||||
return 130;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
return new FGFlowChartLayout(graph, true);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.graph.viewer.VisualEdge;
|
||||
import ghidra.graph.viewer.VisualVertex;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
|
||||
/**
|
||||
* Routes edges orthogonally for vertices positioned in a {@link GridLocationMap}.
|
||||
* This algorithm creates articulation points such that outgoing edges always exit the
|
||||
* start vertex from the bottom and enter the end vertex from the top.
|
||||
* <P>
|
||||
* There are only three types of edges created by this algorithm. The first
|
||||
* type is an edge with one segment that goes directly from a start vertex straight down to an
|
||||
* end vertex.
|
||||
* <P>
|
||||
* The second type is an edge with three segments that goes down from the start vertex,
|
||||
* goes left or right to the column of the end vertex and then goes down to a child vertex.
|
||||
* <P>
|
||||
* The third type consists of 5 segments and can connect any two vertices in the graph. It starts
|
||||
* by going down to the next row from the start vertex, then goes left or right until it finds a
|
||||
* column where there are no vertices between that row and the row above the end vertex. It then
|
||||
* goes left or right in that row to the column of the end vertex and then down to that vertex.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
|
||||
public class OrthogonalEdgeRouter<V extends VisualVertex, E extends VisualEdge<V>> {
|
||||
private Function<E, GridRange> excludedColumnsFunction;
|
||||
private GridLocationMap<V, E> grid;
|
||||
private Map<Integer, Column<V>> columnMap;
|
||||
|
||||
public OrthogonalEdgeRouter(GridLocationMap<V, E> grid) {
|
||||
this.grid = grid;
|
||||
columnMap = grid.columnsMap();
|
||||
excludedColumnsFunction = e -> new GridRange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a list of articulations points in grid space for each edge in the given collection
|
||||
* and sets those points into the {@link GridLocationMap}.
|
||||
* @param edges computes articulations points in grid space and sets them on the grid
|
||||
*/
|
||||
public void computeAndSetEdgeArticulations(Collection<E> edges) {
|
||||
for (E e : edges) {
|
||||
V v1 = e.getStart();
|
||||
V v2 = e.getEnd();
|
||||
GridPoint p1 = grid.gridPoint(v1);
|
||||
GridPoint p2 = grid.gridPoint(v2);
|
||||
int routingCol = findRoutingColumn(e, p1, p2);
|
||||
List<GridPoint> edgePoints = getEdgePoints(p1, p2, routingCol);
|
||||
grid.setArticulations(e, edgePoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a function that can be used to prevent edges from being routed in a range of columns.
|
||||
* One use of this is to prevent back edges from intersecting any child trees in its ancestor
|
||||
* hierarchy between the start vertex and the end vertex.
|
||||
* @param excludedColumnsFunction the function to call to compute a range of columns to
|
||||
* prevent routing edges.
|
||||
*/
|
||||
public void setColumnExclusionFunction(Function<E, GridRange> excludedColumnsFunction) {
|
||||
this.excludedColumnsFunction = excludedColumnsFunction;
|
||||
}
|
||||
|
||||
private int findRoutingColumn(E e, GridPoint p1, GridPoint p2) {
|
||||
if (p2.row == p1.row + 2) {
|
||||
return p1.col; // direct child
|
||||
}
|
||||
int startRow = Math.min(p1.row + 1, p2.row - 1);
|
||||
int endRow = Math.max(p1.row + 1, p2.row - 1);
|
||||
int startCol = Math.min(p1.col, p2.col);
|
||||
int endCol = Math.max(p1.col, p2.col);
|
||||
boolean isBackEdge = p2.row <= p1.row;
|
||||
|
||||
// If not a back edge, try to route in between start and end columns, but we decided not
|
||||
// to ever route back edges in between so that back edges have C-shape or backwards C-shape
|
||||
// and not a Z-shape.
|
||||
if (!isBackEdge) {
|
||||
// try if either start or end column is open
|
||||
if (isOpenPath(p1.col, startRow, endRow)) {
|
||||
return p1.col;
|
||||
}
|
||||
if (isOpenPath(p2.col, startRow, endRow)) {
|
||||
return p2.col;
|
||||
}
|
||||
|
||||
for (int col = startCol + 1; col <= endCol - 1; col++) {
|
||||
if (isOpenPath(col, startRow, endRow)) {
|
||||
return col;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get an optional excluded range where we don't want to route a specific edge. By
|
||||
// default the range is empty, allowing any column.
|
||||
GridRange excludedRange = excludedColumnsFunction.apply(e);
|
||||
|
||||
// try each each left and right column expanding outwards, starting at the columns of
|
||||
// the start and end vertex.
|
||||
for (int i = 1; i <= startCol; i++) {
|
||||
int left = startCol - i;
|
||||
int right = endCol + i;
|
||||
|
||||
boolean leftExcluded = excludedRange.contains(left);
|
||||
boolean rightExcluded = excludedRange.contains(right);
|
||||
boolean leftValid = !leftExcluded && isOpenPath(left, startRow, endRow);
|
||||
boolean rightValid = !rightExcluded && isOpenPath(right, startRow, endRow);
|
||||
if (leftValid) {
|
||||
if (!rightValid) {
|
||||
return left;
|
||||
}
|
||||
// if both are open, prefer left for forward edges, right for back edges
|
||||
return p1.row < p2.row ? left : right;
|
||||
}
|
||||
else if (rightValid) {
|
||||
return right;
|
||||
}
|
||||
}
|
||||
return 0; // 0 is always open as we avoid putting vertices in the 0 column
|
||||
}
|
||||
|
||||
private boolean isOpenPath(int col, int startRow, int endRow) {
|
||||
Column<V> column = columnMap.get(col);
|
||||
if (column == null) {
|
||||
return true;
|
||||
}
|
||||
return column.isOpenBetween(startRow, endRow);
|
||||
}
|
||||
|
||||
private List<GridPoint> getEdgePoints(GridPoint p1, GridPoint p2, int routingCol) {
|
||||
List<GridPoint> points = new ArrayList<GridPoint>();
|
||||
|
||||
points.add(p1);
|
||||
|
||||
if (routingCol == p1.col) {
|
||||
if (routingCol != p2.col) {
|
||||
points.add(new GridPoint(p2.row - 1, p1.col));
|
||||
points.add(new GridPoint(p2.row - 1, p2.col));
|
||||
}
|
||||
}
|
||||
else {
|
||||
points.add(new GridPoint(p1.row + 1, p1.col));
|
||||
points.add(new GridPoint(p1.row + 1, routingCol));
|
||||
if (routingCol != p2.col) {
|
||||
points.add(new GridPoint(p2.row - 1, routingCol));
|
||||
points.add(new GridPoint(p2.row - 1, p2.col));
|
||||
}
|
||||
}
|
||||
points.add(p2);
|
||||
return points;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
|
||||
/**
|
||||
* Computes positions in layout space for {@link GridLocationMap}s that have orthogonally routed
|
||||
* edges.
|
||||
* <P>
|
||||
* At this point, the grid has been populated with vertices at specific grid points and edges with
|
||||
* lists of grid articulation points for their routing. Conceptually, vertices live at the
|
||||
* intersection of the grid lines and edges are routed along grid lines. While in grid space,
|
||||
* vertices have no size and edges can overlap each other along grid lines. In order to map these
|
||||
* virtual grid locations to points in layout space, we need to use the size of each vertex and
|
||||
* offsets assigned to parallel edge segments that share a grid line.
|
||||
* <P>
|
||||
* We first need to compute sizes for the rows and columns in the grid. For purposes of this
|
||||
* algorithm, the size of a row N is defined to be the distance between grid line row N and grid
|
||||
* line row N+1. The same applies to column sizes. The way vertex sizes are applied is slightly
|
||||
* different for rows and column. Vertices are centered on grid column lines, but
|
||||
* completely below grid row lines. So if a vertex is at grid point 1,1, all of its height is
|
||||
* assigned to grid row 1. But its width is split between grid column 0 and grid column 1. Edges
|
||||
* work similarly, in that parallel horizontal edge segments extend below their grid row, but
|
||||
* parallel column segments are split so that some have offsets that are before the vertical
|
||||
* grid line and some are after.
|
||||
* <P>
|
||||
* The row sizing is straight forward. Even rows only contain edges and odd rows only contain
|
||||
* vertices. Since the height of a vertex is assigned completely to one row, that row's height
|
||||
* is simply the maximum height of all the vertices in that row, plus any row padding.
|
||||
* <P>
|
||||
* Column sizing is more complicated. The width of any column is going to be the the max of either
|
||||
* 1) vertices that half extend from the left + the thickness of edges that extend the right, OR
|
||||
* 2) vertices that half extend from the right + the thickness of edges the extend from the left.
|
||||
* Also, column padding is applied differently. For columns, padding is not just added to the
|
||||
* column width like in rows. Instead, it acts as a minimum "edge thickness". In other words if the
|
||||
* edge thickness is less than the padding, the edge thickness doesn't make the gaps bigger. Only if
|
||||
* the edge thickness is greater the the column padding, then it determines the gap and the
|
||||
* column padding contributes nothing.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class OrthogonalGridSizer<V, E> {
|
||||
private static final int EDGE_ROW_PADDING = 25; // space before an edge row
|
||||
private static final int VERTEX_ROW_PADDING = 35; // space before a vertex vow
|
||||
private static final int COL_PADDING = 30; // minimum space for a column
|
||||
private static final int CONDENSED_EDGE_ROW_PADDING = 15;
|
||||
private static final int CONDENSED_VERTEX_ROW_PADDING = 25;
|
||||
private static final int CONDENSED_COL_PADDING = 15;
|
||||
|
||||
private int[] rowHeights;
|
||||
private int[] beforeColEdgeWidths;
|
||||
private int[] afterColEdgeWidths;
|
||||
private int[] colVertexWidths;
|
||||
private int edgeSpacing;
|
||||
|
||||
public OrthogonalGridSizer(GridLocationMap<V, E> gridMap, EdgeSegmentMap<E> segmentMap,
|
||||
Function<V, Shape> transformer, int edgeSpacing) {
|
||||
this.edgeSpacing = edgeSpacing;
|
||||
rowHeights = new int[gridMap.height()];
|
||||
beforeColEdgeWidths = new int[gridMap.width()];
|
||||
afterColEdgeWidths = new int[gridMap.width()];
|
||||
colVertexWidths = new int[gridMap.width()];
|
||||
|
||||
addVertexSizes(gridMap, transformer);
|
||||
|
||||
addEdgeRowSizes(gridMap, segmentMap);
|
||||
|
||||
addEdgeColSizes(gridMap, segmentMap);
|
||||
}
|
||||
|
||||
public GridCoordinates getGridCoordinates(boolean isCondensed) {
|
||||
int[] rowStarts = new int[rowCount() + 1];
|
||||
int[] colStarts = new int[colCount() + 1];
|
||||
int vertexRowPadding = isCondensed ? CONDENSED_VERTEX_ROW_PADDING : VERTEX_ROW_PADDING;
|
||||
int edgeRowPadding = isCondensed ? CONDENSED_EDGE_ROW_PADDING : EDGE_ROW_PADDING;
|
||||
int colPadding = isCondensed ? CONDENSED_COL_PADDING : COL_PADDING;
|
||||
for (int row = 1; row < rowStarts.length; row++) {
|
||||
// edges rows are even, vertex rows are odd
|
||||
int rowPadding = row % 2 == 0 ? edgeRowPadding : vertexRowPadding;
|
||||
rowStarts[row] = rowStarts[row - 1] + height(row - 1, rowPadding);
|
||||
}
|
||||
for (int col = 1; col < colStarts.length; col++) {
|
||||
colStarts[col] = colStarts[col - 1] + width(col - 1, colPadding);
|
||||
}
|
||||
|
||||
return new GridCoordinates(rowStarts, colStarts);
|
||||
}
|
||||
|
||||
private int rowCount() {
|
||||
return rowHeights.length;
|
||||
}
|
||||
|
||||
private int colCount() {
|
||||
return colVertexWidths.length;
|
||||
}
|
||||
|
||||
private int height(int row, int rowPadding) {
|
||||
return rowHeights[row] + rowPadding;
|
||||
}
|
||||
|
||||
private int width(int col, int minColPadding) {
|
||||
int leftEdgeWidth = Math.max(afterColEdgeWidths[col], minColPadding);
|
||||
int leftVertexWidth = colVertexWidths[col] / 2;
|
||||
int rightEdgeWidth = 0;
|
||||
int rightVertexWidth = 0;
|
||||
if (col < colVertexWidths.length - 1) {
|
||||
rightEdgeWidth = Math.max(beforeColEdgeWidths[col + 1], minColPadding);
|
||||
rightVertexWidth = colVertexWidths[col + 1] / 2;
|
||||
}
|
||||
|
||||
int width = Math.max(leftEdgeWidth + rightVertexWidth, rightEdgeWidth + leftVertexWidth);
|
||||
width = Math.max(width, leftEdgeWidth + rightEdgeWidth);
|
||||
return width;
|
||||
}
|
||||
|
||||
private void addEdgeColSizes(GridLocationMap<V, E> gridMap, EdgeSegmentMap<E> segmentMap) {
|
||||
for (ColSegmentList<E> colSegments : segmentMap.colSegments()) {
|
||||
int col = colSegments.getCol();
|
||||
int edgesToLeft = -colSegments.getMinOffset();
|
||||
int edgesToRight = colSegments.getMaxOffset();
|
||||
addColumnEdgeWidth(col, edgesToLeft * edgeSpacing, edgesToRight * edgeSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEdgeRowSizes(GridLocationMap<V, E> gridMap, EdgeSegmentMap<E> segmentMap) {
|
||||
// edge rows have no vertices, so its height just depends on the number of parallel edges
|
||||
for (RowSegmentList<E> rowSegments : segmentMap.rowSegments()) {
|
||||
int row = rowSegments.getRow();
|
||||
int edgeCount = rowSegments.getMaxOffset() - rowSegments.getMinOffset();
|
||||
addRowHeight(row, edgeCount * edgeSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
private void addVertexSizes(GridLocationMap<V, E> gridMap, Function<V, Shape> transformer) {
|
||||
Map<V, GridPoint> vertexPoints = gridMap.getVertexPoints();
|
||||
|
||||
for (Entry<V, GridPoint> entry : vertexPoints.entrySet()) {
|
||||
V v = entry.getKey();
|
||||
GridPoint p = entry.getValue();
|
||||
Shape shape = transformer.apply(v);
|
||||
Rectangle vertexBounds = shape.getBounds();
|
||||
addRowHeight(p.row, vertexBounds.height);
|
||||
addColumnVertexWidth(p.col, vertexBounds.width);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRowHeight(int row, int height) {
|
||||
rowHeights[row] = Math.max(rowHeights[row], height);
|
||||
}
|
||||
|
||||
private void addColumnVertexWidth(int col, int width) {
|
||||
colVertexWidths[col] = Math.max(colVertexWidths[col], width);
|
||||
}
|
||||
|
||||
private void addColumnEdgeWidth(int col, int beforeWidth, int afterWidth) {
|
||||
beforeColEdgeWidths[col] = Math.max(beforeColEdgeWidths[col], beforeWidth);
|
||||
afterColEdgeWidths[col] = Math.max(afterColEdgeWidths[col], afterWidth);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ghidra.graph.viewer.VisualEdge;
|
||||
import ghidra.graph.viewer.VisualVertex;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
|
||||
/**
|
||||
* Maps the grid points of vertices and edges in a {@link GridLocationMap} to x,y locations in
|
||||
* layout space. The mapping is specifically for grids that contain edges that have been given
|
||||
* orthogonally routed articulation points.
|
||||
* <P>
|
||||
* First the edges have to be organized into row and column segments so that overlapping segments
|
||||
* can be assigned offsets from the grid lines so that they won't overlap in layout space. Also,
|
||||
* the offset information is need to provide "thickness" for the edges in a row or column.
|
||||
* Next, the grid size and positioning in layout space is computed. Finally, it is a simple matter
|
||||
* to translate grid points for vertices directly into points in layout space. Edge points are
|
||||
* similar, but they need to add their assigned offset for each segment to its corresponding
|
||||
* translated grid articulation point.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class OrthogonalGridToLayoutMapper<V extends VisualVertex, E extends VisualEdge<V>> {
|
||||
private static final int EDGE_SPACING = 7;
|
||||
private static final int CONDENSED_EDGE_SPACING = 5;
|
||||
|
||||
private EdgeSegmentMap<E> segmentMap;
|
||||
private GridLocationMap<V, E> grid;
|
||||
private Function<V, Shape> transformer;
|
||||
private GridCoordinates gridCoordinates;
|
||||
private int edgeSpacing;
|
||||
|
||||
public OrthogonalGridToLayoutMapper(GridLocationMap<V, E> grid, Function<V, Shape> transformer,
|
||||
boolean isCondensed) {
|
||||
this.grid = grid;
|
||||
this.transformer = transformer;
|
||||
this.edgeSpacing = isCondensed ? CONDENSED_EDGE_SPACING : EDGE_SPACING;
|
||||
|
||||
// organize the edge articulations into segments by row and column
|
||||
segmentMap = new EdgeSegmentMap<E>(grid);
|
||||
|
||||
// compute row and column sizes
|
||||
OrthogonalGridSizer<V, E> sizer =
|
||||
new OrthogonalGridSizer<>(grid, segmentMap, transformer, edgeSpacing);
|
||||
|
||||
// get the grid coordinates from the sizer.
|
||||
gridCoordinates = sizer.getGridCoordinates(isCondensed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of vertices to points in layout space.
|
||||
* @return a map of vertices to points in layout space
|
||||
*/
|
||||
public Map<V, Point2D> getVertexLocations() {
|
||||
|
||||
Map<V, Point2D> vertexLocations = new HashMap<>();
|
||||
|
||||
for (Entry<V, GridPoint> entry : grid.getVertexPoints().entrySet()) {
|
||||
V v = entry.getKey();
|
||||
GridPoint p = entry.getValue();
|
||||
|
||||
Shape shape = transformer.apply(v);
|
||||
Rectangle vertexBounds = shape.getBounds();
|
||||
int x = gridCoordinates.x(p.col);
|
||||
int y = gridCoordinates.y(p.row) + (vertexBounds.height >> 1);
|
||||
|
||||
Point2D location = new Point2D.Double(x, y);
|
||||
vertexLocations.put(v, location);
|
||||
}
|
||||
return vertexLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of edges to lists of articulation points in layout space.
|
||||
* @param vertexLocations the locations of vertices already mapped in layout space.
|
||||
* @return a map of edges to lists of articulation points in layout space
|
||||
*/
|
||||
public Map<E, List<Point2D>> getEdgeLocations(Map<V, Point2D> vertexLocations) {
|
||||
Map<E, List<Point2D>> edgeLocations = new HashMap<>();
|
||||
for (E edge : grid.edges()) {
|
||||
|
||||
List<GridPoint> gridPoints = grid.getArticulations(edge);
|
||||
List<Point2D> points = new ArrayList<>(gridPoints.size());
|
||||
|
||||
GridPoint gp = gridPoints.get(0);
|
||||
EdgeSegment<E> edgeSegment = segmentMap.getColumnSegment(edge, gp);
|
||||
|
||||
int offset = edgeSegment.getOffset();
|
||||
double x = gridCoordinates.x(gp.col) + offset * edgeSpacing;
|
||||
double y = vertexLocations.get(edge.getStart()).getY();
|
||||
points.add(new Point2D.Double(x, y));
|
||||
|
||||
for (int i = 1; i < gridPoints.size() - 1; i++) {
|
||||
edgeSegment = edgeSegment.nextSegment();
|
||||
gp = gridPoints.get(i);
|
||||
if (i % 2 == 0) {
|
||||
offset = edgeSegment.getOffset();
|
||||
x = gridCoordinates.x(gp.col) + offset * edgeSpacing;
|
||||
points.add(new Point2D.Double(x, y));
|
||||
|
||||
}
|
||||
else {
|
||||
offset = edgeSegment.getOffset();
|
||||
y = gridCoordinates.y(gp.row) + offset * edgeSpacing;
|
||||
points.add(new Point2D.Double(x, y));
|
||||
}
|
||||
}
|
||||
gp = gridPoints.get(gridPoints.size() - 1);
|
||||
y = vertexLocations.get(edge.getEnd()).getY();
|
||||
points.add(new Point2D.Double(x, y));
|
||||
edgeLocations.put(edge, points);
|
||||
}
|
||||
|
||||
return edgeLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the computed grid coordinates.
|
||||
* @return the computed grid coordinates
|
||||
*/
|
||||
public GridCoordinates getGridCoordinates() {
|
||||
return gridCoordinates;
|
||||
}
|
||||
|
||||
public List<Integer> getEdgeOffsets(E edge) {
|
||||
List<Integer> offsets = new ArrayList<>();
|
||||
List<GridPoint> gridPoints = grid.getArticulations(edge);
|
||||
|
||||
GridPoint gp = gridPoints.get(0);
|
||||
EdgeSegment<E> edgeSegment = segmentMap.getColumnSegment(edge, gp);
|
||||
while (edgeSegment != null) {
|
||||
offsets.add(edgeSegment.getOffset());
|
||||
edgeSegment = edgeSegment.nextSegment();
|
||||
}
|
||||
return offsets;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
segmentMap.dispose();
|
||||
grid.dispose();
|
||||
gridCoordinates = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.graph.viewer.layout.GridPoint;
|
||||
|
||||
/**
|
||||
* Horizontal edge segments of an edge with articulation points. Each pair of points in the list
|
||||
* of articulation points corresponds to either a column segment or a row segment. There is
|
||||
* a built-in assumption in the compareTo algorithm that the list of articulation points always
|
||||
* start and end with a column segment. See {@link EdgeSegment} for more information.
|
||||
*
|
||||
* @param <E> The edge type
|
||||
*/
|
||||
public class RowSegment<E> extends EdgeSegment<E> implements Comparable<RowSegment<E>> {
|
||||
// specifies the orientation of the column attached to this segment (either left or right)
|
||||
enum ColumnOrientation {
|
||||
UP, // the attached column extends upwards from this row
|
||||
DOWN // the attached column extends downwards from this row
|
||||
}
|
||||
|
||||
private ColumnSegment<E> next;
|
||||
private ColumnSegment<E> previous;
|
||||
|
||||
RowSegment(ColumnSegment<E> previous, E e, List<GridPoint> points, int pointIndex) {
|
||||
super(e, points, pointIndex);
|
||||
this.previous = previous;
|
||||
// row segments always have a follow-on column segment
|
||||
this.next = new ColumnSegment<E>(this, e, points, pointIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grid row index this row segment.
|
||||
* @return the grid row index this row segment
|
||||
*/
|
||||
public int getRow() {
|
||||
return points.get(pointIndex).row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the column where this row segment starts. Note that this is different
|
||||
* from the left column. The start column is in the order of the articulation points whereas
|
||||
* the left column is always the left most spatially column (lower column index) of either the
|
||||
* start column or end column.
|
||||
* @return the index of the grid column for the start point of this segment
|
||||
*/
|
||||
public int getStartCol() {
|
||||
return points.get(pointIndex).col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the column where this row segment ends. Note that this is different
|
||||
* from the right column. The end column is in the order of the articulation points whereas
|
||||
* the right column is always the right most spatially column (higher column index) of either
|
||||
* the start column or end column.
|
||||
* @return the index of the grid column for the start point of this segment
|
||||
*/
|
||||
public int getEndCol() {
|
||||
return points.get(pointIndex + 1).col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column index of the left most column of this row segment.
|
||||
* @return the column index of the left most column of this row segment
|
||||
*/
|
||||
public int getLeftCol() {
|
||||
return Math.min(getStartCol(), getEndCol());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column index of the right most column of this row segment.
|
||||
* @return the column index of the right most column of this row segment
|
||||
*/
|
||||
public int getRightCol() {
|
||||
return Math.max(getStartCol(), getEndCol());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RowSegment<E> other) {
|
||||
int result = compareLefts(other);
|
||||
if (result == 0) {
|
||||
result = compareRights(other);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given row segment overlaps horizontally.
|
||||
* @param other the other column segment to compare
|
||||
* @return true if these would overlap if drawn with same y row coordinate
|
||||
*/
|
||||
public boolean overlaps(RowSegment<E> other) {
|
||||
if (getLeftCol() > other.getRightCol()) {
|
||||
return false;
|
||||
}
|
||||
if (getRightCol() < other.getLeftCol()) {
|
||||
return false;
|
||||
}
|
||||
// if the rows are exactly adjacent and if where they touch they are both attached
|
||||
// to terminal column segments, then don't consider them to overlap.
|
||||
if (getLeftCol() == other.getRightCol()) {
|
||||
return !(isLeftTerminal() && other.isRightTerminal());
|
||||
}
|
||||
else if (getRightCol() == other.getLeftCol()) {
|
||||
return !(isRightTerminal() && other.isLeftTerminal());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares row segments strictly based on the relationship of the connected column segments
|
||||
* at the left of these segments.
|
||||
* @param other the other row segment to compare
|
||||
* @return a negative integer, zero, or a positive integer as this object
|
||||
* is less than, equal to, or greater than the specified object.
|
||||
*/
|
||||
public int compareLefts(RowSegment<E> other) {
|
||||
// first, just check the rows of this segment and the other, if the rows are
|
||||
// not the same, then one column clearly above the other.
|
||||
|
||||
int result = getRow() - other.getRow();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We are in the same grid row, so the order is determined by the segment at the
|
||||
// left of the column. There are 2 possible orientations for the left column. 1) it can
|
||||
// extend upwards from our row, or it can extend downwards from our row.
|
||||
// If our left orientation is not the same as the other row segment, the order is
|
||||
// simply UP < DOWN in order to reduce edge crossings. Edges on the top
|
||||
// go up and edges on the bottom go down, so they won't cross each other.
|
||||
ColumnOrientation myLeftColOrientation = getOrientationForLeftColumn();
|
||||
ColumnOrientation otherLeftColOrientation = other.getOrientationForLeftColumn();
|
||||
|
||||
// if they are not the same, then UP < DOWN
|
||||
if (myLeftColOrientation != otherLeftColOrientation) {
|
||||
return myLeftColOrientation.compareTo(otherLeftColOrientation);
|
||||
}
|
||||
|
||||
// Both this segment's left and the other segment's left have the same orientation.
|
||||
// Compare the left column segments and use those results to determine our ordering
|
||||
// top to bottom. If the left columns extend upward, then which ever column is left most
|
||||
// (lower value), should have it's associated row lower spatially (higher row value)
|
||||
// Keeping left most columns(lower value) as lower rows allows their shape to avoid being
|
||||
// crossed by the wider shape, which will be lower.
|
||||
//
|
||||
// And if the left rows extends downwards, the inverse applies.
|
||||
ColumnSegment<E> myLeftColSegment = getLeftColSegment();
|
||||
ColumnSegment<E> otherLeftColSegment = other.getLeftColSegment();
|
||||
|
||||
if (myLeftColOrientation == ColumnOrientation.UP) {
|
||||
return -myLeftColSegment.compareTops(otherLeftColSegment);
|
||||
}
|
||||
return myLeftColSegment.compareBottoms(otherLeftColSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares row segments strictly based on the relationship of the connected column segments
|
||||
* at the right of these segments.
|
||||
* @param other the other row segment to compare
|
||||
* @return a negative integer, zero, or a positive integer as this object
|
||||
* is less than, equal to, or greater than the specified object.
|
||||
*/
|
||||
public int compareRights(RowSegment<E> other) {
|
||||
// first, just check the rows of this segment and the other, if the rows are
|
||||
// not the same, then one column clearly above the other.
|
||||
int result = getRow() - other.getRow();
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// We are in the same grid row, so the order is determined by the segment at the
|
||||
// right of the column. There are 2 possible orientations for the right column. 1) it can
|
||||
// extend upwards from our row, or it can extend downwards from our row.
|
||||
// If our right orientation is not the same as the other row segment, the order is
|
||||
// simply UP < DOWN in order to reduce edge crossings. Edges on the top
|
||||
// go up and edges on the bottom go down, so they won't cross each other.
|
||||
|
||||
ColumnOrientation myRightColOrientation = getOrientationForRightColumn();
|
||||
ColumnOrientation otherRightColOrientation = other.getOrientationForRightColumn();
|
||||
|
||||
// if they are not the same, then UP < DOWN
|
||||
if (myRightColOrientation != otherRightColOrientation) {
|
||||
return myRightColOrientation.compareTo(otherRightColOrientation);
|
||||
}
|
||||
|
||||
// Both this segment's right column and the other segment's right column have the same
|
||||
// orientation. Compare the right column segments and use those results to determine our
|
||||
// ordering top to bottom. If the right columns extend upward, then which ever column is
|
||||
// left most (lower value), should have it's associated row higher spatially (lower row
|
||||
// value). Keeping left most columns(lower value) as higher rows (lower values) allows
|
||||
// their shape to avoid being crossed by the wider shape, which will be lower.
|
||||
//
|
||||
// And if the right rows extends downwards, the inverse applies.
|
||||
ColumnSegment<E> myRightColSegment = getRightColSegment();
|
||||
ColumnSegment<E> otherRightColSegment = other.getRightColSegment();
|
||||
|
||||
if (myRightColOrientation == ColumnOrientation.UP) {
|
||||
return myRightColSegment.compareTops(otherRightColSegment);
|
||||
}
|
||||
return -myRightColSegment.compareBottoms(otherRightColSegment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnSegment<E> nextSegment() {
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnSegment<E> previousSegment() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
private ColumnOrientation getOrientationForLeftColumn() {
|
||||
ColumnSegment<E> leftSegment = getLeftColSegment();
|
||||
int leftColOtherRow = flowsLeft() ? leftSegment.getEndRow() : leftSegment.getStartRow();
|
||||
return leftColOtherRow < getRow() ? ColumnOrientation.UP : ColumnOrientation.DOWN;
|
||||
}
|
||||
|
||||
private ColumnOrientation getOrientationForRightColumn() {
|
||||
ColumnSegment<E> rightSegment = getRightColSegment();
|
||||
int rightColOtherRow = flowsLeft() ? rightSegment.getStartRow() : rightSegment.getEndRow();
|
||||
return rightColOtherRow < getRow() ? ColumnOrientation.UP : ColumnOrientation.DOWN;
|
||||
}
|
||||
|
||||
private ColumnSegment<E> getLeftColSegment() {
|
||||
return flowsLeft() ? next : previous;
|
||||
}
|
||||
|
||||
private ColumnSegment<E> getRightColSegment() {
|
||||
return flowsLeft() ? previous : next;
|
||||
}
|
||||
|
||||
private boolean flowsLeft() {
|
||||
return getStartCol() > getEndCol();
|
||||
}
|
||||
|
||||
private boolean isLeftTerminal() {
|
||||
ColumnSegment<E> left = getLeftColSegment();
|
||||
return left.isStartSegment() || left.isEndSegment();
|
||||
}
|
||||
|
||||
private boolean isRightTerminal() {
|
||||
ColumnSegment<E> right = getRightColSegment();
|
||||
return right.isStartSegment() || right.isEndSegment();
|
||||
}
|
||||
|
||||
public ColumnSegment<E> last() {
|
||||
return next.last();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
/**
|
||||
* Maintains a collection of RowSegments for the same grid row.
|
||||
*
|
||||
* @param <E> edge type
|
||||
*/
|
||||
public class RowSegmentList<E> {
|
||||
protected List<RowSegment<E>> edgeSegments = new ArrayList<>();
|
||||
int row;
|
||||
|
||||
public RowSegmentList(int row) {
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
protected void addSegment(RowSegment<E> segment) {
|
||||
edgeSegments.add(segment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return edgeSegments.toString();
|
||||
}
|
||||
|
||||
public int getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum offset of any edges in this segment list.
|
||||
* @return the minimum offset of any edges in this segment list
|
||||
*/
|
||||
public int getMinOffset() {
|
||||
int minOffset = 0;
|
||||
for (EdgeSegment<E> edgeSegment : edgeSegments) {
|
||||
minOffset = Math.min(minOffset, edgeSegment.getOffset());
|
||||
}
|
||||
return minOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum offset of any edges in this segment list.
|
||||
* @return the maximum offset of any edges in this segment list
|
||||
*/
|
||||
public int getMaxOffset() {
|
||||
int maxOffset = 0;
|
||||
for (EdgeSegment<E> edgeSegment : edgeSegments) {
|
||||
maxOffset = Math.max(maxOffset, edgeSegment.getOffset());
|
||||
}
|
||||
return maxOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns offsets for overlapping row segments. Parallel overlapping edges must be offset
|
||||
* from each other when assigned to layout space to avoid drawing over each other. Each
|
||||
* edge offset represents 1/2 the edge spacing distance. The reason offsets are assigned 2
|
||||
* apart from each other is to be consistent with column segment offsets which must be 2 apart
|
||||
* so that even numbers of edges can be centered on the grid line.
|
||||
*/
|
||||
public void assignOffsets() {
|
||||
// sorts the row edge segments from top to bottom
|
||||
Collections.sort(edgeSegments);
|
||||
|
||||
Map<Integer, RowSegmentList<E>> offsetMap =
|
||||
LazyMap.lazyMap(new HashMap<>(), k -> new RowSegmentList<E>(0));
|
||||
for (int i = 0; i < edgeSegments.size(); i++) {
|
||||
RowSegment<E> segment = edgeSegments.get(i);
|
||||
assignOffset(offsetMap, segment);
|
||||
}
|
||||
}
|
||||
|
||||
protected void assignOffset(Map<Integer, RowSegmentList<E>> offsetMap, RowSegment<E> segment) {
|
||||
|
||||
// assigning offsets to rows is easy, just find the first offset group that we don't
|
||||
// overlap with.
|
||||
|
||||
int offset = 0;
|
||||
RowSegmentList<E> segments = offsetMap.get(offset);
|
||||
|
||||
while (segments.hasOverlappingSegment(segment)) {
|
||||
offset += 2;
|
||||
segments = offsetMap.get(offset);
|
||||
}
|
||||
segment.setOffset(offset);
|
||||
segments.addSegment(segment);
|
||||
}
|
||||
|
||||
private boolean hasOverlappingSegment(RowSegment<E> segment) {
|
||||
for (RowSegment<E> edgeSegment : edgeSegments) {
|
||||
if (segment.overlaps(edgeSegment)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -56,7 +56,7 @@ public class JgtNamedLayout extends AbstractFGLayout {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getVertexLocation(FGVertex v, Column col, Row<FGVertex> row,
|
||||
protected Point2D getVertexLocation(FGVertex v, Column<FGVertex> col, Row<FGVertex> row,
|
||||
java.awt.Rectangle bounds) {
|
||||
return getCenteredVertexLocation(v, col, row, bounds);
|
||||
}
|
||||
|
@ -148,14 +148,14 @@ public class JgtNamedLayout extends AbstractFGLayout {
|
|||
for (FGEdge fgEdge : edges) {
|
||||
monitor.checkCancelled();
|
||||
|
||||
List<java.awt.Point> newPoints = new ArrayList<>();
|
||||
List<GridPoint> newPoints = new ArrayList<>();
|
||||
|
||||
List<Point> articulations = articulator.apply(fgEdge);
|
||||
for (Point point : articulations) {
|
||||
|
||||
Integer col = columns.get(point.x);
|
||||
Integer row = rows.get(point.y);
|
||||
newPoints.add(new java.awt.Point(col, row));
|
||||
newPoints.add(new GridPoint(row, col));
|
||||
}
|
||||
|
||||
// The jung layout will provide articulations at the vertex points. We do not want to
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -627,10 +627,13 @@ public class FGViewUpdater extends VisualGraphViewUpdater<FGVertex, FGEdge> {
|
|||
parentVertex.setVertexType(oldVertexType);
|
||||
childVertex.setVertexType(oldVertexType);
|
||||
}
|
||||
|
||||
graph.addVertex(parentVertex);
|
||||
graph.addVertex(childVertex);
|
||||
|
||||
FunctionGraph fg = (FunctionGraph) graph;
|
||||
if (vertexToSplit == fg.getRootVertex()) {
|
||||
fg.setRootVertex(parentVertex);
|
||||
}
|
||||
//
|
||||
// Second: add the edges from the original vertices to the new vertices
|
||||
//
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 972 B |
Binary file not shown.
After Width: | Height: | Size: 970 B |
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -77,7 +77,7 @@ public class TestFGLayoutProvider extends FGLayoutProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getVertexLocation(FGVertex v, Column col, Row<FGVertex> row,
|
||||
protected Point2D getVertexLocation(FGVertex v, Column<FGVertex> col, Row<FGVertex> row,
|
||||
Rectangle bounds) {
|
||||
return getCenteredVertexLocation(v, col, row, bounds);
|
||||
}
|
||||
|
@ -257,8 +257,8 @@ public class TestFGLayoutProvider extends FGLayoutProvider {
|
|||
// -->Maybe positioning is simple enough?
|
||||
//
|
||||
|
||||
Column startCol = layoutLocations.col(startVertex);
|
||||
Column endCol = layoutLocations.col(endVertex);
|
||||
Column<FGVertex> startCol = layoutLocations.col(startVertex);
|
||||
Column<FGVertex> endCol = layoutLocations.col(endVertex);
|
||||
Point2D start = vertexLayoutLocations.get(startVertex);
|
||||
Point2D end = vertexLayoutLocations.get(endVertex);
|
||||
List<Point2D> articulations = new ArrayList<>();
|
||||
|
|
|
@ -0,0 +1,420 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
import org.junit.Before;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.graphs.*;
|
||||
import ghidra.graph.support.TestVisualGraph;
|
||||
import ghidra.graph.viewer.GraphComponent;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
abstract public class AbstractFlowChartLayoutTest extends AbstractDockingTest {
|
||||
protected TestVisualGraph g;
|
||||
protected GridLocationMap<AbstractTestVertex, TestEdge> grid;
|
||||
protected OrthogonalGridToLayoutMapper<AbstractTestVertex, TestEdge> layoutMap;
|
||||
private boolean alignLeft;
|
||||
|
||||
protected LabelTestVertex A = v('A');
|
||||
protected LabelTestVertex B = v('B');
|
||||
protected LabelTestVertex C = v('C');
|
||||
protected LabelTestVertex D = v('D');
|
||||
protected LabelTestVertex E = v('E');
|
||||
protected LabelTestVertex F = v('F');
|
||||
protected LabelTestVertex G = v('G');
|
||||
protected LabelTestVertex H = v('H');
|
||||
protected LabelTestVertex I = v('I');
|
||||
protected LabelTestVertex J = v('J');
|
||||
protected LabelTestVertex K = v('K');
|
||||
|
||||
protected AbstractFlowChartLayoutTest(boolean alignLeft) {
|
||||
this.alignLeft = alignLeft;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
g = new TestVisualGraph();
|
||||
}
|
||||
|
||||
protected void showGraph() {
|
||||
|
||||
Swing.runNow(() -> {
|
||||
JFrame frame = new JFrame("Graph Viewer Test");
|
||||
|
||||
TestFlowChartLayout layout = new TestFlowChartLayout(g, A, alignLeft);
|
||||
g.setLayout(layout);
|
||||
GraphComponent<AbstractTestVertex, TestEdge, TestVisualGraph> graphComponent =
|
||||
new GraphComponent<>(g);
|
||||
graphComponent.setSatelliteVisible(false);
|
||||
|
||||
frame.setSize(new Dimension(800, 800));
|
||||
frame.setLocation(2400, 100);
|
||||
frame.getContentPane().add(graphComponent.getComponent());
|
||||
frame.setVisible(true);
|
||||
frame.validate();
|
||||
});
|
||||
}
|
||||
|
||||
protected void assertEdge(EdgeVerifier edgeVerifier) {
|
||||
edgeVerifier.checkEdge();
|
||||
}
|
||||
|
||||
protected void assertVertices(String expected) {
|
||||
String actual = generateCompactGridVertexString();
|
||||
if (!expected.equals(actual)) {
|
||||
reportGridError(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
private void reportGridError(String expectedString, String actualString) {
|
||||
String[] actual = StringUtilities.toLines(actualString, false);
|
||||
String[] expected = StringUtilities.toLines(expectedString, false);
|
||||
assertEquals("Number of rows don't match! ", expected.length, actual.length);
|
||||
checkCols(expected);
|
||||
assertEquals("Number of columns don't match! ", expected[0].length(), actual[0].length());
|
||||
|
||||
if (!expectedString.equals(actualString)) {
|
||||
printGridsToConsole(actual, expected);
|
||||
int firstRowDiff = findFirstRowDiff(actual, expected);
|
||||
int firstColDiff = findFirstColDiff(actual[firstRowDiff], expected[firstRowDiff]);
|
||||
fail("Graphs don't match! First diff is at row " + firstRowDiff + ", col " +
|
||||
firstColDiff + ". See console for details.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int findFirstColDiff(String string1, String string2) {
|
||||
for (int i = 0; i < string1.length(); i++) {
|
||||
if (string1.charAt(i) != string2.charAt(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int findFirstRowDiff(String[] actual, String[] expected) {
|
||||
for (int i = 0; i < actual.length; i++) {
|
||||
if (!actual[i].equals(expected[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void printGridsToConsole(String[] actual, String[] expected) {
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter out = new PrintWriter(writer);
|
||||
out.println("Graphs don't match!\n");
|
||||
String expectedLabel = "Expected: ";
|
||||
String actualLabel = " Actual: ";
|
||||
String diffLabel = " Diffs: ";
|
||||
String[] diffs = computeDiffs(actual, expected);
|
||||
for (int row = 0; row < expected.length; row++) {
|
||||
out.print(expectedLabel);
|
||||
out.print(expected[row]);
|
||||
out.print(actualLabel);
|
||||
out.print(actual[row]);
|
||||
out.print(diffLabel);
|
||||
out.println(diffs[row]);
|
||||
expectedLabel = " ";
|
||||
actualLabel = expectedLabel;
|
||||
diffLabel = expectedLabel;
|
||||
}
|
||||
Msg.error(this, writer.toString());
|
||||
}
|
||||
|
||||
private String[] computeDiffs(String[] actual, String[] expected) {
|
||||
String[] diffs = new String[actual.length];
|
||||
for (int i = 0; i < diffs.length; i++) {
|
||||
diffs[i] = computeDiffString(actual[i], expected[i]);
|
||||
}
|
||||
return diffs;
|
||||
}
|
||||
|
||||
private String computeDiffString(String string1, String string2) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int i = 0; i < string1.length(); i++) {
|
||||
char c = string1.charAt(i) == string2.charAt(i) ? '.' : 'x';
|
||||
buf.append(c);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private void checkCols(String[] expected) {
|
||||
int len = expected[0].length();
|
||||
for (String line : expected) {
|
||||
if (line.length() != len) {
|
||||
fail("Invalid graph grid specified in test. Lines vary in length!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String generateCompactGridEdgeString(TestEdge e, List<GridPoint> points) {
|
||||
char[][] gridChars = createEmptyGridChars();
|
||||
fillGridCharColumn(gridChars, points.get(0), points.get(1));
|
||||
|
||||
for (int i = 1; i < points.size() - 2; i++) {
|
||||
fillGridCharRow(gridChars, points.get(i), points.get(i + 1));
|
||||
fillGridCharColumn(gridChars, points.get(i + 1), points.get(i + 2));
|
||||
}
|
||||
|
||||
AbstractTestVertex start = e.getStart();
|
||||
AbstractTestVertex end = e.getEnd();
|
||||
GridPoint startPoint = grid.gridPoint(start);
|
||||
GridPoint endPoint = grid.gridPoint(end);
|
||||
gridChars[startPoint.row][startPoint.col] = start.toString().charAt(0);
|
||||
gridChars[endPoint.row][endPoint.col] = end.toString().charAt(0);
|
||||
|
||||
return toString(gridChars);
|
||||
}
|
||||
|
||||
private void fillGridCharRow(char[][] gridChars, GridPoint p1, GridPoint p2) {
|
||||
int row = p1.row;
|
||||
int startCol = Math.min(p1.col, p2.col);
|
||||
int endCol = Math.max(p1.col, p2.col);
|
||||
for (int col = startCol; col <= endCol; col++) {
|
||||
gridChars[row][col] = '*';
|
||||
}
|
||||
}
|
||||
|
||||
private void fillGridCharColumn(char[][] gridChars, GridPoint p1, GridPoint p2) {
|
||||
int col = p1.col;
|
||||
int startRow = Math.min(p1.row, p2.row);
|
||||
int endRow = Math.max(p1.row, p2.row);
|
||||
for (int row = startRow; row <= endRow; row++) {
|
||||
gridChars[row][col] = '*';
|
||||
}
|
||||
}
|
||||
|
||||
private String generateCompactGridVertexString() {
|
||||
char[][] gridChars = createEmptyGridChars();
|
||||
|
||||
for (Entry<AbstractTestVertex, GridPoint> entry : grid.getVertexPoints().entrySet()) {
|
||||
char id = entry.getKey().toString().charAt(0);
|
||||
GridPoint point = entry.getValue();
|
||||
gridChars[point.row][point.col] = id;
|
||||
}
|
||||
|
||||
return toString(gridChars);
|
||||
}
|
||||
|
||||
private String toString(char[][] gridChars) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int row = 0; row < grid.height(); row++) {
|
||||
for (int col = 0; col < grid.width(); col++) {
|
||||
buf.append(gridChars[row][col]);
|
||||
}
|
||||
buf.append("\n");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private char[][] createEmptyGridChars() {
|
||||
int rows = grid.height();
|
||||
int cols = grid.width();
|
||||
char[][] gridChars = new char[rows][cols];
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
gridChars[row][col] = '.';
|
||||
}
|
||||
}
|
||||
return gridChars;
|
||||
}
|
||||
|
||||
protected EdgeVerifier e(LabelTestVertex start, LabelTestVertex end) {
|
||||
return new EdgeVerifier(start, end);
|
||||
}
|
||||
|
||||
protected class EdgeVerifier {
|
||||
|
||||
private TestEdge edge;
|
||||
private List<GridPoint> expectedPoints = new ArrayList<>();
|
||||
private List<Integer> expectedOffsets = new ArrayList<>();
|
||||
|
||||
public EdgeVerifier(LabelTestVertex start, LabelTestVertex end) {
|
||||
this.edge = new TestEdge(start, end);
|
||||
expectedPoints.add(grid.gridPoint(start));
|
||||
}
|
||||
|
||||
public EdgeVerifier colSegment(int rows, int offset) {
|
||||
GridPoint lastPoint = expectedPoints.get(expectedPoints.size() - 1);
|
||||
GridPoint p = new GridPoint(lastPoint.row + rows, lastPoint.col);
|
||||
if (!grid.containsPoint(p)) {
|
||||
fail("Bad column specification. Row movement of " + rows + " exceeds grid size!");
|
||||
}
|
||||
expectedPoints.add(p);
|
||||
expectedOffsets.add(offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EdgeVerifier rowSegment(int cols, int offset) {
|
||||
GridPoint lastPoint = expectedPoints.get(expectedPoints.size() - 1);
|
||||
GridPoint p = new GridPoint(lastPoint.row, lastPoint.col + cols);
|
||||
if (!grid.containsPoint(p)) {
|
||||
fail("Bad row specification. Column movement of " + cols + " exceeds grid size!");
|
||||
}
|
||||
expectedPoints.add(p);
|
||||
expectedOffsets.add(offset);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void checkEdge() {
|
||||
List<GridPoint> actual = grid.getArticulations(edge);
|
||||
if (expectedPoints.size() < 2) {
|
||||
fail("Specified Edge for edge " + edge + " is missing segment specifications!");
|
||||
}
|
||||
|
||||
if (actual.isEmpty()) {
|
||||
fail("Expected edge articulations for " + edge + "not found!");
|
||||
}
|
||||
|
||||
if (!actual.equals(expectedPoints)) {
|
||||
|
||||
printEdgeDiffs(actual);
|
||||
fail("Articulations for edge " + edge + " don't match! See console for details");
|
||||
}
|
||||
|
||||
List<Integer> actualOffsets = layoutMap.getEdgeOffsets(edge);
|
||||
for (int i = 0; i < expectedOffsets.size(); i++) {
|
||||
GridPoint p1 = expectedPoints.get(i);
|
||||
GridPoint p2 = expectedPoints.get(i + 1);
|
||||
assertEquals(
|
||||
"Edge Offsets differ for edge " + edge + " at segment " + i +
|
||||
", from " + p1 + " to " + p2 + "! ",
|
||||
expectedOffsets.get(i), actualOffsets.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void printEdgeDiffs(List<GridPoint> actualPoints) {
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter out = new PrintWriter(writer);
|
||||
String expectedString = generateCompactGridEdgeString(edge, expectedPoints);
|
||||
String actualString = generateCompactGridEdgeString(edge, actualPoints);
|
||||
String[] expected = StringUtilities.toLines(expectedString, false);
|
||||
String[] actual = StringUtilities.toLines(actualString, false);
|
||||
|
||||
out.println("Edge articulations don't match for " + edge.toString() + "!\n");
|
||||
String expectedLabel = "Expected: ";
|
||||
String actualLabel = " Actual: ";
|
||||
for (int row = 0; row < expected.length; row++) {
|
||||
out.print(expectedLabel);
|
||||
out.print(expected[row]);
|
||||
out.print(actualLabel);
|
||||
out.println(actual[row]);
|
||||
expectedLabel = " ";
|
||||
actualLabel = expectedLabel;
|
||||
|
||||
}
|
||||
Msg.error(this, writer.toString());
|
||||
}
|
||||
}
|
||||
|
||||
protected int offset(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
protected int down(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
protected int up(int i) {
|
||||
return -i;
|
||||
}
|
||||
|
||||
protected int left(int i) {
|
||||
return -i;
|
||||
}
|
||||
|
||||
protected int right(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
protected void applyLayout() throws CancelledException {
|
||||
TestFlowChartLayout layout = new TestFlowChartLayout(g, A, alignLeft);
|
||||
grid = layout.performInitialGridLayout(g);
|
||||
Function<AbstractTestVertex, Shape> transformer = v -> new Rectangle(0, 0, 50, 50);
|
||||
layoutMap =
|
||||
new OrthogonalGridToLayoutMapper<AbstractTestVertex, TestEdge>(grid, transformer, true);
|
||||
}
|
||||
|
||||
// a shortcut for edge(v(startId), v(endId)), for readability
|
||||
protected TestEdge edge(LabelTestVertex v1, LabelTestVertex v2) {
|
||||
TestEdge testEdge = new TestEdge(v1, v2);
|
||||
g.addEdge(testEdge);
|
||||
return testEdge;
|
||||
}
|
||||
|
||||
protected LabelTestVertex v(char id) {
|
||||
return new LabelTestVertex("" + id);
|
||||
}
|
||||
|
||||
private static class TestFlowChartLayout
|
||||
extends AbstractFlowChartLayout<AbstractTestVertex, TestEdge> {
|
||||
|
||||
private AbstractTestVertex root;
|
||||
|
||||
protected TestFlowChartLayout(DefaultVisualGraph<AbstractTestVertex, TestEdge> graph,
|
||||
AbstractTestVertex root, boolean leftAlign) {
|
||||
super(graph, new TestEdgeComparator(), leftAlign);
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractVisualGraphLayout<AbstractTestVertex, TestEdge> createClonedLayout(
|
||||
VisualGraph<AbstractTestVertex, TestEdge> newGraph) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractTestVertex getRoot(VisualGraph<AbstractTestVertex, TestEdge> g) {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GridLocationMap<AbstractTestVertex, TestEdge> performInitialGridLayout(
|
||||
VisualGraph<AbstractTestVertex, TestEdge> g) throws CancelledException {
|
||||
|
||||
return super.performInitialGridLayout(g);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class TestEdgeComparator implements Comparator<TestEdge> {
|
||||
|
||||
@Override
|
||||
public int compare(TestEdge e1, TestEdge e2) {
|
||||
return e1.toString().compareTo(e2.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.graph.support.TestVisualGraph;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class FlowChartLayoutTest extends AbstractFlowChartLayoutTest {
|
||||
|
||||
public FlowChartLayoutTest() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() {
|
||||
g = new TestVisualGraph();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicRootWithTwoChildren() throws CancelledException {
|
||||
edge(A, B);
|
||||
edge(A, C);
|
||||
applyLayout();
|
||||
showGraph();
|
||||
|
||||
assertVertices("""
|
||||
....
|
||||
..A.
|
||||
....
|
||||
.B.C
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(1), offset(-1))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(A, C)
|
||||
.colSegment(down(1), offset(1))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicGraphWithThreeChildren() throws CancelledException {
|
||||
edge(A, B);
|
||||
edge(A, C);
|
||||
edge(A, D);
|
||||
applyLayout();
|
||||
|
||||
// showGraph();
|
||||
|
||||
assertVertices("""
|
||||
......
|
||||
...A..
|
||||
......
|
||||
.B.C.D
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(2), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(A, C)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(A, D)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicGraphWithBackEdge() throws CancelledException {
|
||||
edge(A, B);
|
||||
edge(A, C);
|
||||
edge(A, D);
|
||||
edge(D, A);
|
||||
applyLayout();
|
||||
|
||||
// showGraph();
|
||||
|
||||
assertVertices("""
|
||||
.......
|
||||
...A...
|
||||
.......
|
||||
.B.C.D.
|
||||
.......
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(2), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(A, C)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(A, D)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(D, A)
|
||||
.colSegment(down(1), offset(0))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(up(4), offset(0))
|
||||
.rowSegment(left(3), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplexGraph() throws CancelledException {
|
||||
|
||||
//@formatter:off
|
||||
/*
|
||||
A
|
||||
|
|
||||
B
|
||||
|
|
||||
sink C <---D
|
||||
| |
|
||||
| E
|
||||
| // \
|
||||
| |F G
|
||||
| ||\ |
|
||||
| |H | /
|
||||
| | \|/
|
||||
| | I
|
||||
| | / \
|
||||
| |/ \
|
||||
.<-K---->J sink
|
||||
|
||||
*/
|
||||
//@formatter:on edge(A, B);
|
||||
edge(A, B);
|
||||
edge(B, D);
|
||||
edge(D, C);
|
||||
edge(D, E);
|
||||
|
||||
edge(E, F);
|
||||
edge(E, G);
|
||||
edge(E, K);
|
||||
edge(F, H);
|
||||
edge(F, I);
|
||||
edge(G, I);
|
||||
edge(H, I);
|
||||
edge(I, J);
|
||||
edge(I, K);
|
||||
edge(K, C);
|
||||
edge(K, J);
|
||||
|
||||
applyLayout();
|
||||
|
||||
// showGraph();
|
||||
|
||||
assertVertices("""
|
||||
.....
|
||||
...A.
|
||||
.....
|
||||
...B.
|
||||
.....
|
||||
...D.
|
||||
.....
|
||||
...E.
|
||||
.....
|
||||
..F.G
|
||||
.....
|
||||
..H..
|
||||
.....
|
||||
..I..
|
||||
.....
|
||||
..K..
|
||||
.....
|
||||
.C.J.
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(B, D)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(D, C)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(2), offset(0))
|
||||
.colSegment(down(11), offset(-1)));
|
||||
|
||||
assertEdge(e(D, E)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(E, F)
|
||||
.colSegment(down(1), offset(-1))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(E, G)
|
||||
.colSegment(down(1), offset(3))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(E, K)
|
||||
.colSegment(down(7), offset(1))
|
||||
.rowSegment(left(1), offset(2))
|
||||
.colSegment(down(1), offset(2)));
|
||||
|
||||
assertEdge(e(F, H)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(F, I)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(2), offset(1))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(-2)));
|
||||
|
||||
assertEdge(e(G, I)
|
||||
.colSegment(down(3), offset(0))
|
||||
.rowSegment(left(2), offset(0))
|
||||
.colSegment(down(1), offset(2)));
|
||||
|
||||
assertEdge(e(H, I)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(I, J)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(3), offset(-1)));
|
||||
assertEdge(e(I, K)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(K, C)
|
||||
.colSegment(down(1), offset(-1))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(1), offset(1)));
|
||||
|
||||
assertEdge(e(K, J)
|
||||
.colSegment(down(1), offset(1))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(-3)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
public class LeftAlignedFlowChartLayoutTest extends AbstractFlowChartLayoutTest {
|
||||
|
||||
public LeftAlignedFlowChartLayoutTest() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicRootWithTwoChildren() throws CancelledException {
|
||||
edge(A, B);
|
||||
edge(A, C);
|
||||
applyLayout();
|
||||
|
||||
// showGraph();
|
||||
// Msg.out(grid.toStringGrid());
|
||||
|
||||
assertVertices("""
|
||||
....
|
||||
.A..
|
||||
....
|
||||
.B.C
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(A, C)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicGraphWithThreeChildren() throws CancelledException {
|
||||
edge(A, B);
|
||||
edge(A, C);
|
||||
edge(A, D);
|
||||
applyLayout();
|
||||
|
||||
// showGraph();
|
||||
Msg.out(grid.toStringGrid());
|
||||
|
||||
assertVertices("""
|
||||
......
|
||||
.A....
|
||||
......
|
||||
.B.C.D
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(A, C)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(2))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(A, D)
|
||||
.colSegment(down(1), offset(4))
|
||||
.rowSegment(right(4), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicGraphWithBackEdge() throws CancelledException {
|
||||
edge(A, B);
|
||||
edge(A, C);
|
||||
edge(A, D);
|
||||
edge(D, A);
|
||||
applyLayout();
|
||||
|
||||
// showGraph();
|
||||
// Msg.out(grid.toStringGrid());
|
||||
|
||||
assertVertices("""
|
||||
.......
|
||||
.A.....
|
||||
.......
|
||||
.B.C.D.
|
||||
.......
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(A, C)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(2))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(A, D)
|
||||
.colSegment(down(1), offset(4))
|
||||
.rowSegment(right(4), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
assertEdge(e(D, A)
|
||||
.colSegment(down(1), offset(0))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(up(4), offset(0))
|
||||
.rowSegment(left(5), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComplexGraph() throws CancelledException {
|
||||
|
||||
//@formatter:off
|
||||
/*
|
||||
A
|
||||
|
|
||||
B
|
||||
|
|
||||
sink C <---D
|
||||
| |
|
||||
| E
|
||||
| // \
|
||||
| |F G
|
||||
| ||\ |
|
||||
| |H | /
|
||||
| | \|/
|
||||
| | I
|
||||
| | / \
|
||||
| |/ \
|
||||
.<-K---->J sink
|
||||
|
||||
*/
|
||||
//@formatter:on edge(A, B);
|
||||
edge(A, B);
|
||||
edge(B, D);
|
||||
edge(D, C);
|
||||
edge(D, E);
|
||||
|
||||
edge(E, F);
|
||||
edge(E, G);
|
||||
edge(E, K);
|
||||
edge(F, H);
|
||||
edge(F, I);
|
||||
edge(G, I);
|
||||
edge(H, I);
|
||||
edge(I, J);
|
||||
edge(I, K);
|
||||
edge(K, C);
|
||||
edge(K, J);
|
||||
|
||||
applyLayout();
|
||||
|
||||
showGraph();
|
||||
Msg.out(grid.toStringGrid());
|
||||
|
||||
assertVertices("""
|
||||
....
|
||||
.A..
|
||||
....
|
||||
.B..
|
||||
....
|
||||
.D..
|
||||
....
|
||||
.E..
|
||||
....
|
||||
.F.G
|
||||
....
|
||||
.H..
|
||||
....
|
||||
.I..
|
||||
....
|
||||
.K..
|
||||
....
|
||||
.C.J
|
||||
""");
|
||||
|
||||
assertEdge(e(A, B)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(B, D)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(D, C)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(10), offset(-2))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(-2)));
|
||||
|
||||
assertEdge(e(D, E)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(E, F)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(E, G)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(0))
|
||||
.colSegment(down(1), offset(0)));
|
||||
|
||||
assertEdge(e(E, K)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(6), offset(0))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(-2)));
|
||||
|
||||
assertEdge(e(F, H)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(F, I)
|
||||
.colSegment(down(1), offset(-2))
|
||||
.rowSegment(left(1), offset(0))
|
||||
.colSegment(down(2), offset(2))
|
||||
.rowSegment(right(1), offset(0))
|
||||
.colSegment(down(1), offset(-2)));
|
||||
|
||||
assertEdge(e(G, I)
|
||||
.colSegment(down(3), offset(0))
|
||||
.rowSegment(left(2), offset(0))
|
||||
.colSegment(down(1), offset(2)));
|
||||
|
||||
assertEdge(e(H, I)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(I, J)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(0))
|
||||
.colSegment(down(3), offset(1)));
|
||||
assertEdge(e(I, K)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(K, C)
|
||||
.colSegment(down(2), offset(0)));
|
||||
|
||||
assertEdge(e(K, J)
|
||||
.colSegment(down(1), offset(2))
|
||||
.rowSegment(right(2), offset(0))
|
||||
.colSegment(down(1), offset(-1)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,982 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.app.plugin.core.functiongraph.graph.layout.flowchart;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.graph.graphs.TestEdge;
|
||||
import ghidra.graph.graphs.TestVertex;
|
||||
import ghidra.graph.viewer.layout.GridPoint;
|
||||
|
||||
public class EdgeSegmentTest {
|
||||
private static int DOWN = 1;
|
||||
private static int UP = -1;
|
||||
private static int LEFT = -1;
|
||||
private static int RIGHT = 1;
|
||||
private static int UP_2 = -2;
|
||||
private static int DOWN_2 = 2;
|
||||
private static int LEFT_2 = -2;
|
||||
private static int RIGHT_2 = 2;
|
||||
|
||||
private TestVertex v1 = v(1);
|
||||
private TestVertex v2 = v(2);
|
||||
private TestVertex v3 = v(3);
|
||||
private TestVertex v4 = v(4);
|
||||
private TestEdge e12 = e(v1, v2);
|
||||
private TestEdge e13 = e(v1, v2);
|
||||
private TestEdge e14 = e(v1, v4);
|
||||
private TestEdge e23 = e(v2, v3);
|
||||
private TestEdge e24 = e(v2, v4);
|
||||
private TestEdge e34 = e(v3, v4);
|
||||
|
||||
//==================================================================================================
|
||||
// The following tests compare starting edge segments.
|
||||
//=================================================================================================
|
||||
@Test
|
||||
public void testStartSegmentsInTotallyDifferentColumns() {
|
||||
ColumnSegment<TestEdge> col1 = segment(e12, p(0, 1), DOWN);
|
||||
ColumnSegment<TestEdge> col2 = segment(e13, p(0, 2), DOWN);
|
||||
|
||||
assertLessThan(col1, col2);
|
||||
assertGreaterThan(col2, col1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareStartSegmentByDirectionOfBottomRow() {
|
||||
GridPoint p = p(0, 0);
|
||||
ColumnSegment<TestEdge> down = segment(e12, p, DOWN_2);
|
||||
ColumnSegment<TestEdge> left = segment(e13, p, DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> right = segment(e14, p, DOWN, RIGHT, DOWN);
|
||||
|
||||
assertLessThan(left, down);
|
||||
assertGreaterThan(down, left);
|
||||
|
||||
assertLessThan(down, right);
|
||||
assertGreaterThan(right, down);
|
||||
|
||||
assertLessThan(left, right);
|
||||
assertGreaterThan(right, left);
|
||||
|
||||
// test edges are equals to themselves
|
||||
assertEquals(down, down);
|
||||
assertEquals(left, left);
|
||||
assertEquals(right, right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareStartBothLeftButDifferentRowLevels() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> down1_left = segment(e13, p, DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> down2_left = segment(e14, p, DOWN_2, LEFT, DOWN);
|
||||
|
||||
assertLessThan(down1_left, down2_left);
|
||||
assertGreaterThan(down2_left, down1_left);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareStartBothRightButDifferentRowLevels() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> down1_right = segment(e13, p, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> down2_right = segment(e14, p, DOWN_2, RIGHT, DOWN);
|
||||
|
||||
assertLessThan(down2_right, down1_right);
|
||||
assertGreaterThan(down1_right, down2_right);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareStartBothLeftThenOppositeDirection() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> leftUp = segment(e13, p, DOWN, LEFT, UP);
|
||||
ColumnSegment<TestEdge> leftDown = segment(e14, p, DOWN, LEFT, DOWN);
|
||||
|
||||
assertLessThan(leftUp, leftDown);
|
||||
assertGreaterThan(leftDown, leftUp);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareStartBothRightsThenOppositeDirection() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> rightUp = segment(e13, p, DOWN, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> rightDown = segment(e14, p, DOWN, RIGHT, DOWN);
|
||||
|
||||
assertLessThan(rightDown, rightUp);
|
||||
assertGreaterThan(rightUp, rightDown);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftDownButDifferentLeftColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> left1 = segment(e13, p, DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> left2 = segment(e14, p, DOWN, LEFT_2, DOWN);
|
||||
|
||||
assertLessThan(left2, left1);
|
||||
assertGreaterThan(left1, left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftUpButDifferentLeftColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> left1 = segment(e13, p, DOWN, LEFT, UP);
|
||||
ColumnSegment<TestEdge> left2 = segment(e14, p, DOWN, LEFT_2, UP);
|
||||
|
||||
assertLessThan(left1, left2);
|
||||
assertGreaterThan(left2, left1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightDownButDifferentRightColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> right1 = segment(e13, p, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> right2 = segment(e14, p, DOWN, RIGHT_2, DOWN);
|
||||
|
||||
assertLessThan(right1, right2);
|
||||
assertGreaterThan(right2, right1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightUpButDifferentRightColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> right1 = segment(e13, p, DOWN, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> right2 = segment(e14, p, DOWN, RIGHT_2, UP);
|
||||
|
||||
assertLessThan(right2, right1);
|
||||
assertGreaterThan(right1, right2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftUpButUpperRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_u_left = segment(e13, p, DOWN, LEFT, UP, LEFT);
|
||||
ColumnSegment<TestEdge> l_u_right = segment(e14, p, DOWN, LEFT, UP, RIGHT);
|
||||
|
||||
assertLessThan(l_u_right, l_u_left);
|
||||
assertGreaterThan(l_u_left, l_u_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftDownButLowerRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_d_left = segment(e13, p, DOWN, LEFT, DOWN, LEFT);
|
||||
ColumnSegment<TestEdge> l_d_right = segment(e14, p, DOWN, LEFT, DOWN, RIGHT);
|
||||
|
||||
assertLessThan(l_d_left, l_d_right);
|
||||
assertGreaterThan(l_d_right, l_d_left);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightUpButUpperRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_u_left = segment(e13, p, DOWN, RIGHT, UP, LEFT);
|
||||
ColumnSegment<TestEdge> r_u_right = segment(e14, p, DOWN, RIGHT, UP, RIGHT);
|
||||
|
||||
assertLessThan(r_u_right, r_u_left);
|
||||
assertGreaterThan(r_u_left, r_u_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightDownButLowerRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_d_left = segment(e13, p, DOWN, RIGHT, DOWN, LEFT);
|
||||
ColumnSegment<TestEdge> r_d_right = segment(e14, p, DOWN, RIGHT, DOWN, RIGHT);
|
||||
|
||||
assertLessThan(r_d_left, r_d_right);
|
||||
assertGreaterThan(r_d_right, r_d_left);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftUpLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_u_left = segment(e13, p, DOWN, LEFT, UP, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> l_u_left2 = segment(e14, p, DOWN, LEFT, UP, LEFT_2, DOWN);
|
||||
|
||||
assertLessThan(l_u_left2, l_u_left);
|
||||
assertGreaterThan(l_u_left, l_u_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftUpRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_u_right = segment(e13, p, DOWN, LEFT, UP, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> l_u_right2 = segment(e14, p, DOWN, LEFT, UP, RIGHT_2, DOWN);
|
||||
|
||||
assertLessThan(l_u_right, l_u_right2);
|
||||
assertGreaterThan(l_u_right2, l_u_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftDownLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_d_left = segment(e13, p, DOWN, LEFT, DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> l_d_left2 = segment(e14, p, DOWN, LEFT, DOWN, LEFT_2, DOWN);
|
||||
|
||||
assertLessThan(l_d_left2, l_d_left);
|
||||
assertGreaterThan(l_d_left, l_d_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareLeftDownRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_d_right = segment(e13, p, DOWN, LEFT, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> l_d_right2 = segment(e14, p, DOWN, LEFT, DOWN, RIGHT_2, DOWN);
|
||||
|
||||
assertLessThan(l_d_right, l_d_right2);
|
||||
assertGreaterThan(l_d_right2, l_d_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightUpLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_u_left = segment(e13, p, DOWN, RIGHT, UP, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> r_u_left2 = segment(e14, p, DOWN, RIGHT, UP, LEFT_2, DOWN);
|
||||
|
||||
assertLessThan(r_u_left2, r_u_left);
|
||||
assertGreaterThan(r_u_left, r_u_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightUpRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_u_right = segment(e13, p, DOWN, RIGHT, UP, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> r_u_right2 = segment(e14, p, DOWN, RIGHT, UP, RIGHT_2, DOWN);
|
||||
|
||||
assertLessThan(r_u_right, r_u_right2);
|
||||
assertGreaterThan(r_u_right2, r_u_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightDownLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_d_left = segment(e13, p, DOWN, RIGHT, DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> r_d_left2 = segment(e14, p, DOWN, RIGHT, DOWN, LEFT_2, DOWN);
|
||||
|
||||
assertLessThan(r_d_left2, r_d_left);
|
||||
assertGreaterThan(r_d_left, r_d_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareRightDownRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_d_right = segment(e13, p, DOWN, RIGHT, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> r_d_right2 = segment(e14, p, DOWN, RIGHT, DOWN, RIGHT_2, DOWN);
|
||||
|
||||
assertLessThan(r_d_right, r_d_right2);
|
||||
assertGreaterThan(r_d_right2, r_d_right);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// The following tests compare ending edge segments.
|
||||
//=================================================================================================
|
||||
|
||||
@Test
|
||||
public void testCompareEndsInTotallyDifferentColumns() {
|
||||
GridPoint p1 = p(0, 0);
|
||||
GridPoint p2 = p(0, 1);
|
||||
|
||||
ColumnSegment<TestEdge> col0 = endSegment(e12, p1, UP_2);
|
||||
ColumnSegment<TestEdge> col1 = endSegment(e12, p2, UP_2);
|
||||
assertLessThan(col0, col1);
|
||||
assertGreaterThan(col1, col0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsFromDifferentDirections() {
|
||||
GridPoint p = p(5, 5);
|
||||
|
||||
ColumnSegment<TestEdge> fromRight = endSegment(e14, p, UP, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> fromAbove = endSegment(e24, p, UP);
|
||||
ColumnSegment<TestEdge> fromLeft = endSegment(e34, p, UP, LEFT, UP);
|
||||
|
||||
assertLessThan(fromLeft, fromAbove);
|
||||
assertLessThan(fromAbove, fromRight);
|
||||
assertLessThan(fromLeft, fromRight);
|
||||
|
||||
assertGreaterThan(fromAbove, fromLeft);
|
||||
assertGreaterThan(fromRight, fromAbove);
|
||||
assertGreaterThan(fromRight, fromAbove);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndBothLeftButDifferentRowLevels() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> up1_left = endSegment(e13, p, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> up2_left = segment(e23, p, UP_2, LEFT, UP);
|
||||
|
||||
assertLessThan(up1_left, up2_left);
|
||||
assertGreaterThan(up2_left, up1_left);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndBothRightButDifferentRowLevels() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> up1_right = endSegment(e13, p, UP, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> up2_right = segment(e23, p, UP_2, RIGHT, UP);
|
||||
|
||||
assertLessThan(up2_right, up1_right);
|
||||
assertGreaterThan(up1_right, up2_right);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndBothLeftThenOppositeDirection() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> u_l_up = endSegment(e13, p, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> u_l_down = endSegment(e14, p, UP, LEFT, DOWN);
|
||||
|
||||
assertLessThan(u_l_down, u_l_up);
|
||||
assertGreaterThan(u_l_up, u_l_down);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndBothRightsThenOppositeDirection() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> u_r_up = endSegment(e13, p, UP, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> u_r_down = endSegment(e14, p, UP, RIGHT, DOWN);
|
||||
|
||||
assertLessThan(u_r_up, u_r_down);
|
||||
assertGreaterThan(u_r_down, u_r_up);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftDownButDifferentLeftColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> left1 = endSegment(e13, p, UP, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> left2 = endSegment(e14, p, UP, LEFT_2, DOWN);
|
||||
|
||||
assertLessThan(left1, left2);
|
||||
assertGreaterThan(left2, left1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftUpButDifferentLeftColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> left1 = endSegment(e13, p, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> left2 = endSegment(e14, p, UP, LEFT_2, UP);
|
||||
|
||||
assertLessThan(left2, left1);
|
||||
assertGreaterThan(left1, left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightDownButDifferentRightColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> right1 = endSegment(e13, p, UP, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> right2 = endSegment(e14, p, UP, RIGHT_2, DOWN);
|
||||
|
||||
assertLessThan(right2, right1);
|
||||
assertGreaterThan(right1, right2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightUpButDifferentRightColumn() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> right1 = endSegment(e13, p, UP, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> right2 = endSegment(e14, p, UP, RIGHT_2, UP);
|
||||
|
||||
assertLessThan(right1, right2);
|
||||
assertGreaterThan(right2, right1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftUpButUpperRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_u_left = endSegment(e13, p, UP, LEFT, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> l_u_right = endSegment(e14, p, UP, LEFT, UP, RIGHT, UP);
|
||||
|
||||
assertLessThan(l_u_left, l_u_right);
|
||||
assertGreaterThan(l_u_right, l_u_left);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftDownButLowerRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_d_left = endSegment(e13, p, UP, LEFT, DOWN, LEFT, UP);
|
||||
ColumnSegment<TestEdge> l_d_right = endSegment(e14, p, UP, LEFT, DOWN, RIGHT, UP);
|
||||
|
||||
assertLessThan(l_d_right, l_d_left);
|
||||
assertGreaterThan(l_d_left, l_d_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightUpButUpperRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_u_left = endSegment(e13, p, UP, RIGHT, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> r_u_right = endSegment(e14, p, UP, RIGHT, UP, RIGHT, UP);
|
||||
|
||||
assertLessThan(r_u_left, r_u_right);
|
||||
assertGreaterThan(r_u_right, r_u_left);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightDownButLowerRowDifferentDirections() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_d_left = endSegment(e13, p, UP, RIGHT, DOWN, LEFT, UP);
|
||||
ColumnSegment<TestEdge> r_d_right = endSegment(e14, p, UP, RIGHT, DOWN, RIGHT, UP);
|
||||
|
||||
assertLessThan(r_d_right, r_d_left);
|
||||
assertGreaterThan(r_d_left, r_d_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftUpLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_u_left = endSegment(e13, p, UP, LEFT, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> l_u_left2 = endSegment(e14, p, UP, LEFT, UP, LEFT_2, UP);
|
||||
|
||||
assertLessThan(l_u_left2, l_u_left);
|
||||
assertGreaterThan(l_u_left, l_u_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftUpRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_u_right = endSegment(e13, p, UP, LEFT, UP, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> l_u_right2 = endSegment(e14, p, UP, LEFT, UP, RIGHT_2, UP);
|
||||
|
||||
assertLessThan(l_u_right, l_u_right2);
|
||||
assertGreaterThan(l_u_right2, l_u_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftDownLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_d_left = endSegment(e13, p, UP, LEFT, DOWN, LEFT, UP);
|
||||
ColumnSegment<TestEdge> l_d_left2 = endSegment(e14, p, UP, LEFT, DOWN, LEFT_2, UP);
|
||||
|
||||
assertLessThan(l_d_left2, l_d_left);
|
||||
assertGreaterThan(l_d_left, l_d_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsLeftDownRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> l_d_right = endSegment(e13, p, UP, LEFT, DOWN, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> l_d_right2 = endSegment(e14, p, UP, LEFT, DOWN, RIGHT_2, UP);
|
||||
|
||||
assertLessThan(l_d_right, l_d_right2);
|
||||
assertGreaterThan(l_d_right2, l_d_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightUpLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_u_left = endSegment(e13, p, UP, RIGHT, UP, LEFT, UP);
|
||||
ColumnSegment<TestEdge> r_u_left2 = endSegment(e14, p, UP, RIGHT, UP, LEFT_2, UP);
|
||||
|
||||
assertLessThan(r_u_left2, r_u_left);
|
||||
assertGreaterThan(r_u_left, r_u_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightUpRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_u_right = endSegment(e13, p, UP, RIGHT, UP, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> r_u_right2 = endSegment(e14, p, UP, RIGHT, UP, RIGHT_2, UP);
|
||||
|
||||
assertLessThan(r_u_right, r_u_right2);
|
||||
assertGreaterThan(r_u_right2, r_u_right);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightDownLeftButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_d_left = endSegment(e13, p, UP, RIGHT, DOWN, LEFT, UP);
|
||||
ColumnSegment<TestEdge> r_d_left2 = endSegment(e14, p, UP, RIGHT, DOWN, LEFT_2, UP);
|
||||
|
||||
assertLessThan(r_d_left2, r_d_left);
|
||||
assertGreaterThan(r_d_left, r_d_left2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareEndsRightDownRightButFinalColDiffers() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> r_d_right = endSegment(e13, p, UP, RIGHT, DOWN, RIGHT, UP);
|
||||
ColumnSegment<TestEdge> r_d_right2 = endSegment(e14, p, UP, RIGHT, DOWN, RIGHT_2, UP);
|
||||
|
||||
assertLessThan(r_d_right, r_d_right2);
|
||||
assertGreaterThan(r_d_right2, r_d_right);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// The following tests compare interior edge segments are consistent with each other
|
||||
//=================================================================================================
|
||||
@Test
|
||||
public void testInteriorRightDownRight() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, RIGHT, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, RIGHT, DOWN, RIGHT_2, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorRightDownLeft() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, RIGHT, DOWN, LEFT_2, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, RIGHT, DOWN, LEFT, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorRightUpRight() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, RIGHT, UP, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, RIGHT, UP, RIGHT_2, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg2, colSeg1);
|
||||
assertGreaterThan(colSeg1, colSeg2);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorRightUpLeft() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, RIGHT, UP, LEFT_2, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, RIGHT, UP, LEFT, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg2, colSeg1);
|
||||
assertGreaterThan(colSeg1, colSeg2);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorLeftDownRight() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, LEFT, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, LEFT, DOWN, RIGHT_2, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorLeftDownLeft() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, LEFT, DOWN, LEFT_2, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, LEFT, DOWN, LEFT, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorLeftUpRight() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, LEFT, UP, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, LEFT, UP, RIGHT_2, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg2, colSeg1);
|
||||
assertGreaterThan(colSeg1, colSeg2);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg2, rowSeg1);
|
||||
assertGreaterThan(rowSeg1, rowSeg2);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInteriorLeftUpLeft() {
|
||||
GridPoint p = p(0, 0);
|
||||
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p, DOWN, LEFT, UP, LEFT_2, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p, DOWN, LEFT, UP, LEFT, DOWN);
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
|
||||
RowSegment<TestEdge> rowSeg1 = colSeg1.nextSegment();
|
||||
RowSegment<TestEdge> rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg2, colSeg1);
|
||||
assertGreaterThan(colSeg1, colSeg2);
|
||||
|
||||
rowSeg1 = colSeg1.nextSegment();
|
||||
rowSeg2 = colSeg2.nextSegment();
|
||||
assertLessThan(rowSeg1, rowSeg2);
|
||||
assertGreaterThan(rowSeg2, rowSeg1);
|
||||
|
||||
colSeg1 = rowSeg1.nextSegment();
|
||||
colSeg2 = rowSeg2.nextSegment();
|
||||
assertLessThan(colSeg1, colSeg2);
|
||||
assertGreaterThan(colSeg2, colSeg1);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// The following tests are for miscellaneous methods in ColumnSegment or RowSegment
|
||||
//=================================================================================================
|
||||
@Test
|
||||
public void testColumnSegmentOverlapColumn1TotallyAboveColumn2() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(100, 0), DOWN);
|
||||
assertFalse(colSeg1.overlaps(colSeg2));
|
||||
assertFalse(colSeg2.overlaps(colSeg1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColumnSegmentEndDoesNotOverlapColumnSegmentStartToSameVertex() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(2, 0), DOWN);
|
||||
assertFalse(colSeg1.overlaps(colSeg2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColumnStartSegmentToSharedRowFromEndSegmentDependsOnRowOffsets() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 1), DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 0), DOWN, RIGHT, DOWN);
|
||||
assertTrue(colSeg1.overlaps(colSeg2.last()));
|
||||
|
||||
// now fix offset for secondRow to be down a bit
|
||||
colSeg2.nextSegment().setOffset(1);
|
||||
assertFalse(colSeg1.overlaps(colSeg2.last()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowSegmentOverlapSeg1TotallyBeforeSeg2() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 10), DOWN, RIGHT, DOWN);
|
||||
assertFalse(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertFalse(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowsLeavingSameVertexInOppositeDirectionsDontOverlap() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 0), DOWN, RIGHT, DOWN);
|
||||
assertFalse(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertFalse(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowsLeavingSameVertexInSameDirectionOverlap() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 0), DOWN, LEFT, DOWN);
|
||||
assertTrue(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertTrue(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowsEnteringSameVertexInOppositeDirectionsDontOverlap() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 2), DOWN, LEFT, DOWN);
|
||||
assertFalse(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertFalse(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowsEnteringSameVertexInSameDirectionsOverlap() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN, RIGHT_2, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 1), DOWN, RIGHT, DOWN);
|
||||
assertTrue(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertTrue(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowsThatStartEndOnSameColumnAndOneIsTerminalAndOtherIsnt() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 0), DOWN, RIGHT, DOWN, RIGHT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 1), DOWN, RIGHT, DOWN);
|
||||
assertTrue(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertTrue(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRowsThatStartEndOnSameColumnAndOneIsTerminalAndOtherIsntLeft() {
|
||||
ColumnSegment<TestEdge> colSeg1 = segment(e13, p(0, 1), DOWN, LEFT, DOWN, LEFT, DOWN);
|
||||
ColumnSegment<TestEdge> colSeg2 = segment(e14, p(0, 0), DOWN, LEFT, DOWN);
|
||||
assertTrue(colSeg1.nextSegment().overlaps(colSeg2.nextSegment()));
|
||||
assertTrue(colSeg2.nextSegment().overlaps(colSeg1.nextSegment()));
|
||||
|
||||
}
|
||||
|
||||
private void assertCompareEquals(ColumnSegment<TestEdge> s1, ColumnSegment<TestEdge> s2) {
|
||||
int result = s1.compareTo(s2);
|
||||
if (result != 0) {
|
||||
fail("Expected comparsion to be equals, but compareTo was " + result);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLessThan(ColumnSegment<TestEdge> s1, ColumnSegment<TestEdge> s2) {
|
||||
int result = s1.compareTo(s2);
|
||||
if (result >= 0) {
|
||||
fail("Expected comparsion to be less than, but compareTo was " + result);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertGreaterThan(RowSegment<TestEdge> s1, RowSegment<TestEdge> s2) {
|
||||
int result = s1.compareTo(s2);
|
||||
if (result <= 0) {
|
||||
fail("Expected comparsion to be greater than, but compareTo was " + result);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLessThan(RowSegment<TestEdge> s1, RowSegment<TestEdge> s2) {
|
||||
int result = s1.compareTo(s2);
|
||||
if (result >= 0) {
|
||||
fail("Expected comparsion to be less than, but compareTo was " + result);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertGreaterThan(ColumnSegment<TestEdge> s1, ColumnSegment<TestEdge> s2) {
|
||||
int result = s1.compareTo(s2);
|
||||
if (result <= 0) {
|
||||
fail("Expected comparsion to be greater than, but compareTo was " + result);
|
||||
}
|
||||
}
|
||||
|
||||
private ColumnSegment<TestEdge> segment(TestEdge e, GridPoint p, int... flows) {
|
||||
return new ColumnSegment<>(e, points(p, flows));
|
||||
}
|
||||
|
||||
private ColumnSegment<TestEdge> endSegment(TestEdge e, GridPoint end, int... flows) {
|
||||
return new ColumnSegment<>(e, pointsReverseOrder(end, flows)).last();
|
||||
}
|
||||
|
||||
private GridPoint p(int row, int col) {
|
||||
return new GridPoint(row, col);
|
||||
}
|
||||
|
||||
private List<GridPoint> points(GridPoint start, int... flows) {
|
||||
List<GridPoint> points = new ArrayList<>();
|
||||
points.add(start);
|
||||
GridPoint next = new GridPoint(start.row, start.col);
|
||||
for (int i = 0; i < flows.length; i++) {
|
||||
if (i % 2 == 0) {
|
||||
next.row += flows[i];
|
||||
}
|
||||
else {
|
||||
next.col += flows[i];
|
||||
}
|
||||
points.add(new GridPoint(next.row, next.col));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
private List<GridPoint> pointsReverseOrder(GridPoint end, int... flows) {
|
||||
List<GridPoint> points = new ArrayList<>();
|
||||
points.add(end);
|
||||
GridPoint next = new GridPoint(end.row, end.col);
|
||||
for (int i = 0; i < flows.length; i++) {
|
||||
if (i % 2 == 0) {
|
||||
next.row += flows[i];
|
||||
}
|
||||
else {
|
||||
next.col += flows[i];
|
||||
}
|
||||
points.add(new GridPoint(next.row, next.col));
|
||||
}
|
||||
Collections.reverse(points);
|
||||
return points;
|
||||
}
|
||||
|
||||
private TestVertex v(int id) {
|
||||
return new TestVertex(Integer.toString(id));
|
||||
}
|
||||
|
||||
private TestEdge e(TestVertex vertex1, TestVertex vertex2) {
|
||||
return new TestEdge(v1, v2);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue