mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +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
|
@ -44,6 +44,8 @@ src/main/resources/images/fgpaths.png||GHIDRA||reviewed||END|
|
|||
src/main/resources/images/fgrevblock.png||GHIDRA||reviewed||END|
|
||||
src/main/resources/images/field.header.png||GHIDRA||reviewed|Custom icon|END|
|
||||
src/main/resources/images/fullscreen_view.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
src/main/resources/images/function_graph_flowchart.png||GHIDRA||||END|
|
||||
src/main/resources/images/function_graph_flowchart_left.png||GHIDRA||||END|
|
||||
src/main/resources/images/graph_view.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
src/main/resources/images/id.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
src/main/resources/images/paintbrush.png||FAMFAMFAM Icons - CC 2.5||||END|
|
||||
|
|
|
@ -21,6 +21,8 @@ color.bg.plugin.functiongraph.paint.icon = color.palette.lightcornflowerblue
|
|||
|
||||
|
||||
icon.plugin.functiongraph.layout.experimental = package_development.png
|
||||
icon.plugin.functiongraph.layout.flowchart = function_graph_flowchart.png
|
||||
icon.plugin.functiongraph.layout.flowchart.left = function_graph_flowchart_left.png
|
||||
icon.plugin.functiongraph.action.vertex.xrefs = brick_link.png
|
||||
icon.plugin.functiongraph.action.vertex.maximize = fullscreen_view.png
|
||||
icon.plugin.functiongraph.action.vertex.minimize = graph_view.png
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -171,9 +171,8 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
|
||||
Address entryPoint = function.getEntryPoint();
|
||||
FGVertex vertex = getVertex(jungGraph, entryPoint);
|
||||
Integer row = gridLocations.row(vertex);
|
||||
Integer col = gridLocations.col(vertex);
|
||||
if (row != 0 || col != 0) {
|
||||
GridPoint gridPoint = gridLocations.gridPoint(vertex);
|
||||
if (gridPoint.row != 0 && gridPoint.col != 0) {
|
||||
Msg.debug(this, "Function graph has entry point not at top of layout: " + entryPoint);
|
||||
}
|
||||
|
||||
|
@ -338,7 +337,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
int startColumn = Math.min(start.columnIndex, end.columnIndex);
|
||||
int endColumn = Math.max(start.columnIndex, end.columnIndex);
|
||||
|
||||
Column rightmostLoopColumn = layoutToGridMap.col(rightmostLoopVertex);
|
||||
Column<FGVertex> rightmostLoopColumn = layoutToGridMap.col(rightmostLoopVertex);
|
||||
endColumn = Math.max(endColumn, rightmostLoopColumn.index);
|
||||
|
||||
// Look for any vertices that are no part of the loop, but are placed inside
|
||||
|
@ -351,8 +350,8 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
// place the right x position to the right of the rightmost vertex, not
|
||||
// extending past the next column
|
||||
FGVertex rightmostVertex = getRightmostVertex(interlopers);
|
||||
Column rightmostColumn = layoutToGridMap.col(rightmostVertex);
|
||||
Column nextColumn = layoutToGridMap.nextColumn(rightmostColumn);
|
||||
Column<FGVertex> rightmostColumn = layoutToGridMap.col(rightmostVertex);
|
||||
Column<FGVertex> nextColumn = layoutToGridMap.nextColumn(rightmostColumn);
|
||||
Vertex2d rightmostV2d = vertex2dFactory.get(rightmostVertex);
|
||||
|
||||
// the padding used for these two lines is somewhat arbitrary and may be changed
|
||||
|
@ -646,7 +645,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
}
|
||||
else {
|
||||
// going up we swing out to the right; grab the column that is out to the right
|
||||
Column rightColumn = vertex2dFactory.getColumn(edgeX);
|
||||
Column<FGVertex> rightColumn = vertex2dFactory.getColumn(edgeX);
|
||||
endColumn = rightColumn.index;
|
||||
}
|
||||
|
||||
|
@ -855,7 +854,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
@ -1128,7 +1127,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
this.edgeOffset = edgeOffset;
|
||||
}
|
||||
|
||||
Column getColumn(double x) {
|
||||
Column<FGVertex> getColumn(double x) {
|
||||
return layoutToGridMap.getColumnContaining((int) x);
|
||||
}
|
||||
|
||||
|
@ -1163,7 +1162,7 @@ public class DecompilerNestedLayout extends AbstractFGLayout {
|
|||
|
||||
private FGVertex v;
|
||||
private Row<FGVertex> row;
|
||||
private Column column;
|
||||
private Column<FGVertex> column;
|
||||
private int rowIndex;
|
||||
private int columnIndex;
|
||||
private Point2D center; // center point of vertex shape
|
||||
|
|
|
@ -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.
|
||||
|
@ -63,7 +63,7 @@ public class BowTieLayout extends AbstractVisualGraphLayout<FcgVertex, FcgEdge>
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getVertexLocation(FcgVertex v, Column col, Row<FcgVertex> row,
|
||||
protected Point2D getVertexLocation(FcgVertex v, Column<FcgVertex> col, Row<FcgVertex> row,
|
||||
Rectangle bounds) {
|
||||
return getCenteredVertexLocation(v, col, row, bounds);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -485,4 +485,21 @@ public class CollectionUtils {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the only element from the given collection; null if the collection is null or empty
|
||||
* or size is greater than 1. This is meant to clients to get the one and only element in
|
||||
* a collection of size 1.
|
||||
*
|
||||
* @param c the collection
|
||||
* @return the item
|
||||
* @see #any(Collection)
|
||||
*/
|
||||
public static <T> T get(Collection<T> c) {
|
||||
if (c == null || c.size() > 1) {
|
||||
return null;
|
||||
}
|
||||
return any((Iterable<T>) c);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -627,4 +627,48 @@ public class GraphAlgorithms {
|
|||
recursivePrint(g, v2, set, depth + 1, ps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of list of vertices sorted topologically such that for every edge
|
||||
* V1 -> V2, the V1 vertex will appear in the resulting list before the V2 vertex. Normally,
|
||||
* this is only defined for acyclic graphs. For purposes of this implementation, a root vertex
|
||||
* is given as a start point and any edge encountered by following edges from the root that
|
||||
* results in a "back" edge (i.e any edge that points to a previously visited vertex) is
|
||||
* ignored, effectively making the graph acyclic (somewhat arbitrarily depending the order in
|
||||
* which vertexes are visited which is determined by the given edge comparator). Also, note
|
||||
* that any vertex in the graph that is not reachable from the given root will not appear in
|
||||
* the resulting list of sorted vertices.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
* @param g the graph
|
||||
* @param root the start node for traversing the graph (will always be the first node in the
|
||||
* resulting list)
|
||||
* @param edgeComparator provides an ordering for traversing the graph which can impact which
|
||||
* edges are ignored as "back" edges and ultimately affect the final ordering
|
||||
* @return a list of vertices reachable from the given root vertex, sorted topologically
|
||||
*/
|
||||
public static <V, E extends GEdge<V>> List<V> topologicalSort(GDirectedGraph<V, E> g, V root,
|
||||
Comparator<E> edgeComparator) {
|
||||
GraphToTreeAlgorithm<V, E> algorithm = new GraphToTreeAlgorithm<V, E>(g, edgeComparator);
|
||||
return algorithm.topolocigalSort(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a general directed graph into a tree graph with the given vertex as the root. It
|
||||
* does this by first doing a topological sort (which ignores back edges) and greedily accepting
|
||||
* the first incoming edge based on the sorted vertex order.
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
* @param g the graph to be converted into a tree
|
||||
* @param root the vertex to be used as the root
|
||||
* @param edgeComparator provides a priority ordering of edges with higher priority edges
|
||||
* getting first shot at claiming children for its sub-tree.
|
||||
* @return a graph with edges removed such that the graph is a tree.
|
||||
*/
|
||||
public static <V, E extends GEdge<V>> GDirectedGraph<V, E> toTree(GDirectedGraph<V, E> g,
|
||||
V root, Comparator<E> edgeComparator) {
|
||||
GraphToTreeAlgorithm<V, E> algorithm = new GraphToTreeAlgorithm<V, E>(g, edgeComparator);
|
||||
return algorithm.toTree(root);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
/* ###
|
||||
* 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.graph;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
import ghidra.graph.jung.JungDirectedGraph;
|
||||
|
||||
/**
|
||||
* This class provides an algorithm for topological graph sorting and an algorithm for using
|
||||
* that topological sort to create a tree structure from the graph using that topological sort.
|
||||
* <P>
|
||||
* In general topological sorting and converting to a tree, require an acyclic graph. However,
|
||||
* by supplying a root vertex, the graph can be made to be acyclic by traversing the graph from
|
||||
* that root and discarding any edges the return to a "visited" vertex. This has a side effect of
|
||||
* ignoring any nodes that are not reachable from the root node. Also, this algorithm class is
|
||||
* constructed with an edge comparator which can also determine the order nodes are traversed,
|
||||
* thereby affecting the final ordering or tree structure. Higher priority edges will be processed
|
||||
* first, making those edges least likely to be removed as "back" edges.
|
||||
* <P>
|
||||
* To convert a general graph to a tree, some subset of the the graphs original edges are used to
|
||||
* form the tree. There are many possible different trees that can be created in this way. This
|
||||
* algorimth's goal is to create a tree such that if all the original "forward" edges are added
|
||||
* back to the tree, they only flow down the tree. This is useful for creating a nicely organized
|
||||
* layout of vertices and edges when drawn.
|
||||
*
|
||||
* @param <V> The vertex type
|
||||
* @param <E> The edge type
|
||||
*/
|
||||
public class GraphToTreeAlgorithm<V, E extends GEdge<V>> {
|
||||
private GDirectedGraph<V, E> graph;
|
||||
private Comparator<E> edgeComparator;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param graph the graph from with to create a tree
|
||||
* @param edgeComparator provides a priority ordering of edges with higher priority edges
|
||||
* getting first shot at claiming children for its sub-tree.
|
||||
*/
|
||||
public GraphToTreeAlgorithm(GDirectedGraph<V, E> graph, Comparator<E> edgeComparator) {
|
||||
this.graph = graph;
|
||||
this.edgeComparator = edgeComparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tree graph with the given vertex as the root from this object's graph.
|
||||
*
|
||||
* @param root the vertex to be used as the root
|
||||
* getting first shot at claiming children for its sub-tree.
|
||||
* @return a graph with edges removed such that the graph is a tree.
|
||||
*/
|
||||
public GDirectedGraph<V, E> toTree(V root) {
|
||||
|
||||
// first sort the vertices topologically
|
||||
List<V> sorted = topolocigalSort(root);
|
||||
|
||||
// Visit nodes in the sorted order and track the longest path to each node from the root.
|
||||
Map<V, Depth> depthMap = assignDepths(root, sorted);
|
||||
|
||||
// Assign vertices to the tree in the sorted order and only using edges where the "from"
|
||||
// vertex (parent) is at a depth 1 less then the depth of "to" vertex. This will ensure
|
||||
// that the tree is ordered such that if all the original forward edges are added back in,
|
||||
// they would always flow down the tree.
|
||||
return createTree(root, sorted, depthMap);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the vertices in this graph topologically.
|
||||
*
|
||||
* @param root the start node for traversing the graph (will always be the first node in the
|
||||
* resulting list)
|
||||
* @return a list of vertices reachable from the given root vertex, sorted topologically
|
||||
*/
|
||||
public List<V> topolocigalSort(V root) {
|
||||
|
||||
Set<V> visited = new HashSet<>();
|
||||
List<V> ordered = new ArrayList<>();
|
||||
|
||||
Deque<VertexChildIterator> stack = new ArrayDeque<>();
|
||||
|
||||
stack.push(new VertexChildIterator(root));
|
||||
visited.add(root);
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
VertexChildIterator childIterator = stack.getFirst();
|
||||
if (childIterator.hasNext()) {
|
||||
V child = childIterator.next();
|
||||
|
||||
// only process the child if never seen before, otherwise it is a loop back
|
||||
if (!visited.contains(child)) {
|
||||
stack.push(new VertexChildIterator(child));
|
||||
visited.add(child);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ordered.add(childIterator.getParent());
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
Collections.reverse(ordered);
|
||||
return ordered;
|
||||
}
|
||||
|
||||
private JungDirectedGraph<V, E> createTree(V root, List<V> sorted, Map<V, Depth> depthMap) {
|
||||
Set<V> visited = new HashSet<>();
|
||||
visited.add(root);
|
||||
|
||||
JungDirectedGraph<V, E> tree = new JungDirectedGraph<V, E>();
|
||||
for (V v : sorted) {
|
||||
tree.addVertex(v);
|
||||
}
|
||||
|
||||
for (V parent : sorted) {
|
||||
Depth parentDepth = depthMap.get(parent);
|
||||
Collection<E> outEdges = graph.getOutEdges(parent);
|
||||
for (E e : outEdges) {
|
||||
V child = e.getEnd();
|
||||
if (visited.contains(child)) {
|
||||
continue; // already assigned
|
||||
}
|
||||
Depth childDepth = depthMap.get(child);
|
||||
if (childDepth.isDirectChildOf(parentDepth)) {
|
||||
tree.addEdge(e);
|
||||
visited.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
private Map<V, Depth> assignDepths(V root, List<V> sorted) {
|
||||
|
||||
Set<V> visited = new HashSet<>();
|
||||
Map<V, Depth> depthMap = LazyMap.lazyMap(new HashMap<>(), k -> new Depth());
|
||||
|
||||
depthMap.put(root, new Depth());
|
||||
for (V parent : sorted) {
|
||||
visited.add(parent);
|
||||
Depth parentDepth = depthMap.get(parent);
|
||||
List<E> edges = new ArrayList<>();
|
||||
Collection<E> out = graph.getOutEdges(parent);
|
||||
if (out != null) {
|
||||
edges.addAll(out);
|
||||
}
|
||||
edges.sort(edgeComparator);
|
||||
for (E e : edges) {
|
||||
V child = e.getEnd();
|
||||
if (visited.contains(child)) {
|
||||
continue; // loop backs are ignored
|
||||
}
|
||||
Depth childDepth = depthMap.get(child);
|
||||
childDepth.adjustDepth(parentDepth);
|
||||
}
|
||||
}
|
||||
return depthMap;
|
||||
}
|
||||
|
||||
// traces the distance from the root of the tree
|
||||
private static class Depth {
|
||||
private int depth = 0;
|
||||
|
||||
private void adjustDepth(Depth parentDepth) {
|
||||
depth = Math.max(depth, parentDepth.depth + 1);
|
||||
}
|
||||
|
||||
private boolean isDirectChildOf(Depth parentDepth) {
|
||||
return depth == parentDepth.depth + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private class VertexChildIterator {
|
||||
private V parent;
|
||||
private Iterator<E> it;
|
||||
|
||||
VertexChildIterator(V parent) {
|
||||
this.parent = parent;
|
||||
Collection<E> out = graph.getOutEdges(parent);
|
||||
List<E> outEdges = new ArrayList<>();
|
||||
if (out != null) {
|
||||
outEdges.addAll(out);
|
||||
}
|
||||
outEdges.sort(edgeComparator);
|
||||
it = outEdges.reversed().iterator();
|
||||
}
|
||||
|
||||
V getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
public V next() {
|
||||
return it.next().getEnd();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,7 +627,7 @@ public class GraphComponent<V extends VisualVertex, E extends VisualEdge<V>, G e
|
|||
return satelliteViewer;
|
||||
}
|
||||
|
||||
protected VisualGraphViewUpdater<V, E> getViewUpdater() {
|
||||
public VisualGraphViewUpdater<V, E> getViewUpdater() {
|
||||
return primaryViewer.getViewUpdater();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -335,8 +335,9 @@ public class VisualGraphViewUpdater<V extends VisualVertex, E extends VisualEdge
|
|||
setGraphScale(graphInfo.getZoom());
|
||||
|
||||
Point layoutPoint = graphInfo.getLayoutTranslateCoordinates();
|
||||
multiLayerTransformer.getTransformer(Layer.LAYOUT).setTranslate(layoutPoint.x,
|
||||
layoutPoint.y);
|
||||
multiLayerTransformer.getTransformer(Layer.LAYOUT)
|
||||
.setTranslate(layoutPoint.x,
|
||||
layoutPoint.y);
|
||||
|
||||
Point viewPoint = graphInfo.getViewTranslateCoordinates();
|
||||
multiLayerTransformer.getTransformer(Layer.VIEW).setTranslate(viewPoint.x, viewPoint.y);
|
||||
|
@ -443,4 +444,8 @@ public class VisualGraphViewUpdater<V extends VisualVertex, E extends VisualEdge
|
|||
stopVertexTwinkleAnimation();
|
||||
}
|
||||
|
||||
public void relayoutGraph() {
|
||||
scheduleViewChangeJob(new RelayoutFunctionGraphJob<>(primaryViewer, isAnimationEnabled()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -337,7 +337,7 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
|||
}
|
||||
|
||||
// DEGUG triggers grid lines to be printed; useful for debugging
|
||||
// VisualGraphRenderer.DEBUG_ROW_COL_MAP.put(this, layoutLocations.copy());
|
||||
// VisualGraphRenderer.setGridPainter(new GridPainter(layoutLocations.getGridCoordinates()));
|
||||
|
||||
layoutLocations.dispose();
|
||||
gridLocations.dispose();
|
||||
|
@ -356,7 +356,7 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
|||
monitor.checkCancelled();
|
||||
|
||||
Row<V> row = layoutLocations.row(vertex);
|
||||
Column column = layoutLocations.col(vertex);
|
||||
Column<V> column = layoutLocations.col(vertex);
|
||||
|
||||
Shape shape = transformer.apply(vertex);
|
||||
Rectangle bounds = shape.getBounds();
|
||||
|
@ -366,7 +366,7 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
|||
return newLocations;
|
||||
}
|
||||
|
||||
protected Point2D getVertexLocation(V v, Column col, Row<V> row, Rectangle bounds) {
|
||||
protected Point2D getVertexLocation(V v, Column<V> col, Row<V> row, Rectangle bounds) {
|
||||
int x = col.x - bounds.x;
|
||||
int y = row.y - bounds.y;
|
||||
return new Point2D.Double(x, y);
|
||||
|
@ -381,7 +381,7 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
|||
* @param bounds the bounds of the vertex in the layout space
|
||||
* @return the centered location
|
||||
*/
|
||||
protected Point2D getCenteredVertexLocation(V v, Column col, Row<V> row, Rectangle bounds) {
|
||||
protected Point2D getCenteredVertexLocation(V v, Column<V> col, Row<V> row, Rectangle bounds) {
|
||||
//
|
||||
// Move x over to compensate for vertex painting. Edges are drawn from the center of the
|
||||
// vertex. Thus, if you have vertices with two different widths, then the edge between
|
||||
|
@ -409,9 +409,9 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
|||
monitor.checkCancelled();
|
||||
|
||||
List<Point2D> newArticulations = new ArrayList<>();
|
||||
for (Point gridPoint : layoutLocations.articulations(edge)) {
|
||||
Row<V> row = layoutLocations.row(gridPoint.y);
|
||||
Column column = layoutLocations.col(gridPoint.x);
|
||||
for (GridPoint gridPoint : layoutLocations.articulations(edge)) {
|
||||
Row<V> row = layoutLocations.row(gridPoint.row);
|
||||
Column<V> column = layoutLocations.col(gridPoint.col);
|
||||
|
||||
Point2D location = getEdgeLocation(column, row);
|
||||
newArticulations.add(location);
|
||||
|
@ -421,11 +421,11 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
|||
return newEdgeArticulations;
|
||||
}
|
||||
|
||||
protected Point2D getEdgeLocation(Column col, Row<V> row) {
|
||||
protected Point2D getEdgeLocation(Column<V> col, Row<V> row) {
|
||||
return new Point2D.Double(col.x, row.y);
|
||||
}
|
||||
|
||||
protected Point2D getCenteredEdgeLocation(Column col, Row<V> row) {
|
||||
protected Point2D getCenteredEdgeLocation(Column<V> col, Row<V> row) {
|
||||
//
|
||||
// half-height offsets the articulation points, which keeps long edge lines from
|
||||
// overlapping as much
|
||||
|
|
|
@ -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.
|
||||
|
@ -16,16 +16,22 @@
|
|||
package ghidra.graph.viewer.layout;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ghidra.graph.viewer.GraphViewerUtils;
|
||||
|
||||
/**
|
||||
* A row in a grid. This class stores it's row index, its x offset and its width. The
|
||||
* A column in a grid. This class stores its column index, its x offset and its width. The
|
||||
* x value is the layout space x value of a {@link Point2D} object. That is, unlike the
|
||||
* {@link GridLocationMap}, the x value of this object is in layout space and not indexes
|
||||
* of a grid.
|
||||
*
|
||||
* <p>This class maintains a collection of vertices on this column, organized by column index. You
|
||||
* can get the column of a vertex from {@link #getRow(Object)}.
|
||||
* @param <V> The vertex type
|
||||
*/
|
||||
public class Column {
|
||||
public class Column<V> {
|
||||
|
||||
/** The <b>layout</b> x coordinate of the column */
|
||||
public int x = -1;
|
||||
|
@ -33,11 +39,26 @@ public class Column {
|
|||
|
||||
/** The grid index of this column (0, 1...n) for the number of columns */
|
||||
public int index = Integer.MAX_VALUE;
|
||||
// Note: these must change together (they are effectively a BiDi map)
|
||||
private TreeMap<Integer, V> verticesByRow = new TreeMap<>();
|
||||
private Map<V, Integer> rowsByVertex = new HashMap<>();
|
||||
|
||||
public Column(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public void setRow(V v, int row) {
|
||||
rowsByVertex.put(v, row);
|
||||
verticesByRow.put(row, v);
|
||||
}
|
||||
|
||||
public int getRow(V v) {
|
||||
if (!rowsByVertex.containsKey(v)) {
|
||||
throw new IllegalArgumentException("Vertex is not in row: " + v);
|
||||
}
|
||||
return rowsByVertex.get(v);
|
||||
}
|
||||
|
||||
public int getPaddedWidth(boolean isCondensed) {
|
||||
if (isCondensed) {
|
||||
return width + GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING_CONDENSED;
|
||||
|
@ -61,4 +82,13 @@ public class Column {
|
|||
"}";
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
public boolean isOpenBetween(int startRow, int endRow) {
|
||||
Entry<Integer, V> ceilingEntry = verticesByRow.ceilingEntry(startRow);
|
||||
if (ceilingEntry == null) {
|
||||
return true;
|
||||
}
|
||||
int nextRow = ceilingEntry.getKey();
|
||||
return nextRow > endRow;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/* ###
|
||||
* 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.graph.viewer.layout;
|
||||
|
||||
/**
|
||||
* Tracks the minimum and maximum indexes for both rows and columns.
|
||||
*/
|
||||
|
||||
public class GridBounds {
|
||||
private int minRow = 0;
|
||||
private int maxRow = 0;
|
||||
private int minCol = 0;
|
||||
private int maxCol = 0;
|
||||
|
||||
/**
|
||||
* Updates the bounds for the given GridPoint.
|
||||
* @param p the gridPoint used to update the minimums and maximums
|
||||
*/
|
||||
public void update(GridPoint p) {
|
||||
minRow = Math.min(minRow, p.row);
|
||||
maxRow = Math.max(maxRow, p.row);
|
||||
minCol = Math.min(minCol, p.col);
|
||||
maxCol = Math.max(maxCol, p.col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts the columns bounds by the given amount
|
||||
* @param rowShift the amount to shift the row bounds.
|
||||
* @param colShift the amount to shift the column bounds.
|
||||
* @throws IllegalArgumentException if the shift would make the minimum column negative
|
||||
*/
|
||||
public void shift(int rowShift, int colShift) {
|
||||
minCol += colShift;
|
||||
maxCol += colShift;
|
||||
minRow += rowShift;
|
||||
maxRow += rowShift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
buffy.append("Grid Bounds: ");
|
||||
if (minRow == Integer.MAX_VALUE) {
|
||||
return "Empty";
|
||||
}
|
||||
|
||||
buffy.append("rows: ").append(minRow).append(" -> ").append(maxRow);
|
||||
buffy.append(", ");
|
||||
buffy.append("cols: ").append(minCol).append(" -> ").append(maxCol);
|
||||
return buffy.toString();
|
||||
}
|
||||
|
||||
public int maxCol() {
|
||||
return maxCol;
|
||||
}
|
||||
|
||||
public int minCol() {
|
||||
// handle case when grid is empty
|
||||
if (minCol > maxCol) {
|
||||
return 0;
|
||||
}
|
||||
return minCol;
|
||||
}
|
||||
|
||||
public int maxRow() {
|
||||
return maxRow;
|
||||
}
|
||||
|
||||
public int minRow() {
|
||||
// handle case when grid is empty
|
||||
if (minRow > maxRow) {
|
||||
return 0;
|
||||
}
|
||||
return minRow;
|
||||
}
|
||||
|
||||
public boolean contains(GridPoint p) {
|
||||
if (p.row < minRow || p.row > maxRow) {
|
||||
return false;
|
||||
}
|
||||
if (p.col < minCol || p.col > maxCol) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* ###
|
||||
* 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.graph.viewer.layout;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
|
||||
/**
|
||||
* Tracks the mapping of grid coordinates (rows, columns) to space coordinates (x, y)
|
||||
*/
|
||||
public class GridCoordinates {
|
||||
private int[] rowStarts;
|
||||
private int[] colStarts;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param rowCoordinates an array containing the y locations for all rows in a grid
|
||||
* @param columnCoordinates an array containing the x locations for all columns in a grid
|
||||
*/
|
||||
public GridCoordinates(int[] rowCoordinates, int[] columnCoordinates) {
|
||||
rowStarts = rowCoordinates;
|
||||
colStarts = columnCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x value for a given column.
|
||||
* @param col the column index in the grid
|
||||
* @return the x coordinate assigned to the given column index
|
||||
*/
|
||||
public int x(int col) {
|
||||
return colStarts[col];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y value for a given row.
|
||||
* @param row the row index in the grid
|
||||
* @return the y coordinate assigned to the given row index
|
||||
*/
|
||||
public int y(int row) {
|
||||
return rowStarts[row];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total bounds for the grid
|
||||
* @return the total bounds for the grid
|
||||
*/
|
||||
public Rectangle getBounds() {
|
||||
return new Rectangle(0, 0, colStarts[colStarts.length - 1],
|
||||
rowStarts[rowStarts.length - 1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the number of rows in the grid.
|
||||
* @return the number of rows in the grid
|
||||
*/
|
||||
public int rowCount() {
|
||||
return rowStarts.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the number of columns in the grid.
|
||||
* @return the number of columns in the grid
|
||||
*/
|
||||
public int columnCount() {
|
||||
return colStarts.length;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
@ -15,49 +15,86 @@
|
|||
*/
|
||||
package ghidra.graph.viewer.layout;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.collections4.Factory;
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
|
||||
/**
|
||||
* An object that maps vertices to rows and columns and edges to their articulation points.
|
||||
* This class is essentially a container that allows layout algorithms to store results, which
|
||||
* can later be turned into layout positioning points. The integer point values in this
|
||||
* class are row, column grid values, starting at 0,0.
|
||||
*
|
||||
* <P>Note: the Point2D values for the edge articulations use x,y values that are row and
|
||||
* column index values, the same values as calling {@link #row(Object) row(V)} and {@link #col(Object) col(V)}.
|
||||
*
|
||||
* <P>After building the grid using this class, clients can call {@link #rows()} to get
|
||||
* high-order object that represent rows.
|
||||
* An object that maps vertices and edge articulation points to rows and columns in a grid. This
|
||||
* class is essentially a container that allows layout algorithms to store results as it lays
|
||||
* out vertices and edges in a virtual grid. Later, this information can be used in conjunction
|
||||
* with vertex size information and padding information to transform these grid coordinates to
|
||||
* layout space coordinates.
|
||||
* <P>
|
||||
* This object also has methods for manipulating the grid such as shifting it up, down, left, right,
|
||||
* and merging in other GridLocationMaps
|
||||
* <P>
|
||||
* After building the grid using this class, clients can call {@link #rows()}, {@link #rowsMap()},
|
||||
* or {@link #columnsMap()} to get high-order objects that represent rows or columns.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class GridLocationMap<V, E> {
|
||||
|
||||
private Factory<Point> rowColFactory = () -> new Point();
|
||||
protected Map<V, GridPoint> vertexPoints =
|
||||
LazyMap.lazyMap(new HashMap<V, GridPoint>(), v -> new GridPoint(0, 0));
|
||||
|
||||
private Map<V, Point> vertexPoints = LazyMap.lazyMap(new HashMap<V, Point>(), rowColFactory);
|
||||
private Map<E, List<Point>> edgePoints = new HashMap<>();
|
||||
protected Map<E, List<GridPoint>> edgePoints = new HashMap<>();
|
||||
private GridBounds gridBounds = new GridBounds();
|
||||
// Tree based algorithms might want to track the column of the root node as it changes when
|
||||
// the grid is shifted or merged.Useful for determining the position of a parent node when
|
||||
// building bottom up.
|
||||
private int rootColumn = 0;
|
||||
|
||||
Set<V> vertices() {
|
||||
public GridLocationMap() {
|
||||
rootColumn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that includes an initial "root" vertex.
|
||||
* @param root the initial vertex
|
||||
* @param row the row for the initial vertex
|
||||
* @param col the column for the initial vertex.
|
||||
*/
|
||||
public GridLocationMap(V root, int row, int col) {
|
||||
this.rootColumn = col;
|
||||
set(root, new GridPoint(row, col));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the column of the initial vertex in this grid.
|
||||
* @return the column of the initial vertex in this grid
|
||||
*/
|
||||
public int getRootColumn() {
|
||||
return rootColumn;
|
||||
}
|
||||
|
||||
public Set<V> vertices() {
|
||||
return vertexPoints.keySet();
|
||||
}
|
||||
|
||||
Set<E> edges() {
|
||||
public Set<E> edges() {
|
||||
return edgePoints.keySet();
|
||||
}
|
||||
|
||||
public void setArticulations(E edge, List<Point> articulations) {
|
||||
edgePoints.put(edge, articulations);
|
||||
public Map<V, GridPoint> getVertexPoints() {
|
||||
return vertexPoints;
|
||||
}
|
||||
|
||||
public List<Point> getArticulations(E edge) {
|
||||
List<Point> list = edgePoints.get(edge);
|
||||
public void setArticulations(E edge, List<GridPoint> articulations) {
|
||||
edgePoints.put(edge, articulations);
|
||||
if (articulations != null) {
|
||||
for (GridPoint gridPoint : articulations) {
|
||||
gridBounds.update(gridPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<GridPoint> getArticulations(E edge) {
|
||||
List<GridPoint> list = edgePoints.get(edge);
|
||||
if (list == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -65,25 +102,44 @@ public class GridLocationMap<V, E> {
|
|||
}
|
||||
|
||||
public void row(V vertex, int row) {
|
||||
vertexPoints.get(vertex).y = row;
|
||||
GridPoint gridPoint = vertexPoints.get(vertex);
|
||||
gridPoint.row = row;
|
||||
gridBounds.update(gridPoint);
|
||||
}
|
||||
|
||||
public void col(V vertex, int col) {
|
||||
vertexPoints.get(vertex).x = col;
|
||||
GridPoint gridPoint = vertexPoints.get(vertex);
|
||||
gridPoint.col = col;
|
||||
gridBounds.update(gridPoint);
|
||||
}
|
||||
|
||||
public void set(V v, int row, int col) {
|
||||
Point p = vertexPoints.get(v);
|
||||
p.x = col;
|
||||
p.y = row;
|
||||
set(v, new GridPoint(row, col));
|
||||
}
|
||||
|
||||
public void set(V v, GridPoint gridPoint) {
|
||||
vertexPoints.put(v, gridPoint);
|
||||
gridBounds.update(gridPoint);
|
||||
}
|
||||
|
||||
public int row(V vertex) {
|
||||
return vertexPoints.get(vertex).y;
|
||||
GridPoint gridPoint = vertexPoints.get(vertex);
|
||||
if (gridPoint != null) {
|
||||
return gridPoint.row;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int col(V vertex) {
|
||||
return vertexPoints.get(vertex).x;
|
||||
GridPoint gridPoint = vertexPoints.get(vertex);
|
||||
if (gridPoint != null) {
|
||||
return gridPoint.col;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public GridPoint gridPoint(V vertex) {
|
||||
return vertexPoints.get(vertex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,143 +148,82 @@ public class GridLocationMap<V, E> {
|
|||
* @return the rows in this grid
|
||||
*/
|
||||
public List<Row<V>> rows() {
|
||||
|
||||
Map<Integer, Row<V>> rowsByIndex = new HashMap<>();
|
||||
|
||||
Set<Entry<V, Point>> entrySet = vertexPoints.entrySet();
|
||||
for (Entry<V, Point> entry : entrySet) {
|
||||
V v = entry.getKey();
|
||||
Point gridPoint = entry.getValue();
|
||||
int rowIndex = gridPoint.y;
|
||||
Row<V> row = getRow(rowsByIndex, rowIndex);
|
||||
row.index = rowIndex;
|
||||
row.setColumn(v, gridPoint.x);
|
||||
}
|
||||
|
||||
Map<Integer, Row<V>> rowsByIndex = rowsMap();
|
||||
List<Row<V>> rows = new ArrayList<>(rowsByIndex.values());
|
||||
rows.sort((r1, r2) -> r1.index - r2.index);
|
||||
return rows;
|
||||
}
|
||||
|
||||
private Row<V> getRow(Map<Integer, Row<V>> rows, int rowIndex) {
|
||||
Row<V> row = rows.get(rowIndex);
|
||||
if (row == null) {
|
||||
row = new Row<>(rowIndex);
|
||||
rows.put(rowIndex, row);
|
||||
/**
|
||||
* Returns a mapping or row indexes to Row objects in this grid
|
||||
*
|
||||
* @return the rows in this grid
|
||||
*/
|
||||
public Map<Integer, Row<V>> rowsMap() {
|
||||
Map<Integer, Row<V>> rowsByIndex = LazyMap.lazyMap(new HashMap<>(), r -> new Row<V>(r));
|
||||
|
||||
Set<Entry<V, GridPoint>> entrySet = vertexPoints.entrySet();
|
||||
for (Entry<V, GridPoint> entry : entrySet) {
|
||||
V v = entry.getKey();
|
||||
GridPoint gridPoint = entry.getValue();
|
||||
int rowIndex = gridPoint.row;
|
||||
Row<V> row = rowsByIndex.get(rowIndex);
|
||||
row.setColumn(v, gridPoint.col);
|
||||
}
|
||||
return row;
|
||||
return rowsByIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates each row within the grid such that it's x values are set to center the row in
|
||||
* Returns a mapping or column indexes to Column objects in this grid
|
||||
*
|
||||
* @return the columns in this grid
|
||||
*/
|
||||
public Map<Integer, Column<V>> columnsMap() {
|
||||
Map<Integer, Column<V>> columnsMap =
|
||||
LazyMap.lazyMap(new HashMap<>(), c -> new Column<V>(c));
|
||||
|
||||
Set<Entry<V, GridPoint>> entrySet = vertexPoints.entrySet();
|
||||
for (Entry<V, GridPoint> entry : entrySet) {
|
||||
V v = entry.getKey();
|
||||
GridPoint gridPoint = entry.getValue();
|
||||
int colIndex = gridPoint.col;
|
||||
Column<V> col = columnsMap.get(colIndex);
|
||||
col.setRow(v, gridPoint.row);
|
||||
}
|
||||
return columnsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates each row within the grid such that it's column values are set to center the row in
|
||||
* the grid. Each row will be updated so that all its columns start at zero. After that,
|
||||
* each column will be centered in the grid.
|
||||
*/
|
||||
public void centerRows() {
|
||||
zeroAlignGrid();
|
||||
GridRange[] vertexColumnRanges = getVertexColumnRanges();
|
||||
int maxRowWidth = getMaxRowWidth(vertexColumnRanges);
|
||||
|
||||
List<Row<V>> rows = rows();
|
||||
int maxCol = columnCount(rows);
|
||||
for (Row<V> row : rows) {
|
||||
|
||||
row = zeroRowColumns(row);
|
||||
|
||||
int rowColumnCount = row.getColumnCount();
|
||||
if (rowColumnCount == maxCol) {
|
||||
continue; // already the full size; no need to center
|
||||
}
|
||||
|
||||
int delta = maxCol - rowColumnCount;
|
||||
int offset = delta / 2;
|
||||
List<V> vertices = row.getVertices();
|
||||
for (V v : vertices) {
|
||||
if (v == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int oldCol = col(v);
|
||||
set(v, row.index, oldCol + offset);
|
||||
}
|
||||
|
||||
row.dispose();
|
||||
for (GridPoint p : allPoints()) {
|
||||
GridRange range = vertexColumnRanges[p.row];
|
||||
int extraSpace = maxRowWidth - range.width();
|
||||
int shift = extraSpace / 2 - range.min;
|
||||
p.col += shift;
|
||||
}
|
||||
}
|
||||
|
||||
private int maxColumnIndex(List<Row<V>> rows) {
|
||||
int maxCol = 0;
|
||||
for (Row<V> row : rows) {
|
||||
maxCol = Math.max(maxCol, row.getEndColumn());
|
||||
private int getMaxRowWidth(GridRange[] vertexColumnRanges) {
|
||||
int maxWidth = 0;
|
||||
for (GridRange gridRange : vertexColumnRanges) {
|
||||
maxWidth = Math.max(maxWidth, gridRange.width());
|
||||
}
|
||||
return maxCol;
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
private int maxRowIndex(List<Row<V>> rows) {
|
||||
int maxRow = 0;
|
||||
for (Row<V> row : rows) {
|
||||
maxRow = Math.max(maxRow, row.index);
|
||||
}
|
||||
return maxRow;
|
||||
}
|
||||
|
||||
private int columnCount(List<Row<V>> rows) {
|
||||
|
||||
int maxCount = 0;
|
||||
for (Row<V> row : rows) {
|
||||
maxCount = Math.max(maxCount, row.getColumnCount());
|
||||
}
|
||||
return maxCount;
|
||||
}
|
||||
|
||||
// private int rowCount(List<Row<V>> rows) {
|
||||
// int minRow = 0;
|
||||
// int maxRow = 0;
|
||||
// for (Row<V> row : rows) {
|
||||
// minRow = Math.min(minRow, row.index);
|
||||
// maxRow = Math.max(maxRow, row.index);
|
||||
// }
|
||||
// return (maxRow - minRow) + 1; // +1 for zero-based
|
||||
// }
|
||||
|
||||
private Row<V> zeroRowColumns(Row<V> row) {
|
||||
|
||||
int start = row.getStartColumn();
|
||||
int offset = -start;
|
||||
|
||||
Row<V> updatedRow = new Row<>();
|
||||
updatedRow.index = row.index;
|
||||
for (V v : row.getVertices()) {
|
||||
int oldCol = col(v);
|
||||
int newCol = oldCol + offset;
|
||||
set(v, row.index, newCol);
|
||||
updatedRow.setColumn(v, newCol);
|
||||
}
|
||||
|
||||
row.dispose();
|
||||
return updatedRow;
|
||||
}
|
||||
|
||||
GridLocationMap<V, E> copy() {
|
||||
GridLocationMap<V, E> map = new GridLocationMap<>();
|
||||
|
||||
map.vertexPoints = new HashMap<>();
|
||||
Set<Entry<V, Point>> entries = vertexPoints.entrySet();
|
||||
for (Entry<V, Point> entry : entries) {
|
||||
map.vertexPoints.put(entry.getKey(), (Point) entry.getValue().clone());
|
||||
}
|
||||
|
||||
map.edgePoints = new HashMap<>();
|
||||
Set<Entry<E, List<Point>>> edgeEntries = edgePoints.entrySet();
|
||||
for (Entry<E, List<Point>> entry : edgeEntries) {
|
||||
|
||||
List<Point> points = entry.getValue();
|
||||
List<Point> clonedPoints = new ArrayList<>(points.size());
|
||||
for (Point p : points) {
|
||||
clonedPoints.add((Point) p.clone());
|
||||
}
|
||||
|
||||
map.edgePoints.put(entry.getKey(), clonedPoints);
|
||||
}
|
||||
|
||||
return map;
|
||||
/**
|
||||
* Shifts the grid so that its first row and column are at 0.
|
||||
*/
|
||||
public void zeroAlignGrid() {
|
||||
shift(-gridBounds.minRow(), -gridBounds.minCol());
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
|
@ -236,6 +231,103 @@ public class GridLocationMap<V, E> {
|
|||
edgePoints.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts the rows and columns for all points in this map by the given amount.
|
||||
* @param rowShift the amount to shift the rows of each point
|
||||
* @param colShift the amount to shift the columns of each point
|
||||
*/
|
||||
public void shift(int rowShift, int colShift) {
|
||||
if (rowShift == 0 && colShift == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (GridPoint p : allPoints()) {
|
||||
p.row += rowShift;
|
||||
p.col += colShift;
|
||||
}
|
||||
rootColumn += colShift;
|
||||
gridBounds.shift(rowShift, colShift);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows in this grid map. Note that this includes empty rows
|
||||
* starting at the 0 row.
|
||||
* @return the number of rows in this grid map
|
||||
*/
|
||||
public int height() {
|
||||
return gridBounds.maxRow() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of columns in this grid map. Note that this includes empty columns
|
||||
* starting at the 0 column.
|
||||
* @return the number of columns in this grid map
|
||||
*/
|
||||
public int width() {
|
||||
return gridBounds.maxCol() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum/max column for all rows in the grid. This method is only defined for
|
||||
* grids that have no negative rows. This is because the array returned will be 0 based, with
|
||||
* the entry at index 0 containing the column bounds for row 0 and so on.
|
||||
* @return the minimum/max column for all rows in the grid
|
||||
* @throws IllegalStateException if this method is called on a grid with negative rows.
|
||||
*/
|
||||
public GridRange[] getVertexColumnRanges() {
|
||||
if (gridBounds.minRow() < 0) {
|
||||
throw new IllegalStateException(
|
||||
"getVertexColumnRanges not defined for grids with negative rows!");
|
||||
}
|
||||
GridRange[] rowRanges = new GridRange[height()];
|
||||
|
||||
for (int i = 0; i < rowRanges.length; i++) {
|
||||
rowRanges[i] = new GridRange();
|
||||
}
|
||||
|
||||
for (GridPoint p : vertexPoints.values()) {
|
||||
rowRanges[p.row].add(p.col);
|
||||
}
|
||||
return rowRanges;
|
||||
}
|
||||
|
||||
public boolean containsVertex(V v) {
|
||||
return vertexPoints.containsKey(v);
|
||||
}
|
||||
|
||||
public boolean containsEdge(E e) {
|
||||
return edgePoints.containsKey(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds in the vertices and edges from another GridLocationMap with each point in the other
|
||||
* grid map shifted by the given row and column amounts.
|
||||
* @param other the other GridLocationMap to add to this one.
|
||||
* @param rowShift the amount to shift the rows in the grid points from the other grid before
|
||||
* adding them to this grid
|
||||
* @param colShift the amount to shift the columns in the grid points from the other grid before
|
||||
* adding them to this grid
|
||||
*/
|
||||
public void add(GridLocationMap<V, E> other, int rowShift, int colShift) {
|
||||
|
||||
for (Entry<V, GridPoint> entry : other.vertexPoints.entrySet()) {
|
||||
V v = entry.getKey();
|
||||
GridPoint point = entry.getValue();
|
||||
set(v, new GridPoint(point.row + rowShift, point.col + colShift));
|
||||
}
|
||||
|
||||
for (Entry<E, List<GridPoint>> entry : other.edgePoints.entrySet()) {
|
||||
E e = entry.getKey();
|
||||
List<GridPoint> points = entry.getValue();
|
||||
List<GridPoint> shiftedPoints = new ArrayList<>(points.size());
|
||||
for (GridPoint point : points) {
|
||||
shiftedPoints.add(new GridPoint(point.row + rowShift, point.col + colShift));
|
||||
}
|
||||
setArticulations(e, shiftedPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[\n\tvertex points=" + vertexPoints +
|
||||
|
@ -247,66 +339,71 @@ public class GridLocationMap<V, E> {
|
|||
* @return a string representation of this grid
|
||||
*/
|
||||
public String toStringGrid() {
|
||||
|
||||
GridLocationMap<V, E> copy = copy();
|
||||
zeroAlignGrid(copy);
|
||||
|
||||
List<Row<V>> rows = copy.rows();
|
||||
int columnCount = copy.maxColumnIndex(rows) + 1;
|
||||
int rowCount = copy.maxRowIndex(rows) + 1;
|
||||
|
||||
Object[][] vGrid = new Object[rowCount][columnCount];
|
||||
for (Row<V> row : rows) {
|
||||
List<V> vertices = row.getVertices();
|
||||
for (V v : vertices) {
|
||||
vGrid[row.index][row.getColumn(v)] = v;
|
||||
}
|
||||
int minRow = gridBounds.minRow();
|
||||
int minCol = gridBounds.minCol();
|
||||
if (minRow > 10 || minCol > 10) {
|
||||
GridLocationMap<V, E> copy = copy();
|
||||
copy.zeroAlignGrid();
|
||||
return "grid upper left (row,col) = (" + minRow + ", " + minCol + ")\n" +
|
||||
copy.toStringGrid();
|
||||
}
|
||||
|
||||
StringBuilder buffy = new StringBuilder("\n");
|
||||
for (int row = 0; row < rowCount; row++) {
|
||||
for (int col = 0; col < columnCount; col++) {
|
||||
Object o = vGrid[row][col];
|
||||
buffy.append(' ');
|
||||
if (o == null) {
|
||||
buffy.append('-');
|
||||
//buffy.append(' ');
|
||||
}
|
||||
else {
|
||||
buffy.append('v');
|
||||
}
|
||||
buffy.append(' ');
|
||||
}
|
||||
buffy.append('\n');
|
||||
}
|
||||
String[][] vGrid = new String[height()][width()];
|
||||
|
||||
for (Entry<V, GridPoint> entry : vertexPoints.entrySet()) {
|
||||
V v = entry.getKey();
|
||||
GridPoint p = entry.getValue();
|
||||
vGrid[p.row][p.col] = normalizeVertexName(v.toString());
|
||||
}
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
buffy.append("\n");
|
||||
for (int row = 0; row < vGrid.length; row++) {
|
||||
for (int col = 0; col < vGrid[row].length; col++) {
|
||||
String name = vGrid[row][col];
|
||||
name = name == null ? ". " : name;
|
||||
buffy.append(name);
|
||||
buffy.append("");
|
||||
}
|
||||
buffy.append("\n");
|
||||
}
|
||||
return buffy.toString();
|
||||
}
|
||||
|
||||
// moves all rows and columns as needed to convert the grid origin to 0,0
|
||||
private static <V, E> void zeroAlignGrid(GridLocationMap<V, E> grid) {
|
||||
private GridLocationMap<V, E> copy() {
|
||||
GridLocationMap<V, E> map = new GridLocationMap<>();
|
||||
map.rootColumn = rootColumn;
|
||||
|
||||
int smallestColumnIndex = 0;
|
||||
int smallestRowIndex = 0;
|
||||
List<Row<V>> rows = grid.rows();
|
||||
for (Row<V> row : rows) {
|
||||
smallestRowIndex = Math.min(smallestRowIndex, row.index);
|
||||
smallestColumnIndex = Math.min(smallestColumnIndex, row.getStartColumn());
|
||||
Set<Entry<V, GridPoint>> entries = vertexPoints.entrySet();
|
||||
for (Entry<V, GridPoint> entry : entries) {
|
||||
map.set(entry.getKey(), new GridPoint(entry.getValue()));
|
||||
}
|
||||
|
||||
int globalColumnOffset = -smallestColumnIndex;
|
||||
int globalRowOffset = -smallestRowIndex;
|
||||
|
||||
for (Row<V> row : rows) {
|
||||
|
||||
List<V> vertices = row.getVertices();
|
||||
for (V v : vertices) {
|
||||
int oldCol = grid.col(v);
|
||||
int oldRow = grid.row(v);
|
||||
int newCol = globalColumnOffset + oldCol;
|
||||
int newRow = globalRowOffset + oldRow;
|
||||
grid.set(v, newRow, newCol);
|
||||
}
|
||||
Set<Entry<E, List<GridPoint>>> edgeEntries = edgePoints.entrySet();
|
||||
for (Entry<E, List<GridPoint>> entry : edgeEntries) {
|
||||
List<GridPoint> points = entry.getValue();
|
||||
List<GridPoint> copy = new ArrayList<>(points.size());
|
||||
points.forEach(p -> copy.add(new GridPoint(p)));
|
||||
map.setArticulations(entry.getKey(), copy);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private String normalizeVertexName(String name) {
|
||||
if (name.length() > 8) {
|
||||
return name.substring(0, 8);
|
||||
}
|
||||
return name + " ".substring(name.length());
|
||||
}
|
||||
|
||||
private Iterable<GridPoint> allPoints() {
|
||||
Stream<GridPoint> vPoints = vertexPoints.values().stream();
|
||||
Stream<GridPoint> ePoints = edgePoints.values().stream().flatMap(l -> l.stream());
|
||||
Stream<GridPoint> streams = Stream.concat(vPoints, ePoints);
|
||||
return () -> streams.iterator();
|
||||
}
|
||||
|
||||
public boolean containsPoint(GridPoint p) {
|
||||
return gridBounds.contains(p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* ###
|
||||
* 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.graph.viewer.layout;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Row and column information for points in a {@link GridLocationMap}. Using these instead
|
||||
* of java Points, makes the code that translates from grid space to layout space much less
|
||||
* confusing.
|
||||
*/
|
||||
public class GridPoint {
|
||||
|
||||
public int row;
|
||||
public int col;
|
||||
|
||||
public GridPoint(int row, int col) {
|
||||
this.row = row;
|
||||
this.col = col;
|
||||
}
|
||||
|
||||
public GridPoint(GridPoint point) {
|
||||
this.row = point.row;
|
||||
this.col = point.col;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(col, row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GridPoint other = (GridPoint) obj;
|
||||
return col == other.col && row == other.row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(r=" + row + ",c=" + col + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/* ###
|
||||
* 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.graph.viewer.layout;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class for reporting the min/max columns in a row or the min/max rows in a column
|
||||
*/
|
||||
public class GridRange {
|
||||
public int min;
|
||||
public int max;
|
||||
|
||||
public GridRange() {
|
||||
this(Integer.MAX_VALUE, Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
public GridRange(int min, int max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public void add(int value) {
|
||||
min = Math.min(value, min);
|
||||
max = Math.max(value, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + min + " -> " + max + "]";
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return min > max;
|
||||
}
|
||||
|
||||
public boolean contains(int value) {
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(max, min);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
GridRange other = (GridRange) obj;
|
||||
return max == other.max && min == other.min;
|
||||
}
|
||||
|
||||
public int width() {
|
||||
if (isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return max - min + 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -15,9 +15,9 @@
|
|||
*/
|
||||
package ghidra.graph.viewer.layout;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -43,7 +43,7 @@ public class LayoutLocationMap<V, E> {
|
|||
private int numColumns;
|
||||
|
||||
private TreeMap<Integer, Row<V>> rowsByIndex = new TreeMap<>();
|
||||
private TreeMap<Integer, Column> columnsByIndex = new TreeMap<>();
|
||||
private TreeMap<Integer, Column<V>> columnsByIndex = new TreeMap<>();
|
||||
|
||||
private boolean isCondensed = false;
|
||||
private GridLocationMap<V, E> gridLocations;
|
||||
|
@ -54,29 +54,12 @@ public class LayoutLocationMap<V, E> {
|
|||
this.gridLocations = gridLocations;
|
||||
|
||||
Set<V> vertices = gridLocations.vertices();
|
||||
Set<E> edges = gridLocations.edges();
|
||||
MinMaxRowColumn minMax = getMinMaxRowColumnValues(vertices, edges, monitor);
|
||||
numRows = minMax.maxRow + 1;
|
||||
numColumns = minMax.maxCol + 1;
|
||||
numRows = gridLocations.height();
|
||||
numColumns = gridLocations.width();
|
||||
|
||||
initializeLayoutLocations(transformer, vertices, monitor);
|
||||
}
|
||||
|
||||
private LayoutLocationMap() {
|
||||
// copy constructor
|
||||
}
|
||||
|
||||
public LayoutLocationMap<V, E> copy() {
|
||||
LayoutLocationMap<V, E> map = new LayoutLocationMap<>();
|
||||
map.isCondensed = isCondensed;
|
||||
map.numRows = numRows;
|
||||
map.numColumns = numColumns;
|
||||
map.rowsByIndex = new TreeMap<>(rowsByIndex);
|
||||
map.columnsByIndex = new TreeMap<>(columnsByIndex);
|
||||
map.gridLocations = gridLocations.copy();
|
||||
return map;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
rowsByIndex.clear();
|
||||
columnsByIndex.clear();
|
||||
|
@ -90,19 +73,19 @@ public class LayoutLocationMap<V, E> {
|
|||
return numColumns;
|
||||
}
|
||||
|
||||
public Column col(V v) {
|
||||
public Column<V> col(V v) {
|
||||
Integer col = gridLocations.col(v);
|
||||
return doGetColumn(col);
|
||||
}
|
||||
|
||||
public Column col(int gridX) {
|
||||
public Column<V> col(int gridX) {
|
||||
return doGetColumn(gridX);
|
||||
}
|
||||
|
||||
public Column getColumnContaining(int x) {
|
||||
Column column = null;
|
||||
Collection<Column> values = columnsByIndex.values();
|
||||
for (Column nextColumn : values) {
|
||||
public Column<V> getColumnContaining(int x) {
|
||||
Column<V> column = null;
|
||||
Collection<Column<V>> values = columnsByIndex.values();
|
||||
for (Column<V> nextColumn : values) {
|
||||
if (x < nextColumn.x) {
|
||||
return column;
|
||||
}
|
||||
|
@ -111,10 +94,10 @@ public class LayoutLocationMap<V, E> {
|
|||
return column;
|
||||
}
|
||||
|
||||
private Column doGetColumn(int index) {
|
||||
Column column = columnsByIndex.get(index);
|
||||
private Column<V> doGetColumn(int index) {
|
||||
Column<V> column = columnsByIndex.get(index);
|
||||
if (column == null) {
|
||||
column = new Column(index);
|
||||
column = new Column<>(index);
|
||||
columnsByIndex.put(index, column);
|
||||
}
|
||||
return column;
|
||||
|
@ -125,10 +108,10 @@ public class LayoutLocationMap<V, E> {
|
|||
*
|
||||
* @return the columns in this location map, sorted from lowest index to highest
|
||||
*/
|
||||
public Collection<Column> columns() {
|
||||
List<Column> result = new ArrayList<>();
|
||||
Collection<Column> values = columnsByIndex.values();
|
||||
for (Column column : values) {
|
||||
public Collection<Column<V>> columns() {
|
||||
List<Column<V>> result = new ArrayList<>();
|
||||
Collection<Column<V>> values = columnsByIndex.values();
|
||||
for (Column<V> column : values) {
|
||||
result.add(column);
|
||||
}
|
||||
return result;
|
||||
|
@ -148,17 +131,17 @@ public class LayoutLocationMap<V, E> {
|
|||
return results;
|
||||
}
|
||||
|
||||
public Column lastColumn() {
|
||||
public Column<V> lastColumn() {
|
||||
|
||||
Entry<Integer, Column> lastEntry = columnsByIndex.lastEntry();
|
||||
Entry<Integer, Column<V>> lastEntry = columnsByIndex.lastEntry();
|
||||
if (lastEntry == null) {
|
||||
return null;
|
||||
}
|
||||
return lastEntry.getValue();
|
||||
}
|
||||
|
||||
public Column nextColumn(Column column) {
|
||||
Column nextColumn = doGetColumn(column.index + 1);
|
||||
public Column<V> nextColumn(Column<V> column) {
|
||||
Column<V> nextColumn = doGetColumn(column.index + 1);
|
||||
if (!nextColumn.isInitialized()) {
|
||||
// last column?
|
||||
nextColumn.x = column.x + column.getPaddedWidth(isCondensed);
|
||||
|
@ -166,12 +149,12 @@ public class LayoutLocationMap<V, E> {
|
|||
return nextColumn;
|
||||
}
|
||||
|
||||
public List<Point> articulations(E e) {
|
||||
public List<GridPoint> articulations(E e) {
|
||||
return gridLocations.getArticulations(e);
|
||||
}
|
||||
|
||||
public Row<V> row(V v) {
|
||||
Integer row = gridLocations.row(v);
|
||||
int row = gridLocations.row(v);
|
||||
return doGetRow(row);
|
||||
}
|
||||
|
||||
|
@ -215,7 +198,7 @@ public class LayoutLocationMap<V, E> {
|
|||
|
||||
public List<Integer> getColOffsets() {
|
||||
ArrayList<Integer> list = new ArrayList<>();
|
||||
for (Column column : columnsByIndex.values()) {
|
||||
for (Column<V> column : columnsByIndex.values()) {
|
||||
list.add(column.x);
|
||||
}
|
||||
return list;
|
||||
|
@ -231,70 +214,55 @@ public class LayoutLocationMap<V, E> {
|
|||
columnsByIndex + "]";
|
||||
}
|
||||
|
||||
public GridCoordinates getGridCoordinates() {
|
||||
Row<?> lastRow = lastRow();
|
||||
Column<?> lastColumn = lastColumn();
|
||||
if (lastRow == null || lastColumn == null) {
|
||||
return new GridCoordinates(new int[0], new int[0]);
|
||||
}
|
||||
|
||||
// add 1 to compute a row y value and a column x value for closing the grid
|
||||
int[] rowStarts = new int[lastRow.index + 1];
|
||||
int[] colStarts = new int[lastColumn.index + 1];
|
||||
|
||||
for (Row<?> row : rowsByIndex.values()) {
|
||||
rowStarts[row.index] = row.y;
|
||||
}
|
||||
for (Column<?> col : columnsByIndex.values()) {
|
||||
colStarts[col.index] = col.x;
|
||||
}
|
||||
|
||||
// Give any empty rows or columns the coordinate of the row or column that precedes it
|
||||
// since it takes no space. (Otherwise all the empty row or column labels would overwrite
|
||||
// themselves at the 0 row or 0 column.
|
||||
for (int row = 1; row < rowStarts.length; row++) {
|
||||
if (rowStarts[row] == 0) {
|
||||
rowStarts[row] = rowStarts[row - 1];
|
||||
}
|
||||
}
|
||||
for (int col = 1; col < colStarts.length; col++) {
|
||||
if (colStarts[col] == 0) {
|
||||
colStarts[col] = colStarts[col - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// close the grid
|
||||
rowStarts[rowStarts.length - 1] = lastRow.y + lastRow.getPaddedHeight(isCondensed);
|
||||
colStarts[colStarts.length - 1] = lastColumn.x + lastColumn.getPaddedWidth(isCondensed);
|
||||
|
||||
return new GridCoordinates(rowStarts, colStarts);
|
||||
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Initialization Code
|
||||
//==================================================================================================
|
||||
|
||||
private MinMaxRowColumn getMinMaxRowColumnValues(Collection<V> vertices, Collection<E> edges,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
MinMaxRowColumn minMax = new MinMaxRowColumn();
|
||||
|
||||
for (V v : vertices) {
|
||||
monitor.checkCancelled();
|
||||
|
||||
int row = gridLocations.row(v);
|
||||
if (row > minMax.maxRow) {
|
||||
minMax.maxRow = row;
|
||||
}
|
||||
if (row < minMax.minRow) {
|
||||
minMax.minRow = row;
|
||||
}
|
||||
|
||||
int column = gridLocations.col(v);
|
||||
if (column > minMax.maxCol) {
|
||||
minMax.maxCol = column;
|
||||
}
|
||||
if (column < minMax.minCol) {
|
||||
minMax.minCol = column;
|
||||
}
|
||||
}
|
||||
|
||||
for (E edge : edges) {
|
||||
monitor.checkCancelled();
|
||||
|
||||
List<Point> articulations = gridLocations.getArticulations(edge);
|
||||
if (articulations.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Point location : articulations) {
|
||||
int row = location.y;
|
||||
if (row > minMax.maxRow) {
|
||||
minMax.maxRow = row;
|
||||
}
|
||||
if (row < minMax.minRow) {
|
||||
minMax.minRow = row;
|
||||
}
|
||||
|
||||
int column = location.x;
|
||||
if (column > minMax.maxCol) {
|
||||
minMax.maxCol = column;
|
||||
}
|
||||
if (column < minMax.minCol) {
|
||||
minMax.minCol = column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minMax;
|
||||
}
|
||||
|
||||
private void initializeLayoutLocations(Function<V, Shape> transformer, Collection<V> vertices,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
// create this class's rows from the grid
|
||||
List<Row<V>> gridRows = gridLocations.rows();
|
||||
Collection<Row<V>> gridRows = gridLocations.rowsMap().values();
|
||||
for (Row<V> row : gridRows) {
|
||||
rowsByIndex.put(row.index, row);
|
||||
}
|
||||
|
@ -308,7 +276,7 @@ public class LayoutLocationMap<V, E> {
|
|||
monitor.checkCancelled();
|
||||
|
||||
Row<V> row = row(vertex);
|
||||
Column column = col(vertex);
|
||||
Column<V> column = col(vertex);
|
||||
Shape shape = transformer.apply(vertex);
|
||||
Rectangle bounds = shape.getBounds();
|
||||
if (bounds.width > column.width) {
|
||||
|
@ -349,7 +317,7 @@ public class LayoutLocationMap<V, E> {
|
|||
for (int i = 0; i < n; i++) {
|
||||
monitor.checkCancelled();
|
||||
|
||||
Column column = col(i);
|
||||
Column<V> column = col(i);
|
||||
column.x = offset;
|
||||
offset += column.getPaddedWidth(isCondensed);
|
||||
}
|
||||
|
|
|
@ -1,23 +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.graph.viewer.layout;
|
||||
|
||||
public class MinMaxRowColumn {
|
||||
public int minRow = Integer.MAX_VALUE;
|
||||
public int maxRow = -1;
|
||||
public int minCol = Integer.MAX_VALUE;
|
||||
public int maxCol = -1;
|
||||
}
|
|
@ -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.
|
||||
|
@ -29,7 +29,7 @@ import ghidra.graph.viewer.GraphViewerUtils;
|
|||
* of a grid.
|
||||
*
|
||||
* <p>This class maintains a collection of vertices on this row, organized by column index. You
|
||||
* can get the column of a vertex from {@link #getColumn(Object) getColumn(V)}.
|
||||
* can get the column of a vertex from {@link #getColumn(Object)}
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
*/
|
||||
|
@ -42,7 +42,7 @@ public class Row<V> {
|
|||
/** The grid index of this row (0, 1...n) for the number of rows */
|
||||
public int index = Integer.MAX_VALUE;
|
||||
|
||||
// Note: this must change together (they are effectively a BiDi map)
|
||||
// Note: these must change together (they are effectively a BiDi map)
|
||||
private TreeMap<Integer, V> verticesByColumn = new TreeMap<>();
|
||||
private Map<V, Integer> columnsByVertex = new HashMap<>();
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/* ###
|
||||
* 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.graph.viewer.renderer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import edu.uci.ics.jung.visualization.*;
|
||||
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.graph.viewer.layout.GridCoordinates;
|
||||
|
||||
/**
|
||||
* Class for painting the underlying grid used to layout a graph. Used as a visual aid when
|
||||
* debugging grid based graph layouts.
|
||||
*/
|
||||
public class GridPainter {
|
||||
|
||||
private GridCoordinates grid;
|
||||
|
||||
public GridPainter(GridCoordinates gridCoordinates) {
|
||||
this.grid = gridCoordinates;
|
||||
}
|
||||
|
||||
public void paintLayoutGridCells(RenderContext<?, ?> renderContext, Layout<?, ?> layout) {
|
||||
|
||||
if (grid == null) {
|
||||
return;
|
||||
}
|
||||
int rowCount = grid.rowCount();
|
||||
int colCount = grid.columnCount();
|
||||
|
||||
GraphicsDecorator g = renderContext.getGraphicsContext();
|
||||
Color originalColor = g.getColor();
|
||||
Color gridColor = Palette.ORANGE;
|
||||
Color textColor = Palette.BLACK;
|
||||
|
||||
Rectangle bounds = grid.getBounds();
|
||||
int width = bounds.width;
|
||||
int height = bounds.height;
|
||||
|
||||
MultiLayerTransformer transformer = renderContext.getMultiLayerTransformer();
|
||||
int previous = -1;
|
||||
for (int row = 0; row < rowCount; row++) {
|
||||
int y = grid.y(row);
|
||||
if (y == previous) {
|
||||
continue; // don't paint empty rows
|
||||
}
|
||||
previous = y;
|
||||
Point2D start = new Point2D.Double(0, y);
|
||||
Point2D end = new Point2D.Double(width, y);
|
||||
start = transformer.transform(Layer.LAYOUT, start);
|
||||
end = transformer.transform(Layer.LAYOUT, end);
|
||||
|
||||
g.setColor(textColor);
|
||||
g.drawString(Integer.toString(row), (float) start.getX() - 20,
|
||||
(float) (start.getY() + 5));
|
||||
|
||||
g.setColor(gridColor);
|
||||
g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY());
|
||||
}
|
||||
|
||||
previous = -1;
|
||||
for (int col = 0; col < colCount; col++) {
|
||||
int x = grid.x(col);
|
||||
if (x == previous) {
|
||||
continue;
|
||||
}
|
||||
previous = x;
|
||||
Point2D start = new Point2D.Double(x, 0);
|
||||
Point2D end = new Point2D.Double(x, height);
|
||||
start = transformer.transform(Layer.LAYOUT, start);
|
||||
end = transformer.transform(Layer.LAYOUT, end);
|
||||
|
||||
g.setColor(textColor);
|
||||
g.drawString(Integer.toString(col), (float) start.getX() - 5,
|
||||
(float) (start.getY() - 10));
|
||||
|
||||
g.setColor(gridColor);
|
||||
g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY());
|
||||
}
|
||||
|
||||
g.setColor(originalColor);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -15,21 +15,15 @@
|
|||
*/
|
||||
package ghidra.graph.viewer.renderer;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import edu.uci.ics.jung.visualization.*;
|
||||
import edu.uci.ics.jung.visualization.layout.ObservableCachingLayout;
|
||||
import edu.uci.ics.jung.visualization.RenderContext;
|
||||
import edu.uci.ics.jung.visualization.renderers.Renderer;
|
||||
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.graph.viewer.*;
|
||||
import ghidra.graph.viewer.edge.BasicEdgeLabelRenderer;
|
||||
import ghidra.graph.viewer.layout.*;
|
||||
|
||||
/**
|
||||
* This was created to add the ability to paint selected vertices above other vertices. We need
|
||||
|
@ -42,11 +36,16 @@ import ghidra.graph.viewer.layout.*;
|
|||
public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>>
|
||||
extends edu.uci.ics.jung.visualization.renderers.BasicRenderer<V, E> {
|
||||
|
||||
private static GridPainter gridPainter;
|
||||
|
||||
/**
|
||||
* Used for displaying grid information for graph layouts
|
||||
* Sets a painter to show an underlying grid. (To see a layout's associated grid, search
|
||||
* for calls to this method and un-comment them)
|
||||
* @param gridPainter A painter that paints the grid that a layout was based on.
|
||||
*/
|
||||
public static Map<VisualGraphLayout<?, ?>, LayoutLocationMap<?, ?>> DEBUG_ROW_COL_MAP =
|
||||
new HashMap<>();
|
||||
public static void setGridPainter(GridPainter gridPainter) {
|
||||
VisualGraphRenderer.gridPainter = gridPainter;
|
||||
}
|
||||
|
||||
private Renderer.EdgeLabel<V, E> edgeLabelRenderer = new BasicEdgeLabelRenderer<>();
|
||||
|
||||
|
@ -73,6 +72,9 @@ public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>
|
|||
private void mimickSuperPaintingWithoutPaintingSelectedVertices(
|
||||
RenderContext<V, E> renderContext, Layout<V, E> layout) {
|
||||
|
||||
if (gridPainter != null) {
|
||||
gridPainter.paintLayoutGridCells(renderContext, layout);
|
||||
}
|
||||
for (E e : layout.getGraph().getEdges()) {
|
||||
|
||||
renderEdge(renderContext, layout, e);
|
||||
|
@ -94,7 +96,6 @@ public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>
|
|||
// renderEdgeLabel(renderContext, layout, e);
|
||||
// }
|
||||
|
||||
paintLayoutGridCells(renderContext, layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -124,81 +125,4 @@ public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>
|
|||
edgeLabelRenderer.labelEdge(rc, layout, e, xform.apply(e));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" }) // the types in the cast matter not
|
||||
private void paintLayoutGridCells(RenderContext<V, E> renderContext, Layout<V, E> layout) {
|
||||
|
||||
// to enable this debug, search java files for commented-out uses of 'DEBUG_ROW_COL_MAP'
|
||||
Layout<V, E> key = layout;
|
||||
if (layout instanceof ObservableCachingLayout) {
|
||||
key = ((ObservableCachingLayout) layout).getDelegate();
|
||||
}
|
||||
LayoutLocationMap<?, ?> locationMap = DEBUG_ROW_COL_MAP.get(key);
|
||||
if (locationMap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rowCount = locationMap.getRowCount();
|
||||
if (rowCount == 0) {
|
||||
return; // ?
|
||||
}
|
||||
|
||||
GraphicsDecorator g = renderContext.getGraphicsContext();
|
||||
Color originalColor = g.getColor();
|
||||
Color gridColor = Palette.ORANGE;
|
||||
Color textColor = Palette.BLACK;
|
||||
|
||||
boolean isCondensed = locationMap.isCondensed();
|
||||
Row<?> lastRow = locationMap.lastRow();
|
||||
Column lastColumn = locationMap.lastColumn();
|
||||
|
||||
if (lastRow == null || lastColumn == null) {
|
||||
return; // empty graph?
|
||||
}
|
||||
|
||||
int width = lastColumn.x + lastColumn.getPaddedWidth(isCondensed);
|
||||
int height = lastRow.y + lastRow.getPaddedHeight(isCondensed);
|
||||
|
||||
MultiLayerTransformer transformer = renderContext.getMultiLayerTransformer();
|
||||
for (Row<?> row : locationMap.rows()) {
|
||||
Point2D start = new Point2D.Double(0, row.y);
|
||||
start = transformer.transform(Layer.LAYOUT, start);
|
||||
g.setColor(textColor);
|
||||
g.drawString(Integer.toString(row.index), (float) start.getX() - 20,
|
||||
(float) (start.getY() + 5));
|
||||
|
||||
Point2D end = new Point2D.Double(width, row.y);
|
||||
end = transformer.transform(Layer.LAYOUT, end);
|
||||
g.setColor(gridColor);
|
||||
g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY());
|
||||
}
|
||||
|
||||
// close the grid
|
||||
Point2D start = new Point2D.Double(0, lastRow.y + lastRow.getPaddedHeight(isCondensed));
|
||||
start = transformer.transform(Layer.LAYOUT, start);
|
||||
Point2D end = new Point2D.Double(width, lastRow.y + lastRow.getPaddedHeight(isCondensed));
|
||||
end = transformer.transform(Layer.LAYOUT, end);
|
||||
g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY());
|
||||
|
||||
for (Column column : locationMap.columns()) {
|
||||
start = new Point2D.Double(column.x, 0);
|
||||
start = transformer.transform(Layer.LAYOUT, start);
|
||||
g.setColor(textColor);
|
||||
g.drawString(Integer.toString(column.index), (float) start.getX() - 5,
|
||||
(float) (start.getY() - 10));
|
||||
|
||||
end = new Point2D.Double(column.x, height);
|
||||
end = transformer.transform(Layer.LAYOUT, end);
|
||||
g.setColor(gridColor);
|
||||
g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY());
|
||||
}
|
||||
|
||||
// close the grid
|
||||
start = new Point2D.Double(lastColumn.x + lastColumn.getPaddedWidth(isCondensed), 0);
|
||||
start = transformer.transform(Layer.LAYOUT, start);
|
||||
end = new Point2D.Double(lastColumn.x + lastColumn.getPaddedWidth(isCondensed), height);
|
||||
end = transformer.transform(Layer.LAYOUT, end);
|
||||
g.drawLine((int) start.getX(), (int) start.getY(), (int) end.getX(), (int) end.getY());
|
||||
|
||||
g.setColor(originalColor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,10 +34,12 @@ import docking.test.AbstractDockingTest;
|
|||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import ghidra.graph.graphs.AbstractTestVertex;
|
||||
import ghidra.graph.graphs.TestEdge;
|
||||
import ghidra.graph.support.*;
|
||||
import ghidra.graph.support.TestLayoutProvider;
|
||||
import ghidra.graph.support.TestVisualGraph;
|
||||
import ghidra.graph.viewer.event.mouse.VisualGraphMouseTrackingGraphMousePlugin;
|
||||
import ghidra.graph.viewer.event.mouse.VisualGraphPluggableGraphMouse;
|
||||
import ghidra.graph.viewer.event.picking.GPickedState;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -73,7 +75,6 @@ public abstract class AbstractVisualGraphTest extends AbstractDockingTest {
|
|||
protected void buildAndLayoutGraph() throws CancelledException {
|
||||
// the test machine has odd Swing exceptions when we construct UIs off the Swing thread
|
||||
graph = runSwing(() -> buildGraph());
|
||||
|
||||
TestLayoutProvider layoutProvider = createLayoutProvider();
|
||||
graph.setLayout(layoutProvider.getLayout(graph, TaskMonitor.DUMMY));
|
||||
graphComponent = runSwing(() -> createGraphComponent(layoutProvider));
|
||||
|
@ -190,7 +191,7 @@ public abstract class AbstractVisualGraphTest extends AbstractDockingTest {
|
|||
GraphViewerUtils.translatePointFromViewSpaceToLayoutSpace(viewPoint, viewer);
|
||||
|
||||
swing(() -> {
|
||||
TestGraphLayout layout = graph.getLayout();
|
||||
VisualGraphLayout<AbstractTestVertex, TestEdge> layout = graph.getLayout();
|
||||
Point2D p = layout.apply(v);
|
||||
layout.setLocation(v,
|
||||
new Point2D.Double(p.getX() + layoutPoint.getX(), p.getY() + layoutPoint.getY()));
|
||||
|
|
|
@ -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.
|
||||
|
@ -26,12 +26,11 @@ import java.util.List;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import edu.uci.ics.jung.algorithms.layout.KKLayout;
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import generic.test.AbstractGTest;
|
||||
import ghidra.graph.graphs.*;
|
||||
import ghidra.graph.support.*;
|
||||
import ghidra.graph.support.TestVertexTooltipProvider;
|
||||
import ghidra.graph.support.TestVertexTooltipProvider.SpyTooltip;
|
||||
import ghidra.graph.support.TestVisualGraph;
|
||||
|
||||
public class GraphViewerTest extends AbstractVisualGraphTest {
|
||||
|
||||
|
@ -53,16 +52,6 @@ public class GraphViewerTest extends AbstractVisualGraphTest {
|
|||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestLayoutProvider createLayoutProvider() {
|
||||
return new TestLayoutProvider() {
|
||||
@Override
|
||||
protected Layout<AbstractTestVertex, TestEdge> createJungLayout(TestVisualGraph g) {
|
||||
return new KKLayout<>(g);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
viewer = graphComponent.getPrimaryViewer();
|
||||
|
|
|
@ -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,10 +77,10 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
protected TestV[] generateSimplyConnectedGraph(int nVertices) {
|
||||
TestV[] vertices = new TestV[nVertices];
|
||||
for (int i = 0; i < nVertices; i++) {
|
||||
vertices[i] = vertex(i);
|
||||
vertices[i] = v(i);
|
||||
}
|
||||
for (int i = 0; i < nVertices - 1; i++) {
|
||||
edge(vertices[i], vertices[i + 1]);
|
||||
e(vertices[i], vertices[i + 1]);
|
||||
}
|
||||
return vertices;
|
||||
}
|
||||
|
@ -88,12 +88,12 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
protected TestV[] generateCompletelyConnectedGraph(int nVertices) {
|
||||
TestV[] vertices = new TestV[nVertices];
|
||||
for (int i = 0; i < nVertices; i++) {
|
||||
vertices[i] = vertex(i);
|
||||
vertices[i] = v(i);
|
||||
}
|
||||
for (int i = 0; i < nVertices; i++) {
|
||||
for (int j = 0; j < nVertices; j++) {
|
||||
if (i != j) {
|
||||
edge(vertices[i], vertices[j]);
|
||||
e(vertices[i], vertices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,12 +103,12 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
protected TestV[] generateHalflyConnectedGraph(int nVertices) {
|
||||
TestV[] vertices = new TestV[nVertices];
|
||||
for (int i = 0; i < nVertices; i++) {
|
||||
vertices[i] = vertex(i);
|
||||
vertices[i] = v(i);
|
||||
}
|
||||
|
||||
// at least one straight line through the graph
|
||||
for (int i = 0; i < nVertices - 1; i++) {
|
||||
edge(vertices[i], vertices[i + 1]);
|
||||
e(vertices[i], vertices[i + 1]);
|
||||
}
|
||||
|
||||
// extra connections
|
||||
|
@ -116,7 +116,7 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
for (int i = 0; i < nVertices; i++) {
|
||||
for (int j = 0; j < n; j++) {
|
||||
if (i != j) {
|
||||
edge(vertices[i], vertices[j]);
|
||||
e(vertices[i], vertices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -126,12 +126,12 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
protected TestV[] generateHalflyConnectedGraphNoBacktracking(int nVertices) {
|
||||
TestV[] vertices = new TestV[nVertices];
|
||||
for (int i = 0; i < nVertices; i++) {
|
||||
vertices[i] = vertex(i);
|
||||
vertices[i] = v(i);
|
||||
}
|
||||
|
||||
// at least one straight line through the graph
|
||||
for (int i = 0; i < nVertices - 1; i++) {
|
||||
edge(vertices[i], vertices[i + 1]);
|
||||
e(vertices[i], vertices[i + 1]);
|
||||
}
|
||||
|
||||
// extra connections
|
||||
|
@ -139,7 +139,7 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
for (int i = 0; i < nVertices; i++) {
|
||||
for (int j = i; j < n; j++) {
|
||||
if (i != j) {
|
||||
edge(vertices[i], vertices[j]);
|
||||
e(vertices[i], vertices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,15 +165,15 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
|||
Assert.fail("Unexpected set size");
|
||||
}
|
||||
|
||||
protected TestV vertex(int id) {
|
||||
protected TestV v(int id) {
|
||||
return new TestV(id);
|
||||
}
|
||||
|
||||
protected TestV vertex(String id) {
|
||||
protected TestV v(String id) {
|
||||
return new TestV(id);
|
||||
}
|
||||
|
||||
protected TestE edge(TestV start, TestV end) {
|
||||
protected TestE e(TestV start, TestV end) {
|
||||
TestE e = new TestE(start, end);
|
||||
g.addEdge(e);
|
||||
return e;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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.
|
||||
|
@ -88,34 +88,34 @@ public class GraphAlgorithmsVisualDebugger extends AbstractGraphAlgorithmsTest {
|
|||
Paths: v1, v3, v5, v10
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v7 = vertex(7);
|
||||
TestV v8 = vertex(8);
|
||||
TestV v9 = vertex(9);
|
||||
TestV v10 = vertex(10);
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
TestV v7 = v(7);
|
||||
TestV v8 = v(8);
|
||||
TestV v9 = v(9);
|
||||
TestV v10 = v(10);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
e(v1, v2);
|
||||
e(v1, v3);
|
||||
|
||||
edge(v2, v1); // back edge
|
||||
e(v2, v1); // back edge
|
||||
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
edge(v3, v6);
|
||||
e(v3, v4);
|
||||
e(v3, v5);
|
||||
e(v3, v6);
|
||||
|
||||
edge(v5, v10);
|
||||
e(v5, v10);
|
||||
|
||||
edge(v6, v7);
|
||||
e(v6, v7);
|
||||
|
||||
edge(v7, v8);
|
||||
edge(v7, v9);
|
||||
e(v7, v8);
|
||||
e(v7, v9);
|
||||
|
||||
edge(v9, v6); // back edge
|
||||
e(v9, v6); // back edge
|
||||
|
||||
AlgorithmSteppingTaskMonitor steppingMonitor = new AlgorithmSteppingTaskMonitor();
|
||||
steppingMonitor = new AlgorithmSelfSteppingTaskMonitor(500);
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
/* ###
|
||||
* 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.graph;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class GraphToTreeTest extends AbstractGraphAlgorithmsTest {
|
||||
private Comparator<TestE> edgeComparator = (e1, e2) -> e1.toString().compareTo(e2.toString());
|
||||
|
||||
@Override
|
||||
protected GDirectedGraph<TestV, TestE> createGraph() {
|
||||
return GraphFactory.createDirectedGraph();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleGraph() {
|
||||
// v1 -> v2 -> v3
|
||||
// |
|
||||
// v4 -> v5 -> v6
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
e(v1, v2);
|
||||
e(v2, v3);
|
||||
e(v1, v4);
|
||||
e(v4, v5);
|
||||
e(v5, v6);
|
||||
|
||||
// expected same graph
|
||||
// v1 -> v2 -> v3
|
||||
// |
|
||||
// v4 -> v5 -> v6
|
||||
|
||||
GDirectedGraph<TestV, TestE> tree = GraphAlgorithms.toTree(g, v1, edgeComparator);
|
||||
assertEdges(tree, e(v1, v2), e(v2, v3), e(v1, v4), e(v4, v5), e(v5, v6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphWithLoop() {
|
||||
// v1 -> v2 -> v3 <----
|
||||
// | |
|
||||
// v4 -> v5 -> v6 |
|
||||
// | |
|
||||
// |->---->----->-----|
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
|
||||
e(v1, v2);
|
||||
e(v2, v3);
|
||||
e(v1, v4);
|
||||
e(v4, v5);
|
||||
e(v5, v6);
|
||||
e(v4, v3);
|
||||
|
||||
// expected:
|
||||
// v1 -> v2 -> v3
|
||||
// |
|
||||
// v4 -> v5 -> v6
|
||||
|
||||
GDirectedGraph<TestV, TestE> tree = GraphAlgorithms.toTree(g, v1, edgeComparator);
|
||||
assertEdges(tree, e(v1, v2), e(v2, v3), e(v1, v4), e(v4, v5), e(v5, v6));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleLoop() {
|
||||
// V1 <- V4 <--------
|
||||
// | ^ ^
|
||||
// v | |
|
||||
// V2 -> V3 -> V5 -> V6
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
|
||||
e(v1, v2);
|
||||
e(v2, v3);
|
||||
e(v3, v4);
|
||||
e(v3, v5);
|
||||
e(v5, v6);
|
||||
e(v6, v4);
|
||||
e(v4, v1);
|
||||
|
||||
// expected:
|
||||
//
|
||||
// V1 -> V2 -> V3 -> V5 -> V6 -> V4
|
||||
|
||||
GDirectedGraph<TestV, TestE> tree = GraphAlgorithms.toTree(g, v1, edgeComparator);
|
||||
assertEdges(tree, e(v1, v2), e(v2, v3), e(v3, v5), e(v5, v6), e(v6, v4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterleavedBranching() {
|
||||
|
||||
/*
|
||||
.<-v1->.
|
||||
| | |
|
||||
| v4 |
|
||||
| | |
|
||||
>--v3 |
|
||||
| |
|
||||
v2--<
|
||||
*/
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
|
||||
e(v1, v3);
|
||||
e(v1, v4);
|
||||
e(v1, v2);
|
||||
e(v4, v3);
|
||||
e(v3, v2);
|
||||
|
||||
// expected:
|
||||
// v1-> v4 -> v3 -> v2
|
||||
|
||||
GDirectedGraph<TestV, TestE> tree = GraphAlgorithms.toTree(g, v1, edgeComparator);
|
||||
assertEdges(tree, e(v1, v4), e(v4, v3), e(v3, v2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedGraph() {
|
||||
|
||||
//@formatter:off
|
||||
/*
|
||||
v1
|
||||
|
|
||||
v2
|
||||
|
|
||||
sink v3 <- v4
|
||||
/\ |
|
||||
| v5
|
||||
| / \
|
||||
| v6 v7
|
||||
| |\ |
|
||||
| v8 \ |
|
||||
| \ |
|
||||
| \ |
|
||||
| v9
|
||||
| / \
|
||||
| / \
|
||||
.<--v11-->v10 sink
|
||||
|
||||
*/
|
||||
//@formatter:on
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
TestV v7 = v(7);
|
||||
TestV v8 = v(8);
|
||||
TestV v9 = v(9);
|
||||
TestV v10 = v(10);
|
||||
TestV v11 = v(11);
|
||||
|
||||
e(v1, v2);
|
||||
e(v2, v4);
|
||||
e(v4, v3);
|
||||
e(v4, v5);
|
||||
|
||||
e(v5, v6);
|
||||
e(v5, v7);
|
||||
e(v6, v8);
|
||||
e(v6, v9);
|
||||
e(v7, v9);
|
||||
e(v8, v9);
|
||||
e(v9, v10);
|
||||
e(v9, v11);
|
||||
e(v11, v3);
|
||||
e(v11, v10);
|
||||
|
||||
//@formatter:off
|
||||
/* expected
|
||||
|
||||
v1
|
||||
|
|
||||
v2
|
||||
|
|
||||
sink v4
|
||||
|
|
||||
v5
|
||||
/ \
|
||||
v6 v7
|
||||
|
|
||||
v8
|
||||
|
|
||||
v9
|
||||
|
|
||||
v11-->v10
|
||||
|
|
||||
v3
|
||||
*/
|
||||
//@formatter:on
|
||||
|
||||
GDirectedGraph<TestV, TestE> tree = GraphAlgorithms.toTree(g, v1, edgeComparator);
|
||||
assertEdges(tree, e(v1, v2), e(v2, v4), e(v4, v5), e(v5, v6),
|
||||
e(v5, v7), e(v6, v8), e(v8, v9), e(v9, v11), e(v11, v10), e(v11, v3));
|
||||
}
|
||||
|
||||
private void assertEdges(GDirectedGraph<TestV, TestE> tree, TestE... edges) {
|
||||
Set<TestE> allEdges = new HashSet<>(tree.getEdges());
|
||||
assertEquals("edge count: ", edges.length, allEdges.size());
|
||||
for (TestE edge : edges) {
|
||||
if (!allEdges.contains(edge)) {
|
||||
fail("Missing expected edge: " + edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
@ -58,25 +58,25 @@ public class MutableGDirectedGraphWrapperTest extends AbstractGraphAlgorithmsTes
|
|||
v6
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1); // Root
|
||||
TestV v2 = vertex(2); // Root
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v1 = v(1); // Root
|
||||
TestV v2 = v(2); // Root
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
|
||||
TestE e1 = edge(v1, v3);
|
||||
TestE e2 = edge(v3, v5);
|
||||
TestE e3 = edge(v5, v6);
|
||||
TestE e1 = e(v1, v3);
|
||||
TestE e2 = e(v3, v5);
|
||||
TestE e3 = e(v5, v6);
|
||||
|
||||
TestE e4 = edge(v2, v4);
|
||||
TestE e5 = edge(v4, v6);
|
||||
TestE e4 = e(v2, v4);
|
||||
TestE e5 = e(v4, v6);
|
||||
|
||||
//
|
||||
// Now create the second graph above using a mutable wrapper
|
||||
//
|
||||
MutableGDirectedGraphWrapper<TestV, TestE> wrapper = new MutableGDirectedGraphWrapper<>(g);
|
||||
TestV fakeRoot = vertex("Fake Root");
|
||||
TestV fakeRoot = v("Fake Root");
|
||||
TestE fakeEdge1 = new TestE(fakeRoot, v1);
|
||||
TestE fakeEdge2 = new TestE(fakeRoot, v2);
|
||||
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
/* ###
|
||||
* 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.graph;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class TopologicalGraphSortTest extends AbstractGraphAlgorithmsTest {
|
||||
private Comparator<TestE> edgeComparator = (e1, e2) -> e1.toString().compareTo(e2.toString());
|
||||
|
||||
@Override
|
||||
protected GDirectedGraph<TestV, TestE> createGraph() {
|
||||
return GraphFactory.createDirectedGraph();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleGraph() {
|
||||
// v1 -> v2 -> v3
|
||||
// |
|
||||
// v4 -> v5 -> v6
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
e(v1, v2);
|
||||
e(v2, v3);
|
||||
e(v1, v4);
|
||||
e(v4, v5);
|
||||
e(v5, v6);
|
||||
|
||||
List<TestV> sorted = GraphAlgorithms.topologicalSort(g, v1, edgeComparator);
|
||||
assertList(sorted, v1, v2, v3, v4, v5, v6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphWithLoop() {
|
||||
// v1 -> v2 -> v3 <----
|
||||
// | |
|
||||
// v4 -> v5 -> v6 |
|
||||
// | |
|
||||
// |->---->----->-----|
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
|
||||
e(v1, v2);
|
||||
e(v2, v3);
|
||||
e(v1, v4);
|
||||
e(v4, v5);
|
||||
e(v5, v6);
|
||||
e(v4, v3);
|
||||
|
||||
List<TestV> sorted = GraphAlgorithms.topologicalSort(g, v1, edgeComparator);
|
||||
assertList(sorted, v1, v2, v4, v3, v5, v6);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleLoop() {
|
||||
// V1 <- V4 <--------
|
||||
// | ^ ^
|
||||
// v | |
|
||||
// V2 -> V3 -> V5 -> V6
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
|
||||
e(v1, v2);
|
||||
e(v2, v3);
|
||||
e(v3, v4);
|
||||
e(v3, v5);
|
||||
e(v5, v6);
|
||||
e(v6, v4);
|
||||
e(v4, v1);
|
||||
|
||||
List<TestV> sorted = GraphAlgorithms.topologicalSort(g, v1, edgeComparator);
|
||||
assertList(sorted, v1, v2, v3, v5, v6, v4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterleavedBranching() {
|
||||
|
||||
/*
|
||||
.<-v1->.
|
||||
| | |
|
||||
| v4 |
|
||||
| | |
|
||||
>--v3 |
|
||||
| |
|
||||
v2--<
|
||||
*/
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
|
||||
e(v1, v3);
|
||||
e(v1, v4);
|
||||
e(v1, v2);
|
||||
e(v4, v3);
|
||||
e(v3, v2);
|
||||
|
||||
List<TestV> sorted = GraphAlgorithms.topologicalSort(g, v1, edgeComparator);
|
||||
assertList(sorted, v1, v4, v3, v2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedGraph() {
|
||||
|
||||
//@formatter:off
|
||||
/*
|
||||
v1
|
||||
|
|
||||
v2
|
||||
|
|
||||
sink v3 <- v4
|
||||
/\ |
|
||||
| v5
|
||||
| / \
|
||||
| v6 v7
|
||||
| |\ |
|
||||
| v8 \ |
|
||||
| \ |
|
||||
| \ |
|
||||
| v9
|
||||
| / \
|
||||
| / \
|
||||
.<--v11-->v10 sink
|
||||
|
||||
*/
|
||||
//@formatter:on
|
||||
|
||||
TestV v1 = v(1);
|
||||
TestV v2 = v(2);
|
||||
TestV v3 = v(3);
|
||||
TestV v4 = v(4);
|
||||
TestV v5 = v(5);
|
||||
TestV v6 = v(6);
|
||||
TestV v7 = v(7);
|
||||
TestV v8 = v(8);
|
||||
TestV v9 = v(9);
|
||||
TestV v10 = v(10);
|
||||
TestV v11 = v(11);
|
||||
|
||||
e(v1, v2);
|
||||
e(v2, v4);
|
||||
e(v4, v3);
|
||||
e(v4, v5);
|
||||
|
||||
e(v5, v6);
|
||||
e(v5, v7);
|
||||
e(v6, v8);
|
||||
e(v6, v9);
|
||||
e(v7, v9);
|
||||
e(v8, v9);
|
||||
e(v9, v10);
|
||||
e(v9, v11);
|
||||
e(v11, v3);
|
||||
e(v11, v10);
|
||||
|
||||
List<TestV> sorted = GraphAlgorithms.topologicalSort(g, v1, edgeComparator);
|
||||
assertList(sorted, v1, v2, v4, v5, v6, v8, v7, v9, v11, v10, v3);
|
||||
|
||||
}
|
||||
|
||||
private void assertList(List<TestV> sorted, TestV... vertices) {
|
||||
if (vertices.length != sorted.size()) {
|
||||
failed(sorted, vertices);
|
||||
}
|
||||
assertEquals(vertices.length, sorted.size());
|
||||
for (int i = 0; i < vertices.length; i++) {
|
||||
if (vertices[i] != sorted.get(i)) {
|
||||
failed(sorted, vertices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void failed(List<TestV> sorted, TestV[] vertices) {
|
||||
String expected =
|
||||
Arrays.stream(vertices).map(v -> v.toString()).collect(Collectors.joining(", "));
|
||||
String actual = sorted.stream().map(v -> v.toString()).collect(Collectors.joining(", "));
|
||||
fail("expected: \"" + expected + "\", but got: \"" + actual + "\"");
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
@ -314,13 +314,10 @@ public class TestGraphAlgorithmSteppingViewerPanel<V, E extends GEdge<V>> extend
|
|||
GridLocationMap<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> grid,
|
||||
int row, int col) {
|
||||
|
||||
int existing = grid.row(v);
|
||||
if (existing > 0) {
|
||||
return; // already processed
|
||||
if (grid.containsVertex(v)) {
|
||||
return;
|
||||
}
|
||||
|
||||
grid.row(v, row);
|
||||
grid.col(v, col);
|
||||
grid.set(v, row, col);
|
||||
int nextRow = row++;
|
||||
|
||||
Collection<AlgorithmTestSteppingEdge<V>> children = g.getOutEdges(v);
|
||||
|
|
|
@ -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.
|
||||
|
@ -27,20 +27,23 @@ import generic.theme.GThemeDefaults.Colors.Palette;
|
|||
*/
|
||||
public class LabelTestVertex extends AbstractTestVertex {
|
||||
|
||||
private JLabel label = new GDLabel();
|
||||
private JLabel label;
|
||||
|
||||
public LabelTestVertex(String name) {
|
||||
super(name);
|
||||
label.setText(name);
|
||||
label.setPreferredSize(new Dimension(50, 50));
|
||||
label.setBackground(Palette.GOLD);
|
||||
label.setOpaque(true);
|
||||
label.setBorder(BorderFactory.createRaisedBevelBorder());
|
||||
label.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
if (label == null) {
|
||||
label = new GDLabel();
|
||||
label.setText(getName());
|
||||
label.setPreferredSize(new Dimension(50, 50));
|
||||
label.setBackground(Palette.GOLD);
|
||||
label.setOpaque(true);
|
||||
label.setBorder(BorderFactory.createRaisedBevelBorder());
|
||||
label.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -22,7 +22,7 @@ import javax.swing.JComponent;
|
|||
*/
|
||||
public class TestVertex extends AbstractTestVertex {
|
||||
|
||||
protected TestVertex(String name) {
|
||||
public TestVertex(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -22,9 +22,10 @@ import javax.swing.Icon;
|
|||
|
||||
import edu.uci.ics.jung.algorithms.layout.DAGLayout;
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import ghidra.graph.graphs.TestEdge;
|
||||
import ghidra.graph.graphs.AbstractTestVertex;
|
||||
import ghidra.graph.graphs.TestEdge;
|
||||
import ghidra.graph.viewer.layout.LayoutProvider;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
|
||||
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -33,7 +34,8 @@ import ghidra.util.task.TaskMonitor;
|
|||
/**
|
||||
* A layout provider used for testing.
|
||||
*/
|
||||
public class TestLayoutProvider implements LayoutProvider<AbstractTestVertex, TestEdge, TestVisualGraph> {
|
||||
public class TestLayoutProvider
|
||||
implements LayoutProvider<AbstractTestVertex, TestEdge, TestVisualGraph> {
|
||||
|
||||
private ArticulatedEdgeTransformer<AbstractTestVertex, TestEdge> edgeShapeTransformer =
|
||||
new ArticulatedEdgeTransformer<>();
|
||||
|
@ -41,7 +43,8 @@ public class TestLayoutProvider implements LayoutProvider<AbstractTestVertex, Te
|
|||
new ArticulatedEdgeRenderer<>();
|
||||
|
||||
@Override
|
||||
public TestGraphLayout getLayout(TestVisualGraph g, TaskMonitor monitor)
|
||||
public VisualGraphLayout<AbstractTestVertex, TestEdge> getLayout(TestVisualGraph g,
|
||||
TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
Layout<AbstractTestVertex, TestEdge> jungLayout = new DAGLayout<>(g);
|
||||
|
||||
|
@ -54,11 +57,6 @@ public class TestLayoutProvider implements LayoutProvider<AbstractTestVertex, Te
|
|||
return new TestGraphLayout(jungLayout);
|
||||
}
|
||||
|
||||
// template method to allow tests to override the Jung layout in use
|
||||
protected Layout<AbstractTestVertex, TestEdge> createJungLayout(TestVisualGraph g) {
|
||||
return new DAGLayout<>(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLayoutName() {
|
||||
return "Test Layout";
|
||||
|
|
|
@ -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.
|
||||
|
@ -18,20 +18,21 @@ package ghidra.graph.support;
|
|||
import java.util.Collection;
|
||||
|
||||
import ghidra.graph.graphs.*;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
|
||||
/**
|
||||
* A visual graph implementation used for testing.
|
||||
*/
|
||||
public class TestVisualGraph extends DefaultVisualGraph<AbstractTestVertex, TestEdge> {
|
||||
|
||||
private TestGraphLayout layout;
|
||||
private VisualGraphLayout<AbstractTestVertex, TestEdge> layout;
|
||||
|
||||
@Override
|
||||
public TestGraphLayout getLayout() {
|
||||
public VisualGraphLayout<AbstractTestVertex, TestEdge> getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void setLayout(TestGraphLayout layout) {
|
||||
public void setLayout(VisualGraphLayout<AbstractTestVertex, TestEdge> layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package ghidra.graph.viewer.layout;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -244,6 +243,56 @@ public class GridLocationMapTest {
|
|||
assertCoordinates(v7, 2, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetVertexColumnRanges() {
|
||||
V v1 = new V("v1");
|
||||
V v2 = new V("v2");
|
||||
V v3 = new V("v3");
|
||||
locations.set(v1, 0, 0);
|
||||
locations.set(v2, 2, 4);
|
||||
locations.set(v3, 2, 6);
|
||||
|
||||
GridRange[] columnRanges = locations.getVertexColumnRanges();
|
||||
assertEquals(3, columnRanges.length);
|
||||
assertEquals(new GridRange(0, 0), columnRanges[0]);
|
||||
assertEquals(new GridRange(), columnRanges[1]);
|
||||
assertEquals(new GridRange(4, 6), columnRanges[2]);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddGrids() {
|
||||
// This method creates two grids with points and edge articulations. It then merges
|
||||
// the other map into the first map with a given row and column shift. It then checks
|
||||
// the merged grid has all the edge points and vertex points in the expected locations.
|
||||
V v1 = new V("v1");
|
||||
V v2 = new V("v2");
|
||||
E e1 = new E(v1, v2);
|
||||
|
||||
V v3 = new V("v3");
|
||||
V v4 = new V("v4");
|
||||
E e2 = new E(v3, v4);
|
||||
|
||||
locations.set(v1, 0, 0);
|
||||
locations.set(v2, 2, 4);
|
||||
locations.setArticulations(e1, List.of(new GridPoint(3, 3)));
|
||||
|
||||
GridLocationMap<V, E> otherMap = new GridLocationMap<>();
|
||||
otherMap.set(v3, 1, 1);
|
||||
otherMap.set(v4, 2, 2);
|
||||
otherMap.setArticulations(e1, List.of(new GridPoint(1, 2)));
|
||||
|
||||
locations.add(otherMap, 10, 10);
|
||||
|
||||
assertEquals(13, locations.width());
|
||||
assertEquals(13, locations.height());
|
||||
|
||||
assertCoordinates(v1, 0, 0);
|
||||
assertCoordinates(v2, 2, 4);
|
||||
assertCoordinates(v3, 11, 11);
|
||||
assertCoordinates(v4, 12, 12);
|
||||
|
||||
}
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
@ -253,7 +302,7 @@ public class GridLocationMapTest {
|
|||
List<Row<V>> rows = locations.rows();
|
||||
Row<V> row = getRow(rows, rowIndex);
|
||||
assertEquals("Row " + rowIndex + " has wrong column count", size,
|
||||
(int) row.getColumnCount());
|
||||
row.getColumnCount());
|
||||
assertEquals(startColumnIndex, (int) row.getStartColumn());
|
||||
}
|
||||
|
||||
|
@ -279,8 +328,8 @@ public class GridLocationMapTest {
|
|||
}
|
||||
|
||||
private void assertCoordinates(V v, int row, int col) {
|
||||
assertEquals("Row not set for '" + v + "'", row, (int) locations.row(v));
|
||||
assertEquals("Column not set for '" + v + "'", col, (int) locations.col(v));
|
||||
assertEquals("Row not set for '" + v + "'", row, locations.row(v));
|
||||
assertEquals("Column not set for '" + v + "'", col, locations.col(v));
|
||||
}
|
||||
|
||||
private class V extends TestVertex {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue