mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 21:54:10 +08:00
GP-4988 Created flow chart function graph layout
This commit is contained in:
@@ -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
|
||||
|
||||
+5
-3
@@ -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
|
||||
|
||||
+18
-16
@@ -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) {
|
||||
|
||||
-35
@@ -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
|
||||
}
|
||||
}
|
||||
+245
@@ -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();
|
||||
}
|
||||
}
|
||||
+238
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+346
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+112
@@ -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();
|
||||
}
|
||||
+118
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+98
@@ -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;
|
||||
}
|
||||
}
|
||||
+54
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+54
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+173
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+181
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+165
@@ -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;
|
||||
}
|
||||
}
|
||||
+267
@@ -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();
|
||||
}
|
||||
}
|
||||
+116
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-5
@@ -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 |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 970 B |
+5
-5
@@ -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<>();
|
||||
|
||||
+420
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+264
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
+258
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
+982
File diff suppressed because it is too large
Load Diff
+11
-12
@@ -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
|
||||
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-11
@@ -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;
|
||||
}
|
||||
}
|
||||
+295
-198
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+70
-102
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+14
-90
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -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();
|
||||
|
||||
+15
-15
@@ -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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user