GP-3402 - Updated the Graph service API to better manage concurrent

threaded accesses
This commit is contained in:
dragonmacher 2023-05-09 15:41:31 -04:00
parent de55e42686
commit bde74ad4d3
12 changed files with 422 additions and 387 deletions

View file

@ -56,11 +56,6 @@ public abstract class AddressBasedGraphDisplayListener
program.addListener(this); program.addListener(this);
} }
@Override
public void graphClosed() {
dispose();
}
@Override @Override
public void locationFocusChanged(AttributedVertex vertex) { public void locationFocusChanged(AttributedVertex vertex) {
Address address = getAddress(vertex); Address address = getAddress(vertex);

View file

@ -26,10 +26,10 @@ import ghidra.util.task.TaskMonitor;
/** /**
* Ghidra service interface for managing and directing graph output. It purpose is to discover * Ghidra service interface for managing and directing graph output. It purpose is to discover
* available graphing display providers and (if more than one) allow the user to select the currently * available graphing display providers and (if more than one) allow the user to select the
* active graph consumer. Clients that generate graphs don't have to worry about how to display them * currently active graph consumer. Clients that generate graphs don't have to worry about how to
* or export graphs. They simply send their graphs to the broker and register for graph events if * display them or export graphs. They simply send their graphs to the broker and register for graph
* they want interactive support. * events if they want interactive support.
*/ */
@ServiceInfo(defaultProvider = GraphDisplayBrokerPlugin.class, description = "Get a Graph Display") @ServiceInfo(defaultProvider = GraphDisplayBrokerPlugin.class, description = "Get a Graph Display")
public interface GraphDisplayBroker { public interface GraphDisplayBroker {

View file

@ -17,13 +17,12 @@ package ghidra.graph.export;
import java.util.*; import java.util.*;
import org.jgrapht.Graph;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import ghidra.app.services.GraphDisplayBroker; import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -66,7 +65,8 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
*/ */
private void doSetGraphData(AttributedGraph attributedGraph) { private void doSetGraphData(AttributedGraph attributedGraph) {
List<AttributedGraphExporter> exporters = findGraphExporters(); List<AttributedGraphExporter> exporters = findGraphExporters();
GraphExporterDialog dialog = new GraphExporterDialog(attributedGraph, exporters); GraphExporterDialog dialog =
Swing.runNow(() -> new GraphExporterDialog(attributedGraph, exporters));
tool.showDialog(dialog); tool.showDialog(dialog);
} }
@ -79,8 +79,7 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
} }
@Override @Override
public void setGraph(AttributedGraph graph, String title, boolean append, public void setGraph(AttributedGraph graph, String title, boolean append, TaskMonitor monitor) {
TaskMonitor monitor) {
this.title = title; this.title = title;
this.graph = graph; this.graph = graph;
doSetGraphData(graph); doSetGraphData(graph);
@ -92,9 +91,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
this.setGraph(graph, title, append, monitor); this.setGraph(graph, title, append, monitor);
} }
/**
* remove all vertices and edges from the {@link Graph}
*/
@Override @Override
public void clear() { public void clear() {
// not interactive, so N/A // not interactive, so N/A

View file

@ -258,8 +258,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
// this lens' delegate is the viewer's VIEW layer, abandoned above // this lens' delegate is the viewer's VIEW layer, abandoned above
.delegate(transformer) .delegate(transformer)
.build(); .build();
LensGraphMouse lensGraphMouse = LensGraphMouse lensGraphMouse = DefaultLensGraphMouse.builder()
DefaultLensGraphMouse.builder()
.magnificationFloor(1.f) .magnificationFloor(1.f)
.magnificationCeiling(60.f) .magnificationCeiling(60.f)
.magnificationDelta(.2f) .magnificationDelta(.2f)
@ -831,10 +830,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
public void close() { public void close() {
graphDisplayProvider.remove(this); graphDisplayProvider.remove(this);
if (listener != null) { if (listener != null) {
listener.graphClosed(); listener.dispose();
}
listener = null; listener = null;
}
componentProvider.closeComponent(); componentProvider.closeComponent();
if (graphDisplayOptions != null) { if (graphDisplayOptions != null) {
graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener); graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener);
} }
@ -843,7 +844,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override @Override
public void setGraphDisplayListener(GraphDisplayListener listener) { public void setGraphDisplayListener(GraphDisplayListener listener) {
if (this.listener != null) { if (this.listener != null) {
this.listener.graphClosed(); // This is a bit odd to do here, but this seems like the easiest way to cleanup any
// previous listener when reusing the graph display.
this.listener.dispose();
} }
this.listener = listener; this.listener = listener;
} }
@ -975,12 +978,10 @@ public class DefaultGraphDisplay implements GraphDisplay {
configureViewerPreferredSize(); configureViewerPreferredSize();
Swing.runNow(() -> {
// set the graph but defer the layout algorithm setting // set the graph but defer the layout algorithm setting
viewer.getVisualizationModel().setGraph(graph, false); viewer.getVisualizationModel().setGraph(graph, false);
configureFilters(); configureFilters();
setInitialLayoutAlgorithm(); setInitialLayoutAlgorithm();
});
componentProvider.setVisible(true); componentProvider.setVisible(true);
} }
@ -1310,7 +1311,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
addedActions.add(action); addedActions.add(action);
Swing.runLater(() -> componentProvider.addLocalAction(action)); componentProvider.addLocalAction(action);
} }
@Override @Override

View file

@ -16,6 +16,7 @@
package ghidra.graph.visualization; package ghidra.graph.visualization;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@ -30,7 +31,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
private static final String PREFERENCES_KEY = "GRAPH_DISPLAY_SERVICE"; private static final String PREFERENCES_KEY = "GRAPH_DISPLAY_SERVICE";
private static final String DEFAULT_SATELLITE_STATE = "DEFAULT_SATELLITE_STATE"; private static final String DEFAULT_SATELLITE_STATE = "DEFAULT_SATELLITE_STATE";
private final Set<DefaultGraphDisplay> displays = new HashSet<>(); private final Set<DefaultGraphDisplayWrapper> displays = new CopyOnWriteArraySet<>();
private PluginTool pluginTool; private PluginTool pluginTool;
private Options options; private Options options;
private int displayCounter = 1; private int displayCounter = 1;
@ -52,16 +53,22 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
@Override @Override
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
return Swing.runNow(() -> { return Swing.runNow(() -> {
if (reuseGraph && !displays.isEmpty()) { if (reuseGraph && !displays.isEmpty()) {
DefaultGraphDisplay visibleGraph = (DefaultGraphDisplay) getActiveGraphDisplay(); DefaultGraphDisplayWrapper visibleGraph =
(DefaultGraphDisplayWrapper) getActiveGraphDisplay();
// set a temporary dummy graph; clients will set a real graph
visibleGraph.setGraph(new AttributedGraph("Empty", null), visibleGraph.setGraph(new AttributedGraph("Empty", null),
new DefaultGraphDisplayOptions(), "", false, monitor); new DefaultGraphDisplayOptions(), "", false, monitor);
visibleGraph.restoreToDefaultSetOfActions(); visibleGraph.restoreDefaultState();
return visibleGraph; return visibleGraph;
} }
DefaultGraphDisplay display = new DefaultGraphDisplay(this, displayCounter++); DefaultGraphDisplayWrapper display =
new DefaultGraphDisplayWrapper(this, displayCounter++);
displays.add(display); displays.add(display);
return display; return display;
}); });
@ -72,27 +79,28 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
if (displays.isEmpty()) { if (displays.isEmpty()) {
return null; return null;
} }
// get the sorted displays in order to pick the newest graph
return getAllGraphDisplays().get(0); return getAllGraphDisplays().get(0);
} }
@Override @Override
public List<GraphDisplay> getAllGraphDisplays() { public List<GraphDisplay> getAllGraphDisplays() {
return Swing.runNow(() -> { return displays.stream().sorted().collect(Collectors.toList());
return displays.stream()
.sorted((d1, d2) -> -(d1.getId() - d2.getId())) // largest/newest IDs come first
.collect(Collectors.toList());
});
} }
@Override @Override
public void initialize(PluginTool tool, Options graphOptions) { public void initialize(PluginTool tool, Options graphOptions) {
this.pluginTool = tool; this.pluginTool = tool;
this.options = graphOptions; this.options = graphOptions;
Swing.assertSwingThread("Graph preferences must be accessed on the Swing thread");
preferences = pluginTool.getWindowManager().getPreferenceState(PREFERENCES_KEY); preferences = pluginTool.getWindowManager().getPreferenceState(PREFERENCES_KEY);
if (preferences == null) { if (preferences == null) {
preferences = new PreferenceState(); preferences = new PreferenceState();
pluginTool.getWindowManager().putPreferenceState(PREFERENCES_KEY, preferences); pluginTool.getWindowManager().putPreferenceState(PREFERENCES_KEY, preferences);
} }
defaultSatelliteState = preferences.getBoolean(DEFAULT_SATELLITE_STATE, false); defaultSatelliteState = preferences.getBoolean(DEFAULT_SATELLITE_STATE, false);
} }
@ -103,11 +111,12 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
@Override @Override
public void dispose() { public void dispose() {
// first copy to new set to avoid concurrent modification exception
HashSet<DefaultGraphDisplay> set = new HashSet<>(displays); // Calling close() will trigger the display to call back to this class's remove(). Avoid
for (DefaultGraphDisplay display : set) { // unnecessary copies in the 'copy on write' set by closing after clearing the set.
display.close(); Set<GraphDisplay> set = new HashSet<>(displays);
} displays.clear();
set.forEach(d -> d.close());
} }
@Override @Override
@ -115,8 +124,8 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
return new HelpLocation("GraphServices", "Default_Graph_Display"); return new HelpLocation("GraphServices", "Default_Graph_Display");
} }
public void remove(DefaultGraphDisplay defaultGraphDisplay) { void remove(DefaultGraphDisplay defaultGraphDisplay) {
displays.remove(defaultGraphDisplay); displays.removeIf(wrapper -> wrapper.isDelegate(defaultGraphDisplay));
} }
boolean getDefaultSatelliteState() { boolean getDefaultSatelliteState() {
@ -124,9 +133,8 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
} }
void setDefaultSatelliteState(boolean b) { void setDefaultSatelliteState(boolean b) {
Swing.assertSwingThread("Graph preferences must be accessed on the Swing thread");
defaultSatelliteState = b; defaultSatelliteState = b;
preferences.putBoolean(DEFAULT_SATELLITE_STATE, b); preferences.putBoolean(DEFAULT_SATELLITE_STATE, b);
} }
} }

