map = attributed.getAttributeMap();
- if (map.get("Code") != null) {
+ if (map.containsKey("Code")) {
String code = StringEscapeUtils.escapeHtml4(map.get("Code"));
return "" + String.join("", Splitter.on('\n').split(code));
}
+ if ("Collapsed".equals(map.get("VertexType"))) {
+ String name = StringEscapeUtils.escapeHtml4(map.get("Name"));
+ return "" + String.join("
",
+ Splitter.on(',').split(name));
+ }
return map.get("Name");
}
}
diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java
index 2c34b2b8a2..bdb0671aec 100644
--- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java
+++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java
@@ -78,6 +78,12 @@ public class AttributedEdge extends Attributed {
return id;
}
+ @Override
+ public String setAttribute(String key, String value) {
+ clearCache();
+ return super.setAttribute(key, value);
+ }
+
@Override
public int hashCode() {
return id.hashCode();
@@ -97,4 +103,9 @@ public class AttributedEdge extends Attributed {
AttributedEdge other = (AttributedEdge) obj;
return id.equals(other.id);
}
+
+ private void clearCache() {
+ this.htmlString = null;
+ }
+
}
diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java
index bb6c1fd9d3..dded3d3fa0 100644
--- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java
+++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java
@@ -17,11 +17,10 @@ package ghidra.service.graph;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import org.apache.commons.text.StringEscapeUtils;
-import java.util.Set;
-
/**
* Graph vertex with attributes
*/
@@ -57,6 +56,12 @@ public class AttributedVertex extends Attributed {
setAttribute("Name", name);
}
+ @Override
+ public String setAttribute(String key, String value) {
+ clearCache();
+ return super.setAttribute(key, value);
+ }
+
/**
* Returns the id for this vertex
* @return the id for this vertex
@@ -79,7 +84,7 @@ public class AttributedVertex extends Attributed {
return getName() + " (" + id + ")";
}
- public void clearCache() {
+ private void clearCache() {
this.htmlString = null;
}
diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java
index 39584dd51d..ff22139d45 100644
--- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java
+++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/GraphActionTest.java
@@ -28,18 +28,19 @@ import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.visualization.DefaultGraphDisplayComponentProvider;
+import ghidra.graph.visualization.GroupVertex;
import ghidra.service.graph.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.task.TaskMonitor;
public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest {
- private List listenerCalls = new ArrayList<>();
private TestEnv env;
private PluginTool tool;
private AttributedGraph graph;
private ComponentProvider graphComponentProvider;
private GraphDisplay display;
+ private GraphSpy graphSpy = new GraphSpy();
private AttributedVertex a;
private AttributedVertex b;
private AttributedVertex c;
@@ -245,6 +246,194 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest {
assertFalse(contains(newGraph, "F"));
}
+ @Test
+ public void testCollapseVertices() {
+ assertEquals(6, display.getGraph().getVertexCount());
+ select(a, b, c);
+
+ collapse();
+
+ assertEquals(4, graph.getVertexCount());
+ GroupVertex groupVertex = findGroupVertex();
+ Set containedVertices = groupVertex.getContainedVertices();
+ assertEquals(3, containedVertices.size());
+ assertTrue(containedVertices.contains(a));
+ assertTrue(containedVertices.contains(b));
+ assertTrue(containedVertices.contains(c));
+ }
+
+ @Test
+ public void testExpandVertices() {
+ assertEquals(6, display.getGraph().getVertexCount());
+ select(a, b, c);
+
+ collapse();
+
+ assertEquals(4, graph.getVertexCount());
+ GroupVertex groupVertex = findGroupVertex();
+ assertNotNull(groupVertex);
+ select(groupVertex);
+
+ expand();
+ assertEquals(6, graph.getVertexCount());
+ groupVertex = findGroupVertex();
+ assertNull(groupVertex);
+ }
+
+ @Test
+ public void testSelectNodeThatIsGrouped() {
+ select(a, b, c);
+ collapse();
+
+ clearSelection();
+ assertTrue(display.getSelectedVertices().isEmpty());
+
+ // 'b' is inside the group, selecting 'b' should select the group node
+ select(b);
+
+ Set selectedVertices = display.getSelectedVertices();
+ assertEquals(1, selectedVertices.size());
+ AttributedVertex vertex = selectedVertices.iterator().next();
+ assertTrue(vertex instanceof GroupVertex);
+
+ }
+
+ @Test
+ public void testSelectNodeThatIsDoubleGrouped() {
+ select(a, b, c);
+ collapse();
+ select(findGroupVertex(), d);
+ collapse();
+
+ clearSelection();
+ assertTrue(display.getSelectedVertices().isEmpty());
+
+ select(b);
+ Set selectedVertices = display.getSelectedVertices();
+ assertEquals(1, selectedVertices.size());
+ AttributedVertex vertex = selectedVertices.iterator().next();
+ assertTrue(vertex instanceof GroupVertex);
+ assertEquals(4, ((GroupVertex) vertex).getContainedVertices().size());
+
+ }
+
+ @Test
+ public void testFocusNodeThatIsGrouped() {
+ select(a, b, c);
+ collapse();
+
+ clearSelection();
+ assertTrue(display.getSelectedVertices().isEmpty());
+
+ setFocusedVertex(b);
+
+ AttributedVertex vertex = display.getFocusedVertex();
+ assertTrue(vertex instanceof GroupVertex);
+ }
+
+ @Test
+ public void testFocusNodeThatIsDoubleGrouped() {
+ select(a, b, c);
+ collapse();
+ select(findGroupVertex(), d);
+ collapse();
+ setFocusedVertex(e);
+ assertEquals(e, display.getFocusedVertex());
+
+ setFocusedVertex(b);
+
+ AttributedVertex vertex = display.getFocusedVertex();
+ assertTrue(vertex instanceof GroupVertex);
+ assertEquals(4, ((GroupVertex) vertex).getContainedVertices().size());
+ }
+
+ @Test
+ public void testListenerNotificatinWhenGroupNodeFocused() {
+ select(a, b, c);
+ collapse();
+ GroupVertex group = findGroupVertex();
+ setFocusedVertex(e);
+
+ graphSpy.clear();
+ setFocusedVertex(group, true);
+ waitForSwing();
+
+ assertTrue(graphSpy.isFocused(a));
+ }
+
+ @Test
+ public void testListenerNotificatinWhenDoubleGroupedNodeFocused() {
+ select(a, b, c);
+ collapse();
+ select(findGroupVertex(), d);
+ collapse();
+
+ GroupVertex group = findGroupVertex();
+ setFocusedVertex(e);
+
+ graphSpy.clear();
+ setFocusedVertex(group, true);
+
+ waitForSwing();
+ assertTrue(graphSpy.isFocused(a));
+ }
+
+ @Test
+ public void testSelectNotificatinWhenGroupNodeFocused() {
+ select(a, b, c);
+ collapse();
+ GroupVertex group = findGroupVertex();
+ clearSelection();
+ graphSpy.clear();
+ selectFromGui(group);
+
+ waitForSwing();
+ assertTrue(graphSpy.isSelected(a, b, c));
+ }
+
+ @Test
+ public void testSelectNotificatinWhenDoubleGroupedNodeFocused() {
+ select(a, b, c);
+ collapse();
+ select(findGroupVertex(), d);
+ collapse();
+
+ GroupVertex group = findGroupVertex();
+ clearSelection();
+ graphSpy.clear();
+ selectFromGui(group);
+
+ waitForSwing();
+ assertTrue(graphSpy.isSelected(a, b, c, d));
+ }
+
+ private void clearSelection() {
+ select();
+ }
+
+ private void collapse() {
+ DockingActionIf action = getAction(tool, "Collapse Selected");
+ GraphActionContext context =
+ new GraphActionContext(graphComponentProvider, graph, null, null);
+ performAction(action, context, true);
+ }
+
+ private void expand() {
+ DockingActionIf action = getAction(tool, "Expand Selected");
+ GraphActionContext context =
+ new GraphActionContext(graphComponentProvider, graph, null, null);
+ performAction(action, context, true);
+ }
+
+ private GroupVertex findGroupVertex() {
+ for (AttributedVertex vertex : graph.vertexSet()) {
+ if (vertex instanceof GroupVertex) {
+ return (GroupVertex) vertex;
+ }
+ }
+ return null;
+ }
+
private boolean contains(AttributedGraph g, String vertexId) {
return g.getVertex(vertexId) != null;
}
@@ -264,8 +453,20 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest {
});
}
+ private void selectFromGui(AttributedVertex... vertices) {
+ runSwing(() -> {
+ Set vetexSet = new HashSet<>(Arrays.asList(vertices));
+ display.selectVertices(vetexSet, EventTrigger.GUI_ACTION);
+ });
+ }
+
private void setFocusedVertex(AttributedVertex vertex) {
- runSwing(() -> display.setFocusedVertex(vertex, EventTrigger.INTERNAL_ONLY));
+ setFocusedVertex(vertex, false);
+ }
+
+ private void setFocusedVertex(AttributedVertex vertex, boolean fireEvent) {
+ EventTrigger trigger = fireEvent ? EventTrigger.GUI_ACTION : EventTrigger.INTERNAL_ONLY;
+ runSwing(() -> display.setFocusedVertex(vertex, trigger));
}
class TestGraphDisplayListener implements GraphDisplayListener {
@@ -278,24 +479,17 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest {
@Override
public void graphClosed() {
- listenerCalls.add(name + ": graph closed");
+ // do nothing
}
@Override
- public void selectionChanged(Set verrtices) {
- StringBuilder buf = new StringBuilder();
- buf.append(name);
- buf.append(": selected: ");
- for (AttributedVertex vertex : verrtices) {
- buf.append(vertex.getId());
- buf.append(",");
- }
- listenerCalls.add(buf.toString());
+ public void selectionChanged(Set vertices) {
+ graphSpy.setSelection(vertices);
}
@Override
public void locationFocusChanged(AttributedVertex vertex) {
- listenerCalls.add(name + ": focus: " + vertex.getId());
+ graphSpy.focusChanged(vertex);
}
@Override
@@ -305,6 +499,34 @@ public class GraphActionTest extends AbstractGhidraHeadedIntegrationTest {
}
+ class GraphSpy {
+ AttributedVertex focusedVertex;
+ Set selectedVertices;
+
+ public void focusChanged(AttributedVertex vertex) {
+ this.focusedVertex = vertex;
+ }
+
+ public boolean isSelected(AttributedVertex... vertices) {
+ Set expected = new HashSet<>(Arrays.asList(vertices));
+ return expected.equals(selectedVertices);
+ }
+
+ public boolean isFocused(AttributedVertex a) {
+ return a == focusedVertex;
+ }
+
+ public void clear() {
+ focusedVertex = null;
+ selectedVertices = null;
+ }
+
+ public void setSelection(Set vertices) {
+ this.selectedVertices = vertices;
+ }
+
+ }
+
private AttributedGraph createGraph() {
AttributedGraph g = new AttributedGraph();
a = g.addVertex("A");