View file

@ -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.graph.visualization;
import java.util.Set;
import docking.action.DockingActionIf;
import docking.widgets.EventTrigger;
import ghidra.service.graph.*;
import ghidra.util.Swing;
import ghidra.util.task.TaskMonitor;
/**
* A {@link DefaultGraphDisplay} wrapper created to ensure all accesses to the delegate are on the
* Swing thread. This API is meant to be used concurrently. We do not want to force clients to
* have to understand when to use the Swing thread. Thus, this class handles that for the clients.
* Also, by having Swing accesses managed here, the {@link DefaultGraphDisplay} can assume that all
* its work will be done on the Swing thread.
*/
public class DefaultGraphDisplayWrapper
implements GraphDisplay, Comparable<DefaultGraphDisplayWrapper> {
private DefaultGraphDisplay delegate;
DefaultGraphDisplayWrapper(DefaultGraphDisplayProvider displayProvider, int id) {
delegate = Swing.runNow(() -> new DefaultGraphDisplay(displayProvider, id));
}
void restoreDefaultState() {
Swing.runNow(() -> delegate.restoreToDefaultSetOfActions());
}
boolean isDelegate(DefaultGraphDisplay other) {
return other == delegate;
}
@Override
public void setGraphDisplayListener(GraphDisplayListener listener) {
Swing.runNow(() -> delegate.setGraphDisplayListener(listener));
}
@Override
public void setFocusedVertex(AttributedVertex vertex, EventTrigger eventTrigger) {
Swing.runNow(() -> delegate.setFocusedVertex(vertex));
}
@Override
public AttributedGraph getGraph() {
return Swing.runNow(() -> delegate.getGraph());
}
@Override
public AttributedVertex getFocusedVertex() {
return Swing.runNow(() -> delegate.getFocusedVertex());
}
@Override
public void selectVertices(Set<AttributedVertex> vertexSet, EventTrigger eventTrigger) {
Swing.runNow(() -> delegate.selectVertices(vertexSet, eventTrigger));
}
@Override
public Set<AttributedVertex> getSelectedVertices() {
return Swing.runNow(() -> delegate.getSelectedVertices());
}
@Override
public void close() {
Swing.runNow(() -> delegate.close());
}
@Override
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
boolean append, TaskMonitor monitor) {
Swing.runNow(() -> delegate.setGraph(graph, options, title, append, monitor));
}
@Override
public void clear() {
Swing.runNow(() -> delegate.clear());
}
@Override
public void updateVertexName(AttributedVertex vertex, String newName) {
Swing.runNow(() -> delegate.updateVertexName(vertex, newName));
}
@Override
public String getGraphTitle() {
return Swing.runNow(() -> delegate.getGraphTitle());
}
@Override
public void addAction(DockingActionIf action) {
Swing.runNow(() -> delegate.addAction(action));
}
@Override
public int compareTo(DefaultGraphDisplayWrapper other) {
// note: no need for call to Swing, assuming ID is immutable
// larger/newer values are preferred so they should be first when sorting
return -(delegate.getId() - other.delegate.getId());
}
}

View file

@ -21,7 +21,6 @@ import java.util.*;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.util.AddEditDialog; import ghidra.app.util.AddEditDialog;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.*; import ghidra.graph.*;
@ -54,8 +53,6 @@ public class BlockGraphTask extends Task {
private boolean showCode = false; private boolean showCode = false;
private int codeLimitPerBlock = 10; private int codeLimitPerBlock = 10;
private ColorizingService colorizingService;
private final static String ENTRY_NEXUS_NAME = "Entry Points"; private final static String ENTRY_NEXUS_NAME = "Entry Points";
private static final int MAX_SYMBOLS = 10; private static final int MAX_SYMBOLS = 10;
private CodeBlockModel blockModel; private CodeBlockModel blockModel;
@ -70,10 +67,10 @@ public class BlockGraphTask extends Task {
private String graphTitle; private String graphTitle;
private ProgramGraphType graphType; private ProgramGraphType graphType;
public BlockGraphTask(ProgramGraphType graphType, public BlockGraphTask(ProgramGraphType graphType, boolean graphEntryPointNexus,
boolean graphEntryPointNexus, boolean reuseGraph, boolean appendGraph, boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection,
PluginTool tool, ProgramSelection selection, ProgramLocation location, ProgramLocation location, CodeBlockModel blockModel,
CodeBlockModel blockModel, GraphDisplayProvider graphProvider) { GraphDisplayProvider graphProvider) {
super("Graph Program", true, false, true); super("Graph Program", true, false, true);
this.graphType = graphType; this.graphType = graphType;
@ -84,24 +81,19 @@ public class BlockGraphTask extends Task {
this.tool = tool; this.tool = tool;
this.blockModel = blockModel; this.blockModel = blockModel;
this.graphProvider = graphProvider; this.graphProvider = graphProvider;
this.colorizingService = tool.getService(ColorizingService.class);
this.selection = selection; this.selection = selection;
this.location = location; this.location = location;
this.program = blockModel.getProgram(); this.program = blockModel.getProgram();
this.graphTitle = graphType.getName() + ": "; this.graphTitle = graphType.getName() + ": ";
} }
/**
* Runs the move memory operation.
*/
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
this.graphScope = getGraphScopeAndGenerateGraphTitle(); this.graphScope = getGraphScopeAndGenerateGraphTitle();
AttributedGraph graph = createGraph(graphTitle); AttributedGraph graph = createGraph(graphTitle);
monitor.setMessage("Generating Graph..."); monitor.setMessage("Generating Graph...");
try { try {
GraphDisplay display = GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor);
graphProvider.getGraphDisplay(reuseGraph, monitor);
GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool); GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool);
if (showCode) { // arrows need to be bigger as this generates larger vertices if (showCode) { // arrows need to be bigger as this generates larger vertices
graphOptions.setArrowLength(30); graphOptions.setArrowLength(30);
@ -140,8 +132,8 @@ public class BlockGraphTask extends Task {
private void addActions(GraphDisplay display, private void addActions(GraphDisplay display,
java.util.function.Function<AttributedVertex, Address> addressFunction) { java.util.function.Function<AttributedVertex, Address> addressFunction) {
display.addAction(new ActionBuilder("Rename Symbol", "Block Graph") display.addAction(
.popupMenuPath("Rename Symbol") new ActionBuilder("Rename Symbol", "Block Graph").popupMenuPath("Rename Symbol")
.withContext(VertexGraphActionContext.class) .withContext(VertexGraphActionContext.class)
.helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename_Symbol")) .helpLocation(new HelpLocation("ProgramGraphPlugin", "Rename_Symbol"))
// only enable action when vertex corresponds to an address // only enable action when vertex corresponds to an address
@ -264,8 +256,7 @@ public class BlockGraphTask extends Task {
} }
private Address graphBlock(AttributedGraph graph, CodeBlock curBB, private Address graphBlock(AttributedGraph graph, CodeBlock curBB,
List<AttributedVertex> entries) List<AttributedVertex> entries) throws CancelledException {
throws CancelledException {
Address[] startAddrs = curBB.getStartAddresses(); Address[] startAddrs = curBB.getStartAddresses();

View file

@ -124,16 +124,16 @@ public class AttributedGraph extends AbstractBaseGraph<AttributedVertex, Attribu
* then that vertex will be returned, but with its name changed to the given name. * then that vertex will be returned, but with its name changed to the given name.
* *
* @param id the unique vertex id that the graph should have a vertex for. * @param id the unique vertex id that the graph should have a vertex for.
* @param name the name to associate with this vertex * @param vertexName the name to associate with this vertex
* @return either an existing vertex with that id, or a newly added vertex with that id * @return either an existing vertex with that id, or a newly added vertex with that id
*/ */
public AttributedVertex addVertex(String id, String name) { public AttributedVertex addVertex(String id, String vertexName) {
if (vertexMap.containsKey(id)) { if (vertexMap.containsKey(id)) {
AttributedVertex vertex = vertexMap.get(id); AttributedVertex vertex = vertexMap.get(id);
vertex.setName(name); vertex.setName(vertexName);
return vertex; return vertex;
} }
AttributedVertex newVertex = new AttributedVertex(id, name); AttributedVertex newVertex = new AttributedVertex(id, vertexName);
addVertex(newVertex); addVertex(newVertex);
return newVertex; return newVertex;
} }

View file

@ -19,11 +19,6 @@ import java.util.Set;
public class DummyGraphDisplayListener implements GraphDisplayListener { public class DummyGraphDisplayListener implements GraphDisplayListener {
@Override
public void graphClosed() {
// I'm a dummy
}
@Override @Override
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) { public GraphDisplayListener cloneWith(GraphDisplay graphDisplay) {
return new DummyGraphDisplayListener(); return new DummyGraphDisplayListener();
@ -31,17 +26,17 @@ public class DummyGraphDisplayListener implements GraphDisplayListener {
@Override @Override
public void selectionChanged(Set<AttributedVertex> vertices) { public void selectionChanged(Set<AttributedVertex> vertices) {
// I'm a dummy // stub
} }
@Override @Override
public void locationFocusChanged(AttributedVertex vertex) { public void locationFocusChanged(AttributedVertex vertex) {
// I'm a dummy // stub
} }
@Override @Override
public void dispose() { public void dispose() {
// I'm a dummy // stub
} }
} }

View file

@ -30,62 +30,6 @@ import ghidra.util.task.TaskMonitor;
* closed. * closed.
*/ */
public interface GraphDisplay { public interface GraphDisplay {
public static final int ALIGN_LEFT = 0; // aligns graph text to the left
public static final int ALIGN_CENTER = 1; // aligns graph text to the center
public static final int ALIGN_RIGHT = 2; // aligns graph text to the right
/**
* values are color names or rgb in hex '0xFF0000' is red
*/
public static final String SELECTED_VERTEX_COLOR = "selectedVertexColor";
/**
* values are color names or rgb in hex '0xFF0000' is red
*/
public static final String SELECTED_EDGE_COLOR = "selectedEdgeColor";
/**
* values are defined as String symbols in LayoutFunction class
*
* KAMADA_KAWAI,FRUCTERMAN_REINGOLD,CIRCLE_MINCROSS,TIDIER_TREE,TIDIER_RADIAL_TREE,
* MIN_CROSS_TOP_DOWN,MIN_CROSS_LONGEST_PATH,MIN_CROSS_NETWORK_SIMPLEX,MIN_CROSS_COFFMAN_GRAHAM,
* EXP_MIN_CROSS_TOP_DOWN,EXP_MIN_CROSS_LONGEST_PATH,EXP_MIN_CROSS_NETWORK_SIMPLEX,
* EXP_MIN_CROSS_COFFMAN_GRAHAM,TREE,RADIAL,BALLOON,GEM
*
* may have no meaning for a different graph visualization library
*/
public static final String INITIAL_LAYOUT_ALGORITHM = "initialLayoutAlgorithm";
/**
* true or false
* may have no meaning for a different graph visualization library
*/
public static final String DISPLAY_VERTICES_AS_ICONS = "displayVerticesAsIcons";
/**
* values are the strings N,NE,E,SE,S,SW,W,NW,AUTO,CNTR
* may have no meaning for a different graph visualization library
*/
public static final String VERTEX_LABEL_POSITION = "vertexLabelPosition";
/**
* true or false, whether edge selection via a mouse click is enabled.
* May not be supported by another graph visualization library
*/
public static final String ENABLE_EDGE_SELECTION = "enableEdgeSelection";
/**
* a comma-separated list of edge type names in priority order
*/
public static final String EDGE_TYPE_PRIORITY_LIST = "edgeTypePriorityList";
/**
* a comma-separated list of edge type names.
* any will be considered a favored edge for the min-cross layout
* algorithms.
* May have no meaning with a different graph visualization library
*/
public static final String FAVORED_EDGES = "favoredEdges";
/** /**
* Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus * Sets a {@link GraphDisplayListener} to be notified when the user changes the vertex focus
@ -156,6 +100,7 @@ public interface GraphDisplay {
* @throws CancelledException thrown if the graphing operation was cancelled * @throws CancelledException thrown if the graphing operation was cancelled
* @deprecated You should now use the form that takes in a {@link GraphDisplayOptions} * @deprecated You should now use the form that takes in a {@link GraphDisplayOptions}
*/ */
@Deprecated
public default void setGraph(AttributedGraph graph, String title, boolean append, public default void setGraph(AttributedGraph graph, String title, boolean append,
TaskMonitor monitor) throws CancelledException { TaskMonitor monitor) throws CancelledException {
setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor); setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor);

View file

@ -21,10 +21,6 @@ import java.util.Set;
* Interface for being notified when the user interacts with a visual graph display * Interface for being notified when the user interacts with a visual graph display
*/ */
public interface GraphDisplayListener { public interface GraphDisplayListener {
/**
* Notification that the graph window has been closed
*/
public void graphClosed();
/** /**
* Notification that the set of selected vertices has changed * Notification that the set of selected vertices has changed
@ -50,7 +46,8 @@ public interface GraphDisplayListener {
public GraphDisplayListener cloneWith(GraphDisplay graphDisplay); public GraphDisplayListener cloneWith(GraphDisplay graphDisplay);
/** /**
* Tells the listener that it is no longer needed and it can release any listeners/resources * Tells the listener that it is no longer needed and it can release any listeners/resources.
* This will be called when a {@link GraphDisplay} is disposed or if this listener is replaced.
*/ */
public void dispose(); public void dispose();

View file

@ -72,9 +72,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(display.getSelectedVertices().isEmpty()); assertTrue(display.getSelectedVertices().isEmpty());
DockingActionIf action = getAction(tool, "Select Vertex"); DockingActionIf action = getAction(tool, "Select Vertex");
VertexGraphActionContext context = VertexGraphActionContext context = new VertexGraphActionContext(graphComponentProvider,
new VertexGraphActionContext(graphComponentProvider, graph, null, null, graph, null, null, graph.getVertex("B"));
graph.getVertex("B"));
performAction(action, context, true); performAction(action, context, true);
Set<AttributedVertex> selectedVertices = display.getSelectedVertices(); Set<AttributedVertex> selectedVertices = display.getSelectedVertices();
@ -102,9 +101,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(4, display.getSelectedVertices().size()); assertEquals(4, display.getSelectedVertices().size());
DockingActionIf action = getAction(tool, "Deselect Vertex"); DockingActionIf action = getAction(tool, "Deselect Vertex");
VertexGraphActionContext context = VertexGraphActionContext context = new VertexGraphActionContext(graphComponentProvider,
new VertexGraphActionContext(graphComponentProvider, graph, null, null, graph, null, null, graph.getVertex("B"));
graph.getVertex("B"));
performAction(action, context, true); performAction(action, context, true);
Set<AttributedVertex> selected = display.getSelectedVertices(); Set<AttributedVertex> selected = display.getSelectedVertices();
@ -121,9 +119,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(display.getSelectedVertices().isEmpty()); assertTrue(display.getSelectedVertices().isEmpty());
DockingActionIf action = getAction(tool, "Select Edge"); DockingActionIf action = getAction(tool, "Select Edge");
EdgeGraphActionContext context = EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph,
new EdgeGraphActionContext(graphComponentProvider, graph, null, null, null, null, graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
performAction(action, context, true); performAction(action, context, true);
Set<AttributedVertex> selectedVerticeIds = display.getSelectedVertices(); Set<AttributedVertex> selectedVerticeIds = display.getSelectedVertices();
@ -135,9 +132,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testDeSelectEdgeAction() { public void testDeSelectEdgeAction() {
DockingActionIf action = getAction(tool, "Select Edge"); DockingActionIf action = getAction(tool, "Select Edge");
EdgeGraphActionContext context = EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph,
new EdgeGraphActionContext(graphComponentProvider, graph, null, null, null, null, graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
performAction(action, context, true); performAction(action, context, true);
Set<AttributedVertex> selectedVertices = display.getSelectedVertices(); Set<AttributedVertex> selectedVertices = display.getSelectedVertices();
@ -155,9 +151,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
public void testSelectEdgeSource() { public void testSelectEdgeSource() {
setFocusedVertex(d); setFocusedVertex(d);
DockingActionIf action = getAction(tool, "Edge Source"); DockingActionIf action = getAction(tool, "Edge Source");
EdgeGraphActionContext context = EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph,
new EdgeGraphActionContext(graphComponentProvider, graph, null, null, null, null, graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
graph.getEdge(graph.getVertex("A"), graph.getVertex("B")));
performAction(action, context, true); performAction(action, context, true);
assertEquals(a, display.getFocusedVertex()); assertEquals(a, display.getFocusedVertex());
@ -167,9 +162,8 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
public void testSelectEdgeTarget() { public void testSelectEdgeTarget() {
setFocusedVertex(d); setFocusedVertex(d);
DockingActionIf action = getAction(tool, "Edge Target"); DockingActionIf action = getAction(tool, "Edge Target");
EdgeGraphActionContext context = EdgeGraphActionContext context = new EdgeGraphActionContext(graphComponentProvider, graph,
new EdgeGraphActionContext(graphComponentProvider, graph, null, null, null, null, graph.getEdge(a, b));
graph.getEdge(a, b));
performAction(action, context, true); performAction(action, context, true);
assertEquals(b, display.getFocusedVertex()); assertEquals(b, display.getFocusedVertex());
@ -523,11 +517,6 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
private class TestGraphDisplayListener implements GraphDisplayListener { private class TestGraphDisplayListener implements GraphDisplayListener {
@Override
public void graphClosed() {
// do nothing
}
@Override @Override
public void selectionChanged(Set<AttributedVertex> vertices) { public void selectionChanged(Set<AttributedVertex> vertices) {
graphSpy.setSelection(vertices); graphSpy.setSelection(vertices);
@ -545,7 +534,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest {
@Override @Override
public void dispose() { public void dispose() {
// do nothing // stub
} }
} }