Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

View file

@ -0,0 +1,4 @@
The UML images and their corresponding xml files were created using https://www.draw.io/. You
can load the xml files into that tool to edit them.

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -0,0 +1 @@
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko/20100101 Firefox/52.0" version="8.5.8" editor="www.draw.io" type="device"><diagram id="e99e79a4-c0f6-7cc5-9868-58554833cb73" name="Page-1">7VpLc+MoEP41PmZKb9tH20kml5maqkxld45EwhIVLDwIv/bXbyMhWwI0TnllZTOTHGLRQAPf1y3oRiN/sdp/5midfWEJpiPPSfYj/3bkea7vOvAjJYdKMvWnlSDlJFGNToJH8g9WQtUv3ZAEF62GgjEqyLotjFme41i0ZIhztms3WzLaHnWNUmwIHmNETelfJBGZkrqOc6p4wCTN1NCTUFU8o/gl5WyTq/FGnr8s/6rqFap1qfZFhhK2a4j8u5G/4IyJ6mm1X2Aqsa1hq/rdd9Qe581xLl7TwfOrHltEN7iecjkxcajBKJeDZQdn5M93GRH4cY1iWbsD+kGWiRWFkguPSh3mAu875+QeVwoWhNkKC36AJqqDHyhwlPFMVHF3YiKqm2QNEtzaepBiPz2qPiEADwqEDkBMPBbeaDb//FQtCQpzWGREYdD5M4enVJTLVhJdAOM1ZBqy/eIWuN6nsIWc65nQHVFqQhf2gJzFkirk7hJwNi9CK2kqJShPx2KFy/8Y1HE0fktQgw5Qn0ixQbRtkwMbFyxwOBzCX+JgtTAH7wXOkwKebGh1mJZpfUMam/MakD3by68PkCPLXlBhkJCt1E1JmpcV0c+N3KHmMSwM81NZx47UgtlzITiKRYuJE8DEAno5ZgfsEmUC2/VMzUgwyXw9P4qXsheDVkta7q5LIhc9X7JcqMOG66nyPVoRKtF+wHSLpdb+N7Sp/g7xHYvv2GiNeqB13Buttyx+IXnaQeLvTFgwJGGTofxQvTf/DC8ch/rLdVAvnPZG6ndciD/K+UJ3QJ7qE0xPRGku9vvQZLrTsDSZgdpdfd7TcAXhTOYEoPRMYQeD9YOohiyqivclmGWE28AJsOCHv6Uc1qqKP1oY4sTIJGgIwlzYhse4daAXiKdYtfLsODdRrBHjmCJBtu0RbTAqdd8YgbmcDvHG+TL0NC6quap+zcSBpiqMzqqq1mioKpk9rvN1ZJux5bsgOzTJ9ocje2K8Ry8lOwrOquqRbDPWexdkj02yo7f07CDszbN1VT2SbYs5z7OcoCIrM5KuwXHDALxrUx6ZlAcflJ+j3IxHr+HfeyIaXEPpx9FA+jaDqWkG4w8zOGcGtij3Sp5/Ga0Tk9bwLXfv6YW0WnZvXVWPtJpx7rvYveuwr8n25IPtc7dltmBZo7nI0Fo+cvxzQ7i81YVAmuNYECbj2pxxiWbP0WkUGJn0wLy2iSzBqX6wvegS0X09LJRRStZlqN+EBei+AiyhZ8DiW26zfAsuQR+4mEF7nRuRWYsWQHUyRVbcFOVLYQYN3Ml6b2ZaqnQmVJffI8BvnR9zUJ7A/yoL4zwQzBGPs0ZCphq3nfb8b3M5dGrXLAAmKNoMF4KzF7xglPHSM3IsB4U3oCaqc0Iq8eTP9dTRiiSJHMZ6W9++z+/BqswjiOWKNJhey9nMePEry2+2tUmkyiQQTRknIluN5Kj6bR9A9CI/Culy05yJLjybBKrNK7BwYk3nFaCK5Ol3WXd7E/XDh6t9QOE5Fh+30dHHJaL1uql752drnLd2evfXMdzxWO9Pps2D/ScnUsVvmBOYtfSL9nn/Bho5XvO4AKeHyVGg97vsHGHL+QyY9DnqqT9WuDQW8L0zii4+REDx9H1T1fz0EZl/9y8=</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1 @@
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko/20100101 Firefox/52.0" version="8.5.8" editor="www.draw.io" type="device"><diagram id="954e937b-a6b1-f117-93c9-03646c8ceb08" name="Page-1">7VrbcqM4EP0aP2aKi8H4MU6cpLZmq6bKW9mZRwVkrI2MWFl27P36lUDiIon4ysST3VQlQY0Qos/p0y3QwL9bbh8pyBe/kwTigeck24F/P/A813cd/k9YdqVl7I9LQ0pRIjvVhhn6B0qjvC5dowSuWh0ZIZihvG2MSZbBmLVsgFLy1u42J7h91xyk0DDMYoBN658oYQtpdR2nPvEEUbqQt44CeeIFxK8pJetM3m/g+fPipzy9BGos2X+1AAl5a5j86cC/o4Sw8mi5vYNY+Fa5rbzuoeNsNW8KM3bQBVF5xQbgtXz239ZZKifHdsohfJ65OIzJMidZMfgEYJRm3IbhXDRXOYhRln4tWvd+yE3mbOQEN5AyuG2Y5OweIVlCRne8y7bNCEUk2XyrUfEUKIsGIK7qCCQT0mrk2hv8QDrE7hzP8M3gzhvcTh7vEeWsg0nB/oEXgmXOnxYLLzwPPD6uM+W/cMtglqz40eM04XzT+8lmKppi4An3V1icfKH8qLQri27gc2/YNLQWbMmf6d69DAR+GOzDwLdhEFwAAt+EwPRRAcozWq0BtiPSgKLs9lw+vAWq8vyvjZffjhk3MgEb9gVY0BEzLb9fsfPG3l7Bcd2efBe+6zsrJ7uYfcUejoK2nLiBRdOHPbl4ZNET7Wm5O29FCcFbJIc8wU245QFh5QLekgWLy59y0nAO9wndfecNRzV+iMaXQDS3iH1X/fhxecYL3vMpAzSF6uFLE0y0wmVF1jSGDbE0/d6kbih9SCEGDG3aY9kcK4f7RhCfXIXhSEvL7mjcHqKclryqWX9oA0XRnoFKHxgDcYTArtEtFx1WBhMqLxxEDrMW6oUcXxwnahMkHKr2N0gRnzGkkkUHcSO0cuPnEEHLNVUxdjQRAo0IY//LMHQDPyr/BqODeHEC6mNbiVGqbPayykuR1NqOqgBnHJoYyqpD44qQRMTXFLeyTmYkN6tmwnvNcUGoeUGjyZxkrMkh0X4AS4SFZ54g3kAxaptcF5DlUPO/E5iJz6bK4QVU2bVU2jIxJWgjBpdO49a/12JtNIn5k0Jat1XyEquXPeX5VCvl6hRY3KwjCf7SaPqWHNsfmmbRPlVVSrecvmASv7YVNGzJq9Opp0FTTd91YDNbKtI1lXT8cUqqZdSqiD9WSN3RnoEup5zu8KiEqRBOwGoBkzphNvA9rbY6GW7veuDWA+9kuPWBLgi3ZW13okqr65Ay3MM5WGP23iL+Hd1GluXM59Hy4YeKuWVR2luMyzVSvWL6sa8MbkV4YEa4fzURPryUoBsDXTDCzeVxn5m7ahwr5Raglbz/j/Rhr7YtUcw9N5PNjGT836T4kFGEcXfx5RnV11+QsZ1kAlgzIrSUsgVJSQbwVyK09ogqbWzB2v4yxMT6YFwPVkPrYvGkFPjInZujLD0l5Zkv9D5Vwqu+Jqg4sCW8qKeEV5Hw7MgY9RsZ6kX1dUSGms0FIoNnDW4/MTQ+VSAEekKwBULYVyAMzwqEQ7l+9jpe1XitODh0IX9m5vfHbYACr7+M3f0p9OgAW2cxQyRTkfX5AsfXvuJVgfQzlkzqO8L5GaTnBDIyA0f57QMSiO3T3Gn8nvG8gOF/OW9EH0h//7wC6rIUv6rVg+VT023C19b+rVnY30w3sMg0TyBLMBJbsowuM4iL7xw1N8QOswykcFlca1zwQOL1qru/hpra85URJhB7WyAGZzkofPvGg6uN20rSXHzKPCha5H6xPwpEby60XSzSlg5VImhuLrDt36g2dZxF/e4cLSK/5V4lWuLETek8TgPHjfKtqWhlJcxPS1FznhCkgMaLXUO/ylt0CBj3H9MAY5S8wjuCCa3DUuiWZlKwSbU10V2iJMFdDGnHeQecFtC7U7tWE9sAHlsA1iuzA/DlzXonZlmy1dtd/em/</diagram></mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1 @@
<mxfile userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko/20100101 Firefox/52.0" version="8.5.8" editor="www.draw.io" type="device"><diagram id="f9204711-c45c-f59e-ff6e-30e3a321b789" name="Page-1">7VptU6M6FP41/agDpNDyUWvVubM744w73d2PaUlpRiBcSG27v36TkPCWoFSper3ujFtykhyS85y3nDACs3h/k8F0850EKBo5VrAfgauR49jAttgPpxwKig/8ghBmOJCDKsI9/oMkUc4LtzhAeWMgJSSiOG0SVyRJ0Io2aDDLyK45bE2i5ltTGCKNcL+CkU79iQO6kVTbsqqOW4TDjXz11JUdS7h6CDOyTeT7Rg5Yi39FdwwVLzk+38CA7GokMB+BWUYILZ7i/QxFXLZKbMW8647ect0ZSmifCWrGI4y2SC1ZLIwelDDYGlP+iPaM5WVAtkvRY7OG2CnivCzW2m0wRfcpXPHuHdMMRtvQOJKD9bWpl6OMon2NJNd6g0iMaHZgQ2TvRIpNqpXjjIv2rgJpOpZjNjV8PAUclIoRlqwr4bAHKZ8OWRlE5UVCJviR845wmIgO798tB/ByxXaKsqrNnkL+K6xmRuKUJFwWjgdjLivBazFyZnP2d1OSQzlVdC8zxaSbwjYiVqSoLTy5uDHT9Qu5Xkr4W9TqI7TmswgbtY6Eaq4xF8nlmiRUWqrtyPY1jHHEwbhF0SPiXAeH3LWbmANfx9z2jJi/HnIwLOQLjHaiU8f7C2su7pZ9u1MD1mb7fj3WSs+ecoXctaf9N1oGKLhUHKwnBTBt6fpE379jGfbvuAMIYKrt/59tEjLVYkFsDwudG/KxI8ysSr+oKWrOYgtOwm+idQU8s9ZZHWjoUn8TsfqD+ZAFzrds9B9IMUlKX/JfdAz9IVJRoGkYtu2+oWMw5UgvQ/AeUsabZUnDhIMPjfHRzt9rYjw2Of9TBXpbT+7me4qSINekyogX/IjBWsuIrB7Y7hlJCcwrmtdClCIr7iOlnGyzFWqqG4VZiGjDh6CgcWx50qGVospQxBzGY/NkY5KVZHdHMM9JO0KS546bHIqly0n1k0aLjzN+hlGxX42RQK7cYj8w9bTtBGCiPaa/JJk//+bP525fmMEXyq9EeWxwy93wkhQlDTjtJtZOC92ELeRXvdED3zqY4P3AdP2WJ3W9t0WTyRweasNSPiDvv2BPYlspR8HxxarinlRVlCM4H7N1V87APrcAkO07lGG2YBbtpcN4iXrV3Yeja5wKHP9H/3G0xk2mjfe4Q2ucp2ncd4gTRhGZH/vlyZ+e2Z3dwiSIEFu5FeA8jYpNJTgWOb94Dvgu1iSL9WimjlAJoej5elwu1ZnrbK9UUR6/fvC+q7OO09foyLTP9t0GEhNDTc8Bp0r7Jqd0DL0MWWnKB4kdLUOeWgMZssZowERAL1y87oRdHs6Oqsx+qsOYazet0vEMhzH3VFZpKpmcIFy79WBdD8qyqwzLvW15qtuy82XLR9iyuhcbwJbVPKwI14SUdy53GXnEQVU/Y+vC7YmfvN7iOq2aGnjDYrsyixrOswiLyzCrKgCzHZY4WXQDOUVHZwdFqmdRwv7b5kglWZgXrz9BhjRp3Yr4vo6Tb8DJdQfAyXQDNrwrbpRQfMs/2anJcGxSZvAeHnoCWjbog/MWan2d9NR/ltWAbvptai+GONwdvfspgKUrgIra76EAoK0A7osVYNxWAJ3VgAqgV1R+HFLuer+hEPGvXy563mIXmXSyzNNebetswaFS5VyryNgXhTMdhP/cwH/OVaN9GOhexXOXN0ev6cbwNllIaJ9QWL92f/QxgqDhelyz1u4ygdUqE3jn+hUgMJ1I7EG+/dHrOQpZnq415KsSUd5xVkiPGYNlT9N914lz1K4LWT9xwB1UlXgWr+nIPGnxbVYdNZqRBzQjEckE0glHmiecLZLCTmbROsQxDoKoS02a338NADNoXfQqf10H2ZSTOsdjzJrVd3aFC6w+ZgTzvw==</diagram></mxfile>

View file

@ -0,0 +1,71 @@
/* ###
* 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 ghidra.util.SystemUtilities;
public class DefaultGEdge<V> implements GEdge<V> {
private V start;
private V end;
public DefaultGEdge(V start, V end) {
this.start = start;
this.end = end;
}
@Override
public V getStart() {
return start;
}
@Override
public V getEnd() {
return end;
}
@Override
public String toString() {
return "[" + start + ", " + end + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((end == null) ? 0 : end.hashCode());
result = prime * result + ((start == null) ? 0 : start.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
DefaultGEdge<?> other = (DefaultGEdge<?>) obj;
return SystemUtilities.isEqual(start, other.start) &&
SystemUtilities.isEqual(end, other.end);
}
}

View file

@ -0,0 +1,212 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.graph;
import java.util.*;
/**
* A directed graph
*
* Unlike {@link GImplicitDirectedGraph}, this graph is constructed explicitly in memory. Edges and
* vertices are added and removed like any other collection, and these elements represent the
* entirety of the graph at any given time.
*
* @param <V> the type of vertices
* @param <E> the type of edges
*/
public interface GDirectedGraph<V, E extends GEdge<V>> {
/**
* Add a vertex
* @param v the vertex
* @return true if the add was successful, false otherwise
*/
public boolean addVertex(V v);
/**
* Remove a vertex
* @param v the vertex
* @return true
*/
public boolean removeVertex(V v);
/**
* Removes the given vertices from the graph
*
* @param vertices the vertices to remove
*/
public void removeVertices(Iterable<V> vertices);
/**
* Add an edge
* @param e the edge
*/
public void addEdge(E e);
/**
* Removes an edge
* @param e the edge
* @return true if the graph contained the given edge
*/
public boolean removeEdge(E e);
/**
* Removes the given edges from the graph
*
* @param edges the edges to remove
*/
public void removeEdges(Iterable<E> edges);
/**
* Locates the edge object for the two vertices
*
* @param start the start vertex
* @param end the end vertex
* @return the edge
*/
public E findEdge(V start, V end);
/**
* Retrieve all the vertices
* @return the vertices
*/
public Collection<V> getVertices();
/**
* Retrieve all the edges
* @return the edges
*/
public Collection<E> getEdges();
/**
* Test if the graph contains a given vertex
* @param v the vertex
* @return true if the vertex is in the graph, or false
*/
public boolean containsVertex(V v);
/**
* Test if the graph contains a given edge
* @param e the ege
* @return true if the edge is in the graph, or false
*/
public boolean containsEdge(E e);
/**
* Test if the graph contains an edge from one given vertex to another
* @param from the source vertex
* @param to the destination vertex
* @return true if such an edge exists, or false
*/
public boolean containsEdge(V from, V to);
/**
* Test if the graph is empty, i.e., contains no vertices or edges
* @return true if the graph is empty, or false
*/
public boolean isEmpty();
/**
* Count the number of vertices in the graph
* @return the count
*/
public int getVertexCount();
/**
* Count the number of edges in the graph
* @return the count
*/
public int getEdgeCount();
/**
* Compute the incident edges that end at the given vertex
*
* @param v the destination vertex
* @return the in-edges to the given vertex
*/
public Collection<E> getInEdges(V v);
/**
* Compute the incident edges that start at the given vertex
*
* @param v the source vertex
* @return the out-edges from the given vertex
*/
public Collection<E> getOutEdges(V v);
/**
* Returns all edges connected to the given vertex
*
* @param v the vertex
* @return the edges
*/
public default Collection<E> getIncidentEdges(V v) {
Set<E> result = new LinkedHashSet<>();
result.addAll(getInEdges(v));
result.addAll(getOutEdges(v));
return result;
}
/**
* Compute a vertex's predecessors
*
* <P>The default implementation computes this from the in-edges
*
* @param v the destination vertex
* @return the predecessors
*/
public default Collection<V> getPredecessors(V v) {
Set<V> result = new LinkedHashSet<>();
for (E edge : getInEdges(v)) {
result.add(edge.getStart());
}
return result;
}
/**
* Compute a vertex's successors
*
* <P>The default implementation compute this from the out-edges
*
* @param v the source vertex
* @return the successors
*/
public default Collection<V> getSuccessors(V v) {
Set<V> result = new LinkedHashSet<>();
for (E edge : getOutEdges(v)) {
result.add(edge.getEnd());
}
return result;
}
/**
* Copy this graph.
*
* <P>Note: the vertices and edges in the copy may be the same instances in the new graph
* and not themselves copies.
*
* @return the new copy
*/
public GDirectedGraph<V, E> copy();
/**
* Creates a new instance of this graph with no vertices or edges. This is useful when
* you wish to build a new graph using the same type as this graph.
*
* @return the new copy
*/
public GDirectedGraph<V, E> emptyCopy();
}

View file

@ -0,0 +1,42 @@
/* ###
* 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;
/**
* An edge in a (usually directed) graph
*
* @param <V> the type of vertices
*/
public interface GEdge<V> {
/**
* Get the start, or tail, of the edge
*
* <P>In the edge x -> y, x is the start
*
* @return the start
*/
public V getStart();
/**
* Get the end, or head, of the edge
*
* <P>In the edge x -> y, y is the end
*
* @return the end
*/
public V getEnd();
}

View file

@ -0,0 +1,61 @@
/* ###
* 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.Comparator;
/**
* A callback to get the weight of an edge
*
* Analogous to Java's {@link Comparator}, this provides a means to override the weight of an edge
* in a graph, or provide a weight in the absence of a natural weight, when executing various graph
* algorithms, e.g., shortest path.
* @param <E> the type of the edge
*/
public interface GEdgeWeightMetric<E extends GEdge<?>> {
public static final GEdgeWeightMetric<?> UNIT_METRIC = (GEdge<?> e) -> 1;
public static final GEdgeWeightMetric<?> NATURAL_METRIC =
(GEdge<?> e) -> ((GWeightedEdge<?>) e).getWeight();
/**
* Measure every edge as having a weight of 1
* @return the metric
*/
@SuppressWarnings("unchecked")
public static <V, E extends GEdge<V>> GEdgeWeightMetric<E> unitMetric() {
return (GEdgeWeightMetric<E>) UNIT_METRIC;
}
/**
* Use the natural weight of each edge
*
* The metric assumes every edge is a {@link GWeightedEdge}. If not, you will likely encounter
* a {@link ClassCastException}.
* @return the metric
*/
@SuppressWarnings("unchecked")
public static <V, E extends GEdge<V>> GEdgeWeightMetric<E> naturalMetric() {
return (GEdgeWeightMetric<E>) NATURAL_METRIC;
}
/**
* Compute or retrieve the weight of the given edge
* @param e the edge
* @return the weight
*/
public double computeWeight(E e);
}

View file

@ -0,0 +1,102 @@
/* ###
* 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.*;
/**
* A directed graph that need not be constructed explicitly
*
* <P>Instead, the graph is constructed (and usually cached) as it is explored. For instance, if
* a path searching algorithm is being applied, incident edges and neighboring nodes need not
* be computed if they're never visited. This allows conceptually large (even infinite) graphs to
* be represented. A graph algorithm can be applied so long as it supports this interface, and
* does not attempt to exhaust an infinite graph.
*
* @param <V> the type of vertices
* @param <E> the type of edges
*/
public interface GImplicitDirectedGraph<V, E extends GEdge<V>> {
/**
* Compute the incident edges that end at the given vertex
*
* (Optional operation)
*
* @note This method ought to return cached results if available
* @note As part of computing in-edges, this method will also provide predecessors
*
* @param v the destination vertex
* @return the in-edges to the given vertex
*/
public Collection<E> getInEdges(V v);
/**
* Compute the incident edges that start at the given vertex
*
* @note This method ought to return cached results if available
* @note As part of computing out-edges, this method will also provide successors
*
* @param v the source vertex
* @return the out-edges from the given vertex
*/
public Collection<E> getOutEdges(V v);
/**
* Compute a vertex's predecessors
*
* The default implementation computes this from the in-edges
*
* @note If a non-default implementation is provided, it ought to return cached results if
* available
*
* @param v the destination vertex
* @return the predecessors
*/
public default Collection<V> getPredecessors(V v) {
Set<V> result = new LinkedHashSet<>();
for (E edge : getInEdges(v)) {
result.add(edge.getStart());
}
return result;
}
/**
* Compute a vertex's successors
*
* The default implementation compute this from the out-edges
*
* @note If a non-default implementation is provided, it ought to return cached results if
* available
*
* @param v the source vertex
* @return the successors
*/
public default Collection<V> getSuccessors(V v) {
Set<V> result = new LinkedHashSet<>();
for (E edge : getOutEdges(v)) {
result.add(edge.getEnd());
}
return result;
}
/**
* Copy some portion of the implicit graph to an explicit graph
*
* Usually, this returns the cached (explored) portion of the graph
* @return a "copy" of this implicit graph
*/
public GDirectedGraph<V, E> copy();
}

View file

@ -0,0 +1,23 @@
/* ###
* 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;
public interface GVertex {
// TODO this is currently here only to be symmetrical with GEdge...maybe methods will
// soon arrive
}

View file

@ -0,0 +1,29 @@
/* ###
* 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;
/**
* An edge having a natural weight
*
* param <V> the type of vertices
*/
public interface GWeightedEdge<V> extends GEdge<V> {
/**
* The natural weight of the edge
* @return the weight
*/
public double getWeight();
}

View file

@ -0,0 +1,628 @@
/* ###
* 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.io.PrintStream;
import java.util.*;
import java.util.stream.Collectors;
import ghidra.graph.algo.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.TimeoutException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TimeoutTaskMonitor;
import util.CollectionUtils;
/**
* A set of convenience methods for performing graph algorithms on a graph.
*
* <P>Some definitions:
* <OL>
* <LI>
* <B>dominance:</B>
* a node 'a' dominates node 'b' if all paths from start to 'b' contain 'a';
* a node always dominates itself (except in 'strict dominance', which is all
* dominators except for itself)
*
* <LI>
* <B>post-dominance:</B>
* A node 'b' is said to post-dominate node 'a' if all paths from 'a'
* to END contain 'b'
*
* <LI>
* <B>immediate dominator:</B>
* the closest dominator of a node
*
* <LI>
* <B>dominance tree:</B>
* A dominator tree is a tree where each node's children are those nodes
* it *immediately* dominates (a idom b)
*
* <LI>
* <B>dominance frontier:</B>
* the immediate successors of the nodes dominated by 'a'; it is the set of
* nodes where d's dominance stops.
*
* <LI>
* <B>strongly connected components:</B>
* a graph is said to be strongly connected if every vertex is reachable
* from every other vertex. The strongly connected components
* of an arbitrary directed graph form a partition into
* subgraphs that are themselves strongly connected.
* <LI>
* <B>graph density:</B>
* <PRE>
* E
* Density = --------
* V(V-1)
* </PRE>
* </OL>
*/
public class GraphAlgorithms {
private GraphAlgorithms() {
// utils; can't create
}
/**
* Returns all source vertices (those with no incoming edges) in the graph.
*
* @param g the graph
* @return source vertices
*/
public static <V, E extends GEdge<V>> Set<V> getSources(GDirectedGraph<V, E> g) {
Set<V> sources = new HashSet<>();
Collection<V> vertices = g.getVertices();
for (V v : vertices) {
Collection<E> inEdges = g.getInEdges(v);
if (inEdges.isEmpty()) {
sources.add(v);
}
}
return sources;
}
/**
* Returns all sink vertices (those with no outgoing edges) in the graph.
*
* @param g the graph
* @return sink vertices
*/
public static <V, E extends GEdge<V>> Set<V> getSinks(GDirectedGraph<V, E> g) {
Set<V> sinks = new HashSet<>();
Collection<V> vertices = g.getVertices();
for (V v : vertices) {
Collection<E> outEdges = g.getOutEdges(v);
if (outEdges.isEmpty()) {
sinks.add(v);
}
}
return sinks;
}
/**
* Returns all descendants for the given vertices in the given graph. Descendants for a given
* vertex are all nodes at the outgoing side of an edge, as well as their outgoing
* vertices, etc.
*
* @param g the graph
* @param vertices the vertices for which to find descendants
* @return the descendants
*/
public static <V, E extends GEdge<V>> Set<V> getDescendants(GDirectedGraph<V, E> g,
Collection<V> vertices) {
Set<E> edges = getEdgesFrom(g, vertices, true);
Set<V> descendants = toVertices(edges);
return descendants;
}
/**
* Returns all ancestors for the given vertices in the given graph. Ancestors for a given
* vertex are all nodes at the incoming side of an edge, as well as their incoming
* vertices, etc.
*
* @param g the graph
* @param vertices the vertices for which to find descendants
* @return the ancestors
*/
public static <V, E extends GEdge<V>> Set<V> getAncestors(GDirectedGraph<V, E> g,
Collection<V> vertices) {
Set<E> edges = getEdgesFrom(g, vertices, false);
Set<V> ancestors = toVertices(edges);
return ancestors;
}
/**
* Returns a set of all edges that are reachable from the given vertex.
*
* @param g the graph
* @param v the vertex for which to get edges
* @param topDown true for outgoing edges; false for incoming edges
* @return the set of edges
*/
public static <V, E extends GEdge<V>> Set<E> getEdgesFrom(GDirectedGraph<V, E> g, V v,
boolean topDown) {
List<V> list = Arrays.asList(v);
Set<E> edges = getEdgesFrom(g, list, topDown);
return edges;
}
/**
* Returns a set of all edges that are reachable from the given collection of vertices.
*
* @param g the graph
* @param vertices the vertices for which to get edges
* @param topDown true for outgoing edges; false for incoming edges
* @return the set of edges
*/
public static <V, E extends GEdge<V>> Set<E> getEdgesFrom(GDirectedGraph<V, E> g,
Collection<V> vertices, boolean topDown) {
GraphNavigator<V, E> navigator = null;
if (topDown) {
navigator = GraphNavigator.topDownNavigator();
}
else {
navigator = GraphNavigator.bottomUpNavigator();
}
Set<E> edges = new HashSet<>();
Set<V> newlyPending = new HashSet<>();
Set<V> pending = new HashSet<>(vertices);
while (!pending.isEmpty()) {
for (V parent : pending) {
Collection<E> outEdges = navigator.getEdges(g, parent);
for (E e : outEdges) {
V destination = navigator.getEnd(e);
if (edges.add(e)) {
newlyPending.add(destination);
}
}
}
pending = newlyPending;
newlyPending = new HashSet<>();
}
return edges;
}
/**
* Creates a subgraph of the given graph for each edge of the given graph that is
* contained in the list of vertices.
*
* @param g the existing graph
* @param vertices the vertices to be in the new graph
* @return the new subgraph
*/
public static <V, E extends GEdge<V>> GDirectedGraph<V, E> createSubGraph(
GDirectedGraph<V, E> g, Collection<V> vertices) {
vertices = CollectionUtils.asSet(vertices); // ensure fast lookup
GDirectedGraph<V, E> subGraph = g.emptyCopy();
for (E e : g.getEdges()) {
V start = e.getStart();
V end = e.getEnd();
if (vertices.contains(start) && vertices.contains(end)) {
subGraph.addEdge(e);
}
}
return subGraph;
}
/**
* Returns a list where each set therein is a strongly connected component of the given
* graph. Each strongly connected component is that in which each vertex is reachable from
* any other vertex in that set.
*
* <P>This method can be used to determine reachability of a set of vertices.
*
* <P>This can also be useful for cycle detection, as a multi-vertex strong component
* is by definition a cycle. This method differs from
* {@link #findCircuits(GDirectedGraph, boolean, TaskMonitor)} in that the latter will
* return cycles within the strong components, or sub-cycles.
*
* @param g the graph
* @return the list of strongly connected components
*/
public static <V, E extends GEdge<V>> Set<Set<V>> getStronglyConnectedComponents(
GDirectedGraph<V, E> g) {
TarjanStronglyConnectedAlgorthm<V, E> algorithm = new TarjanStronglyConnectedAlgorthm<>(g);
return algorithm.getConnectedComponents();
}
/**
* Returns all entry points in the given graph. This includes sources, vertices which
* have no incoming edges, as well as strongly connected sub-graphs. The latter being a
* group vertices where each vertex is reachable from every other vertex. In the case of
* strongly connected components, we pick one of them arbitrarily to be the entry point.
*
* @param g the graph
* @return the entry points into the graph
*/
public static <V, E extends GEdge<V>> Set<V> getEntryPoints(GDirectedGraph<V, E> g) {
Set<V> sources = getSources(g);
Set<V> descendants = getDescendants(g, sources);
Set<V> isolatedVertices = new HashSet<>(g.getVertices());
isolatedVertices.removeAll(sources);
isolatedVertices.removeAll(descendants);
Set<V> entryPoints = new HashSet<>(sources);
if (isolatedVertices.isEmpty()) {
// no unconnected vertices
return entryPoints;
}
GDirectedGraph<V, E> isolatedGraph = createSubGraph(g, isolatedVertices);
Set<Set<V>> strongs = getStronglyConnectedComponents(isolatedGraph);
for (Set<V> set : strongs) {
if (isSelfContainedStrongComponent(g, set)) {
// just pick one to be the entry point
entryPoints.add(set.iterator().next());
}
}
return entryPoints;
}
/**
* Returns the dominance tree of the given graph. A dominator tree of the vertices where each
* node's children are those nodes it *immediately* dominates (a idom b)
*
* @param g the graph
* @param monitor the task monitor
* @return the tree
* @throws CancelledException if the monitor is cancelled
*/
public static <V, E extends GEdge<V>> GDirectedGraph<V, GEdge<V>> findDominanceTree(
GDirectedGraph<V, E> g, TaskMonitor monitor) throws CancelledException {
ChkDominanceAlgorithm<V, E> algorithm = new ChkDominanceAlgorithm<>(g, monitor);
return algorithm.getDominanceTree();
}
/**
* Returns a set of all vertices that are dominated by the given vertex. A node 'a'
* dominates node 'b' if all paths from start to 'b' contain 'a';
* a node always dominates itself (except in 'strict dominance', which is all
* dominators except for itself)
*
* @param g the graph
* @param from the vertex for which to find dominated vertices
* @param monitor the monitor
* @return the set of dominated vertices
* @throws CancelledException if the monitor is cancelled
*/
public static <V, E extends GEdge<V>> Set<V> findDominance(GDirectedGraph<V, E> g, V from,
TaskMonitor monitor) throws CancelledException {
ChkDominanceAlgorithm<V, E> algo = new ChkDominanceAlgorithm<>(g, monitor);
Set<V> dominated = algo.getDominated(from);
return dominated;
}
/**
* Returns a set of all vertices that are post-dominated by the given vertex. A node 'b'
* is said to post-dominate node 'a' if all paths from 'a' to END contain 'b'.
*
* @param g the graph
* @param from the vertex for which to get post-dominated vertices
* @param monitor the monitor
* @return the post-dominated vertices
* @throws CancelledException if the monitor is cancelled
*/
public static <V, E extends GEdge<V>> Set<V> findPostDominance(GDirectedGraph<V, E> g, V from,
TaskMonitor monitor) throws CancelledException {
ChkPostDominanceAlgorithm<V, E> algo = new ChkPostDominanceAlgorithm<>(g, monitor);
Set<V> postDominated = algo.getDominated(from);
return postDominated;
}
/**
* Finds all the circuits, or cycles, in the given graph.
*
* @param g the graph
* @param monitor the task monitor
* @return the circuits
* @throws CancelledException if the monitor is cancelled
*/
public static <V, E extends GEdge<V>> List<List<V>> findCircuits(GDirectedGraph<V, E> g,
TaskMonitor monitor) throws CancelledException {
return findCircuits(g, true, monitor);
}
/**
* Finds all the circuits, or cycles, in the given graph.
*
* @param g the graph
* @param uniqueCircuits true signals to return only unique circuits, where no two
* circuits will contain the same vertex
* @param monitor the task monitor
* @return the circuits
* @throws CancelledException if the monitor is cancelled
*/
public static <V, E extends GEdge<V>> List<List<V>> findCircuits(GDirectedGraph<V, E> g,
boolean uniqueCircuits, TaskMonitor monitor) throws CancelledException {
ListAccumulator<List<V>> accumulator = new ListAccumulator<>();
JohnsonCircuitsAlgorithm<V, E> algorithm = new JohnsonCircuitsAlgorithm<>(g, accumulator);
algorithm.compute(uniqueCircuits, monitor);
return accumulator.asList();
}
/**
* Finds all the circuits, or cycles, in the given graph. <B>This version
* of <tt>findCircuits()</tt> takes a {@link TimeoutTaskMonitor}, which allows for the
* client to control the duration of work.</B> This is useful for finding paths on very
* large, dense graphs.
*
* @param g the graph
* @param uniqueCircuits true signals to return only unique circuits, where no two
* circuits will contain the same vertex
* @param monitor the timeout task monitor
* @return the circuits
* @throws CancelledException if the monitor is cancelled
*/
public static <V, E extends GEdge<V>> List<List<V>> findCircuits(GDirectedGraph<V, E> g,
boolean uniqueCircuits, TimeoutTaskMonitor monitor)
throws CancelledException, TimeoutException {
ListAccumulator<List<V>> accumulator = new ListAccumulator<>();
JohnsonCircuitsAlgorithm<V, E> algorithm = new JohnsonCircuitsAlgorithm<>(g, accumulator);
algorithm.compute(uniqueCircuits, monitor);
return accumulator.asList();
}
/**
* Finds all paths from <tt>start</tt> to <tt>end</tt> in the given graph.
*
* <P><B><U>Warning:</U></B> for large, dense graphs (those with many interconnected
* vertices) this algorithm could run indeterminately, possibly causing the JVM to
* run out of memory.
*
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how
* deep it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will
* not be found.
*
* <P>You are encouraged to call this method with a monitor that will limit the work to
* be done, such as the {@link TimeoutTaskMonitor}.
*
* @param g the graph
* @param start the start vertex
* @param end the end vertex
* @param accumulator the accumulator into which results will be placed
* @param monitor the task monitor
* @throws CancelledException if the operation is cancelled
*/
public static <V, E extends GEdge<V>> void findPaths(GDirectedGraph<V, E> g, V start, V end,
Accumulator<List<V>> accumulator, TaskMonitor monitor) throws CancelledException {
// the algorithm gets run at construction; the results are sent to the accumulator
new FindPathsAlgorithm<>(g, start, end, accumulator, monitor);
}
/**
* Finds all paths from <tt>start</tt> to <tt>end</tt> in the given graph. <B>This version
* of <tt>findPaths()</tt> takes a {@link TimeoutTaskMonitor}, which allows for the
* client to control the duration of work.</B> This is useful for finding paths on very
* large, dense graphs.
*
* <P><B><U>Warning:</U></B> for large, dense graphs (those with many interconnected
* vertices) this algorithm could run indeterminately, possibly causing the JVM to
* run out of memory.
*
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how
* deep it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will
* not be found.
*
* @param g the graph
* @param start the start vertex
* @param end the end vertex
* @param accumulator the accumulator into which results will be placed
* @param monitor the timeout task monitor
* @throws CancelledException if the operation is cancelled
* @throws TimeoutException if the operation passes the timeout period
*/
public static <V, E extends GEdge<V>> void findPaths(GDirectedGraph<V, E> g, V start, V end,
Accumulator<List<V>> accumulator, TimeoutTaskMonitor monitor)
throws CancelledException, TimeoutException {
// the algorithm gets run at construction; the results are sent to the accumulator
new FindPathsAlgorithm<>(g, start, end, accumulator, monitor);
}
/**
* Returns the vertices of the graph in post-order. Pre-order is the order the vertices
* are last visited when performing a depth-first traversal.
*
* @param g the graph
* @param navigator the knower of the direction the graph should be traversed
* @return the vertices
*/
public static <V, E extends GEdge<V>> List<V> getVerticesInPostOrder(GDirectedGraph<V, E> g,
GraphNavigator<V, E> navigator) {
List<V> postOrder = DepthFirstSorter.postOrder(g, navigator);
return postOrder;
}
/**
* Returns the vertices of the graph in pre-order. Pre-order is the order the vertices
* are encountered when performing a depth-first traversal.
*
* @param g the graph
* @param navigator the knower of the direction the graph should be traversed
* @return the vertices
*/
public static <V, E extends GEdge<V>> List<V> getVerticesInPreOrder(GDirectedGraph<V, E> g,
GraphNavigator<V, E> navigator) {
List<V> preOrder = DepthFirstSorter.preOrder(g, navigator);
return preOrder;
}
/**
* Calculates 'complexity depth', which is, for each vertex, the deepest/longest path
* from that vertex for a depth-first traversal. So, for a vertex with a single
* successor that has no children, the depth would be 1.
*
* @param g the graph
* @return the map of each vertex to its complexity depth
*/
public static <V, E extends GEdge<V>> Map<V, Integer> getComplexityDepth(
GDirectedGraph<V, E> g) {
Map<V, Integer> map = new HashMap<>();
List<V> verticesInPostOrder = getVerticesInPostOrder(g, GraphNavigator.topDownNavigator());
for (V v : verticesInPostOrder) {
int maxChildLevel = getMaxChildLevel(g, map, v);
map.put(v, maxChildLevel + 1);
}
return map;
}
/**
* Retain all edges in the graph where each edge's endpoints are in the given set of
* vertices.
*
* @param graph the graph
* @param vertices the vertices of the edges to keep
* @return the set of edges
*/
public static <V, E extends GEdge<V>> Set<E> retainEdges(GDirectedGraph<V, E> graph,
Set<V> vertices) {
//@formatter:off
Collection<E> edges = graph.getEdges();
Set<E> results =
edges.stream()
.filter(e -> vertices.contains(e.getStart()))
.filter(e -> vertices.contains(e.getEnd()))
.collect(Collectors.toSet())
;
//@formatter:on
return results;
}
/**
* Returns the set of vertices contained within the given edges.
*
* @param edges the edges
* @return the vertices
*/
public static <V, E extends GEdge<V>> Set<V> toVertices(Collection<E> edges) {
Set<V> result = new HashSet<>();
for (E e : edges) {
result.add(e.getStart());
result.add(e.getEnd());
}
return result;
}
/**
* A method to debug the given graph by printing it.
*
* @param g the graph to print
*/
public static <V, E extends GEdge<V>> void printGraph(GDirectedGraph<V, E> g, PrintStream ps) {
Set<V> sources = getSources(g);
Set<V> printedSet = new HashSet<>();
ps.println("=================================");
for (V v : sources) {
recursivePrint(g, v, printedSet, 0, ps);
ps.println("---------------------------------");
}
ps.println("=================================");
}
//==================================================================================================
// Private Methods
//==================================================================================================
/**
* Returns true if the given strong component has no incoming edges that are outside of
* the component. This is useful to know, as it signals that the given strong component
* is reachable from outside of that component.
*
* @param g the graph
* @param strongComponent the set of vertices representing a strong component
* @return true if the given strong component has no incoming edges that are outside of
* the component
*/
private static <V, E extends GEdge<V>> boolean isSelfContainedStrongComponent(
GDirectedGraph<V, E> g, Set<V> strongComponent) {
Set<V> parents = new HashSet<>();
for (V v : strongComponent) {
parents.addAll(g.getPredecessors(v));
}
// check to see if the given set of vertices has an incoming edge that is outside of
// the strong component
return strongComponent.containsAll(parents);
}
private static <V, E extends GEdge<V>> int getMaxChildLevel(GDirectedGraph<V, E> g,
Map<V, Integer> levelMap, V v) {
int maxLevel = -1;
Collection<V> successors = g.getSuccessors(v);
for (V child : successors) {
Integer level = levelMap.get(child);
if (level != null && level.intValue() > maxLevel) {
maxLevel = level;
}
}
return maxLevel;
}
private static <V, E extends GEdge<V>> void recursivePrint(GDirectedGraph<V, E> g, V v,
Set<V> set, int depth, PrintStream ps) {
for (int i = 1; i <= depth; i++) {
ps.print(".");
}
if (set.contains(v)) {
ps.println(v + "^ (" + depth + ")");
return;
}
ps.print(v);
if (depth > 0) {
ps.print(" (" + depth + ")");
}
ps.print('\n');
set.add(v);
Collection<V> successors = g.getSuccessors(v);
for (V v2 : successors) {
recursivePrint(g, v2, set, depth + 1, ps);
}
}
}

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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 ghidra.graph.jung.JungDirectedGraph;
public class GraphFactory {
private GraphFactory() {
// can't create this;
}
public static <V, E extends GEdge<V>> GDirectedGraph<V, E> createDirectedGraph() {
return new JungDirectedGraph<V, E>();
}
}

View file

@ -0,0 +1,151 @@
/* ###
* 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.*;
/**
* Class for storing paths with fast "contains" method.
*
* Note: a path can only contain a vertex once.
*
* @param <V>
*/
public class GraphPath<V> {
/** a set for performing quick contains checks */
private Set<V> pathSet = new HashSet<>();
private List<V> pathList = new ArrayList<>();
public GraphPath() {
// default constructor
}
public GraphPath(V v) {
add(v);
}
public GraphPath<V> copy() {
GraphPath<V> newPath = new GraphPath<>();
newPath.pathList.addAll(pathList);
newPath.pathSet.addAll(pathSet);
return newPath;
}
public boolean startsWith(GraphPath<V> otherPath) {
if (size() < otherPath.size()) {
return false;
}
for (int i = 0; i < otherPath.size(); i++) {
if (!pathList.get(i).equals(otherPath.pathList.get(i))) {
return false;
}
}
return true;
}
public GraphPath<V> getCommonStartPath(GraphPath<V> other) {
int n = Math.min(size(), other.size());
for (int i = 0; i < n; i++) {
if (!get(i).equals(other.get(i))) {
return subPath(0, i);
}
}
return subPath(0, n);
}
public int size() {
return pathList.size();
}
public boolean contains(V v) {
return pathSet.contains(v);
}
public void add(V v) {
pathSet.add(v);
pathList.add(v);
}
public V getLast() {
return pathList.get(pathList.size() - 1);
}
public int depth(V v) {
return pathList.indexOf(v);
}
public V get(int depth) {
return pathList.get(depth);
}
public V removeLast() {
V v = pathList.remove(pathList.size() - 1);
pathSet.remove(v);
return v;
}
/**
* Returns all entries that are before the given vertex in this path. The results will
* include the vertex.
*
* @param v the vertex
* @return the predecessors
*/
public Set<V> getPredecessors(V v) {
Set<V> set = new HashSet<>();
int index = pathList.indexOf(v);
if (index < 0) {
return set;
}
set.addAll(pathList.subList(0, index + 1)); // include the vertex
return set;
}
/**
* Returns all entries that are later in this path than the given vertex. The results will
* include the vertex.
*
* @param v the vertex
* @return the successors
*/
public Set<V> getSuccessors(V v) {
Set<V> set = new HashSet<>();
int index = pathList.indexOf(v);
if (index < 0) {
return set;
}
set.addAll(pathList.subList(index, pathList.size())); // include the vertex
return set;
}
@Override
public String toString() {
return pathList.toString();
}
public GraphPath<V> subPath(int start, int end) {
GraphPath<V> subPath = new GraphPath<>();
subPath.pathList = new ArrayList<>(pathList.subList(start, end));
subPath.pathSet = new HashSet<>(subPath.pathList);
return subPath;
}
}

View file

@ -0,0 +1,62 @@
/* ###
* 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.HashSet;
import java.util.Set;
//TODO Do we need this class
public class GraphPathSet<V> {
private Set<GraphPath<V>> paths = new HashSet<>();
public boolean containSomePathStartingWith(GraphPath<V> otherPath) {
for (GraphPath<V> path : paths) {
if (path.startsWith(otherPath)) {
return true;
}
}
return false;
}
public void add(GraphPath<V> path) {
paths.add(path);
}
public Set<GraphPath<V>> getPathsContaining(V v) {
Set<GraphPath<V>> set = new HashSet<>();
for (GraphPath<V> path : paths) {
if (path.contains(v)) {
set.add(path);
}
}
return set;
}
public int size() {
return paths.size();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
for (GraphPath<V> path : paths) {
buf.append(path.toString()).append('\n');
}
return buf.toString();
}
}

View file

@ -0,0 +1,252 @@
/* ###
* 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 java.util.function.BiFunction;
import java.util.function.Function;
import ghidra.graph.jung.JungDirectedGraph;
import util.CollectionUtils;
/**
* A class that can wrap a {@link GDirectedGraph} and allows for vertex and edge additions
* without changing the underlying graph.
*
* <P><B>Warning: </B>As mentioned above, this graph is meant for additive operations. In its
* current form, removal operations will not work. To facilitate removals, this class will
* have to be updated to track removed vertices and edges, using them to correctly report
* the state of the graph for methods like {@link #containsVertex(Object)} and
* {@link #containsEdge(GEdge)}.
*
* <P>Implementation Note: there is some 'magic' in this class to add 'dummy' vertices to the
* graph. To facilitate this, the mutated graph in this class does not have the <tt>V</tt>
* type, but rather is typed on Object. This means that this class can only be used
* generically, with templated types (like by algorithms and such). Any usage of this class
* that expects concrete implementations to be returned can trigger ClassCastExceptions.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class MutableGDirectedGraphWrapper<V, E extends GEdge<V>> implements GDirectedGraph<V, E> {
private GDirectedGraph<V, E> delegate;
private GDirectedGraph<Object, DefaultGEdge<Object>> mutatedGraph;
public MutableGDirectedGraphWrapper(GDirectedGraph<V, E> delegate) {
this.delegate = delegate;
this.mutatedGraph = new JungDirectedGraph<>();
}
public V addDummyVertex(String name) {
@SuppressWarnings("unchecked")
V v = (V) new DummyVertex(name);
mutatedGraph.addVertex(v);
return v;
}
public boolean isDummy(V v) {
return v instanceof DummyVertex;
}
public boolean isDummy(E e) {
return e instanceof DummyEdge;
}
@SuppressWarnings("unchecked")
public E addDummyEdge(V start, V end) {
DummyEdge e = new DummyEdge(start, end);
mutatedGraph.addEdge(e);
return (E) e;
}
@Override
public boolean addVertex(V v) {
return mutatedGraph.addVertex(v);
}
@Override
public boolean removeVertex(V v) {
if (delegate.containsVertex(v)) {
// not sure if this is the right behavior
throw new UnsupportedOperationException();
}
return mutatedGraph.removeVertex(v);
}
@Override
public void removeVertices(Iterable<V> vertices) {
vertices.forEach(v -> removeVertex(v));
}
@Override
public void removeEdges(Iterable<E> edges) {
edges.forEach(e -> removeEdge(e));
}
@SuppressWarnings("unchecked")
@Override
public void addEdge(E e) {
mutatedGraph.addEdge((DefaultGEdge<Object>) e);
}
@SuppressWarnings("unchecked")
@Override
public boolean removeEdge(E e) {
if (delegate.containsEdge(e)) {
// not sure if this is the right behavior
throw new UnsupportedOperationException();
}
return mutatedGraph.removeEdge((DefaultGEdge<Object>) e);
}
@Override
public Collection<V> getVertices() {
Set<V> set = callOnBothGraphs(GDirectedGraph::getVertices);
return set;
}
@Override
public Collection<E> getEdges() {
Set<E> set = callOnBothGraphs(GDirectedGraph::getEdges);
return set;
}
@Override
public boolean containsVertex(V v) {
return delegate.containsVertex(v) || mutatedGraph.containsVertex(v);
}
@SuppressWarnings("unchecked")
@Override
public boolean containsEdge(E e) {
return delegate.containsEdge(e) || mutatedGraph.containsEdge((DefaultGEdge<Object>) e);
}
@Override
public boolean containsEdge(V from, V to) {
return delegate.containsEdge(from, to) || mutatedGraph.containsEdge(from, to);
}
@Override
public E findEdge(V start, V end) {
@SuppressWarnings("unchecked")
E e = (E) mutatedGraph.findEdge(start, end);
if (e != null) {
return e;
}
return delegate.findEdge(start, end);
}
@Override
public boolean isEmpty() {
return getVertexCount() == 0;
}
@Override
public int getVertexCount() {
return delegate.getVertexCount() + mutatedGraph.getVertexCount();
}
@Override
public int getEdgeCount() {
return delegate.getEdgeCount() + mutatedGraph.getEdgeCount();
}
@Override
public Collection<E> getInEdges(V v) {
Set<E> set = callOnBothGraphs(GDirectedGraph::getInEdges, v);
return set;
}
@Override
public Collection<E> getOutEdges(V v) {
Set<E> set = callOnBothGraphs(GDirectedGraph::getOutEdges, v);
return set;
}
@Override
public Collection<V> getPredecessors(V v) {
Set<V> set = callOnBothGraphs(GDirectedGraph::getPredecessors, v);
return set;
}
@Override
public Collection<V> getSuccessors(V v) {
Set<V> set = callOnBothGraphs(GDirectedGraph::getSuccessors, v);
return set;
}
@Override
public GDirectedGraph<V, E> copy() {
MutableGDirectedGraphWrapper<V, E> copy =
new MutableGDirectedGraphWrapper<>(delegate.copy());
for (Object v : mutatedGraph.getVertices()) {
copy.mutatedGraph.addVertex(v);
}
for (DefaultGEdge<Object> e : mutatedGraph.getEdges()) {
copy.mutatedGraph.addEdge(e);
}
return copy;
}
@Override
public GDirectedGraph<V, E> emptyCopy() {
return delegate.emptyCopy();
}
@SuppressWarnings("unchecked")
private <R> Set<R> callOnBothGraphs(Function<GDirectedGraph<V, E>, Collection<R>> f) {
Set<R> set = new HashSet<>();
set.addAll(CollectionUtils.nonNull(f.apply((GDirectedGraph<V, E>) mutatedGraph)));
set.addAll(CollectionUtils.nonNull(f.apply(delegate)));
return set;
}
@SuppressWarnings("unchecked")
private <R> Set<R> callOnBothGraphs(BiFunction<GDirectedGraph<V, E>, V, Collection<R>> f, V v) {
Set<R> set = new HashSet<>();
set.addAll(CollectionUtils.nonNull(f.apply((GDirectedGraph<V, E>) mutatedGraph, v)));
set.addAll(CollectionUtils.nonNull(f.apply(delegate, v)));
return set;
}
private static class DummyVertex {
private String name;
public DummyVertex(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dummy " + name;
}
}
public static class DummyEdge extends DefaultGEdge<Object> {
public DummyEdge(Object start, Object end) {
super(start, end);
}
}
}

View file

@ -0,0 +1,139 @@
/* ###
* 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.awt.Point;
import java.util.Set;
import ghidra.graph.event.VisualGraphChangeListener;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.event.picking.GPickedState;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.graph.viewer.layout.VisualGraphLayout;
/**
* The primary interface for graphs that are to be rendered. This class defines methods
* commonly used in the GUI while extending the primary non-visual graph interface.
*
* <P>The Visual Graph API will typically provide services for taking a Visual Graph and
* creating a UI that handles basic user interaction elements (similar to how complex Java
* widgets handle user interaction for the developer). The Visual Graph is the model of the
* UI components. A typical Visual Graph UI will render developer-defined components,
* handling mouse event translations for the developer.
*
* <P>Some features found in Visual Graphs:
* <UL>
* <LI>Mouse event translation - the JComponent being rendered in the graph will be handed
* mouse events that are relative to its coordinate space, not that of the graph.
* </LI>
* <LI>Hover and Selection - vertex hover and selection events are handled by the API
* </LI>
* <LI>Zooming - zoom level and related events (when zoomed too far, mouse events are
* not passed-through to the component) and handled by the API
* </LI>
* </UL>
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public interface VisualGraph<V extends VisualVertex, E extends VisualEdge<V>>
extends GDirectedGraph<V, E> {
/**
* A callback notifying this graph that the given vertex's location has changed
*
* @param v the vertex
* @param point the new location
* @param changeType the type of change
*/
public void vertexLocationChanged(V v, Point point, ChangeType changeType);
/**
* Returns the focused vertex; null if no vertex has focus. Focus is equivalent to
* being selected, but further distinguishes the vertex as being the only selected
* vertex. This is useful for key event processing.
*
* @return the focused vertex
*/
public V getFocusedVertex();
/**
* Sets the given vertex to be focused or not
*
* <P>Note: this method is called by other APIs to ensure that the graph's notion of the
* focused vertex matches what is happening externally (e.g., from the user clicking the
* screen). If you wish to programmatically focus a vertex, then you should not be calling
* this API directly, but you should instead be using the {@link GPickedState} or one
* of the APIs that uses that, such as the {@link GraphComponent}.
*
* @param v the focused vertex
* @param b true for focused; false for not focused
*/
public void setVertexFocused(V v, boolean b);
/**
* Clears any selected vertices as well as the focused vertex
*/
public void clearSelectedVertices();
/**
* Selects the given vertices
*
* <P>Note: this method is called by other APIs to ensure that the graph's notion of the
* focused vertex matches what is happening externally (e.g., from the user clicking the
* screen). If you wish to programmatically select a vertex, then you should not be calling
* this API directly, but you should instead be using the {@link GPickedState} or one
* of the APIs that uses that, such as the {@link GraphComponent}.
*
* @param vertices the vertices
*/
public void setSelectedVertices(Set<V> vertices);
/**
* Returns the selected vertices.
*
* @return the selected vertices
*/
public Set<V> getSelectedVertices();
/**
* Adds the given listener to this graph
*
* @param l the listener
*/
public void addGraphChangeListener(VisualGraphChangeListener<V, E> l);
/**
* Removes the given listener from this graph
*
* @param l the listener
*/
public void removeGraphChangeListener(VisualGraphChangeListener<V, E> l);
/**
* Returns the layout that has been applied to the graph. The graph does not need its
* layout to function, but rather it is convenient for the visual graph system to be able
* to get the layout from the graph, rather than passing the layout everywhere it is
* needed.
*
* @return the layout applied to the graph
*/
public VisualGraphLayout<V, E> getLayout();
@Override
// overridden to redefine the return type
public VisualGraph<V, E> copy();
}

View file

@ -0,0 +1,253 @@
/* ###
* 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.awt.Component;
import java.awt.event.MouseEvent;
import java.util.*;
import docking.*;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.picking.PickedState;
import ghidra.framework.options.SaveState;
import ghidra.graph.featurette.VgSatelliteFeaturette;
import ghidra.graph.featurette.VisualGraphFeaturette;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.actions.*;
import ghidra.graph.viewer.event.mouse.VertexMouseInfo;
/**
* A base component provider for displaying {@link VisualGraph}s
*
* <p>This class will provide many optional sub-features, enabled as desired by calling the
* various <code>addXyzFeature()</code> methods.
*
* <p>Implementation Notes: to get full functionality, you must:
* <ul>
* <li>Have your plugin call {@link #readConfigState(SaveState)} and
* {@link #writeConfigState(SaveState)} to save user settings.
* </li>
* <li>Enable features you desire after calling your {@link #addToTool()} method.
* </li>
* </ul>
*
* @param <V> the vertex type
* @param <E> the edge type
* @param <G> the graph type
*/
//@formatter:off
public abstract class VisualGraphComponentProvider<V extends VisualVertex,
E extends VisualEdge<V>,
G extends VisualGraph<V, E>>
extends ComponentProvider {
//@formatter:on
// private static final String DISPLAY_POPUPS = "DISPLAY_POPUPS";
private List<VisualGraphFeaturette<V, E, G>> subFeatures = new ArrayList<>();
protected VisualGraphComponentProvider(DockingTool tool, String name, String owner) {
super(tool, name, owner);
}
protected VisualGraphComponentProvider(DockingTool tool, String name, String owner,
Class<?> contextType) {
super(tool, name, owner, contextType);
}
/**
* You must return your graph view from this method
* @return your graph view
*/
public abstract VisualGraphView<V, E, G> getView();
@Override
public void componentHidden() {
subFeatures.forEach(f -> f.providerClosed(this));
}
@Override
public void componentShown() {
subFeatures.forEach(f -> f.providerOpened(this));
}
/**
* Returns true if the satellite is showing, whether in the graph or undocked
* @return true if the satellite is showing, whether in the graph or undocked
*/
public boolean isSatelliteShowing() {
GraphComponent<V, E, G> graphComponent = getView().getGraphComponent();
return graphComponent.isSatelliteShowing();
}
/**
* Returns true if the satellite is embedded in the graph view, whether it is showing or not
* @return true if the satellite is embedded in the graph view, whether it is showing or not
*/
public boolean isSatelliteDocked() {
return getView().isSatelliteDocked();
}
public Set<V> getSelectedVertices() {
VisualGraphView<V, E, G> view = getView();
VisualizationViewer<V, E> viewer = view.getPrimaryGraphViewer();
PickedState<V> pickedState = viewer.getPickedVertexState();
return pickedState.getPicked();
}
protected ComponentProvider getSatelliteProvider() {
VgSatelliteFeaturette<V, E, G> feature = getSatelliteFeature();
if (feature == null) {
return null;
}
return feature.getSatelliteProvider();
}
private VgSatelliteFeaturette<V, E, G> getSatelliteFeature() {
for (VisualGraphFeaturette<V, E, G> feature : subFeatures) {
if (feature instanceof VgSatelliteFeaturette) {
return (VgSatelliteFeaturette<V, E, G>) feature;
}
}
return null;
}
//==================================================================================================
// Featurette Methods
//==================================================================================================
/**
* Adds the satellite viewer functionality to this provider
*/
protected void addSatelliteFeature() {
VgSatelliteFeaturette<V, E, G> satelliteFeature = new VgSatelliteFeaturette<>();
satelliteFeature.init(this);
subFeatures.add(satelliteFeature);
}
/*
Features to provide
Actions
-change layout
-re-layout
-disable popups
Snapshots
Save State
-selected layout
-satellite dock/vis state
Save image
Export graph:
-xml?
-dot
-gephi?
Undo/redo for graph operations (delete; group/ungroup; move)
-rapid pressing will shortcut items
-undo/redo allows us to prune nodes
--how to maintain old nodes/edges? (FitleringVisualGraph)
*/
//==================================================================================================
// Provider Methods
//==================================================================================================
/**
* To be called at the end of this provider's lifecycle
*/
public void dispose() {
subFeatures.forEach(f -> f.remove());
subFeatures.clear();
}
/**
* Writes this providers saveable state to the given state object
*
* @param saveState the state object into which state is to be written
*/
public void writeConfigState(SaveState saveState) {
subFeatures.forEach(f -> f.writeConfigState(saveState));
}
/**
* Reads previously saved state from the given state object
*
* @param saveState the state object that may contain state information for this provider
*/
public void readConfigState(SaveState saveState) {
subFeatures.forEach(f -> f.readConfigState(saveState));
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (event == null) {
VisualGraphView<V, E, G> view = getView();
V v = view.getFocusedVertex();
if (v != null) {
return new VgVertexContext<>(this, v);
}
return new VgActionContext(this);
}
V v = getVertexUnderMouse(event);
if (v != null) {
return new VgVertexContext<>(this, v);
}
Component c = event.getComponent();
if (getView().isSatelliteComponent(c)) {
return new VgSatelliteContext(this);
}
return new VgActionContext(this, c);
}
private V getVertexUnderMouse(MouseEvent event) {
Object source = event.getSource();
GraphViewer<V, E> viewer = getPrimaryGraphViewer(source);
if (viewer == null) {
return null;
}
VertexMouseInfo<V, E> info =
GraphViewerUtils.convertMouseEventToVertexMouseEvent(viewer, event);
if (info == null) {
return null;
}
V vertex = info.getVertex();
return vertex;
}
private GraphViewer<V, E> getPrimaryGraphViewer(Object source) {
GraphViewer<V, E> primaryGraphViewer = getView().getPrimaryGraphViewer();
if (source == primaryGraphViewer) {
return primaryGraphViewer;
}
return null;
}
}

View file

@ -0,0 +1,117 @@
/* ###
* 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.algo;
import java.util.Set;
import ghidra.graph.GEdge;
import ghidra.graph.MutableGDirectedGraphWrapper;
import util.CollectionUtils;
/**
* A general base class for sharing code between graph algorithm implementations.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
abstract class AbstractDominanceAlgorithm<V, E extends GEdge<V>> {
private static final String DUMMY_ROOT_NAME = "Dummy Root Vertex";
private static final String DUMMY_SINK_NAME = "Dummy Sink Vertex";
/**
* Converts multiple source/root nodes in a graph into a single source of which those become
* children.
*
* @param graph the graph
* @param graphNavigator the navigator to determine graph direction
* @return the new single source
* @throws IllegalArgumentException if there is not at least one source node in the graph
*/
protected static <V, E extends GEdge<V>> V unifySources(
MutableGDirectedGraphWrapper<V, E> graph, GraphNavigator<V, E> graphNavigator) {
//
// Unusual Code Alert!
// This method engages in some chicanery. We may need to update the graph, in the case
// where there are multiple roots. In that case, we need to add vertices and edges to
// our graph. The issue is that whatever we create will not be of the same type that
// is in the graph, since we don't know how to create a V or E. So, just add an
// object of our choosing and feel confident that we are the only user of the newly
// 'forced-in' types.
//
// The warnings that arise from this code are suppressed below.
//
Set<V> sources = graphNavigator.getSources(graph);
if (sources.isEmpty()) {
throw new IllegalArgumentException(
"The graph does not contain at least one source node");
}
if (sources.size() == 1) {
return CollectionUtils.any(sources);
}
// Turn the sources into children of a single root.
V dummy = graph.addDummyVertex(DUMMY_ROOT_NAME);
sources.forEach(s -> {
if (graphNavigator.isTopDown()) {
graph.addDummyEdge(dummy, s);
}
else {
graph.addDummyEdge(s, dummy);
}
});
return dummy;
}
/**
* Converts multiple sink/exit nodes in a graph into a single sink of which those become
* parents.
*
* @param graph the graph
* @param graphNavigator the navigator to determine graph direction
* @return the new single sink
* @throws IllegalArgumentException if there is not at least one sink node in the graph
*/
protected static <V, E extends GEdge<V>> V unifySinks(MutableGDirectedGraphWrapper<V, E> graph,
GraphNavigator<V, E> graphNavigator) {
Set<V> sinks = graphNavigator.getSinks(graph);
if (sinks.isEmpty()) {
throw new IllegalArgumentException("The graph does not contain at least one sink node");
}
if (sinks.size() == 1) {
return sinks.iterator().next();
}
// Turn the sinks into children of a single sink.
V dummy = graph.addDummyVertex(DUMMY_SINK_NAME);
sinks.forEach(s -> {
if (graphNavigator.isTopDown()) {
graph.addDummyEdge(s, dummy);
}
else {
graph.addDummyEdge(dummy, s);
}
});
return dummy;
}
}

View file

@ -0,0 +1,248 @@
/* ###
* 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.algo;
import java.util.*;
import org.apache.commons.collections4.map.LazyMap;
import ghidra.graph.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* This algorithm is an implementation of the Cooper, Harvey, Kennedy algorithm.
*
* <P>The algorithm processes the graph in reverse post-order. The runtime of
* this algorithm is approximately <tt>O(V+E*D)</tt> per iteration of the loop, where
* D is the size of the largest dominator set. The number of iterations is
* bound at <tt>d(G) + 3</tt>, where d(G) is the "loop
* connectedness" of the graph.
*
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class ChkDominanceAlgorithm<V, E extends GEdge<V>> extends AbstractDominanceAlgorithm<V, E> {
private GDirectedGraph<V, E> sourceGraph;
private MutableGDirectedGraphWrapper<V, E> mutableGraph;
private V root;
// Note: we track dominators and dominated for lookups after the algorithm finishes them
// A mapping of dominated nodes to their idom
private Map<V, V> dominatorMap = new HashMap<>();
// A mapping of idoms to dominated nodes
private Map<V, List<V>> dominatedMap =
LazyMap.lazyMap(new HashMap<>(), () -> new ArrayList<>());
private GraphNavigator<V, E> navigator;
/**
* Constructor.
*
* @param g the graph
* @param monitor the monitor
* @throws CancelledException if the algorithm is cancelled
* @throws IllegalArgumentException if there are no source vertices in the graph
*/
public ChkDominanceAlgorithm(GDirectedGraph<V, E> g, TaskMonitor monitor)
throws CancelledException {
this(g, GraphNavigator.topDownNavigator(), monitor);
}
ChkDominanceAlgorithm(GDirectedGraph<V, E> g, GraphNavigator<V, E> navigator,
TaskMonitor monitor) throws CancelledException {
this.navigator = navigator;
this.sourceGraph = g;
this.mutableGraph = new MutableGDirectedGraphWrapper<>(g);
root = findRoot();
dominatorMap.put(root, root);
monitor.setMessage("Computing dominance");
computeDominance(monitor);
}
private V findRoot() {
V theRoot = unifySources(mutableGraph, navigator);
unifySinks(mutableGraph, navigator);
return theRoot;
}
private void computeDominance(TaskMonitor monitor) throws CancelledException {
List<V> list = navigator.getVerticesInPostOrder(mutableGraph);
Map<V, Integer> map = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
map.put(list.get(i), i);
}
boolean changed = true;
while (changed) {
monitor.checkCanceled();
changed = false;
// start 1 from the end so we always have a predecessor
for (int i = list.size() - 2; i >= 0; i--) { // process in reverse order
V b = list.get(i);
Collection<V> vertices = navigator.getPredecessors(mutableGraph, b);
Iterator<V> iterator = vertices.iterator();
V newIdom = null;
while (iterator.hasNext()) {
V p = iterator.next();
if (dominatorMap.containsKey(p)) {
newIdom = p;
break;
}
}
if (newIdom == null) {
throw new AssertException("No processed predecessors found for " + b);
}
iterator = vertices.iterator();
while (iterator.hasNext()) {
V p = iterator.next();
if (p == newIdom) {
continue;
}
if (dominatorMap.containsKey(p)) {
newIdom = intersect(p, newIdom, map);
}
}
V idom = dominatorMap.get(b);
if (idom != newIdom) {
V last = dominatorMap.put(b, newIdom);
dominatedMap.get(newIdom).add(b);
if (last != null) {
dominatedMap.get(last).remove(b);
}
changed = true;
}
}
}
}
private V intersect(V v1, V v2, Map<V, Integer> map) {
V finger1 = v1;
V finger2 = v2;
int finger1Index = map.get(finger1);
int finger2Index = map.get(finger2);
while (finger1 != finger2) {
while (finger1Index < finger2Index) {
finger1 = dominatorMap.get(finger1);
finger1Index = map.get(finger1);
}
while (finger2Index < finger1Index) {
if (dominatorMap.get(finger2) == null) {
// this can happen when the dominators for 'finger2' have not
// yet been calculated
return finger1;
}
finger2 = dominatorMap.get(finger2);
finger2Index = map.get(finger2);
}
}
return finger1;
}
/**
* Returns all nodes dominated by the given vertex. A node 'a' dominates node 'b' if
* all paths from start to 'b' contain 'a'.
*
* @param a the vertex
* @return the dominated vertices
*/
public Set<V> getDominated(V a) {
HashSet<V> results = new HashSet<>();
doGetDominated(a, results);
return results;
}
private void doGetDominated(V a, HashSet<V> results) {
List<V> dominated = dominatedMap.get(a);
results.add(a); // a node always dominates itself
dominated.forEach(b -> doGetDominated(b, results));
}
/**
* Returns all nodes that dominate the given vertex. A node 'a' dominates node 'b' if
* all paths from start to 'b' contain 'a'.
*
* @param a the vertex
* @return the dominating vertices
*/
public Set<V> getDominators(V a) {
Set<V> dominators = new HashSet<>();
dominators.add(a);
while (a != root) {
a = getImmediateDominator(a);
dominators.add(a);
}
return dominators;
}
private V getImmediateDominator(V v) {
V dom = dominatorMap.get(v);
if (mutableGraph.isDummy(dom)) {
return null;
}
return dom;
}
/**
* Returns the dominance tree for the given graph, which is tree where each
* node's children are those nodes it *immediately* dominates (a idom b).
*
* @return the dominance tree
*/
public GDirectedGraph<V, GEdge<V>> getDominanceTree() {
GDirectedGraph<V, GEdge<V>> dg = GraphFactory.createDirectedGraph();
// note: we use the source graph here and not the one we mutated for calculating dominance
Collection<V> vertices = sourceGraph.getVertices();
Set<V> sources = navigator.getSources(sourceGraph);
for (V v : vertices) {
if (sources.contains(v)) {
continue;
}
V dominator = getImmediateDominator(v);
if (!dominator.equals(v)) {
dg.addEdge(new DefaultGEdge<>(dominator, v));
}
}
return dg;
}
/**
* Releases cached values used by internal data structures.
*/
public void clear() {
dominatedMap.clear();
dominatorMap.clear();
}
}

View file

@ -0,0 +1,43 @@
/* ###
* 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.algo;
import ghidra.graph.GDirectedGraph;
import ghidra.graph.GEdge;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* This is {@link ChkDominanceAlgorithm} with reverse graph traversal, which allows the
* algorithm to calculate post dominance.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class ChkPostDominanceAlgorithm<V, E extends GEdge<V>> extends ChkDominanceAlgorithm<V, E> {
/**
* Constructor.
*
* @param g the graph
* @param monitor the monitor
* @throws CancelledException if the algorithm is cancelled
*/
public ChkPostDominanceAlgorithm(GDirectedGraph<V, E> g, TaskMonitor monitor)
throws CancelledException {
super(g, GraphNavigator.bottomUpNavigator(), monitor);
}
}

View file

@ -0,0 +1,162 @@
/* ###
* 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.algo;
import java.util.*;
import ghidra.graph.*;
/**
* Processes the given graph depth first and records that order of the vertices.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class DepthFirstSorter<V, E extends GEdge<V>> {
/**
* Returns the vertices of the given graph in post-order, which is the order the vertices
* are last visited when performing a depth-first traversal.
*
* @param g the graph
* @return the vertices in post-order
*/
public static <V, E extends GEdge<V>> List<V> postOrder(GDirectedGraph<V, E> g) {
return postOrder(g, GraphNavigator.topDownNavigator());
}
/**
* Returns the vertices of the given graph in post-order, which is the order the vertices
* are last visited when performing a depth-first traversal.
*
* @param g the graph
* @param navigator the knower of the direction the graph should be traversed
* @return the vertices in post-order
*/
public static <V, E extends GEdge<V>> List<V> postOrder(GDirectedGraph<V, E> g,
GraphNavigator<V, E> navigator) {
DepthFirstSorter<V, E> sorter = new DepthFirstSorter<>(g, navigator);
List<V> list = sorter.getVerticesPostOrder();
sorter.dispose();
return list;
}
/**
* Returns the vertices of the given graph in pre-order, which is the order the vertices
* are encountered when performing a depth-first traversal.
*
* @param g the graph
* @return the vertices in pre-order
*/
public static <V, E extends GEdge<V>> List<V> preOrder(GDirectedGraph<V, E> g) {
return preOrder(g, GraphNavigator.topDownNavigator());
}
/**
* Returns the vertices of the given graph in pre-order, which is the order the vertices
* are encountered when performing a depth-first traversal.
*
* @param g the graph
* @param navigator the knower of the direction the graph should be traversed
* @return the vertices in pre-order
*/
public static <V, E extends GEdge<V>> List<V> preOrder(GDirectedGraph<V, E> g,
GraphNavigator<V, E> navigator) {
DepthFirstSorter<V, E> sorter = new DepthFirstSorter<>(g, navigator);
List<V> list = sorter.getVerticesPreOrder();
sorter.dispose();
return list;
}
//==================================================================================================
// Instance Code
//==================================================================================================
private GDirectedGraph<V, E> g;
GraphNavigator<V, E> navigator;
private LinkedHashSet<V> visited;
private DepthFirstSorter(GDirectedGraph<V, E> g, GraphNavigator<V, E> navigator) {
this.g = g;
this.navigator = navigator;
int vertexCount = g.getVertexCount();
visited = new LinkedHashSet<>(vertexCount);
}
private List<V> getVerticesPostOrder() {
Set<V> seeds = navigator.getSources(g);
for (V v : seeds) {
postOrderVisit(v);
}
for (V v : g.getVertices()) {
if (!visited.contains(v)) {
postOrderVisit(v);
}
}
return new ArrayList<>(visited);
}
private List<V> getVerticesPreOrder() {
Set<V> seeds = GraphAlgorithms.getSources(g);
for (V v : seeds) {
preOrderVisit(v);
}
for (V v : g.getVertices()) {
if (!visited.contains(v)) {
preOrderVisit(v);
}
}
return new ArrayList<>(visited);
}
private void postOrderVisit(V v) {
if (visited.contains(v)) {
return;
}
visited.add(v);
Collection<V> successors = navigator.getSuccessors(g, v);
for (V child : successors) {
postOrderVisit(child);
}
// remove/put back here to update traversal order to be post-order
visited.remove(v);
visited.add(v);
}
private void preOrderVisit(V v) {
if (visited.contains(v)) {
return;
}
visited.add(v);
Collection<V> successors = navigator.getSuccessors(g, v);
for (V child : successors) {
preOrderVisit(child);
}
}
private void dispose() {
visited.clear();
}
}

View file

@ -0,0 +1,298 @@
/* ###
* 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.algo;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.collections4.map.LazyMap;
import generic.util.DequePush;
import ghidra.generic.util.datastruct.DynamicValueSortedTreeMap;
import ghidra.graph.GEdge;
import ghidra.graph.GEdgeWeightMetric;
import ghidra.graph.GImplicitDirectedGraph;
import ghidra.graph.GWeightedEdge;
/**
* Dijkstra's shortest-path algorithm
*
* This implementation computes the shortest paths between two vertices using Dijkstra's
* single-source shortest path finding algorithm. Any time a new source is given, it explores
* all destinations in the graph up to a maximum distance from the source. Thus, this
* implementation is best applied when many queries are anticipated from relatively few sources.
*
* @param <V> the type of vertices
* @param <E> the type of edges
*/
public class DijkstraShortestPathsAlgorithm<V, E extends GEdge<V>> {
protected final Map<V, OneSourceToAll> sources =
LazyMap.lazyMap(new HashMap<>(), (V src) -> new OneSourceToAll(src));
protected final GImplicitDirectedGraph<V, E> graph;
protected final double maxDistance;
protected final GEdgeWeightMetric<E> metric;
/**
* Use Dijkstra's algorithm on the given graph
*
* This constructor assumes the graph's edges are {@link GWeightedEdge}s. If not, you will
* likely encounter a {@link ClassCastException}.
*
* @param graph the graph
* @param maxDistance the maximum distance, or null for no maximum
*/
public DijkstraShortestPathsAlgorithm(GImplicitDirectedGraph<V, E> graph) {
this.graph = graph;
this.maxDistance = Double.POSITIVE_INFINITY;
this.metric = GEdgeWeightMetric.naturalMetric();
}
/**
* Use Dijkstra's algorithm on the given graph with the given maximum distance
*
* This constructor assumes the graph's edges are {@link GWeightedEdge}s. If not, you will
* likely encounter a {@link ClassCastException}.
*
* @param graph the graph
* @param maxDistance the maximum distance, or null for no maximum
*/
public DijkstraShortestPathsAlgorithm(GImplicitDirectedGraph<V, E> graph, double maxDistance) {
this.graph = graph;
this.maxDistance = maxDistance;
this.metric = GEdgeWeightMetric.naturalMetric();
}
/**
* Use Dijstra's algorithm on the given graph with a custom edge weight metric
*
* @param graph the graph
* @param maxDistance the maximum distance, or null for no maximum
* @param metric the function to compute the weight of an edge
*/
public DijkstraShortestPathsAlgorithm(GImplicitDirectedGraph<V, E> graph,
GEdgeWeightMetric<E> metric) {
this.graph = graph;
this.maxDistance = Double.POSITIVE_INFINITY;
this.metric = metric;
}
/**
* Use Dijstra's algorithm on the given graph with the given maximum distance and a custom
* edge weight metric
*
* @param graph the graph
* @param maxDistance the maximum distance, or null for no maximum
* @param metric the function to compute the weight of an edge
*/
public DijkstraShortestPathsAlgorithm(GImplicitDirectedGraph<V, E> graph, double maxDistance,
GEdgeWeightMetric<E> metric) {
this.graph = graph;
this.maxDistance = maxDistance;
this.metric = metric;
}
/**
* Compute the shortest distance to all reachable vertices from the given source
*
* @param v the source vertex
* @return a map of destinations to distances from the given source
*/
public Map<V, Double> getDistancesFromSource(V v) {
OneSourceToAll info = sources.get(v);
return Collections.unmodifiableMap(info.visitedDistance);
}
/**
* Compute the shortest paths from the given source to the given destination
*
* This implementation differs from typical implementations in that paths tied for the shortest
* distance are all returned. Others tend to choose one arbitrarily.
*
* @param src the source
* @param dst the destination
* @return a collection of paths of shortest distance from source to destination
*/
public Collection<Deque<E>> computeOptimalPaths(V src, V dst) {
return sources.get(src).computeOptimalPathsTo(dst);
}
/**
* A class representing all optimal paths from a given source to every other (reachable) vertex
* in the graph
*
* This is the workhorse of path computation, and implements Dijkstra's Shortest Path algorithm
* from one source to all destinations. We considered using JUNG to store the graph and compute
* the paths, but we could not, because we would like to find all paths having the
* optimal distance. If there are ties, JUNG's implementation chooses one arbitrarily; we would
* like all tied paths.
*/
protected class OneSourceToAll {
// For explored, but unvisited nodes
protected final DynamicValueSortedTreeMap<V, Double> queueByDistance =
new DynamicValueSortedTreeMap<>();
// For visited nodes, i.e., their optimal distance is known
protected final Map<V, Double> visitedDistance = new LinkedHashMap<>();
protected final Map<V, Set<E>> bestIns =
LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>());
protected final V source;
/**
* Compute the shortest paths from a given vertex to all other reachable vertices in the
* graph
* @param src the source (seed) vertex
*/
protected OneSourceToAll(V src) {
this.source = src;
queueByDistance.put(src, 0d);
fill();
}
/**
* Recover the shortest paths from the source to the given destination, if it is reachable
* @param dst the destination
* @return a collection of the shortest paths from source to destination, or the empty set
*/
public Collection<Deque<E>> computeOptimalPathsTo(V dst) {
Set<Deque<E>> paths = new HashSet<>();
addPathsTo(paths, dst);
return paths;
}
/**
* Add the shortest paths from the source to the given destination into the given
* collection
*
* This is used internally to recover the shortest paths
* @param paths a place to store the recovered paths
* @param dst the destination
*/
protected void addPathsTo(Collection<Deque<E>> paths, V dst) {
addPathsTo(paths, dst, new LinkedList<>());
}
/**
* Add the shortest paths from source to a given intermediate, continuing along a given
* path to the final destination, into the given collection
*
* This is a recursive method for constructing the shortest paths overall. Assuming the
* given path from intermediate to final destination is the shortest, we can show by
* induction, the computed paths from source to destination are the shortest.
* @param paths a place to store the recovered paths
* @param prev the intermediate destination
* @param soFar a (shortest) path from intermediate to final destination
*/
protected void addPathsTo(Collection<Deque<E>> paths, V prev, Deque<E> soFar) {
if (prev.equals(source)) { // base case:
// The path from source to source is empty, and our path from intermediate to
// destination is actually the path from source to destination. Add it!
paths.add(new LinkedList<>(soFar));
}
else { // inductive case:
/* Dijkstra has computed the best inbound edges. Consider each as a prefix to the
* current path from intermediate to final destination. Since we assume that path
* is an optimal path, and we prefix an optimal inbound edge, the prefixed path is
* an optimal path from a new intermediate source (inbound neighbor) to the final
* destination. So, just recurse, using the new intermediates.
*/
for (E e : bestIns.get(prev)) {
V nextPrev = e.getStart();
try (DequePush<?> push = DequePush.push(soFar, e)) {
addPathsTo(paths, nextPrev, soFar);
}
}
}
}
/**
* Update the record for the given destination with a new offer of shortest distance
*
* If either the record doesn't exist yet, or the new offer beats the current best, then
* a new record is created and replaces the current record. If present, the list of best
* inbound edges is cleared -- because they all correspond to a distance that has just been
* beat. The node is also added and/or moved forward in the queue of unvisited vertices.
*
* If the record exists, and the new offer ties the current offer, nothing happens, but
* the method still returns true, since the corresponding inbound edge could be optimal.
*
* If the record's current best beats the offer, nothing happens, and the method returns
* false, indicating the inbound edge is definitely not optimal.
*
* @param dest the destination whose record to update
* @param newDist the distance offer
* @return true iff the offer is equal to or better than the record's current best
*/
protected boolean addOrUpdate(V dest, double newDist) {
// Is it already visited?
Double curDist = visitedDistance.get(dest);
if (curDist != null) {
return false;
}
// Nope? Well let's update the records.
curDist = queueByDistance.get(dest);
if (curDist == null) {
queueByDistance.put(dest, newDist);
return true;
}
else if (newDist < curDist) {
queueByDistance.put(dest, newDist);
bestIns.get(dest).clear();
return true;
}
else if (newDist == curDist) {
return true;
}
return false;
}
/**
* Compute paths, building out the graph until all reachable vertices have been visited
*/
protected void fill() {
Entry<V, Double> next;
while ((next = queueByDistance.entrySet().poll()) != null) {
// Mark it visited, and save the distance (may not need distance anymore....)
visitedDistance.put(next.getKey(), next.getValue());
fillStep(next.getKey(), next.getValue());
}
}
/**
* Perform one iteration of Dijskstra's path finding algorithm
* @param from the vertex to visit for this iteration
*/
protected void fillStep(V from, double dist) {
for (E e : graph.getOutEdges(from)) {
double newDist = dist + metric.computeWeight(e);
if (newDist > maxDistance) {
continue;
}
V dest = e.getEnd();
if (addOrUpdate(dest, newDist)) {
bestIns.get(dest).add(e);
}
}
}
}
}

View file

@ -0,0 +1,144 @@
/* ###
* 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.algo;
import java.util.*;
import ghidra.graph.GDirectedGraph;
import ghidra.graph.GEdge;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Finds all paths between two vertices for a given graph.
*
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how deep
* it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will not be found.
*
* <P>Note: this algorithm is based entirely on the {@link JohnsonCircuitsAlgorithm}.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class FindPathsAlgorithm<V, E extends GEdge<V>> {
public static final int JAVA_STACK_DEPTH_LIMIT = 2700;
private GDirectedGraph<V, E> g;
private V startVertex;
private V endVertex;
private Stack<V> stack = new Stack<>();
private Set<V> blockedSet = new HashSet<>();
private Map<V, Set<V>> blockedBackEdgesMap = new HashMap<>();
public FindPathsAlgorithm(GDirectedGraph<V, E> g, V start, V end,
Accumulator<List<V>> accumulator, TaskMonitor monitor) throws CancelledException {
this.g = g;
this.startVertex = start;
this.endVertex = end;
monitor.initialize(g.getEdgeCount());
find(accumulator, monitor);
}
private void find(Accumulator<List<V>> accumulator, TaskMonitor monitor)
throws CancelledException {
explore(startVertex, accumulator, 0, monitor);
}
private boolean explore(V v, Accumulator<List<V>> accumulator, int depth, TaskMonitor monitor)
throws CancelledException {
// TODO
// Sigh. We are greatly limited in the size of paths we can processes due to the
// recursive nature of this algorithm. This should be changed to be non-recursive.
if (depth > JAVA_STACK_DEPTH_LIMIT) {
return false;
}
boolean foundPath = false;
blockedSet.add(v);
stack.push(v);
Collection<E> outEdges = getOutEdges(v);
for (E e : outEdges) {
monitor.checkCanceled();
V u = e.getEnd();
if (u.equals(endVertex)) {
outputCircuit(accumulator);
foundPath = true;
monitor.incrementProgress(1);
}
else if (!blockedSet.contains(u)) {
foundPath |= explore(u, accumulator, depth + 1, monitor);
monitor.incrementProgress(1);
}
}
if (foundPath) {
unblock(v);
}
else {
for (E e : outEdges) {
monitor.checkCanceled();
V u = e.getEnd();
addBackEdge(u, v);
}
}
stack.pop();
return foundPath;
}
private Collection<E> getOutEdges(V v) {
Collection<E> outEdges = g.getOutEdges(v);
if (outEdges == null) {
return Collections.emptyList();
}
return outEdges;
}
private void unblock(V v) {
blockedSet.remove(v);
Set<V> set = blockedBackEdgesMap.get(v);
if (set == null) {
return;
}
for (V u : set) {
if (blockedSet.contains(u)) {
unblock(u);
}
}
set.clear();
}
private void addBackEdge(V u, V v) {
Set<V> set = blockedBackEdgesMap.get(u);
if (set == null) {
set = new HashSet<>();
blockedBackEdgesMap.put(u, set);
}
set.add(v);
}
private void outputCircuit(Accumulator<List<V>> accumulator) {
List<V> path = new ArrayList<>(stack);
path.add(endVertex);
accumulator.add(path);
}
}

View file

@ -0,0 +1,167 @@
/* ###
* 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.algo;
import static util.CollectionUtils.*;
import java.util.*;
import ghidra.graph.*;
/**
* The methods on this interface are meant to enable graph traversal in a way that allows
* the underlying graph to be walked from top-down or bottom-up.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class GraphNavigator<V, E extends GEdge<V>> {
private boolean isTopDown;
/**
* Creates a top-down navigator, which is one that traverses the graph from the source
* to the sink.
*
* @return the navigator
*/
public static <V, E extends GEdge<V>> GraphNavigator<V, E> topDownNavigator() {
return new GraphNavigator<>(true);
}
/**
* Creates a bottom-down navigator, which is one that traverses the graph from the sink
* to the source.
*
* @return the navigator
*/
public static <V, E extends GEdge<V>> GraphNavigator<V, E> bottomUpNavigator() {
return new GraphNavigator<>(false);
}
private GraphNavigator(boolean isTopDown) {
this.isTopDown = isTopDown;
}
/**
* Gets all edges leaving the given vertex, depending upon the direction of this navigator.
*
* @param graph the graph
* @param v the vertex
* @return the edges
*/
public Collection<E> getEdges(GDirectedGraph<V, E> graph, V v) {
if (isTopDown) {
return asCollection(graph.getOutEdges(v));
}
return asCollection(graph.getInEdges(v));
}
/**
* Returns true if this navigator processes nodes from the top down; false if nodes are
* processed from the bottom up.
*
* @return true if this navigator processes nodes from the top down; false if nodes are
* processed from the bottom up.
*/
public boolean isTopDown() {
return isTopDown;
}
/**
* Gets all child vertices of the given vertex, depending upon the direction of the
* navigator.
*
* @param graph the graph
* @param v the vertex
* @return the vertices
*/
public Collection<V> getSuccessors(GDirectedGraph<V, E> graph, V v) {
if (isTopDown) {
return graph.getSuccessors(v);
}
return graph.getPredecessors(v);
}
/**
* Gets all parent vertices of the given vertex, depending upon the direction of the
* navigator.
*
* @param graph the graph
* @param v the vertex
* @return the vertices
*/
public Collection<V> getPredecessors(GDirectedGraph<V, E> graph, V v) {
if (isTopDown) {
return asCollection(graph.getPredecessors(v));
}
return asCollection(graph.getSuccessors(v));
}
/**
* Gets the vertex at the end of the given edge, where the 'end' of the edge depends on the
* start vertex.
*
* @param e the edge
* @return the vertex
*/
public V getEnd(E e) {
if (isTopDown) {
return e.getEnd();
}
return e.getStart();
}
/**
* Gets the root vertices of the given graph. If this is a top-down navigator, then the
* sources are returned; otherwise, the sinks are returned.
*
* @param graph the graph
* @return the roots
*/
public Set<V> getSources(GDirectedGraph<V, E> graph) {
if (isTopDown) {
return asSet(GraphAlgorithms.getSources(graph));
}
return asSet(GraphAlgorithms.getSinks(graph));
}
/**
* Gets the exit vertices of the given graph. If this is a top-down navigator, then the
* sinks are returned; otherwise, the sources are returned.
*
* @param graph the graph
* @return the exits
*/
public Set<V> getSinks(GDirectedGraph<V, E> graph) {
if (isTopDown) {
return asSet(GraphAlgorithms.getSinks(graph));
}
return asSet(GraphAlgorithms.getSources(graph));
}
/**
* Returns all vertices in the given graph in the depth-first order. The order will
* be post-order for a top-down navigator and pre-order for a bottom-up navigator.
*
* @param graph the graph
* @return the ordered vertices
*/
public List<V> getVerticesInPostOrder(GDirectedGraph<V, E> graph) {
List<V> postOrder = asList(GraphAlgorithms.getVerticesInPostOrder(graph, this));
return postOrder;
}
}

View file

@ -0,0 +1,155 @@
/* ###
* 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.algo;
import java.util.*;
import ghidra.graph.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Finds all circuits (loops) in the given graph.
*
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how deep
* it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will not be found.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class JohnsonCircuitsAlgorithm<V, E extends GEdge<V>> {
public static final int JAVA_STACK_DEPTH_LIMIT = 2700;
private GDirectedGraph<V, E> g;
private GDirectedGraph<V, E> subGraph;
private Stack<V> stack = new Stack<>();
private V startVertex;
private Set<V> blockedSet = new HashSet<>();
private Map<V, Set<V>> blockedBackEdgesMap = new HashMap<>();
private Accumulator<List<V>> accumulator;
public JohnsonCircuitsAlgorithm(GDirectedGraph<V, E> g, Accumulator<List<V>> accumulator) {
this.g = g;
this.accumulator = accumulator;
}
/**
* Finds the circuits in the graph passed at construction time.
*
* @param uniqueCircuits true signals to return only unique circuits, where no two
* circuits will contain the same vertex
* @param monitor the task monitor
* @throws CancelledException if the monitor is cancelled
*/
public void compute(boolean uniqueCircuits, TaskMonitor monitor) throws CancelledException {
Set<Set<V>> stronglyConnected = GraphAlgorithms.getStronglyConnectedComponents(g);
for (Set<V> set : stronglyConnected) {
if (set.size() < 2) {
continue;
}
subGraph = GraphAlgorithms.createSubGraph(g, set);
List<V> vertices = new ArrayList<>(subGraph.getVertices());
int size = vertices.size() - 1;
if (uniqueCircuits) {
size += 1;
}
for (int i = 0; i < size; i++) {
startVertex = vertices.get(i);
blockedSet.clear();
blockedBackEdgesMap.clear();
circuit(startVertex, 0, monitor);
if (uniqueCircuits) {
subGraph.removeVertex(startVertex);
}
}
}
}
private boolean circuit(V v, int depth, TaskMonitor monitor) throws CancelledException {
monitor.checkCanceled();
// TODO
// Sigh. We are greatly limited in the size of paths we can processes due to the
// recursive nature of this algorithm. This should be changed to be non-recursive.
if (depth > JAVA_STACK_DEPTH_LIMIT) {
return false;
}
boolean foundCircuit = false;
blockedSet.add(v);
stack.push(v);
Collection<E> outEdges = subGraph.getOutEdges(v);
for (E e : outEdges) {
V u = e.getEnd();
if (u.equals(startVertex)) {
outputCircuit();
foundCircuit = true;
}
else if (!blockedSet.contains(u)) {
foundCircuit |= circuit(u, depth + 1, monitor);
}
}
if (foundCircuit) {
unblock(v);
}
else {
for (E e : outEdges) {
V u = e.getEnd();
addBackEdge(u, v);
}
}
stack.pop();
return foundCircuit;
}
private void unblock(V v) {
blockedSet.remove(v);
Set<V> set = blockedBackEdgesMap.get(v);
if (set == null) {
return;
}
for (V u : set) {
if (blockedSet.contains(u)) {
unblock(u);
}
}
set.clear();
}
private void addBackEdge(V u, V v) {
Set<V> set = blockedBackEdgesMap.get(u);
if (set == null) {
set = new HashSet<>();
blockedBackEdgesMap.put(u, set);
}
set.add(v);
}
private void outputCircuit() {
List<V> circuit = new ArrayList<>(stack);
circuit.add(startVertex);
accumulator.add(circuit);
}
}

View file

@ -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.graph.algo;
import java.util.*;
import ghidra.graph.GDirectedGraph;
import ghidra.graph.GEdge;
public class TarjanStronglyConnectedAlgorthm<V, E extends GEdge<V>> {
private GDirectedGraph<V, E> graph;
private Map<V, TarjanVertexInfo> vertexToInfos = new HashMap<>();
private Stack<V> stack = new Stack<>();
private Set<V> set = new HashSet<>();
private Set<Set<V>> stronglyConnectedList = new HashSet<>();
public TarjanStronglyConnectedAlgorthm(GDirectedGraph<V, E> g) {
this.graph = g;
compute();
}
private void compute() {
for (V v : graph.getVertices()) {
if (!vertexToInfos.containsKey(v)) {
strongConnect(v);
}
}
}
private TarjanVertexInfo strongConnect(V v) {
TarjanVertexInfo vInfo = new TarjanVertexInfo();
vertexToInfos.put(v, vInfo);
push(v);
for (E edge : graph.getOutEdges(v)) {
V w = edge.getEnd();
TarjanVertexInfo wInfo = vertexToInfos.get(w);
if (wInfo == null) {
wInfo = strongConnect(w);
vInfo.lowLink = Math.min(vInfo.lowLink, wInfo.lowLink);
}
else if (set.contains(w)) {
vInfo.lowLink = Math.min(vInfo.lowLink, wInfo.index);
}
}
if (vInfo.lowLink == vInfo.index) {
Set<V> connectedSet = new HashSet<>();
connectedSet.add(v);
for (V w = pop(); v != w; w = pop()) {
connectedSet.add(w);
}
stronglyConnectedList.add(connectedSet);
}
return vInfo;
}
private void push(V v) {
stack.push(v);
set.add(v);
}
private V pop() {
V v = stack.pop();
set.remove(v);
return v;
}
public Set<Set<V>> getConnectedComponents() {
return stronglyConnectedList;
}
static class TarjanVertexInfo {
private static int nextIndex = 0;
public int index;
public int lowLink;
public TarjanVertexInfo() {
index = nextIndex;
lowLink = index;
nextIndex++;
}
}
}

View file

@ -0,0 +1,50 @@
/* ###
* 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.event;
/**
* A listener to get notified of graph changes.
*/
public interface VisualGraphChangeListener<V, E> {
/**
* Called when the given vertices have been added from the graph
*
* @param vertices the added vertices
*/
public void verticesAdded(Iterable<V> vertices);
/**
* Called when the given vertices have been removed from the graph
*
* @param vertices the removed vertices
*/
public void verticesRemoved(Iterable<V> vertices);
/**
* Called when the given edges have been added from the graph
*
* @param vertices the added edges
*/
public void edgesAdded(Iterable<E> edges);
/**
* Called when the given edges have been removed from the graph
*
* @param vertices the removed edges
*/
public void edgesRemoved(Iterable<E> edges);
}

View file

@ -0,0 +1,302 @@
/* ###
* 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.featurette;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JComponent;
import docking.*;
import docking.action.MenuData;
import docking.action.ToggleDockingAction;
import ghidra.framework.options.SaveState;
import ghidra.graph.VisualGraph;
import ghidra.graph.VisualGraphComponentProvider;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.actions.*;
import ghidra.util.HelpLocation;
import resources.ResourceManager;
/**
* A sub-feature that provides a satellite viewer to {@link VisualGraphComponentProvider}s
*
* <p>Note: this class installs actions to manipulate the satellite view. For these to be
* correctly enabled, you must produce {@link VgActionContext} objects in your
* {@link VisualGraphComponentProvider#getActionContext(MouseEvent)} method. Specifically,
* the context returned must be a type of {@link VgActionContext}, with the
* {@link VgActionContext#shouldShowSatelliteActions()} returning true.
*
* @param <V> the vertex type
* @param <E> the edge type
* @param <G> the graph type
*/
//@formatter:off
public class VgSatelliteFeaturette<V extends VisualVertex,
E extends VisualEdge<V>,
G extends VisualGraph<V, E>>
implements VisualGraphFeaturette<V, E, G> {
//@formatter:on
private static final Icon ICON = ResourceManager.loadImage("images/network-wireless-16.png");
private static final String DISPLAY_SATELLITE = "DISPLAY_SATELLITE";
private static final String DOCK_SATELLITE = "DOCK_SATELLITE";
private ToggleDockingAction toggleSatelliteAction;
private ToggleDockingAction dockSatelliteAction;
private DockingTool tool;
private VisualGraphView<?, ?, ?> view;
private String owner;
private String providerName;
private String windowGroup;
// a flag for us to know when we were closed because the primary graph windows was closed
private boolean closedByPrimaryProvider;
private VgUndockedSatelliteProvider satelliteProvider;
@Override
public void writeConfigState(SaveState saveState) {
saveState.putBoolean(DOCK_SATELLITE, dockSatelliteAction.isSelected());
saveState.putBoolean(DISPLAY_SATELLITE, toggleSatelliteAction.isSelected());
}
@Override
public void readConfigState(SaveState saveState) {
// Note: we want to restore this value before the satellite visibility, as setting the
// docked state will affect that state, which is what we want when the user executes the
// action, but not when restoring saved settings.
boolean dockSatellite = saveState.getBoolean(DOCK_SATELLITE, true);
dockSatelliteAction.setSelected(dockSatellite);
view.setSatelliteDocked(dockSatellite);
boolean showSatellite = saveState.getBoolean(DISPLAY_SATELLITE, true);
toggleSatelliteAction.setSelected(showSatellite);
view.setSatelliteVisible(showSatellite);
}
@Override
public void init(VisualGraphComponentProvider<V, E, G> provider) {
tool = provider.getTool();
view = provider.getView();
owner = provider.getOwner();
providerName = provider.getName();
windowGroup = provider.getWindowGroup();
view.setSatelliteListener(new SatelliteListener());
addActions(provider);
}
@Override
public void providerOpened(VisualGraphComponentProvider<V, E, G> provider) {
if (satelliteProvider != null) {
// since the provider is not null, we know that it was previously open, closed
satelliteProvider.setVisible(true);
view.setSatelliteVisible(true);
}
}
@Override
public void providerClosed(VisualGraphComponentProvider<V, E, G> provider) {
if (satelliteProvider != null) {
closedByPrimaryProvider = true;
satelliteProvider.closeComponent();
closedByPrimaryProvider = false; // reset
}
}
public ComponentProvider getSatelliteProvider() {
return satelliteProvider;
}
private void addActions(ComponentProvider provider) {
toggleSatelliteAction = new ToggleDockingAction("Display Satellite View", owner) {
@Override
public void actionPerformed(ActionContext context) {
view.setSatelliteVisible(isSelected());
}
@Override
public boolean isAddToPopup(ActionContext context) {
ComponentProvider componentProvider = context.getComponentProvider();
if (componentProvider != provider && componentProvider != satelliteProvider) {
// appear in satellite and the main provider
return false;
}
if (!(context instanceof VisualGraphActionContext)) {
return false;
}
VisualGraphActionContext vgContext = (VisualGraphActionContext) context;
return vgContext.shouldShowSatelliteActions();
}
};
toggleSatelliteAction.setSelected(true);
toggleSatelliteAction.setPopupMenuData(
new MenuData(new String[] { "Display Satellite View" }));
toggleSatelliteAction.setHelpLocation(
new HelpLocation("FunctionCallGraphPlugin", "Satellite_View"));
dockSatelliteAction = new ToggleDockingAction("Dock Satellite View", owner) {
@Override
public void actionPerformed(ActionContext context) {
view.setSatelliteDocked(isSelected());
}
@Override
public boolean isAddToPopup(ActionContext context) {
ComponentProvider componentProvider = context.getComponentProvider();
if (componentProvider != provider && componentProvider != satelliteProvider) {
// appear in satellite and the main provider
return false;
}
if (!(context instanceof VisualGraphActionContext)) {
return false;
}
VisualGraphActionContext vgContext = (VisualGraphActionContext) context;
return vgContext.shouldShowSatelliteActions();
}
};
dockSatelliteAction.setSelected(true);
dockSatelliteAction.setPopupMenuData(new MenuData(new String[] { "Dock Satellite View" }));
dockSatelliteAction.setHelpLocation(
new HelpLocation("FunctionCallGraphPlugin", "Satellite_View"));
// Note: this is not a local action, since it should appear in satellite and the main view
tool.addAction(toggleSatelliteAction);
tool.addAction(dockSatelliteAction);
}
@Override
public void remove() {
if (satelliteProvider != null) {
satelliteProvider.removeFromTool();
}
}
// only remove the provider if the user had docked the satellite
private void closeSatelliteProvider(boolean remove) {
if (satelliteProvider == null) {
return;
}
if (remove) {
satelliteProvider.removeFromTool();
satelliteProvider = null;
}
else {
satelliteProvider.closeComponent();
}
}
private void showSatelliteProvider() {
if (satelliteProvider == null) {
JComponent component = view.getUndockedSatelliteComponent();
satelliteProvider = new VgUndockedSatelliteProvider(tool, component,
providerName + " Satellite", owner, windowGroup);
satelliteProvider.setVisible(true);
}
else {
satelliteProvider.toFront();
}
}
private class VgUndockedSatelliteProvider extends ComponentProvider {
private JComponent satelliteComponent;
public VgUndockedSatelliteProvider(DockingTool tool, JComponent component, String name,
String owner, String windowGroup) {
super(tool, name, owner);
this.satelliteComponent = component;
satelliteComponent.setMinimumSize(new Dimension(400, 400));
// TODO - need generic, shared help for the common abstract graph features;
// will be done in an upcoming ticket
// setHelpLocation(new HelpLocation("Graph", "Satellite_View_Dock"));
// this will group the satellite with the provider
setWindowMenuGroup(windowGroup);
setIcon(ICON);
setDefaultWindowPosition(WindowPosition.WINDOW);
addToTool();
}
@Override
public JComponent getComponent() {
return satelliteComponent;
}
@Override
public ActionContext getActionContext(MouseEvent event) {
return new VgSatelliteContext(this);
}
@Override
public void componentHidden() {
if (!dockSatelliteAction.isSelected()) {
// this implies the user has closed the provider, but it is still undocked;
// sync the state of the view with this provider
view.setSatelliteVisible(false);
}
if (!closedByPrimaryProvider) {
// This is the case where the user closed this satellite provider directly. In
// this case, we do not want to show this provider if the main provider is later
// re-opened.
satelliteProvider.removeFromTool();
satelliteProvider = null;
}
}
}
private class SatelliteListener implements GraphSatelliteListener {
@Override
public void satelliteVisibilityChanged(boolean docked, boolean visible) {
toggleSatelliteAction.setSelected(visible);
dockSatelliteAction.setSelected(docked);
if (docked) {
closeSatelliteProvider(true);
return;
}
if (!visible) { // undocked and not visible
closeSatelliteProvider(false);
}
else { // undocked and visible
showSatelliteProvider();
}
}
}
}

View file

@ -0,0 +1,76 @@
/* ###
* 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.featurette;
import ghidra.framework.options.SaveState;
import ghidra.graph.VisualGraph;
import ghidra.graph.VisualGraphComponentProvider;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
/**
* An interface that represents a sub-feature of a {@link VisualGraphComponentProvider}. This
* allows the base provider to have a set of features ready to be installed by subclasses.
*/
//@formatter:off
public interface VisualGraphFeaturette<V extends VisualVertex,
E extends VisualEdge<V>,
G extends VisualGraph<V, E>> {
//@formatter:on
/**
* Called to initialize this feature when the provider and view are ready
*
* @param provider the provider associated with this feature
*/
public void init(VisualGraphComponentProvider<V, E, G> provider);
/**
* Called when the client wishes to save configuration state. Features can add any state
* they wish to be persisted over tool launches.
*
* @param state the container for state information
*/
public void writeConfigState(SaveState state);
/**
* Called when the client wishes to restore configuration state. Features can read state
* previously saved from a call to {@link #writeConfigState(SaveState)}.
*
* @param state the container for state information
*/
public void readConfigState(SaveState saveState);
/**
* Called when the client provider is opened
*
* @param provider the provider
*/
public void providerOpened(VisualGraphComponentProvider<V, E, G> provider);
/**
* Called when the client provider is closed
*
* @param provider the provider
*/
public void providerClosed(VisualGraphComponentProvider<V, E, G> provider);
/**
* Called when the provider is being disposed
*/
public void remove();
}

View file

@ -0,0 +1,329 @@
/* ###
* 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.graphs;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static util.CollectionUtils.nonNull;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.*;
import org.apache.commons.collections4.IterableUtils;
import com.google.common.collect.Iterables;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
import ghidra.graph.VisualGraph;
import ghidra.graph.event.VisualGraphChangeListener;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.graph.viewer.layout.VisualGraphLayout;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
/**
* A default {@link VisualGraph} that implements basic setup for things like event processing.
*
* <P>Notes:
* <UL>
* <LI><U>Selected Vertices and the Focused Vertex</U> -
* there can be multiple selected vertices, but only a single focused vertex.
* <B>{@link #getSelectedVertices()} will return both
* the selected vertices or the focused vertex if there are no vertices selected.</B>
* </LI>
* <LI>Clicking a single vertex will focus it. Control-clicking multiple vertices will
* cause them all to be selected, with no focused vertex.
* </LI>
* <LI><U>Rendering Edges</U> - edges are rendered with or without articulations if
* they have them. This is built-in to the default graphing edge renderer.
* Some layouts require custom edge rendering and will provide their own
* renderer as needed.
* </LI>
* </UL>
*
*
* @param <V> the vertex type
* @param <E> the edge type
*/
//@formatter:off
public abstract class DefaultVisualGraph<V extends VisualVertex,
E extends VisualEdge<V>>
extends JungDirectedVisualGraph<V, E> {
//@formatter:on
protected V focusedVertex;
private Set<V> selectedVertices = Collections.emptySet();
private WeakSet<VisualGraphChangeListener<V, E>> changeListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
@Override
public abstract DefaultVisualGraph<V, E> copy();
@Override
public void setSelectedVertices(Set<V> selectedVertices) {
clearFocusedVertex();
setVerticesSelected(this.selectedVertices, false);
this.selectedVertices = selectedVertices;
setVerticesSelected(selectedVertices, true);
}
private void setVerticesSelected(Set<V> vertices, boolean selected) {
for (V vertex : vertices) {
vertex.setSelected(selected);
}
}
@Override
public void setVertexFocused(V vertex, boolean focused) {
clearSelectedVertices();
vertex.setFocused(focused);
if (focused) {
vertex.setSelected(focused);
focusedVertex = vertex;
}
}
@Override
public V getFocusedVertex() {
return focusedVertex;
}
private void clearFocusedVertex() {
if (focusedVertex == null) {
return;
}
focusedVertex.setFocused(false);
focusedVertex.setSelected(false);
focusedVertex = null;// can only have one 'focused' vertex at a time
}
@Override
public void clearSelectedVertices() {
clearFocusedVertex();
setVerticesSelected(selectedVertices, false);
selectedVertices.clear();
}
@Override
public Set<V> getSelectedVertices() {
//
// Implementation note: the 'focusedVertex' is considered selected. A selected vertex
// will be considered focused when it is the only selected vertex.
//
// quick check for no selected vertices
boolean hasGroupSelection = selectedVertices.size() > 0;
if (!hasGroupSelection && focusedVertex == null) {
return Collections.emptySet();
}
// there is some vertex selected...
if (hasGroupSelection) {
return new HashSet<>(selectedVertices);
}
// no group selection, there must be a focused vertex
Set<V> set = new HashSet<>();
set.add(focusedVertex);
return set;
}
@Override
public void vertexLocationChanged(V v, Point point, ChangeType type) {
// stub
}
public void dispose() {
selectedVertices.clear();
selectedVertices = Collections.emptySet();
changeListeners.clear();
changeListeners = null;
}
/*
* We need to set the initial location for each vertex so that the various UI graph
* algorithms can work correctly.
*/
protected void initializeLocation(V v) {
VisualGraphLayout<V, E> layout = getLayout();
if (layout == null) {
// we cannot initialize our locations without a layout; this should be set after
// the graph and layout have both been created
return;
}
Point2D location = layout.apply(v);
v.setLocation(location);
}
/**
* A convenience method to combine retrieval of in and out edges for the given vertex
*
* @param v the vertex
* @return the edges
*/
public Iterable<E> getAllEdges(V v) {
Collection<E> in = getInEdges(v);
Collection<E> out = getOutEdges(v);
Iterable<E> concatenated = Iterables.concat(in, out);
return concatenated;
}
/**
* Returns all edges shared between the two given vertices
*
* @param start the start vertex
* @param end the end vertex
* @return the edges
*/
public Iterable<E> getEdges(V start, V end) {
Collection<E> outs = nonNull(getOutEdges(start));
Collection<E> ins = nonNull(getInEdges(end));
Iterable<E> concatenated = concat(outs, ins);
Iterable<E> filtered = filter(concatenated, e -> {
return e.getStart().equals(start) && e.getEnd().equals(end);
});
return filtered;
}
//==================================================================================================
// Overridden Methods / Listener Methods
//==================================================================================================
@Override
public boolean addVertex(V v) {
boolean added = super.addVertex(v);
if (added) {
initializeLocation(v);
verticesAdded(Arrays.asList(v));
}
return added;
}
@Override
public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) {
boolean added = super.addEdge(edge, endpoints, edgeType);
if (added) {
fireEdgesAdded(Arrays.asList(edge));
}
return added;
}
@Override
public boolean removeVertex(V v) {
boolean removed = super.removeVertex(v);
if (removed) {
verticesRemoved(Arrays.asList(v));
}
return removed;
}
@Override
public void removeVertices(Iterable<V> toRemove) {
List<V> removed = new ArrayList<>();
for (V v : toRemove) {
if (super.removeVertex(v)) {
removed.add(v);
}
}
verticesRemoved(removed);
}
@Override
public boolean removeEdge(E edge) {
boolean removed = super.removeEdge(edge);
if (removed) {
fireEdgesRemoved(Arrays.asList(edge));
}
return removed;
}
/**
* Called after one or more vertices have been added. The callback will happen after
* all additions have taken place. This is an extension point for subclasses.
*
* @param added the added vertices
*/
protected void verticesAdded(Collection<V> added) {
fireVerticesAdded(added);
}
/**
* Called after one or more vertices have been removed. The callback will happen after
* all removals have taken place. This is an extension point for subclasses.
*
* @param removed the removed vertices
*/
protected void verticesRemoved(Collection<V> removed) {
fireVerticesRemoved(removed);
}
protected void fireVerticesRemoved(Collection<V> removed) {
if (removed.isEmpty()) {
return;
}
changeListeners.forEach(l -> l.verticesRemoved(removed));
}
protected void fireVerticesAdded(Collection<V> added) {
if (added.isEmpty()) {
return;
}
changeListeners.forEach(l -> l.verticesAdded(added));
}
protected void fireEdgesRemoved(Iterable<E> removed) {
if (IterableUtils.isEmpty(removed)) {
return;
}
changeListeners.forEach(l -> l.edgesRemoved(removed));
}
protected void fireEdgesAdded(Iterable<E> added) {
if (IterableUtils.isEmpty(added)) {
return;
}
changeListeners.forEach(l -> l.edgesAdded(added));
}
@Override
public void addGraphChangeListener(VisualGraphChangeListener<V, E> l) {
changeListeners.add(l);
}
@Override
public void removeGraphChangeListener(VisualGraphChangeListener<V, E> l) {
changeListeners.remove(l);
}
}

View file

@ -0,0 +1,476 @@
/* ###
* 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.graphs;
import static util.CollectionUtils.asList;
import java.util.*;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
import ghidra.generic.function.Callback;
import ghidra.graph.GraphAlgorithms;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import ghidra.graph.viewer.layout.VisualGraphLayout;
import util.CollectionUtils;
/**
* A graph implementation that allows clients to mark vertices and edges as filtered. When
* filtered, a vertex is removed from this graph, but kept around for later unfiltering. Things
* of note:
* <UL>
* <LI>As vertices are filtered, so to will be their edges
* </LI>
* <LI>If additions are made to the graph while it is filtered, the new additions will
* not be added to the current graph, but will be kept in the background for later
* restoring
* </LI>
* <LI>
* </LI>
* </UL>
*
* <P>Implementation Note: this class engages in some odd behavior when removals and additions
* are need to this graph. A distinction is made between events that are generated from
* external clients and those that happen due to filtering and restoring. This distinction
* allows this class to know when to update this graph, based upon whether or not data has
* been filtered. Implementation of this is achieved by using a flag. Currently, this flag
* is thread-safe. If this graph is to be multi-threaded (such as if changes are to be
* made by multiple threads, then this update flag will have to be revisited to ensure thread
* visibility.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public abstract class FilteringVisualGraph<V extends VisualVertex, E extends VisualEdge<V>>
extends DefaultVisualGraph<V, E> {
/**
* A graph that always holds the unfiltered, complete graph data. We use this to restore
* filtered items.
*/
private UnfilteredGraph completeGraph = new UnfilteredGraph();
// a flag to track the stack of updates so we know when to ignore events
private int internalCallCount;
public void filterVertices(Collection<V> toFilter) {
for (V v : toFilter) {
removeVertexFromView(v);
}
}
public void filterEdges(Collection<E> toFilter) {
for (E e : toFilter) {
removeEdgeFromView(e);
}
}
/**
* Restores the given filtered vertices into the graph. This will only happen if both
* endpoints are in the graph.
*
* @param toUnfilter the edges to restore
*/
public void unfilterVertices(Collection<V> toUnfilter) {
maybeRestoreVertices(toUnfilter);
maybeRestoreRelatedEdges(toUnfilter);
}
/**
* Restores the given filtered edges into the graph. This will only happen if both
* endpoints are in the graph.
*
* @param toUnfilter the edges to restore
*/
public void unfilterEdges(Collection<E> toUnfilter) {
maybeRestoreEdges(toUnfilter);
}
public Iterator<V> getAllVertices() {
return completeGraph.getVertices().iterator();
}
public Iterator<E> getAllEdges() {
return completeGraph.getEdges().iterator();
}
public Iterator<V> getFilteredVertices() {
// a vertex is 'filtered' if it is in the complete graph, but not in the current graph
Predicate<? super V> isFiltered = v -> {
return !containsVertex(v) && completeGraph.containsVertex(v);
};
return Iterators.filter(getAllVertices(), isFiltered);
}
public Iterator<E> getFilteredEdges() {
// an edge is 'filtered' if it is in the complete graph, but not in the current graph
Predicate<? super E> isFiltered = e -> {
return !containsEdge(e) && completeGraph.containsEdge(e);
};
return Iterators.filter(getAllEdges(), isFiltered);
}
public Iterator<V> getUnfilteredVertices() {
// a vertex is 'unfiltered' if it is in the current graph
return getVertices().iterator();
}
public Iterator<E> getUnfilteredEdges() {
// an edge is 'unfiltered' if it is in the current graph
return getEdges().iterator();
}
public boolean isFiltered() {
if (completeGraph.getVertexCount() != getVertexCount()) {
return true;
}
return completeGraph.getEdgeCount() != getEdgeCount();
}
public void clearFilter() {
vertices.clear();
edges.clear();
restoreAllVertices();
restoreAllEdges();
}
/**
* Returns all vertices that are reachable by the given vertices.
*
* <P>This method is needed if you wish to find relationships that have been filtered
* out.
*
* @param sourceVertices the vertices for which to find the other reachable vertices
* @return the reachable vertices
*/
public Set<V> getAllReachableVertices(Set<V> sourceVertices) {
Set<E> relatedEdges = new HashSet<>();
for (V v : sourceVertices) {
relatedEdges.addAll(GraphAlgorithms.getEdgesFrom(completeGraph, asList(v), true));
relatedEdges.addAll(GraphAlgorithms.getEdgesFrom(completeGraph, asList(v), false));
}
return GraphAlgorithms.toVertices(relatedEdges);
}
/**
* Returns all edges connected to the given vertices.
*
* <P>This method is needed if you wish to find relationships that have been filtered
* out.
*
* @param sourceVertices the vertices for which to get the edges
* @return the reachable edges
*/
public Set<E> getAllEdges(Set<V> sourceVertices) {
Set<E> connectedEdges = new HashSet<>();
for (V vertex : sourceVertices) {
connectedEdges.addAll(CollectionUtils.nonNull(completeGraph.getIncidentEdges(vertex)));
}
return connectedEdges;
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void restoreAllVertices() {
Collection<V> allVertices = completeGraph.getVertices();
performInternalUpdate(() -> {
allVertices.forEach(v -> addVertex(v));
});
}
private void restoreAllEdges() {
Collection<E> allEdges = completeGraph.getEdges();
performInternalUpdate(() -> {
allEdges.forEach(e -> addEdge(e));
});
}
private void maybeRestoreVertices(Collection<V> toRestore) {
for (V v : toRestore) {
if (!completeGraph.containsVertex(v)) {
// don't restore vertices that were never added through the normal interface
continue;
}
performInternalUpdate(() -> super.addVertex(v));
}
}
private void maybeRestoreEdges(Collection<E> toUnfilter) {
for (E e : toUnfilter) {
if (!completeGraph.containsEdge(e)) {
// don't restore edges that were never added through the normal interface
continue;
}
V start = e.getStart();
V end = e.getEnd();
// only add the edge if both vertices are in the graph
if (containsVertex(start) && containsVertex(end)) {
performInternalUpdate(() -> super.addEdge(e));
}
}
}
private void maybeRestoreRelatedEdges(Collection<V> toUnfilter) {
for (V v : toUnfilter) {
Collection<E> vertexEdges = completeGraph.getIncidentEdges(v);
if (vertexEdges == null) {
continue;
}
for (E e : vertexEdges) {
V start = e.getStart();
V end = e.getEnd();
// only add the edge if both vertices are in the graph
if (containsVertex(start) && containsVertex(end)) {
performInternalUpdate(() -> super.addEdge(e));
}
}
}
}
/**
* This method is to be called internally to remove a vertex from this graph, but not the
* underlying 'complete graph'.
*
* @param v the vertex
*/
private void removeVertexFromView(V v) {
performInternalUpdate(() -> super.removeVertex(v));
}
/**
* This method is to be called internally to remove an edge from this graph, but not the
* underlying 'complete graph'.
*
* @param e the edge
*/
private void removeEdgeFromView(E e) {
performInternalUpdate(() -> super.removeEdge(e));
}
private void performInternalUpdate(Callback c) {
internalCallCount++;
try {
c.call();
}
finally {
internalCallCount--;
}
}
/**
* Performs a remove only if this graph is not in the process of an internal update
* @param c the callback to perform the remove
*/
private void maybePerformRemove(Callback c) {
if (isInternalUpdate()) {
//
// Ignore internal updates.
// We have to know when a remover operation should update us vs our 'complete graph'.
// When the client calls our public API, we wish to update our 'complete graph', when
// we trigger a filter operation to remove content from us, we do NOT want to
// update the 'complete graph', as it stores those removed items for later
// retrieval.
//
return;
}
c.call();
}
private boolean isInternalUpdate() {
return internalCallCount > 0;
}
private void maybePerformAdd(Callback c) {
if (isInternalUpdate()) {
// This is the opposite of removes--we always wish to add vertices when we are in
// the processes of restoring filtered vertices
c.call();
return;
}
// an outside API addition--only add to us if we are not filtered
if (isFiltered()) {
return;
}
// O.K., we are going to perform the add--now we have to mark the update as internal
// so all subsequent additions work (e.g., when adding a new edge, the vertices also
// get added)
performInternalUpdate(c);
}
//==================================================================================================
// Overridden Methods
//==================================================================================================
@Override
public boolean removeVertex(V v) {
boolean removed = super.removeVertex(v);
maybePerformRemove(() -> completeGraph.removeVertex(v));
return removed;
}
@Override
public void removeVertices(Iterable<V> verticesToRemove) {
List<E> edgesToRemove = new LinkedList<>();
List<V> removed = new LinkedList<>();
for (V v : verticesToRemove) {
if (super.containsVertex(v)) {
edgesToRemove.addAll(getIncidentEdges(v));
removed.add(v);
super.removeVertex(v);
}
}
maybePerformRemove(() -> {
completeGraph.removeVertices(verticesToRemove);
completeGraph.removeEdges(edgesToRemove);
});
verticesRemoved(removed);
}
@Override
public boolean removeEdge(E e) {
boolean removed = super.removeEdge(e);
List<E> asList = Arrays.asList(e);
if (removed) {
fireEdgesRemoved(asList);
}
maybePerformRemove(() -> completeGraph.removeEdge(e));
return removed;
}
@Override
public void removeEdges(Iterable<E> toRemove) {
toRemove.forEach(e -> super.removeEdge(e));
super.removeEdges(toRemove);
maybePerformRemove(() -> {
completeGraph.removeEdges(toRemove);
});
fireEdgesRemoved(toRemove);
}
@Override
public boolean addVertex(V v) {
maybePerformAdd(() -> super.addVertex(v));
return completeGraph.addVertex(v);
}
@Override
public void addEdge(E e) {
maybePerformAdd(() -> super.addEdge(e));
completeGraph.addEdge(e);
}
@Override
public boolean addEdge(E e, Pair<? extends V> endpoints, EdgeType type) {
maybePerformAdd(() -> super.addEdge(e, endpoints, type));
return completeGraph.addEdge(e, endpoints, type);
}
@Override
public boolean addEdge(E e, Collection<? extends V> edgeVertices) {
maybePerformAdd(() -> super.addEdge(e, edgeVertices));
return completeGraph.addEdge(e, edgeVertices);
}
@Override
public boolean addEdge(E e, Collection<? extends V> edgeVertices, EdgeType type) {
maybePerformAdd(() -> super.addEdge(e, edgeVertices, type));
return completeGraph.addEdge(e, edgeVertices, type);
}
@Override
public boolean addEdge(E e, V v1, V v2) {
maybePerformAdd(() -> super.addEdge(e, v1, v2));
return completeGraph.addEdge(e, v1, v2);
}
@Override
public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
maybePerformAdd(() -> super.addEdge(e, v1, v2, edgeType));
return completeGraph.addEdge(e, v1, v2, edgeType);
}
@Override
public boolean addEdge(E e, Pair<? extends V> endpoints) {
maybePerformAdd(() -> super.addEdge(e, endpoints));
return completeGraph.addEdge(e, endpoints);
}
@Override
public void dispose() {
completeGraph.dispose();
vertices.clear();
edges.clear();
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class UnfilteredGraph extends DefaultVisualGraph<V, E> {
@Override
public VisualGraphLayout<V, E> getLayout() {
// stub
return null;
}
@Override
public DefaultVisualGraph<V, E> copy() {
// stub
return null;
}
@Override
public void dispose() {
vertices.clear();
edges.clear();
}
}
}

View file

@ -0,0 +1,56 @@
/* ###
* 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.graphs;
import java.util.Collection;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
/**
* A visual graph with methods needed to facilitate grouping of vertices.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public abstract class GroupingVisualGraph<V extends VisualVertex, E extends VisualEdge<V>>
extends DefaultVisualGraph<V, E> {
/**
* Finds a vertex that matches the given vertex.
*
* <P>Grouping can trigger vertex adds and removals. This method is a way for subclasses
* to search for a vertex that matches the given vertex, but may or may not be the same
* instance.
*
* @param v the vertex
* @return the matching vertex or null
*/
public abstract V findMatchingVertex(V v);
/**
* The same as {@link #findMatchingVertex(VisualVertex)}, except that you can provide a
* collection of vertices to be ignored.
*
* <P>This is useful during graph transformations when duplicate vertices may be in the
* graph at the same time.
*
* @param v the vertex
* @param ignore vertices to ignore when searching
* @return the matching vertex or null
*/
public abstract V findMatchingVertex(V v, Collection<V> ignore);
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.graphs;
import ghidra.graph.VisualGraph;
import ghidra.graph.jung.JungDirectedGraph;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
/**
* A class to combine the {@link JungDirectedGraph} and the {@link VisualGraph}
* interfaces
*
* @param <V> the vertex type
* @param <E> the edge type
*/
//@formatter:off
public abstract class JungDirectedVisualGraph <V extends VisualVertex,
E extends VisualEdge<V>>
extends JungDirectedGraph<V, E> implements VisualGraph<V, E> {
//@formatter:on
@Override
// overridden to redefine the return type
public abstract JungDirectedVisualGraph<V, E> copy();
}

View file

@ -0,0 +1,159 @@
/* ###
* 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.job;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import ghidra.util.exception.AssertException;
import ghidra.util.task.BusyListener;
public abstract class AbstractAnimator {
private Logger log = LogManager.getLogger(AbstractAnimator.class);
protected Animator animator;
private boolean hasFinished; // flag to signal not to repeatedly call finish
private BusyListener busyListener;
protected abstract Animator createAnimator();
/**
* A callback given when this animator has run to completion. This will be called whether
* the animator is stopped prematurely or ends naturally.
*/
protected abstract void finished();
public void setBusyListener(BusyListener listener) {
this.busyListener = listener;
}
protected void followOnAnimatorScheduled() {
trace("followOnAnimatorScheduled");
stopMe();
}
public void start() {
trace("start() - " + getClass().getSimpleName());
validateNotFinished();
animator = createAnimator();
trace("\tcreated animator - " + animator);
if (animator == null) {
callFinish();
return;
}
if (busyListener != null) {
animator.addTarget(new TimingTargetAdapter() {
@Override
public void begin() {
busyListener.setBusy(true);
}
@Override
public void end() {
busyListener.setBusy(false);
}
});
}
animator.addTarget(new TimingTargetAdapter() {
@Override
public void end() {
callFinish();
}
});
// this can happen if some external force calls stop() on this animator while it
// is building itself
validateNotFinished();
animator.start();
}
private void validateNotFinished() {
trace("validateNotFinished()");
if (hasFinished) {
trace("\talready finished - programming error!");
throw new AssertException("Called start() on an animator that has already " +
"finished! Animator: " + getClass().getSimpleName());
}
}
private void callFinish() {
trace("callFinish()");
if (hasFinished) {
trace("\talready finished");
return; // already called
}
hasFinished = true;
finished();
}
/**
* Stops this animator <b>and all scheduled animators!</b>
*/
public void stop() {
trace("stop()");
stopMe();
}
protected void stopMe() {
trace("stopMe()");
if (animator == null) {
hasFinished = true;
return;
}
animator.stop();
}
public boolean isRunning() {
trace("isRunning()");
if (amIRunning()) {
trace("\tstill running");
return true;
}
return false;
}
public boolean hasFinished() {
return hasFinished;
}
private boolean amIRunning() {
trace("amIRunning()");
if (animator == null) {
return false;
}
return animator.isRunning();
}
protected void trace(String message) {
log.trace(message + " " + getClass().getSimpleName() + " (" +
System.identityHashCode(this) + ")\t");
}
}

View file

@ -0,0 +1,161 @@
/* ###
* 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.job;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import ghidra.util.Msg;
import ghidra.util.task.BusyListener;
public abstract class AbstractAnimatorJob implements GraphJob {
/**
* A somewhat arbitrary vertex count past which not to animate actions that are intensive.
*/
public static final int TOO_BIG_TO_ANIMATE = 125;
protected Logger log = LogManager.getLogger(AbstractAnimator.class);
private boolean isFinished;
private BusyListener busyListener;
protected Animator animator;
protected boolean isShortcut;
private GraphJobListener finishedListener;
protected abstract Animator createAnimator();
/**
* A callback given when this animator has run to completion. This will be called whether
* the animator is stopped prematurely or ends naturally.
*/
protected abstract void finished();
public void setBusyListener(BusyListener listener) {
this.busyListener = listener;
}
@Override
public boolean canShortcut() {
return true;
}
@Override
public void shortcut() {
trace("shortcut(): " + this);
isShortcut = true;
stop();
}
@Override
public void execute(GraphJobListener listener) {
this.finishedListener = listener;
start();
}
@Override
public boolean isFinished() {
return isFinished;
}
@Override
public void dispose() {
trace("dispose(): " + this);
stop();
}
protected void trace(String message) {
log.trace(message + " " + getClass().getSimpleName() + " (" +
System.identityHashCode(this) + ")\t");
}
void start() {
trace("start() - " + getClass().getSimpleName());
animator = createAnimator();
trace("\tcreated animator - " + animator);
if (animator == null) {
callFinish();
return;
}
if (busyListener != null) {
animator.addTarget(new TimingTargetAdapter() {
@Override
public void begin() {
busyListener.setBusy(true);
}
});
}
animator.addTarget(new TimingTargetAdapter() {
@Override
public void end() {
callFinish();
}
});
animator.start();
}
private void callFinish() {
trace("callFinish()");
if (isFinished) {
trace("\talready finished");
return; // already called
}
try {
finished();
}
catch (Throwable t) {
Msg.error(this, "Unexpected error in AbstractAnimator: ", t);
}
isFinished = true;
// a null listener implies we were shortcut before we were started
trace("\tmaybe notify finished...");
if (finishedListener != null) {
trace("\tlistener is not null--calling");
finishedListener.jobFinished(this);
}
if (busyListener != null) {
busyListener.setBusy(false);
}
}
protected void stop() {
trace("stop()");
if (animator == null) {
callFinish();
return;
}
animator.stop();
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}

View file

@ -0,0 +1,260 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.Map.Entry;
import org.jdesktop.animation.timing.Animator;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.util.Caching;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.*;
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
import ghidra.util.SystemUtilities;
import ghidra.util.task.TaskLauncher;
/**
* A job to transition vertices in a graph for location and visibility. The parent class
* handled the opacity callback. The progress of the job is used by this class to move
* vertices from the the start location to the final destination, where the progress is the
* percentage of the total move to display.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public abstract class AbstractGraphTransitionJob<V extends VisualVertex, E extends VisualEdge<V>>
extends AbstractGraphVisibilityTransitionJob<V, E> {
protected final VisualGraphLayout<V, E> graphLayout;
/** A start and end point for each vertex */
protected Map<V, TransitionPoints> vertexLocations = new HashMap<>();
/** A start and end point for each edge articulation */
protected Map<E, List<ArticulationTransitionPoints>> edgeArticulationLocations =
new HashMap<>();
// not sure why we need these, as opposed to just using the 'edgeArticulationLocations', but
// these points are different--I would like a better description of how these are different
protected Map<E, List<Point2D>> finalEdgeArticulations = new HashMap<>();
protected AbstractGraphTransitionJob(GraphViewer<V, E> viewer, boolean useAnimation) {
super(viewer, useAnimation);
this.graphLayout = viewer.getVisualGraphLayout();
}
/**
* Create the vertex locations that will be transitioned over the life of this animator.
* The locations are in <code>layout space</code>. This method is expected to update
* {@link #vertexLocations} (and optionally {@link #edgeArticulationLocations}).
*/
protected abstract void initializeVertexLocations();
@Override
public boolean canShortcut() {
// for now we've decided to always allow the animation to play out
return false;
}
@Override
public void shortcut() {
throw new UnsupportedOperationException("Cannot shortcut this job: " + this);
}
@Override
protected Animator createAnimator() {
initializeVertexLocations();
clearLocationCache();
return super.createAnimator();
}
@Override
protected void finished() {
clearLocationCache();
installFinalEdgeArticulations();
super.finished();
}
/**
* Callback from our animator.
*/
@Override
public void setPercentComplete(double percentComplete) {
updateNewVertexPositions(percentComplete);
super.setPercentComplete(percentComplete);
}
protected void updatePointFromPercentComplete(TransitionPoints transitionPoints,
double percentComplete, Point2D updatePoint) {
double startX = transitionPoints.startPoint.getX();
double destinationX = transitionPoints.destinationPoint.getX();
double deltaX = (destinationX - startX) * percentComplete;
double startY = transitionPoints.startPoint.getY();
double destinationY = transitionPoints.destinationPoint.getY();
double deltaY = (destinationY - startY) * percentComplete;
double newX = transitionPoints.startPoint.getX() + deltaX;
double newY = transitionPoints.startPoint.getY() + deltaY;
updatePoint.setLocation(newX, newY);
}
protected void installFinalEdgeArticulations() {
Collection<E> edges = graph.getEdges();
for (E edge : edges) {
List<Point2D> articulations = finalEdgeArticulations.get(edge);
if (articulations == null) {
// Depending upon the value of 'relayout', we may have removed articulations
articulations = Collections.emptyList();
}
edge.setArticulationPoints(articulations);
}
}
private void updateNewVertexPositions(double percentComplete) {
//
// The new position is some percentage of the distance between the start
// positions and the destination positions. The grouped vertex does not change positions.
//
Set<Entry<V, TransitionPoints>> entrySet = vertexLocations.entrySet();
for (Entry<V, TransitionPoints> entry : entrySet) {
Point2D newVertexLocation = new Point2D.Double();
updatePointFromPercentComplete(entry.getValue(), percentComplete, newVertexLocation);
// the new values won't be read if we don't clear the cache
clearLocationCache();
V newVertex = entry.getKey();
graphLayout.setLocation(newVertex, newVertexLocation, ChangeType.TRANSIENT);
}
Set<Entry<E, List<ArticulationTransitionPoints>>> edgeEntries =
edgeArticulationLocations.entrySet();
for (Entry<E, List<ArticulationTransitionPoints>> entry : edgeEntries) {
List<ArticulationTransitionPoints> transitions = entry.getValue();
for (ArticulationTransitionPoints transitionPoint : transitions) {
// manipulate the edges locations directly, as not to incur excess object creation
// (the start point is copied from the edge during initialization)
Point2D updatePoint = transitionPoint.pointToUpdate;
updatePointFromPercentComplete(transitionPoint, percentComplete, updatePoint);
}
}
}
protected LayoutPositions<V, E> calculateDefaultLayoutLocations() {
LayoutPositions<V, E> positions = calculateDefaultLayoutLocations(Collections.emptySet());
return positions;
}
protected LayoutPositions<V, E> getCurrentLayoutLocations() {
return LayoutPositions.getCurrentPositions(graph, graphLayout);
}
protected Point2D toLocation(V v) {
return graphLayout.apply(v);
}
// note: due to the caching nature of some layouts, if we don't reset this, then
// some of our GUI calculations will be incorrect (like when we try to fit the
// satellite in it's window). So, we always have to clear the cache when we set locations
protected void clearLocationCache() {
Layout<V, E> jungLayout = viewer.getGraphLayout();
((Caching) jungLayout).clear();
}
//==================================================================================================
// Utility Methods
//==================================================================================================
/**
* Calculates default vertex locations for the current graph by using the current layout,
* excluding those vertices in the given <i>ignore</i> set. The graph,
* layout and vertices will be unaltered.
*
* @param verticesToIgnore The set of vertices which should be excluded from the layout process
* @return The mapping of all arranged vertices to their respective locations
*/
public LayoutPositions<V, E> calculateDefaultLayoutLocations(Set<V> verticesToIgnore) {
VisualGraphLayout<V, E> layout = graph.getLayout();
VisualGraph<V, E> newGraph = graph.copy();
newGraph.removeVertices(verticesToIgnore);
//
// A bit of kludge. The layout in the task will fail if the vertices' components are not
// realized, which must happen on the Swing thread.
//
ensureVerticesComponentsCreated(newGraph);
CalculateLayoutLocationsTask<V, E> task =
new CalculateLayoutLocationsTask<>(newGraph, layout);
new TaskLauncher(task, null, 2000); // this call will block
return task.getLocations();
}
private void ensureVerticesComponentsCreated(VisualGraph<V, E> g) {
SystemUtilities.assertThisIsTheSwingThread(
"Cannot create vertex components off the Swing thread");
Collection<V> vertices = g.getVertices();
for (V v : vertices) {
v.getComponent();
}
}
//==================================================================================================
// Inner Classes
//==================================================================================================
protected class TransitionPoints {
public Point2D startPoint;
public Point2D destinationPoint;
public TransitionPoints(Point2D startPoint, Point2D destinationPoint) {
if (startPoint == null) {
throw new IllegalArgumentException("Start point cannot be null");
}
if (destinationPoint == null) {
throw new IllegalArgumentException("Destination point cannot be null");
}
this.startPoint = startPoint;
this.destinationPoint = destinationPoint;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[start=" + startPoint + ", dest=" +
destinationPoint + "]";
}
}
protected class ArticulationTransitionPoints extends TransitionPoints {
public Point2D pointToUpdate;
public ArticulationTransitionPoints(Point2D currentEdgePoint, Point2D destinationPoint) {
super((Point2D) currentEdgePoint.clone(), destinationPoint);
this.pointToUpdate = currentEdgePoint;
}
}
}

View file

@ -0,0 +1,134 @@
/* ###
* 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.job;
import static util.CollectionUtils.nonNull;
import java.util.*;
import java.util.stream.Collectors;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.*;
/**
* A job that provides an animator and callbacks for transitioning the visibility of
* graph vertices. The opacity value will change from 0 to 1 over the course of the job.
* Subclasses can decide how to use the opacity value as it changes. For example, a
* subclass can fade in or out the vertices provided to the job.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public abstract class AbstractGraphVisibilityTransitionJob<V extends VisualVertex, E extends VisualEdge<V>>
extends AbstractAnimatorJob {
protected static final int NORMAL_DURATION = 1500;
protected static final int FAST_DURATION = 700;
protected int duration = NORMAL_DURATION;
protected final GraphViewer<V, E> viewer;
protected final VisualGraph<V, E> graph;
protected boolean useAnimation;
protected AbstractGraphVisibilityTransitionJob(GraphViewer<V, E> viewer, boolean useAnimation) {
this.useAnimation = useAnimation;
this.viewer = viewer;
this.graph = viewer.getVisualGraph();
// don't animate if we have too many vertices in the graph
if (isTooBigToAnimate()) {
this.useAnimation = false;
}
}
/**
* Returns true if the graph is too large for animation (usually due to performance issues).
*
* @return true if the graph is too large for animation
*/
protected boolean isTooBigToAnimate() {
return graph.getVertexCount() >= TOO_BIG_TO_ANIMATE;
}
/**
* Callback from our animator.
*/
public void setPercentComplete(double percentComplete) {
trace("setPercentComplete() callback: " + percentComplete);
updateOpacity(percentComplete);
viewer.repaint();
}
@Override
protected Animator createAnimator() {
if (!useAnimation) {
return null;
}
updateOpacity(0);
Animator newAnimator =
PropertySetter.createAnimator(duration, this, "percentComplete", 0.0, 1.0);
newAnimator.setAcceleration(0f);
newAnimator.setDeceleration(0.8f);
return newAnimator;
}
@Override
protected void finished() {
setPercentComplete(1D);
viewer.repaint();
}
protected void updateOpacity(double percentComplete) {
// By default we don't change opacity for just moving vertices around. Some children
// may be modifying the graph and they can use this callback to change opacity as the
// job progresses.
}
protected Set<E> getEdges(Collection<V> vertices) {
//@formatter:off
return vertices
.stream()
.map(v -> nonNull(graph.getIncidentEdges(v)))
.flatMap(collection -> collection.stream())
.collect(Collectors.toSet())
;
//@formatter:on
}
protected Collection<E> getEdges(V vertex) {
List<E> edges = new LinkedList<>();
Collection<E> inEdges = nonNull(graph.getInEdges(vertex));
edges.addAll(inEdges);
Collection<E> outEdges = nonNull(graph.getOutEdges(vertex));
edges.addAll(outEdges);
return edges;
}
}

View file

@ -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.graph.job;
import java.util.function.Supplier;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import edu.uci.ics.jung.visualization.VisualizationServer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import edu.uci.ics.jung.visualization.renderers.Renderer.Edge;
import ghidra.graph.viewer.edge.VisualEdgeRenderer;
public class EdgeHoverAnimator<V, E> extends AbstractAnimator {
private static final int MOTION_SPEED = 2000;
private static final Supplier<Float> ANIMATED_DASHED_LINE_FLOAT_GIVER = () -> {
long currentTimeMillis = System.currentTimeMillis();
return (MOTION_SPEED - (currentTimeMillis % MOTION_SPEED)) / (float) MOTION_SPEED;
};
private static final int SLEEP_AMOUNT_MILLISECONDS = 300;
private static final int DURATION = 5000;
private long lastPaintTime = System.nanoTime();
private final VisualizationServer<V, E> primaryViewer;
private final VisualizationServer<V, E> satelliteViewer;
private final boolean useAnimation;
public EdgeHoverAnimator(VisualizationServer<V, E> primaryViewer,
VisualizationServer<V, E> satelliteViewer, boolean useAnimation) {
this.primaryViewer = primaryViewer;
this.satelliteViewer = satelliteViewer;
this.useAnimation = useAnimation;
}
@Override
protected Animator createAnimator() {
if (!useAnimation) {
return null;
}
Animator newAnimator =
PropertySetter.createAnimator(DURATION, this, "nextPaint", 0, DURATION);
newAnimator.setAcceleration(0.0f);
newAnimator.setDeceleration(0.8f);
return newAnimator;
}
@Override
protected void finished() {
paintDashedLineOnce();
}
public void setNextPaint(int nextPaint) {
long currentTime = System.nanoTime();
long ellapsedMilliseconds = (currentTime - lastPaintTime) / 1000000;
if (ellapsedMilliseconds > SLEEP_AMOUNT_MILLISECONDS) {
lastPaintTime = currentTime;
paintDashedLineOnce();
}
}
private void paintDashedLineOnce() {
float newPaintOffset = ANIMATED_DASHED_LINE_FLOAT_GIVER.get();
updateRendererPaintOffset(primaryViewer, newPaintOffset);
updateRendererPaintOffset(satelliteViewer, newPaintOffset);
primaryViewer.repaint();
satelliteViewer.repaint();
}
private void updateRendererPaintOffset(VisualizationServer<V, E> viewer, float newPaintOffset) {
Renderer<V, E> renderer = viewer.getRenderer();
Edge<V, E> edgeRenderer = renderer.getEdgeRenderer();
if (!(edgeRenderer instanceof VisualEdgeRenderer)) {
return; // something is wrong here!
}
VisualEdgeRenderer<?, ?> functionGraphEdgeRenderer =
(VisualEdgeRenderer<?, ?>) edgeRenderer;
functionGraphEdgeRenderer.setDashingPatternOffset(newPaintOffset);
}
}

View file

@ -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.graph.job;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.util.Objects;
import org.jdesktop.animation.timing.Animator;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import ghidra.graph.viewer.*;
// TODO doc - the area is expected to be vertex relative, where
// vertex relative means that the value is from inside the vertex, or the vertex's
// coordinate space (like a component that is inside the vertex)
public class EnsureAreaVisibleAnimatorFunctionGraphJob<V extends VisualVertex, E extends VisualEdge<V>>
extends MoveViewAnimatorFunctionGraphJob<V, E> {
private final SatelliteGraphViewer<V, E> satelliteViewer;
private final V vertex;
private final Rectangle visibleArea;
private Point2D preCreatedDestinaton;
public EnsureAreaVisibleAnimatorFunctionGraphJob(VisualizationViewer<V, E> primaryViewer,
SatelliteGraphViewer<V, E> satelliteViewer, V vertex, Rectangle visibleArea,
boolean useAnimation) {
super(primaryViewer, useAnimation);
this.satelliteViewer = Objects.requireNonNull(satelliteViewer);
this.vertex = Objects.requireNonNull(vertex);
this.visibleArea = Objects.requireNonNull(visibleArea);
}
@Override
protected Animator createAnimator() {
Rectangle viewSpaceRectangle =
GraphViewerUtils.translateRectangleFromVertexRelativeSpaceToViewSpace(viewer, vertex,
visibleArea);
//
// Get the point to which we will move if any of the cursor is obscured
//
Point newPoint =
new Point((int) viewSpaceRectangle.getCenterX(), (int) viewSpaceRectangle.getCenterY());
// get the point of the cursor, centered in the vertex (this prevents jumping from
// side-to-side as we move the vertex)
Rectangle vertexBounds = GraphViewerUtils.getVertexBoundsInViewSpace(viewer, vertex);
int vertexCenterX = vertexBounds.x + (vertexBounds.width >> 1);
newPoint.x = vertexCenterX;
// see if the cursor bounds are not completely in screen
Rectangle viewerBounds = viewer.getBounds();
if (!viewerBounds.contains(viewSpaceRectangle)) {
preCreatedDestinaton = newPoint;
return super.createAnimator();
}
if (!satelliteViewer.isDocked()) {
return null; // cannot obscure if not docked
}
if (!satelliteViewer.isShowing()) {
return null; // nothing to do
}
// see if the satellite is hiding the area
Rectangle satelliteBounds = satelliteViewer.getBounds();
if (!satelliteBounds.contains(viewSpaceRectangle)) {
return null; // nothing to do
}
preCreatedDestinaton = newPoint;
return super.createAnimator();
}
@Override
protected Point2D createDestination() {
if (preCreatedDestinaton == null) {
return null; // we chose not to change move the view
}
return GraphViewerUtils.getOffsetFromCenterForPointInViewSpace(viewer,
preCreatedDestinaton);
}
@Override
public void setOffset(Point2D offsetFromOriginalPoint) {
if (preCreatedDestinaton == null) {
// This method gets called back after the animator is finished. If we chose not to
// do work, then just exit
return;
}
super.setOffset(offsetFromOriginalPoint);
}
}

View file

@ -0,0 +1,209 @@
/* ###
* 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.job;
import static util.CollectionUtils.asSet;
import static util.CollectionUtils.asStream;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import ghidra.graph.graphs.FilteringVisualGraph;
import ghidra.graph.viewer.*;
/**
* Uses the given filter to fade out vertices that do not pass. Vertices that pass the filter
* will be included in the graph. Not only will passing vertices be included, but so too
* will any vertices reachable from those vertices.
*
* <P>This job will update the graph so that any previously filtered vertices will be put
* back into the graph.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class FilterVerticesJob<V extends VisualVertex, E extends VisualEdge<V>>
extends AbstractGraphVisibilityTransitionJob<V, E> {
/** The minimum threshold for visibility when fading out */
private static final double MIN_ALPHA = 0.2;
// false will leave the vertices faded out, but not removed from the graph
private boolean removeVertices;
private FilteringVisualGraph<V, E> filterGraph;
private Set<V> passedVertices;
private Set<V> failedVertices;
private Set<E> failedEdges;
private Set<E> passedEdges;
private Predicate<V> filter;
/**
* Constructor
*
* @param viewer the viewer upon which to operate
* @param graph the graph to filter
* @param filter the predicate used to determine what passes the filter
* @param remove true signals to remove the vertices from the view; false signals to leave
* them visible, but faded to show that they failed the filter
*/
public FilterVerticesJob(GraphViewer<V, E> viewer, FilteringVisualGraph<V, E> graph,
Predicate<V> filter, boolean remove) {
super(viewer, true);
this.filter = filter;
this.removeVertices = remove;
this.filterGraph = graph;
// Note: we cannot initialize here due to the fact that another job may be running
// when this job is created. That job will be making changes to the graph after
// this constructor is called, which could invalidate work done at this point.
}
@Override
void start() {
initialize();
super.start();
}
private void initialize() {
//
// Basic Algorithm
// 1) Filter the entire universe of vertices/edges
// 2) Get current vertices/edges that need to be removed (this happens by simply
// not restoring those vertices/edges that are already filtered)
// 3) Restore any filtered vertices that now pass the filter
//
// 1)
//@formatter:off
Iterator<V> vertices = filterGraph.getAllVertices();
Set<V> matching = asStream(vertices)
.filter(filter)
.collect(Collectors.toSet())
;
//@formatter:on
Set<V> related = filterGraph.getAllReachableVertices(matching);
matching.addAll(related);
passedVertices = matching;
// 2)
failedVertices = findCurrentVerticesFailingTheFitler(matching);
failedEdges = filterGraph.getAllEdges(failedVertices);
Set<E> allRelatedEdges = filterGraph.getAllEdges(passedVertices);
allRelatedEdges.removeAll(failedEdges);
passedEdges = allRelatedEdges;
// 3)
filterGraph.unfilterVertices(passedVertices);
}
private Set<V> findCurrentVerticesFailingTheFitler(Set<V> validVertices) {
UnmodifiableIterator<V> nonMatchingIterator =
Iterators.filter(filterGraph.getUnfilteredVertices(), v -> !validVertices.contains(v));
Set<V> nonMatching = asSet(nonMatchingIterator);
return nonMatching;
}
/*
* This is the callback from the animator, with values that range from 0.0 to 1.0.
*/
@Override
protected void updateOpacity(double percentComplete) {
//
// Fade Out will start with the current alpha, bringing the value down to the minimum.
// This will only have an effect if the current alpha is not already at the minimum,
// such as from a previous filter.
//
double percentRemaining = 1.0 - percentComplete;
double fadeOutAlpha = Math.max(percentRemaining, getMinimumAlpha());
failedVertices.forEach(v -> fadeOutAlpha(v, fadeOutAlpha));
failedEdges.forEach(e -> fadeOutAlpha(e, fadeOutAlpha));
//
// Fade In will start with the current alpha, bringing the value up to 1.0. This will
// only have an effect if the current alpha is not already 1.0, such as from a
// previous filter.
//
double fadeInAlpha = percentComplete;
passedVertices.forEach(v -> fadeInAlpha(v, fadeInAlpha));
passedEdges.forEach(e -> fadeInAlpha(e, fadeInAlpha));
}
private double getMinimumAlpha() {
return removeVertices ? 0D : MIN_ALPHA;
}
private void fadeOutAlpha(V v, double fadeOutAlpha) {
// keep the old alpha if it is already faded out
double alpha = v.getAlpha();
double newAlpha = Math.min(alpha, fadeOutAlpha);
v.setAlpha(newAlpha);
}
private void fadeOutAlpha(E e, double fadeOutAlpha) {
// keep the old alpha if it is already faded out
double alpha = e.getAlpha();
double newAlpha = Math.min(alpha, fadeOutAlpha);
e.setAlpha(newAlpha);
}
private void fadeInAlpha(V v, double fadeInAlpha) {
// keep the new alpha if it is already faded in
double alpha = v.getAlpha();
double newAlpha = Math.max(alpha, fadeInAlpha);
v.setAlpha(newAlpha);
}
private void fadeInAlpha(E e, double fadeInAlpha) {
// keep the new alpha if it is already faded in
double alpha = e.getAlpha();
double newAlpha = Math.max(alpha, fadeInAlpha);
e.setAlpha(newAlpha);
}
@Override
protected void finished() {
// We know that init() will setup our vertices to filter. If they are null, then
// init() wasn't called, because start() was never called.
if (passedVertices == null) {
initialize();
}
super.finished();
if (removeVertices) {
filterGraph.filterVertices(failedVertices);
}
}
}

View file

@ -0,0 +1,187 @@
/* ###
* 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.job;
import static ghidra.graph.viewer.GraphViewerUtils.*;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashSet;
import java.util.Set;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import util.CollectionUtils;
/**
* A job to scale one or more viewers such that the contained graph will fit entirely inside the
* viewing area.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class FitGraphToViewJob<V extends VisualVertex, E extends VisualEdge<V>>
implements GraphJob {
private final Set<VisualizationViewer<V, E>> viewers = new HashSet<>();
private boolean isFinished;
private final boolean onlyResizeWhenTooBig;
@SafeVarargs
public FitGraphToViewJob(VisualizationServer<V, E>... viewers) {
for (VisualizationServer<V, E> viewer : viewers) {
if (!(viewer instanceof VisualizationViewer)) {
throw new IllegalArgumentException("VisualizationServer is not an instance of " +
"VisualizationViewer. We currently need this for bounds information.");
}
this.viewers.add((VisualizationViewer<V, E>) viewer);
}
this.onlyResizeWhenTooBig = false;
}
public FitGraphToViewJob(VisualizationServer<V, E> viewer, boolean onlyResizeWhenTooBig) {
if (!(viewer instanceof VisualizationViewer)) {
throw new IllegalArgumentException("VisualizationServer is not an instance of " +
"VisualizationViewer. We currently need this for bounds information.");
}
this.viewers.add((VisualizationViewer<V, E>) viewer);
this.onlyResizeWhenTooBig = onlyResizeWhenTooBig;
}
@Override
public boolean canShortcut() {
return true;
}
@Override
public void execute(GraphJobListener listener) {
if (isFinished) {
return;
}
if (graphIsEmpty()) {
return;
}
for (VisualizationViewer<V, E> viewer : viewers) {
Rectangle graphBounds = getTotalGraphSizeInLayoutSpace(viewer);
boolean resized = scaleToFitViewer(viewer, graphBounds);
if (resized) {
centerGraph(viewer, graphBounds);
}
}
isFinished = true;
listener.jobFinished(this);
}
private boolean graphIsEmpty() {
VisualizationViewer<V, E> viewer = CollectionUtils.any(viewers);
Graph<V, E> graph = viewer.getGraphLayout().getGraph();
return graph.getVertexCount() == 0;
}
@Override
public boolean isFinished() {
return isFinished;
}
@Override
public void shortcut() {
// just mark as finished and skip the work; this allows this job to be run many times,
// with only the last one performing any work
isFinished = true;
}
@Override
public void dispose() {
isFinished = true;
}
private boolean scaleToFitViewer(VisualizationViewer<V, E> visualizationViewer,
Rectangle2D graphBounds) {
Dimension windowSize = visualizationViewer.getSize();
Rectangle bounds = graphBounds.getBounds();
Shape viewShape = translateShapeFromLayoutSpaceToViewSpace(bounds, visualizationViewer);
Rectangle viewBounds = viewShape.getBounds();
boolean fitsInView =
viewBounds.width < windowSize.width && viewBounds.height < windowSize.height;
if (onlyResizeWhenTooBig && fitsInView) {
return false;
}
Double scaleRatio = getScaleRatioToFitInDimension(bounds.getSize(), windowSize);
if (scaleRatio == null) {
return true;
}
if (scaleRatio > 1.0) {
scaleRatio = 1.0;
}
// add some padding and make it relative to the new scale
int unscaledPaddingSize = 10;
addPaddingToRectangle((int) (unscaledPaddingSize / scaleRatio), bounds);
scaleRatio = getScaleRatioToFitInDimension(bounds.getSize(), windowSize);
if (scaleRatio == null) {
return true;
}
RenderContext<V, E> renderContext = visualizationViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
MutableTransformer viewTransformer = multiLayerTransformer.getTransformer(Layer.VIEW);
viewTransformer.setScale(scaleRatio, scaleRatio, new Point(0, 0));
return true;
}
private void centerGraph(VisualizationViewer<V, E> visualizationViewer, Rectangle graphBounds) {
RenderContext<?, ?> context = visualizationViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = context.getMultiLayerTransformer();
// ...get the offset we need to revert the view translation back to zero
MutableTransformer viewTransformer = multiLayerTransformer.getTransformer(Layer.VIEW);
viewTransformer.setTranslate(0, 0);
// ...get the offset we need to revert the layout translation back to zero
MutableTransformer layoutTransformer = multiLayerTransformer.getTransformer(Layer.LAYOUT);
layoutTransformer.setTranslate(0, 0);
// ...get the center of the graph and center the viewer over that point
Point2D viewCenter = visualizationViewer.getCenter();
viewCenter = translatePointFromViewSpaceToLayoutSpace(viewCenter, visualizationViewer);
Point2D.Double graphCenter =
new Point2D.Double(graphBounds.getCenterX(), graphBounds.getCenterY());
double centerX = graphCenter.getX() - viewCenter.getX();
double centerY = graphCenter.getY() - viewCenter.getY();
layoutTransformer.setTranslate(-centerX, -centerY);
}
@Override
public String toString() {
return "Fit Graph to View Job";
}
}

View file

@ -0,0 +1,60 @@
/* ###
* 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.job;
/**
* A graph job is an item of work that needs to be performed.
*
* @see GraphJobRunner
*/
public interface GraphJob {
/**
* Tells this job to do its work. This call will be on the Swing thread. It is required
* that the given listener be called on the Swing thread when the job is finished.
*
* @param listener the listener this job is expected to call when its work is finished
*/
public void execute(GraphJobListener listener);
/**
* Returns true if the job can be told to stop running, but to still perform any final
* work before being done.
*
* @return true if the job can be shortcut
*/
public boolean canShortcut();
/**
* Tells this job to stop running, but to still perform any final work before being done.
*
* <P>Note: if your job is multi-threaded, then you must make sure to end your thread and
* work before returning from this method. If that cannot be done in a timely manner, then
* your {@link #canShortcut()} should return false.
*/
public void shortcut();
/**
* Returns true if this job has finished its work
* @return true if this job has finished its work
*/
public boolean isFinished();
/**
* Call to immediately stop this job, ignoring any exceptions or state issues that arise.
*/
public void dispose();
}

View file

@ -0,0 +1,24 @@
/* ###
* 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.job;
/**
* A listener to {@link GraphJob} state
*/
public interface GraphJobListener {
public void jobFinished(GraphJob job);
}

View file

@ -0,0 +1,345 @@
/* ###
* 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.job;
import java.util.*;
import ghidra.generic.function.Callback;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.QueueStub;
import ghidra.util.exception.AssertException;
/**
* A class to run {@link GraphJob}s. This class will queue jobs and will run them
* in the Swing thread. Job implementations may be multi-threaded, as they choose, by managing
* threads themselves. This is different than a typical job runner, which is usually
* itself threaded.
* <P>
* A job is considered finished when {@link #jobFinished(GraphJob)}
* is called on this class. After this callback, the next job will be run.
* <P>
* {@link #setFinalJob(GraphJob)} sets a job to be run last, after all jobs in the queue
* have finished.
*
* <P>When a job is added via {@link #schedule(GraphJob)}, any currently running job will
* be told to finish immediately, if it's {@link GraphJob#canShortcut()} returns true. If it
* cannot be shortcut, then it will be allowed to finish. Further, this logic will be applied
* to each job in the queue. So, if there are multiple jobs in the queue, which all return
* true for {@link GraphJob#canShortcut()}, then they will each be shortcut (allowing them
* to complete) before running the newly scheduled job.
*
* <P>This class is thread-safe in that you can {@link #schedule(GraphJob)} jobs from any
* thread.
*
* <P>Synchronization Policy: the methods that mutate fields of this class or read them
* must be synchronized.
*/
public class GraphJobRunner implements GraphJobListener {
private Queue<GraphJob> queue = new LinkedList<>();
private GraphJob finalJob;
private GraphJob currentJob;
private boolean isShortcutting = false;
public void schedule(GraphJob job) {
trace("schedule() - " + job);
Objects.requireNonNull(job, "Graph job cannot be null");
if (job.isFinished()) {
throw new IllegalArgumentException("cannot schedule a finished job!");
}
queue.add(job);
swing(this::shortCutAndRunNextJob);
}
private void swing(Runnable r) {
SystemUtilities.runIfSwingOrPostSwingLater(r);
}
/**
* Sets a job to run after all currently running and queued jobs. If a final job was already
* set, then that job will be replaced with the given job.
*
* @param job the job to run
*/
public synchronized void setFinalJob(GraphJob job) {
trace("setFinalJob() - " + job);
if (job.isFinished()) {
throw new IllegalArgumentException("cannot schedule a finished job!");
}
// simply overwrite any pending final job, as we can only have one
finalJob = Objects.requireNonNull(job, "Graph job cannot be null");
swing(this::maybeRunNextJob);
}
public synchronized boolean isBusy() {
if (!queue.isEmpty()) {
return true;
}
if (finalJob != null) {
return true;
}
return currentJob != null;
}
/*for testing*/ synchronized GraphJob getCurrentJob() {
return currentJob;
}
/**
* Causes all jobs to be finished as quickly as possible, calling {@link GraphJob#shortcut()}
* on each job.
*
* <P>Note: some jobs are not shortcut-able and will finish on their own time. Any jobs
* queued behind a non-shortcut-able job will <b>not</b> be shortcut.
*
* @see #dispose()
*/
public void finishAllJobs() {
swing(this::shortCutAll);
}
/**
* Clears any pending jobs, stops the currently running job ungracefully and updates this
* class so that any new jobs added will be ignored.
*/
public synchronized void dispose() {
trace("dispose()");
clearAllJobs();
queue = new QueueStub<>();
}
private synchronized void clearAllJobs() {
trace("clearAllJobs()");
finalJob = null;
Queue<GraphJob> oldQueue = queue;
queue = new QueueStub<>();
oldQueue.clear();
trace("\tcurrent job: " + currentJob);
if (currentJob != null) {
currentJob.dispose();
}
currentJob = null;
}
@Override
public void jobFinished(GraphJob job) {
String methodName = "jobFinished()";
trace(methodName + " " + job);
SystemUtilities.assertThisIsTheSwingThread(
"jobFinished() must be called in the Swing thread.");
synchronized (this) {
if (currentJob != null && job != currentJob) {
throw new AssertException(
"Received a callback from a job that is not my current job! Current job: " +
currentJob + " and finished job: " + job);
}
trace("\t" + methodName + " setting currentJob to null");
currentJob = null;
maybeRunNextJob();
}
}
/**
* Shortcut as many jobs as possible to clear the queue and then trigger the run of the
* remaining jobs.
*/
private synchronized void shortCutAndRunNextJob() {
String methodName = "shortcut()";
trace(methodName + " - currentJob?: " + currentJob);
if (queue.isEmpty()) {
trace("\t" + methodName + " no pending jobs; leaving");
// nothing to shortcut (leave any current job running when there are none waiting)
return;
}
performShortcutFunction(() -> {
shortcutAsMuchAsPossible(false);
});
//
// Run whatever is left
//
trace("\t" + methodName + " at end; calling runNextJob()");
maybeRunNextJob();
}
private synchronized void shortCutAll() {
String methodName = "shortcutAll()";
trace(methodName + " - currentJob?: " + currentJob);
performShortcutFunction(() -> {
boolean allWereShortcut = shortcutAsMuchAsPossible(true);
trace("\t\twere all jobs shortcut? " + allWereShortcut);
if (allWereShortcut) {
shortcutFinalJob();
}
});
}
private void performShortcutFunction(Callback callback) {
isShortcutting = true;
trace("\tset isShortcutting = true");
try {
callback.call();
}
finally {
isShortcutting = false;
trace("\t\tset isShortcutting = false");
}
}
private boolean shortcutAsMuchAsPossible(boolean shortcutAll) {
//
// See if we can shortcut the current job
//
if (!shortcutCurrentJob()) {
// cannot stop the current job; allow it to finish, processing the pending jobs later
return false;
}
if (!shortcutPendingJobs(shortcutAll)) {
return false;
}
return true;
}
/**
* Attempts to shortcut the currently running job, if there is one
*
* @return false if there is a currently running job that cannot be shortcut
*/
private boolean shortcutCurrentJob() {
String methodName = "shortcutCurrentJob()";
if (currentJob == null) {
return true; // nothing to do
}
if (!currentJob.canShortcut()) {
// can't stop the current job--let it go
trace("\t" + methodName + " current job cannot be shortcut; leaving");
return false;
}
trace("\t" + methodName + " calling shortcut on current job: " + currentJob);
// the shortcut() may trigger a callback to set the currentJob; we don't want to null
// out the current job if it is changed, but we do if it is not
GraphJob job = currentJob;
currentJob = null;
job.shortcut();
trace("\t\t" + methodName + " after calling shortcut on current job: " + job);
return true;
}
private boolean shortcutPendingJobs(boolean shortcutAll) {
//
// Attempt to shortcut the remaining items in the queue that we can shortcut, optionally
// leaving the last job. (The last job is the most recently added and
// presumably the one the client wants us to run.)
//
String methodName = "shortcutPendingJobs()";
trace("\t" + methodName + " queued job count: " + queue.size());
int limit = shortcutAll ? 0 : 1;
while (queue.size() > limit) {
GraphJob nextJob = queue.peek();
if (!nextJob.canShortcut()) {
// can't stop the next job--leave it to be run later
trace("\t" + methodName + " found pending job; cannot shortcut: " + nextJob);
return false;
}
nextJob = queue.poll();
trace("\t" + methodName + " calling shortcut on pending job: " + nextJob);
nextJob.shortcut();
trace("\t\t" + methodName + " after calling shortcut on pending job: " + nextJob);
}
return true;
}
private void shortcutFinalJob() {
trace("shortcutFinalJob() - " + finalJob);
if (finalJob != null && finalJob.canShortcut()) {
finalJob.shortcut();
finalJob = null;
}
}
private synchronized void maybeRunNextJob() {
String methodName = "maybeRunNextJob()";
trace(methodName);
if (isShortcutting) {
trace("\t" + methodName + " shortcutting the queue - not running the next job " +
currentJob);
return;
}
trace("\t" + methodName + " currentJob: " + currentJob);
if (currentJob != null) {
trace("\t" + methodName + " is it finished?: " + currentJob.isFinished());
// sanity check!
if (currentJob.isFinished()) {
throw new IllegalStateException("The following job did not call jobFinished() " +
"after performing its work: " + currentJob);
}
// do nothing if a job is running
trace("\t" + methodName + " not running another job; current job is not finished");
return;
}
GraphJob nextJob = queue.poll();
if (nextJob != null) {
trace("\t" + methodName + " setting currentJob to: " + nextJob + " and last job: " +
currentJob);
currentJob = nextJob;
currentJob.execute(this);
return;
}
if (finalJob != null) {
trace("\t" + methodName + " setting currentJob to final job: " + finalJob +
" and last job: " + currentJob);
currentJob = finalJob;
finalJob = null;
currentJob.execute(this);
}
}
private void trace(String message) {
Msg.trace(this, message);
}
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.VisualizationServer;
import ghidra.graph.viewer.GraphViewerUtils;
public class MoveVertexToCenterAnimatorFunctionGraphJob<V, E>
extends MoveViewAnimatorFunctionGraphJob<V, E> {
private final V vertex;
public MoveVertexToCenterAnimatorFunctionGraphJob(VisualizationServer<V, E> viewer, V vertex,
boolean useAnimation) {
super(viewer, useAnimation);
this.vertex = vertex;
}
@Override
protected Point2D createDestination() {
return GraphViewerUtils.getVertexOffsetFromLayoutCenter(viewer, vertex);
}
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.VisualizationServer;
import ghidra.graph.viewer.GraphViewerUtils;
public class MoveVertexToCenterTopAnimatorFunctionGraphJob<V, E>
extends MoveViewAnimatorFunctionGraphJob<V, E> {
private final V vertex;
public MoveVertexToCenterTopAnimatorFunctionGraphJob(VisualizationServer<V, E> viewer, V vertex,
boolean useAnimation) {
super(viewer, useAnimation);
this.vertex = vertex;
}
@Override
protected Point2D createDestination() {
return GraphViewerUtils.getVertexOffsetFromLayoutCenterTop(viewer, vertex);
}
}

View file

@ -0,0 +1,113 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import edu.uci.ics.jung.visualization.*;
public abstract class MoveViewAnimatorFunctionGraphJob<V, E>
extends AbstractAnimatorJob {
private static final int PIXELS_PER_SECOND = 750;
private static final int FRAME_PER_SECOND = 10;
private final Point2D lastPoint = new Point2D.Double();
private int totalFrames;
protected final VisualizationViewer<V, E> viewer;
private MultiLayerTransformer multiLayerTransformer;
private final boolean useAnimation;
private Point2D destination;
public MoveViewAnimatorFunctionGraphJob(VisualizationServer<V, E> viewer,
boolean useAnimation) {
if (!(viewer instanceof VisualizationViewer)) {
throw new IllegalArgumentException("VisualizationServer is not an instance of " +
"VisualizationViewer. We currently need this for bounds information.");
}
this.viewer = (VisualizationViewer<V, E>) viewer;
this.useAnimation = useAnimation;
}
protected abstract Point2D createDestination();
@Override
protected Animator createAnimator() {
destination = createDestination();
RenderContext<V, E> renderContext = viewer.getRenderContext();
multiLayerTransformer = renderContext.getMultiLayerTransformer();
if (!useAnimation) {
return null;
}
double offsetX = destination.getX();
double offsetY = destination.getY();
double durationX = Math.abs(offsetX / PIXELS_PER_SECOND);
double durationY = Math.abs(offsetY / PIXELS_PER_SECOND);
int totalFramesX = (int) (durationX * FRAME_PER_SECOND);
int totalFramesY = (int) (durationY * FRAME_PER_SECOND);
int mostFrames = Math.max(totalFramesX, totalFramesY);
mostFrames = Math.min(mostFrames, 15); // limit the time to something reasonable
totalFrames = Math.max(1, mostFrames); // at least one frame
double timeInSeconds = (double) totalFrames / (double) FRAME_PER_SECOND;
int duration = (int) Math.round(timeInSeconds * 1000); // put into millis
Point2D start = new Point2D.Double();
Animator newAnimator =
PropertySetter.createAnimator(duration, this, "offset", start, destination);
newAnimator.setAcceleration(0.2f);
newAnimator.setDeceleration(0.8f);
return newAnimator;
}
@Override
protected void finished() {
if (isShortcut) {
destination = createDestination();
}
setOffset(destination);
}
public void setOffset(Point2D offsetFromOriginalPoint) {
// calculate how far the given offset is from the final destination
double newX = offsetFromOriginalPoint.getX();
double newY = offsetFromOriginalPoint.getY();
double deltaX = newX - lastPoint.getX();
double deltaY = newY - lastPoint.getY();
lastPoint.setLocation(newX, newY);
if (deltaX == 0 && deltaY == 0) {
return;
}
multiLayerTransformer.getTransformer(Layer.LAYOUT).translate(deltaX, deltaY);
viewer.repaint();
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.VisualizationServer;
public class MoveViewToLayoutSpacePointAnimatorFunctionGraphJob<V, E> extends
MoveViewAnimatorFunctionGraphJob<V, E> {
private final Point2D layoutSpacePoint;
public MoveViewToLayoutSpacePointAnimatorFunctionGraphJob(VisualizationServer<V, E> viewer,
Point2D layoutSpacePoint, boolean useAnimation) {
super(viewer, useAnimation);
this.layoutSpacePoint = layoutSpacePoint;
}
@Override
protected Point2D createDestination() {
return layoutSpacePoint;
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.VisualizationServer;
import ghidra.graph.viewer.GraphViewerUtils;
public class MoveViewToViewSpacePointAnimatorFunctionGraphJob<V, E>
extends MoveViewAnimatorFunctionGraphJob<V, E> {
private Point2D viewSpacePoint;
public MoveViewToViewSpacePointAnimatorFunctionGraphJob(VisualizationServer<V, E> viewer,
Point2D viewSpacePoint, boolean useAnimation) {
super(viewer, useAnimation);
this.viewSpacePoint = viewSpacePoint;
}
@Override
protected Point2D createDestination() {
return GraphViewerUtils.getOffsetFromCenterForPointInViewSpace(viewer, viewSpacePoint);
}
}

View file

@ -0,0 +1,170 @@
/* ###
* 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.job;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.Map.Entry;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.LayoutPositions;
public class RelayoutFunctionGraphJob<V extends VisualVertex, E extends VisualEdge<V>>
extends AbstractGraphTransitionJob<V, E> {
public RelayoutFunctionGraphJob(GraphViewer<V, E> viewer, boolean useAnimation) {
super(viewer, useAnimation);
}
@Override
protected Animator createAnimator() {
initializeVertexLocations();
clearLocationCache();
if (!useAnimation) {
return null;
}
updateOpacity(0);
Animator newAnimator =
PropertySetter.createAnimator(duration, this, "percentComplete", 0.0, 1.0);
newAnimator.setAcceleration(0f);
newAnimator.setDeceleration(0.8f);
return newAnimator;
}
@Override
protected void finished() {
super.finished();
// TODO it is jarring to re-fit the graph if the user was zoomed in. I'm torn on what the right
// thing to do is
// FunctionGraphView view = controller.getView();
// FunctionGraphUtils.fitGraphToViewerNow(view.getPrimaryGraphViewer(), true);
}
@Override
protected void initializeVertexLocations() {
//
// this may be the same as the current locations, or they may be updated, depending upon
// user options
//
LayoutPositions<V, E> futurePositions = calculateDefaultLayoutLocations();
Map<V, Point2D> destinationLocations = futurePositions.getVertexLocations();
finalEdgeArticulations = futurePositions.getEdgeArticulations();
//
// Create the new vertex locations.
//
Collection<V> vertices = graph.getVertices();
for (V vertex : vertices) {
Point2D currentPoint = toLocation(vertex);
Point2D startPoint = (Point2D) currentPoint.clone();
Point2D vertexPoint = destinationLocations.get(vertex);
Point2D destinationPoint = (Point2D) vertexPoint.clone();
TransitionPoints transitionPoints = new TransitionPoints(startPoint, destinationPoint);
vertexLocations.put(vertex, transitionPoints);
}
//
// We have to move edge articulations--create transition points.
//
Map<E, List<Point2D>> edgeArticulations = futurePositions.getEdgeArticulations();
Set<Entry<E, List<Point2D>>> entrySet = edgeArticulations.entrySet();
for (Entry<E, List<Point2D>> entry : entrySet) {
E edge = entry.getKey();
List<Point2D> currentArticulations = edge.getArticulationPoints();
List<Point2D> newArticulations = entry.getValue();
List<ArticulationTransitionPoints> transitionPoints = getArticulationTransitionPoints(
currentArticulations, newArticulations, destinationLocations, edge);
edgeArticulationLocations.put(edge, transitionPoints);
}
}
private List<ArticulationTransitionPoints> getArticulationTransitionPoints(
List<Point2D> currentArticulations, List<Point2D> newArticulations,
Map<V, Point2D> destinationLocations, E edge) {
if (currentArticulations.size() > newArticulations.size()) {
return getArticulationTransitionPointsWhenStartingWithMorePoints(currentArticulations,
newArticulations, destinationLocations, edge);
}
return getArticulationTransitionPointsWhenStartingWithLessPoints(currentArticulations,
newArticulations, destinationLocations, edge);
}
private List<ArticulationTransitionPoints> getArticulationTransitionPointsWhenStartingWithLessPoints(
List<Point2D> currentArticulations, List<Point2D> newArticulations,
Map<V, Point2D> destinationLocations, E edge) {
List<ArticulationTransitionPoints> transitionPoints = new ArrayList<>();
//
// We will have to add articulations to the current edge now so that we can
// animate their creation.
//
List<Point2D> newStartArticulationsPoints = new ArrayList<>();
// default to start vertex so to handle the case where we started with no articulations
Point2D lastValidStartPoint = toLocation(edge.getStart());
for (int i = 0; i < newArticulations.size(); i++) {
Point2D endPoint = newArticulations.get(i);
Point2D startPoint = (Point2D) lastValidStartPoint.clone();
if (i < currentArticulations.size()) {
// prefer the new articulations, while we have some
startPoint = currentArticulations.get(i);
lastValidStartPoint = startPoint;
}
transitionPoints.add(new ArticulationTransitionPoints(startPoint, endPoint));
newStartArticulationsPoints.add(startPoint);
}
edge.setArticulationPoints(newStartArticulationsPoints);
return transitionPoints;
}
private List<ArticulationTransitionPoints> getArticulationTransitionPointsWhenStartingWithMorePoints(
List<Point2D> currentArticulations, List<Point2D> newArticulations,
Map<V, Point2D> destinationLocations, E edge) {
List<ArticulationTransitionPoints> transitionPoints = new ArrayList<>();
for (int i = 0; i < currentArticulations.size(); i++) {
Point2D startPoint = currentArticulations.get(i);
Point2D endPoint = (Point2D) startPoint.clone();
if (i < newArticulations.size()) {
// prefer the new articulations, while we have some
endPoint = newArticulations.get(i);
}
transitionPoints.add(new ArticulationTransitionPoints(startPoint, endPoint));
}
return transitionPoints;
}
}

View file

@ -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.job;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.Animator.RepeatBehavior;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
import edu.uci.ics.jung.visualization.VisualizationServer;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
/**
* A class to animate a vertex in order to draw attention to it.
*
* Note: this class is not a {@link AbstractAnimatorJob} so that it can run concurrently
* with jobs in the graph (jobs run one-at-a-time).
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class TwinkleVertexAnimator<V extends VisualVertex, E extends VisualEdge<V>>
extends AbstractAnimator {
private final VisualizationServer<V, E> viewer;
private final V vertex;
private final boolean useAnimation;
private double startEmphasis;
public TwinkleVertexAnimator(VisualizationServer<V, E> viewer, V vertex, boolean useAnimation) {
this.viewer = viewer;
this.vertex = vertex;
this.useAnimation = useAnimation;
}
@Override
protected Animator createAnimator() {
if (!useAnimation) {
return null;
}
startEmphasis = vertex.getEmphasis();
Animator newAnimator = PropertySetter.createAnimator(500, this, "currentEmphasis", 0.0, .5);
newAnimator.setAcceleration(0.0f);
newAnimator.setDeceleration(0.0f);
newAnimator.setRepeatCount(4); // up and down twice
newAnimator.setRepeatBehavior(RepeatBehavior.REVERSE); // emphasize the first pass, then de-emphasizes
return newAnimator;
}
@Override
protected void finished() {
vertex.setEmphasis(startEmphasis);
}
public void setCurrentEmphasis(double currentEmphasis) {
vertex.setEmphasis(currentEmphasis);
viewer.repaint();
}
public V getVertex() {
return vertex;
}
}

View file

@ -0,0 +1,70 @@
/* ###
* 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.jung;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import ghidra.graph.GDirectedGraph;
import ghidra.graph.GEdge;
public class JungDirectedGraph<V, E extends GEdge<V>> extends DirectedSparseGraph<V, E>
implements GDirectedGraph<V, E> {
@Override
public void addEdge(E e) {
super.addEdge(e, e.getStart(), e.getEnd());
}
@Override
public void removeVertices(Iterable<V> toRemove) {
toRemove.forEach(v -> super.removeVertex(v));
}
@Override
public void removeEdges(Iterable<E> toRemove) {
toRemove.forEach(e -> super.removeEdge(e));
}
@Override
public boolean containsEdge(V from, V to) {
return findEdge(from, to) != null;
}
@Override
public GDirectedGraph<V, E> emptyCopy() {
JungDirectedGraph<V, E> newGraph = new JungDirectedGraph<>();
return newGraph;
}
@Override
public GDirectedGraph<V, E> copy() {
JungDirectedGraph<V, E> newGraph = new JungDirectedGraph<>();
for (V v : vertices.keySet()) {
newGraph.addVertex(v);
}
for (E e : edges.keySet()) {
newGraph.addEdge(e);
}
return newGraph;
}
@Override
public boolean isEmpty() {
return getVertexCount() == 0;
}
}

View file

@ -0,0 +1,294 @@
/* ###
* 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.jung;
import java.lang.reflect.Constructor;
import java.util.Collection;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;
import ghidra.graph.GDirectedGraph;
import ghidra.graph.GEdge;
import ghidra.util.Msg;
/**
* A class that turns a {@link Graph} into a {@link GDirectedGraph}.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class JungToGDirectedGraphAdapter<V, E extends GEdge<V>> implements GDirectedGraph<V, E> {
private Graph<V, E> delegate;
public JungToGDirectedGraphAdapter(Graph<V, E> delegate) {
this.delegate = delegate;
}
@Override
public void addEdge(E e) {
delegate.addEdge(e, e.getStart(), e.getEnd());
}
@Override
public boolean containsEdge(V from, V to) {
return findEdge(from, to) != null;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public GDirectedGraph<V, E> emptyCopy() {
if (delegate instanceof GDirectedGraph) {
return ((GDirectedGraph) delegate).emptyCopy();
}
Class<? extends Graph> clazz = delegate.getClass();
try {
Constructor<? extends Graph> constructor = clazz.getConstructor((Class<?>[]) null);
Graph newGraph = constructor.newInstance((Object[]) null);
return new JungToGDirectedGraphAdapter(newGraph);
}
catch (Exception e) {
// shouldn't happen
Msg.showError(this, null, "Error Creating Graph",
"Unable to create a new instance of graph: " + clazz, e);
return null;
}
}
@Override
public GDirectedGraph<V, E> copy() {
JungToGDirectedGraphAdapter<V, E> newGraph =
(JungToGDirectedGraphAdapter<V, E>) emptyCopy();
for (V v : delegate.getVertices()) {
newGraph.addVertex(v);
}
for (E e : delegate.getEdges()) {
newGraph.delegate.addEdge(e, e.getStart(), e.getEnd());
}
return newGraph;
}
@Override
public boolean isEmpty() {
return getVertexCount() == 0;
}
@Override
public Collection<E> getEdges() {
return delegate.getEdges();
}
@Override
public Collection<E> getInEdges(V vertex) {
return delegate.getInEdges(vertex);
}
@Override
public Collection<V> getVertices() {
return delegate.getVertices();
}
@Override
public Collection<E> getOutEdges(V vertex) {
return delegate.getOutEdges(vertex);
}
@Override
public boolean containsVertex(V vertex) {
return delegate.containsVertex(vertex);
}
@Override
public Collection<V> getPredecessors(V vertex) {
return delegate.getPredecessors(vertex);
}
@Override
public boolean containsEdge(E edge) {
return delegate.containsEdge(edge);
}
@Override
public int getEdgeCount() {
return delegate.getEdgeCount();
}
@Override
public Collection<V> getSuccessors(V vertex) {
return delegate.getSuccessors(vertex);
}
@Override
public int getVertexCount() {
return delegate.getVertexCount();
}
public Collection<V> getNeighbors(V vertex) {
return delegate.getNeighbors(vertex);
}
public int inDegree(V vertex) {
return delegate.inDegree(vertex);
}
@Override
public Collection<E> getIncidentEdges(V vertex) {
return delegate.getIncidentEdges(vertex);
}
public int outDegree(V vertex) {
return delegate.outDegree(vertex);
}
public Collection<V> getIncidentVertices(E edge) {
return delegate.getIncidentVertices(edge);
}
public boolean isPredecessor(V v1, V v2) {
return delegate.isPredecessor(v1, v2);
}
public boolean isSuccessor(V v1, V v2) {
return delegate.isSuccessor(v1, v2);
}
@Override
public E findEdge(V v1, V v2) {
return delegate.findEdge(v1, v2);
}
public int getPredecessorCount(V vertex) {
return delegate.getPredecessorCount(vertex);
}
public int getSuccessorCount(V vertex) {
return delegate.getSuccessorCount(vertex);
}
public V getSource(E directed_edge) {
return delegate.getSource(directed_edge);
}
public Collection<E> findEdgeSet(V v1, V v2) {
return delegate.findEdgeSet(v1, v2);
}
public V getDest(E directed_edge) {
return delegate.getDest(directed_edge);
}
public boolean isSource(V vertex, E edge) {
return delegate.isSource(vertex, edge);
}
@Override
public boolean addVertex(V vertex) {
return delegate.addVertex(vertex);
}
public boolean isDest(V vertex, E edge) {
return delegate.isDest(vertex, edge);
}
public boolean addEdge(E edge, Collection<? extends V> vertices) {
return delegate.addEdge(edge, vertices);
}
public boolean addEdge(E e, V v1, V v2) {
return delegate.addEdge(e, v1, v2);
}
public boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType edge_type) {
return delegate.addEdge(edge, vertices, edge_type);
}
public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) {
return delegate.addEdge(e, v1, v2, edgeType);
}
@Override
public boolean removeVertex(V vertex) {
return delegate.removeVertex(vertex);
}
@Override
public void removeVertices(Iterable<V> vertices) {
vertices.forEach(v -> removeVertex(v));
}
@Override
public void removeEdges(Iterable<E> edges) {
edges.forEach(e -> removeEdge(e));
}
public Pair<V> getEndpoints(E edge) {
return delegate.getEndpoints(edge);
}
public V getOpposite(V vertex, E edge) {
return delegate.getOpposite(vertex, edge);
}
@Override
public boolean removeEdge(E edge) {
return delegate.removeEdge(edge);
}
public boolean isNeighbor(V v1, V v2) {
return delegate.isNeighbor(v1, v2);
}
public boolean isIncident(V vertex, E edge) {
return delegate.isIncident(vertex, edge);
}
public int degree(V vertex) {
return delegate.degree(vertex);
}
public int getNeighborCount(V vertex) {
return delegate.getNeighborCount(vertex);
}
public int getIncidentCount(E edge) {
return delegate.getIncidentCount(edge);
}
public EdgeType getEdgeType(E edge) {
return delegate.getEdgeType(edge);
}
public EdgeType getDefaultEdgeType() {
return delegate.getDefaultEdgeType();
}
public Collection<E> getEdges(EdgeType edge_type) {
return delegate.getEdges(edge_type);
}
public int getEdgeCount(EdgeType edge_type) {
return delegate.getEdgeCount(edge_type);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,162 @@
/* ###
* 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;
import java.awt.Point;
import edu.uci.ics.jung.visualization.*;
import ghidra.framework.options.SaveState;
/**
* An object that allows for storing and restoring of graph perspective data, like the zoom
* level and the position of the graph.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class GraphPerspectiveInfo<V extends VisualVertex, E extends VisualEdge<V>> {
private static final String LAYOUT_TRANSLATE_X = "LAYOUT_TRANSLATE_X";
private static final String LAYOUT_TRANSLATE_Y = "LAYOUT_TRANSLATE_Y";
private static final String VIEW_TRANSLATE_X = "VIEW_TRANSLATE_X";
private static final String VIEW_TRANSLATE_Y = "VIEW_TRANSLATE_Y";
private static final String VIEW_ZOOM = "VIEW_ZOOM";
private static final Point INVALID_POINT = null;
private static final double INVALID_ZOOM = -1D;
/**
* The offset of the transform from the world origin (which at the time of writing is
* the (0,0) at the upper left-hand corner of the GUI. This is for the layout transformer.
*/
private final Point layoutTranslateCoordinates;
/**
* The offset of the transform from the world origin (which at the time of writing is
* the (0,0) at the upper left-hand corner of the GUI. This is for the view transformer,
* which also potentially has a scale applied to the transform.
*/
private final Point viewTranslateCoordinates;
private final double zoom;
private boolean restoreZoom;
public static <V extends VisualVertex, E extends VisualEdge<V>> GraphPerspectiveInfo<V, E> createInvalidGraphPerspectiveInfo() {
return new GraphPerspectiveInfo<>();
}
private GraphPerspectiveInfo() {
// for factory construction
this.zoom = INVALID_ZOOM;
this.restoreZoom = false;
this.layoutTranslateCoordinates = null;
this.viewTranslateCoordinates = null;
}
public GraphPerspectiveInfo(RenderContext<V, E> renderContext, double zoom) {
this.zoom = zoom;
this.restoreZoom = true;
MultiLayerTransformer transformer = renderContext.getMultiLayerTransformer();
double tx = transformer.getTransformer(Layer.LAYOUT).getTranslateX();
double ty = transformer.getTransformer(Layer.LAYOUT).getTranslateY();
this.layoutTranslateCoordinates = new Point((int) tx, (int) ty);
tx = transformer.getTransformer(Layer.VIEW).getTranslateX();
ty = transformer.getTransformer(Layer.VIEW).getTranslateY();
this.viewTranslateCoordinates = new Point((int) tx, (int) ty);
}
public GraphPerspectiveInfo(SaveState saveState) {
double savedZoom = saveState.getDouble(VIEW_ZOOM, INVALID_ZOOM);
int layoutTranslateX = saveState.getInt(LAYOUT_TRANSLATE_X, Integer.MAX_VALUE);
int layoutTranslateY = saveState.getInt(LAYOUT_TRANSLATE_Y, Integer.MAX_VALUE);
if (layoutTranslateX == Integer.MAX_VALUE || layoutTranslateY == Integer.MAX_VALUE) {
layoutTranslateCoordinates = INVALID_POINT;
viewTranslateCoordinates = INVALID_POINT;
zoom = INVALID_ZOOM;
return;
}
int viewTranslateX = saveState.getInt(VIEW_TRANSLATE_X, Integer.MAX_VALUE);
int viewTranslateY = saveState.getInt(VIEW_TRANSLATE_Y, Integer.MAX_VALUE);
if (viewTranslateX == Integer.MAX_VALUE || viewTranslateY == Integer.MAX_VALUE) {
layoutTranslateCoordinates = INVALID_POINT;
viewTranslateCoordinates = INVALID_POINT;
zoom = INVALID_ZOOM;
return;
}
layoutTranslateCoordinates = new Point(layoutTranslateX, layoutTranslateY);
viewTranslateCoordinates = new Point(viewTranslateX, viewTranslateY);
zoom = savedZoom;
restoreZoom = true; // when we are coming from a persisted state, we restore the zoom
}
public void saveState(SaveState saveState) {
if (isInvalid()) {
return;
}
saveState.putDouble(VIEW_ZOOM, zoom);
saveState.putInt(LAYOUT_TRANSLATE_X, layoutTranslateCoordinates.x);
saveState.putInt(LAYOUT_TRANSLATE_Y, layoutTranslateCoordinates.y);
}
public boolean isInvalid() {
return layoutTranslateCoordinates == INVALID_POINT ||
viewTranslateCoordinates == INVALID_POINT;
}
/**
* The offset of the transform from the world origin (which at the time of writing is
* the (0,0) at the upper left-hand corner of the GUI. This is for the layout transformer.
*/
public Point getLayoutTranslateCoordinates() {
return layoutTranslateCoordinates;
}
/**
* The offset of the transform from the world origin (which at the time of writing is
* the (0,0) at the upper left-hand corner of the GUI. This is for the view transformer,
* which also potentially has a scale applied to the transform.
*/
public Point getViewTranslateCoordinates() {
return viewTranslateCoordinates;
}
public boolean isRestoreZoom() {
return restoreZoom;
}
public double getZoom() {
return zoom;
}
@Override
public String toString() {
// @formatter:off
return "{\n\tisRestoreZoom: " + restoreZoom +
",\n\tlayoutTranslateCoordinates: " + layoutTranslateCoordinates +
",\n\tviewTranslateCoordinates: " + viewTranslateCoordinates +
",\n\tzoom=" + zoom +
"\n}";
// @formatter:on
}
}

View file

@ -0,0 +1,30 @@
/* ###
* 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;
/**
* A listener to get notified of changes to the {@link SatelliteGraphViewer}
*/
public interface GraphSatelliteListener {
/**
* Called when the visibility and/or docked state of the watched satellite changes
*
* @param docked true if the satellite is now docked
* @param visible true if the satellite is now visible
*/
public void satelliteVisibilityChanged(boolean docked, boolean visible);
}

View file

@ -0,0 +1,651 @@
/* ###
* 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;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.util.function.Consumer;
import javax.swing.*;
import docking.widgets.PopupWindow;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.picking.MultiPickedState;
import edu.uci.ics.jung.visualization.picking.PickedState;
import generic.util.WindowUtilities;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.edge.VisualGraphPathHighlighter;
import ghidra.graph.viewer.event.mouse.*;
import ghidra.graph.viewer.event.picking.GPickedState;
import ghidra.graph.viewer.layout.VisualGraphLayout;
import ghidra.graph.viewer.options.VisualGraphOptions;
import ghidra.graph.viewer.renderer.VisualGraphRenderer;
import ghidra.util.layout.PairLayout;
/**
* The base viewer for the Graph module. This viewer provides methods for manipulating
* the graph using the mouse.
*
* <P>The viewer is currently an extension of the {@link VisualizationViewer} and as such it
* is accessed by much of the event handling subsystem, such as the mouse plugins, as well as
* the rendering system.
*
* <P>Also, tooltips/popups for edges and vertices are handled by this class.
*
* <P>This class creates a {@link VisualGraphViewUpdater} that perform graph transformations,
* such as panning the graph, with and without animation, as requested.
*
* @param <V> the vertex type
* @param <E> the edge type
*
*/
public class GraphViewer<V extends VisualVertex, E extends VisualEdge<V>>
extends VisualizationViewer<V, E> {
private GPickedState<V> gPickedState;
private Consumer<GraphViewer<V, E>> initializedListener;
private PopupRegulator popupRegulator = new PopupRegulator();
private PopupWindow popupWindow;
private boolean showPopups = true;
private VertexTooltipProvider<V, E> vertexTooltipProvider = new DummyTooltipProvider();
protected VisualGraphOptions options;
private VisualGraphViewUpdater<V, E> viewUpdater;
private VisualGraphPathHighlighter<V, E> pathHighlighter;
public GraphViewer(VisualGraphLayout<V, E> layout, Dimension size) {
super(layout, size);
buildUpdater();
// TODO how slow does this make painting?
//Map<Key, Object> hints = getRenderingHints();
//hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
setRenderer(new VisualGraphRenderer<>(layout.getEdgeLabelRenderer()));
setGraphMouse(new VisualGraphPluggableGraphMouse<>());
PickedState<V> pickedState = getPickedVertexState();
gPickedState = new GPickedState<>((MultiPickedState<V>) pickedState);
setPickedVertexState(gPickedState);
}
private void buildUpdater() {
viewUpdater = createViewUpdater();
pathHighlighter = new VisualGraphPathHighlighter<>(getVisualGraph(), hoverChange -> {
if (hoverChange) {
viewUpdater.animateEdgeHover();
}
else {
repaint();
}
});
//
// The path highlighter is subordinate to the view updater in that the path highlighter
// should not be running while the view updater is running jobs that can mutate the
// graph. To facilitate this, we are using these callbacks below. We can create a more
// uniformed interface if needed, but this setup is very simple and easy to follow.
//
// signal to the path updater to stop work while jobs may be mutating the graph
viewUpdater.addJobScheduledListener(() -> pathHighlighter.stop());
pathHighlighter.setWorkPauser(() -> viewUpdater.isMutatingGraph());
}
protected VisualGraphViewUpdater<V, E> createViewUpdater() {
return new VisualGraphViewUpdater<>(this, getVisualGraph());
}
public VisualGraphLayout<V, E> getVisualGraphLayout() {
return GraphViewerUtils.getVisualGraphLayout(getGraphLayout());
}
@Override
public void setGraphLayout(Layout<V, E> layout) {
if (!(layout instanceof VisualGraphLayout)) {
throw new IllegalArgumentException(getClass().getSimpleName() + " only supports " +
"layouts of type " + VisualGraphLayout.class.getSimpleName());
}
super.setGraphLayout(layout);
}
public VisualGraph<V, E> getVisualGraph() {
VisualGraphLayout<V, E> l = getVisualGraphLayout();
return l.getVisualGraph();
}
@SuppressWarnings("unchecked")
@Override
public VisualGraphPluggableGraphMouse<V, E> getGraphMouse() {
return (VisualGraphPluggableGraphMouse<V, E>) super.getGraphMouse();
}
@Override
public void setGraphMouse(GraphMouse graphMouse) {
if (!(graphMouse instanceof VisualGraphPluggableGraphMouse)) {
throw new IllegalArgumentException(
"GraphViewer must use a VisualGraphPluggableGraphMouse");
}
super.setGraphMouse(graphMouse);
}
public void setGraphOptions(VisualGraphOptions options) {
this.options = options;
}
public VisualGraphOptions getOptions() {
return options;
}
public void setVertexHoverPathHighlightMode(PathHighlightMode hoverMode) {
pathHighlighter.setVertexHoverMode(hoverMode);
}
public void setVertexFocusPathHighlightMode(PathHighlightMode focusMode) {
pathHighlighter.setVertexFocusMode(focusMode);
}
public PathHighlightMode getVertexHoverPathHighlightMode() {
return pathHighlighter.getVertexHoverPathHighlightMode();
}
public PathHighlightMode getVertexFocusPathHighlightMode() {
return pathHighlighter.getVertexFocusPathHighlightMode();
}
public void setViewerInitializedListener(Consumer<GraphViewer<V, E>> listener) {
this.initializedListener = listener;
}
public VisualGraphPathHighlighter<V, E> getPathHighlighter() {
return pathHighlighter;
}
public VisualGraphViewUpdater<V, E> getViewUpdater() {
return viewUpdater;
}
public GPickedState<V> getGPickedVertexState() {
PickedState<V> ps = super.getPickedVertexState();
if (!(ps instanceof GPickedState)) {
throw new IllegalArgumentException(
"GPickedState was not installed or was overrwritten");
}
return (GPickedState<V>) ps;
}
public void setVertexTooltipProvider(VertexTooltipProvider<V, E> vertexTooltipProvider) {
if (vertexTooltipProvider == null) {
vertexTooltipProvider = new DummyTooltipProvider();
}
this.vertexTooltipProvider = vertexTooltipProvider;
}
/**
* When true (the default), the zoom will center wherever the mouse is positioned. False
* will zoom at the center of the view.
*
* @return true if using mouse-relative zoom
*/
public boolean useMouseRelativeZoom() {
return options.useMouseRelativeZoom();
}
/**
* !!Super Hacky Override!!
* The code we are overriding blindly calls add(), without first checking to see if it has
* already been added. Java 6 added a method, removeNotify(), that is called when components
* are removed. When add is called in the overridden method, it triggers a call to remove,
* which triggers removeNotify(). This call is made during the painting process. The problem
* therein is that out buttons borders get reset (see AbstractButton.removeNotify()) when
* we repaint, which means that mouse hovers do not work correctly (SCR 6819).
*/
@Override
public Component add(Component comp) {
if (SwingUtilities.isDescendingFrom(comp, this)) {
return comp;
}
return super.add(comp);
}
/**
* !!Super Hacky Override!!
* This is done to make sure that we center the view when we are fully laid-out. If
* you know of a better way to do this, then, get rid of this overridden method and do
* the good thing.
*/
@Override
protected void paintComponent(Graphics g) {
if (initializedListener != null) {
initializedListener.accept(this);
initializedListener = null;
}
super.paintComponent(g);
}
@Override
public Point2D getCenter() {
Dimension d = getSize();
Container myParent = getParent();
if (myParent != null) {
// this fixes the issue of size not being correct before we've been laid out
d = myParent.getSize();
}
return new Point2D.Float(d.width / 2, d.height / 2);
}
private boolean isScaledPastInteractionThreshold() {
return GraphViewerUtils.isScaledPastVertexInteractionThreshold(this);
}
//==================================================================================================
// Popups and Tooltips
//==================================================================================================
public void setPopupsVisible(boolean visible) {
this.showPopups = visible;
if (!showPopups) {
hidePopupTooltips();
}
}
/*package*/ boolean isPopupShowing() {
return popupWindow != null && popupWindow.isShowing();
}
private void hidePopupTooltips() {
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.hide();
// don't call dispose, or we don't get our componentHidden() callback
// popupWindow.dispose();
}
}
@Override
public String getToolTipText(MouseEvent event) {
return null; // no standard Java tooltips...we will handle later
}
private ToolTipInfo<?> getToolTipInfo(MouseEvent event) {
Layout<V, E> viewerLayout = getGraphLayout();
Point p = event.getPoint();
// check for a vertex hit first, otherwise, we get edge hits when we are hovering
// over a vertex, due to how edges are interpreted as existing all the way to the
// center point of a vertex
V vertex = getPickSupport().getVertex(viewerLayout, p.getX(), p.getY());
if (vertex != null) {
return new VertexToolTipInfo(vertex, event);
}
E edge = getPickSupport().getEdge(viewerLayout, p.getX(), p.getY());
if (edge != null) {
return new EdgeToolTipInfo(edge, event);
}
// no vertex or edge hit; just create a basic info that is essentially a null-object
// placeholder to prevent NPEs
return new VertexToolTipInfo(vertex, event);
}
private void showTooltip(ToolTipInfo<?> info) {
JComponent tipComponent = info.getToolTipComponent();
if (tipComponent == null) {
return;
}
MouseEvent event = info.getMouseEvent();
showPopupWindow(event, tipComponent);
}
private void showPopupWindow(MouseEvent event, JComponent component) {
MenuSelectionManager menuManager = MenuSelectionManager.defaultManager();
if (menuManager.getSelectedPath().length != 0) {
return;
}
Window parentWindow = WindowUtilities.windowForComponent(this);
popupWindow = new PopupWindow(parentWindow, component);
popupWindow.addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
popupRegulator.popupShown();
}
@Override
public void componentHidden(ComponentEvent e) {
popupRegulator.popupHidden();
}
});
popupWindow.showPopup(event);
}
public VertexMouseInfo<V, E> createVertexMouseInfo(MouseEvent e, V v,
Point2D vertexBasedClickPoint) {
return new VertexMouseInfo<>(e, v, vertexBasedClickPoint, this);
}
/*package*/ void setPopupDelay(int delayMs) {
popupRegulator.setPopupDelay(delayMs);
}
public void dispose() {
viewUpdater.dispose();
pathHighlighter.dispose();
removeAll();
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class PopupRegulator {
private int popupDelay = 1000;
/**
* We need this timer because the default mechanism for triggering popups doesn't
* always work. We use this timer in conjunction with a mouse motion listener to
* get the results we want.
*/
private Timer popupTimer;
private MouseEvent popupMouseEvent;
/** the current target (vertex or edge) of a popup window */
private Object nextPopupTarget;
/**
* This value is not null when the user moves the cursor over a target for which a
* popup is already showing. We use this value to prevent showing a popup multiple times
* while over a single node.
*/
private Object lastShownPopupTarget;
/** The tooltip info used when showing the popup */
private ToolTipInfo<?> currentToolTipInfo;
PopupRegulator() {
popupTimer = new Timer(popupDelay, e -> {
if (isPopupShowing()) {
return; // don't show any new popups while the user is perusing
}
showPopupForMouseEvent(popupMouseEvent);
});
popupTimer.setRepeats(false);
addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent e) {
hidePopupTooltips();
popupTimer.stop();
popupMouseEvent = null; // clear any queued popups
}
@Override
public void mouseMoved(MouseEvent e) {
popupMouseEvent = e;
// this clears out the current last popup shown so that the user can
// move off and on a node to re-show the popup
savePopupTarget(e);
// make sure the popup gets triggered eventually
popupTimer.restart();
}
});
}
void setPopupDelay(int delayMs) {
popupTimer.stop();
popupTimer.setDelay(delayMs);
popupTimer.setInitialDelay(delayMs);
popupDelay = delayMs;
}
private void showPopupForMouseEvent(MouseEvent event) {
if (!showPopups) {
return;
}
if (event == null) {
return;
}
ToolTipInfo<?> toolTipInfo = getToolTipInfo(event);
JComponent toolTipComponent = toolTipInfo.getToolTipComponent();
boolean isCustomJavaTooltip = !(toolTipComponent instanceof JToolTip);
if (lastShownPopupTarget == nextPopupTarget && isCustomJavaTooltip) {
//
// Kinda Hacky:
// We don't show repeated popups for the same item (the user has to move away
// and then come back to re-show the popup). However, one caveat to this is that
// we do want to allow the user to see popups for the toolbar actions always. So,
// only return here if we have already shown a popup for the item *and* we are
// using a custom tooltip (which is used to show a vertex tooltip or an edge
// tooltip)
return;
}
currentToolTipInfo = toolTipInfo;
showTooltip(currentToolTipInfo);
}
void popupShown() {
lastShownPopupTarget = nextPopupTarget;
currentToolTipInfo.emphasize();
repaint();
}
void popupHidden() {
currentToolTipInfo.deEmphasize();
repaint();
}
private void savePopupTarget(MouseEvent event) {
nextPopupTarget = null;
V vertex = getVertexForEvent(event);
if (vertex != null) {
nextPopupTarget = vertex;
}
else {
E edge = getEdgeForEvent(event);
nextPopupTarget = edge;
}
if (nextPopupTarget == null) {
// We've moved off of a target. We will clear that last target so the user can
// mouse off of a vertex and back on in order to trigger a new popup
lastShownPopupTarget = null;
}
}
private V getVertexForEvent(MouseEvent event) {
Layout<V, E> viewerLayout = getGraphLayout();
Point p = event.getPoint();
return getPickSupport().getVertex(viewerLayout, p.getX(), p.getY());
}
private E getEdgeForEvent(MouseEvent event) {
Layout<V, E> viewerLayout = getGraphLayout();
Point p = event.getPoint();
return getPickSupport().getEdge(viewerLayout, p.getX(), p.getY());
}
}
/** Basic container object that knows how to generate tooltips */
private abstract class ToolTipInfo<T> {
protected final MouseEvent event;
protected final T graphObject;
private JComponent tooltipComponent;
ToolTipInfo(MouseEvent event, T t) {
this.event = event;
this.graphObject = t;
tooltipComponent = createToolTipComponent(t);
}
protected abstract JComponent createToolTipComponent(T t);
protected abstract void emphasize();
protected abstract void deEmphasize();
MouseEvent getMouseEvent() {
return event;
}
JComponent getToolTipComponent() {
return tooltipComponent;
}
}
private class VertexToolTipInfo extends ToolTipInfo<V> {
VertexToolTipInfo(V vertex, MouseEvent event) {
super(event, vertex);
}
@Override
public JComponent createToolTipComponent(V vertex) {
if (vertex == null) {
return null;
}
if (isScaledPastInteractionThreshold()) {
return vertexTooltipProvider.getTooltip(vertex);
}
VertexMouseInfo<V, E> mouseInfo =
GraphViewerUtils.convertMouseEventToVertexMouseEvent(GraphViewer.this, event);
MouseEvent translatedMouseEvent = mouseInfo.getTranslatedMouseEvent();
String toolTip = vertexTooltipProvider.getTooltipText(vertex, translatedMouseEvent);
if (toolTip == null) {
return null;
}
JToolTip jToolTip = new JToolTip();
jToolTip.setTipText(toolTip);
return jToolTip;
}
@Override
protected void emphasize() {
if (graphObject == null) {
return;
}
// only add a tooltip emphasis when we are scaled to a small size (this prevents
// odd vertex sizing behavior while the user is attempting to interact with the
// vertex)
if (GraphViewerUtils.isScaledPastVertexInteractionThreshold(GraphViewer.this)) {
graphObject.setEmphasis(.25);
}
}
@Override
public void deEmphasize() {
if (graphObject == null) {
return;
}
graphObject.setEmphasis(0);
}
}
private class EdgeToolTipInfo extends ToolTipInfo<E> {
EdgeToolTipInfo(E edge, MouseEvent event) {
super(event, edge);
}
@Override
public JComponent createToolTipComponent(E edge) {
if (edge == null) {
return null;
}
V start = edge.getStart();
V end = edge.getEnd();
JComponent startComponent = vertexTooltipProvider.getTooltip(start, edge);
if (startComponent == null) {
return null;
}
if (start == end) {
// self-loop
JComponent component = new JPanel(new BorderLayout());
component.add(startComponent, BorderLayout.CENTER);
return component;
}
JComponent endComponent = vertexTooltipProvider.getTooltip(end, edge);
if (endComponent == null) {
return null;
}
JComponent component = new JPanel(new PairLayout());
component.add(startComponent);
component.add(endComponent);
return component;
}
@Override
protected void emphasize() {
if (graphObject == null) {
return;
}
graphObject.setEmphasis(1);
}
@Override
public void deEmphasize() {
if (graphObject == null) {
return;
}
graphObject.setEmphasis(0);
}
}
private class DummyTooltipProvider implements VertexTooltipProvider<V, E> {
@Override
public JComponent getTooltip(V v) {
return null;
}
@Override
public String getTooltipText(V v, MouseEvent e) {
return null;
}
@Override
public JComponent getTooltip(V v, E e) {
return null;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
/* ###
* 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;
import ghidra.graph.viewer.edge.VisualGraphPathHighlighter;
/**
* An enum that lists possible states for highlighting paths between vertices in a graph.
*
* @see VisualGraphPathHighlighter
*/
public enum PathHighlightMode {
//@formatter:off
/** Shows all cycles in the graph */
ALLCYCLE,
/** Shows all cycles for a given vertex */
CYCLE,
/** Shows all paths that can reach the given vertex */
IN,
/** Shows all paths coming into and out of a vertex */
INOUT,
/** Shows no paths */
OFF,
/** Shows all paths reachable from the current vertex */
OUT,
/** Shows all paths between two vertices */
PATH,
/** Shows all paths that must have been traveled to reach the current vertex */
SCOPED_FORWARD,
/** Shows all paths that will be traveled after leaving the current vertex */
SCOPED_REVERSE;
//@formatter:on
}

View file

@ -0,0 +1,91 @@
/* ###
* 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;
import java.awt.Dimension;
import edu.uci.ics.jung.visualization.control.SatelliteVisualizationViewer;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import ghidra.graph.viewer.event.mouse.VisualGraphPluggableGraphMouse;
import ghidra.graph.viewer.event.mouse.VisualGraphSatelliteGraphMouse;
import ghidra.graph.viewer.renderer.VisualGraphRenderer;
import ghidra.graph.viewer.renderer.VisualVertexSatelliteRenderer;
/**
* A graph viewer that shows a scaled, complete rendering of the graph with which it is
* associated.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class SatelliteGraphViewer<V extends VisualVertex, E extends VisualEdge<V>>
extends SatelliteVisualizationViewer<V, E> {
protected GraphViewer<V, E> graphViewer;
private boolean docked;
public SatelliteGraphViewer(GraphViewer<V, E> master, Dimension preferredSize) {
super(master, preferredSize);
this.graphViewer = master;
setRenderer(new VisualGraphRenderer<>(null));
setGraphMouse(new VisualGraphSatelliteGraphMouse<>());
}
/**
* Sets the docked state of this viewer. An undocked satellite viewer will be in its
* own window.
*
* @param docked true if this viewer is docked; false if it is undocked
*/
public void setDocked(boolean docked) {
this.docked = docked;
}
/**
* Returns true if this satellite viewer is docked
*
* @return true if this satellite viewer is docked
*/
public boolean isDocked() {
return docked;
}
/**
* Gets the renderer to use with this satellite viewer.
*
* @return the renderer
*/
public Renderer.Vertex<V, E> getPreferredVertexRenderer() {
return new VisualVertexSatelliteRenderer<>();
}
@SuppressWarnings("unchecked")
@Override
public VisualGraphPluggableGraphMouse<V, E> getGraphMouse() {
return (VisualGraphPluggableGraphMouse<V, E>) super.getGraphMouse();
}
@Override
public void setGraphMouse(GraphMouse graphMouse) {
if (!(graphMouse instanceof VisualGraphPluggableGraphMouse)) {
// our parent class will install a graph mouse that is not ours
return;
}
super.setGraphMouse(graphMouse);
}
}

View file

@ -0,0 +1,141 @@
/* ###
* 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;
import java.awt.geom.Point2D;
import java.util.List;
import ghidra.graph.GEdge;
/**
* An edge that contains properties and state related to a user interface.
*
* <P>An edge can be selected, which means that it has been clicked by the user. Also, an
* edge can be part of an active path. This allows the UI to paint the edge differently if it
* is in the active path.
*
* <A NAME="articulations"></A>
* <P><U>Articulations</U> - The start and end points are always part of the
* edge. Any additional points on the edge are considered articulation points. Thus, an edge
* without articulations will be drawn as a straight line. An edge with articulations will
* be drawn as a series of straight lines from point-to-point, allowing the layout algorithm
* to add points to the edge to avoid line crossings; these points are used to make the
* drawing of the edge cleaner.
*
* <P><U>equals() and hashCode()</U> - The graph API allows for cloning of layouts. For this
* to correctly copy layout locations, each edge must override <code>equals</code> and
* <code>hashCode</code> in order to properly find edges across graphs.
*
* @param <V> the vertex type
*/
public interface VisualEdge<V extends VisualVertex> extends GEdge<V> {
/**
* Sets this edge selected
*
* @param selected true to select this edge; false to de-select this vertex
*/
public void setSelected(boolean selected);
/**
* Returns true if this edge is selected
*
* @return true if this edge is selected
*/
public boolean isSelected();
/**
* Sets this edge to be marked as in the active path
*
* @param inActivePath true to be marked as in the active path; false to be marked as not
* in the active path
*/
public void setInActivePath(boolean inActivePath);
/**
* Returns true if this edge is part of an active path (this allows the edge to be
* differently rendered)
*
* @return true if this edge is part of the active path
*/
public boolean isInActivePath();
/**
* Returns the points (in {@link GraphViewerUtils} View Space) of the articulation
*
* <P><A HREF="#articulations">What are articulations?</A>
*
* @return the points (in View Space space) of the articulation.
*/
public List<Point2D> getArticulationPoints();
/**
* Sets the articulation points for the given edge
*
* <P><A HREF="#articulations">What are articulations?</A>
*
* @param points the points
*/
public void setArticulationPoints(List<Point2D> points);
/**
* Creates a new edge of this type using the given vertices.
*
* <P>Implementation Note: the odd type 'E' below is there so that subclasses can return
* the type of their implementation. Basically, the decision was made to have each subclass
* suppress the warning that appears, since they know the type is safe. Alternatively,
* each client would have to cast the return type, which seems less desirable.
*
* @param start the start vertex
* @param end the end vertex
* @return the new edge
*/
public <E extends VisualEdge<V>> E cloneEdge(V start, V end);
//==================================================================================================
// Rendering Methods (these could be refactored into another object in the future)
//==================================================================================================
/**
* Sets the emphasis value for this edge. A value of 0 indicates no emphasis.
*
* @param emphasisLevel the emphasis
*/
public void setEmphasis(double emphasisLevel);
/**
* Returns the emphasis value of this edge. 0 if not emphasized.
*
* @return the emphasis value of this edge.
*/
public double getEmphasis();
/**
* Set the alpha, which determines how much of the edge is visible/see through. 0 is
* completely transparent. This attribute allows transitional for animations.
*
* @param alpha the alpha value
*/
public void setAlpha(double alpha);
/**
* Get the alpha, which determines how much of the edge is visible/see through. 0 is
* completely transparent. This attribute allows transitional for animations.
*
* @return the alpha value
*/
public double getAlpha();
}

View file

@ -0,0 +1,28 @@
/* ###
* 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;
import javax.swing.Icon;
import docking.widgets.EmptyBorderButton;
import ghidra.graph.viewer.actions.VisualGraphContextMarker;
class VisualGraphLayeredPaneButton extends EmptyBorderButton implements VisualGraphContextMarker {
VisualGraphLayeredPaneButton(Icon icon) {
super(icon);
}
}

View file

@ -0,0 +1,69 @@
/* ###
* 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;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationServer;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
/**
* An implementation of {@link ScalingControl} that allows us to zoom in and out of the view.
*/
public class VisualGraphScalingControl implements ScalingControl {
private double crossover = 1.0; // full size
@Override
public void scale(VisualizationServer<?, ?> vv, float amount, Point2D at) {
MutableTransformer layoutTransformer =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
MutableTransformer viewTransformer =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
double modelScale = layoutTransformer.getScale();
double viewScale = viewTransformer.getScale();
double inverseViewScale = Math.sqrt(crossover) / viewScale;
double scale = modelScale * viewScale;
//
// Use the 'at' value unless the options dictate otherwise
//
if (!useMouseRelativeZoom(vv)) {
at = vv.getCenter();
}
if (scale * amount < crossover) {
// scale the viewTransformer, return the layoutTransformer to crossover value
viewTransformer.scale(amount, amount, at);
}
// just restore the scale, but don't adjust the layout
else {
viewTransformer.scale(inverseViewScale, inverseViewScale, at);
}
vv.repaint();
}
private boolean useMouseRelativeZoom(VisualizationServer<?, ?> vv) {
if (!(vv instanceof GraphViewer)) {
return true;
}
GraphViewer<?, ?> graphViewer = (GraphViewer<?, ?>) vv;
return graphViewer.useMouseRelativeZoom();
}
}

View file

@ -0,0 +1,593 @@
/* ###
* 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;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import javax.swing.*;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.event.mouse.VertexTooltipProvider;
import ghidra.graph.viewer.event.mouse.VisualGraphMousePlugin;
import ghidra.graph.viewer.layout.LayoutProvider;
import ghidra.graph.viewer.options.VisualGraphOptions;
import ghidra.graph.viewer.vertex.VertexClickListener;
import ghidra.graph.viewer.vertex.VertexFocusListener;
/**
* A view object, where 'view' is used in the sense of the Model-View-Controller (MVC) pattern.
* This class will contain all UI widgets need to display and interact with a graph.
*
* <p><b><u>Implementation Note:</u></b>
* <ol>
* <li>The graph of this component can be null, changing to non-null values over the
* lifetime of this view. This allows this view to be installed in a UI component, with the
* contents changing as needed.
* </li>
* <li>
* When the graph is {@link #setGraph(VisualGraph) set}, the view portion of the class is
* recreated.
* </li>
* <li>
* At any given point in time there may not be a {@link #graphComponent}. This means that
* this class must maintain settings state that it will apply when the component is created.
* This state is atypical and makes this class a bit harder to understand.
* </li>
* </ol>
*
* @param <V> the vertex type
* @param <E> the edge type
* @param <G> the graph type
*/
//@formatter:off
public class VisualGraphView<V extends VisualVertex,
E extends VisualEdge<V>,
G extends VisualGraph<V, E>> {
//@formatter:on
private static final float ZOOM_OUT_AMOUNT = .9f;
private static final float ZOOM_IN_AMOUNT = 1.1f;
private JPanel viewPanel;
private JPanel viewContentPanel;
/*
* This panel is what we give to clients when they wish to show an undocked satellite.
* As graph data is updated, we set and clear the contents of this panel as needed. This
* allows the client to initialize the satellite window once, with updates controlled by
* this class.
*
* Note: this panel will be empty when docked and when the viewer is not yet built
*/
private JPanel undockedSatelliteContentPanel;
// this can be null
private G graph;
protected GraphComponent<V, E, G> graphComponent;
private Optional<VertexFocusListener<V>> clientFocusListener = Optional.empty();
private VertexFocusListener<V> internalFocusListener = v -> {
clientFocusListener.ifPresent(l -> l.vertexFocused(v));
};
private Optional<VertexClickListener<V, E>> clientVertexClickListener = Optional.empty();
private VertexClickListener<V, E> internalVertexClickListener = (v, info) -> {
AtomicBoolean result = new AtomicBoolean();
clientVertexClickListener.ifPresent(l -> result.set(l.vertexDoubleClicked(v, info)));
return result.get();
};
private Optional<GraphSatelliteListener> clientSatelliteListener = Optional.empty();
// this internal listener is the way we manage keeping our state in sync with the
// graph component, as well as how we notify the client listener
private GraphSatelliteListener internalSatelliteListener = (docked, visible) -> {
// keep our internal state in-sync
showSatellite = visible;
satelliteDocked = docked;
clientSatelliteListener.ifPresent(l -> l.satelliteVisibilityChanged(docked, visible));
};
private boolean satelliteDocked = true;
private boolean showSatellite = true;
private boolean showPopups = true;
private VertexTooltipProvider<V, E> tooltipProvider;
private GraphPerspectiveInfo<V, E> graphInfo;
private PathHighlightMode vertexHoverHighlightMode = PathHighlightMode.OFF;
private PathHighlightMode vertexFocusHighlightMode = PathHighlightMode.OFF;
protected LayoutProvider<V, E, G> layoutProvider;
private final ScalingControl scaler = new VisualGraphScalingControl();
public VisualGraphView() {
build();
}
private void build() {
viewPanel = new JPanel(new BorderLayout()) {
@Override
public Dimension getPreferredSize() {
return new Dimension(1000, 1000);
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
};
viewContentPanel = new JPanel(new BorderLayout());
viewPanel.add(viewContentPanel);
undockedSatelliteContentPanel = new JPanel(new BorderLayout());
}
public JComponent getViewComponent() {
return viewPanel;
}
protected void setSouthComponent(JComponent component) {
viewPanel.add(component, BorderLayout.SOUTH);
}
protected void removeSatellite() {
undockedSatelliteContentPanel.removeAll();
undockedSatelliteContentPanel.validate();
}
/**
* Sets the given layout provider, <b>but does not actually perform a layout</b>.
* @param newLayoutProvider the new provider
*/
public void setLayoutProvider(LayoutProvider<V, E, G> newLayoutProvider) {
this.layoutProvider = newLayoutProvider;
}
public void setGraph(G graph) {
stopAllAnimation();
this.graph = graph;
installGraphViewer();
}
public void setSatelliteListener(GraphSatelliteListener l) {
clientSatelliteListener = Optional.ofNullable(l);
}
public void setVertexFocusListener(VertexFocusListener<V> l) {
clientFocusListener = Optional.ofNullable(l);
}
/**
* Sets a listener that allows clients to be notified of vertex double-clicks. Normal
* mouse processing is handled by the {@link VisualGraphMousePlugin} class. This is a
* convenience method so that clients do not have to deal with the mouse plugin.
*
* @param l the listener
*/
public void setVertexClickListener(VertexClickListener<V, E> l) {
clientVertexClickListener = Optional.ofNullable(l);
}
private void stopAllAnimation() {
VisualGraphViewUpdater<V, E> updater = getViewUpdater();
if (updater != null) {
updater.stopAllAnimation();
}
}
protected void installGraphViewer() {
GraphComponent<V, E, G> newGraphComponent = new GraphComponent<>(graph);
newGraphComponent.setGraphOptions(new VisualGraphOptions());
setGraphComponent(newGraphComponent);
}
protected void setGraphComponent(GraphComponent<V, E, G> newComponent) {
disposeViewer();
this.graphComponent = newComponent;
//
// Initialize
//
graphComponent.setPopupsVisible(showPopups);
if (graphInfo != null) {
graphComponent.setGraphPerspective(graphInfo);
graphInfo = null;
}
graphComponent.setVertexHoverPathHighlightMode(vertexHoverHighlightMode);
graphComponent.setVertexFocusPathHighlightMode(vertexFocusHighlightMode);
GraphViewer<V, E> viewer = graphComponent.getPrimaryViewer();
if (tooltipProvider != null) {
viewer.setVertexTooltipProvider(tooltipProvider);
}
//
// Wire to the UI
//
JComponent component = graphComponent.getComponent();
Rectangle viewPanelBounds = viewContentPanel.getBounds();
component.setBounds(viewPanelBounds);
component.doLayout();
viewContentPanel.removeAll();
viewContentPanel.add(component);
viewContentPanel.validate();
undockedSatelliteContentPanel.removeAll();
graphComponent.setVertexFocusListener(internalFocusListener);
graphComponent.setVertexClickListener(internalVertexClickListener);
graphComponent.setSatelliteLisetener(internalSatelliteListener);
graphComponent.setInitialSatelliteState(showSatellite, satelliteDocked);
if (!satelliteDocked) {
// we must update the undocked panel's component since it may have an old reference
undockedSatelliteContentPanel.add(graphComponent.getSatelliteContentComponent());
undockedSatelliteContentPanel.validate();
}
}
/*
* Sets the contents of the view's content panel to be the given component
*/
protected void setContent(Component c) {
viewContentPanel.removeAll();
viewContentPanel.add(c);
viewPanel.validate();
repaint();
}
protected <T> T getWithBusyCursor(Supplier<T> s) {
Cursor originalCursor = viewPanel.getCursor();
try {
viewPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
T t = s.get();
return t;
}
finally {
viewPanel.setCursor(originalCursor);
}
}
public G getVisualGraph() {
return graph;
}
/**
* Returns the primary viewer of the graph (as opposed to the satellite viewer). The
* viewer returned is responsible for maintaining view information for a given graph.
*
* @return the primary viewer
*/
public GraphViewer<V, E> getPrimaryGraphViewer() {
if (graphComponent == null) {
return null;
}
return graphComponent.getPrimaryViewer();
}
public SatelliteGraphViewer<V, E> getSatelliteViewer() {
return graphComponent.getSatelliteViewer();
}
/**
* Sets the perspective for this view
*
* @param newPerspective the new perspective
*/
public void setGraphPerspective(GraphPerspectiveInfo<V, E> newPerspective) {
if (graphComponent != null) {
graphComponent.setGraphPerspective(newPerspective);
graphInfo = null;
}
else {
graphInfo = newPerspective;
}
}
public GraphPerspectiveInfo<V, E> generateGraphPerspective() {
RenderContext<V, E> context = graphComponent.getRenderContext();
return new GraphPerspectiveInfo<>(context, getZoom());
}
private double getZoom() {
if (graphComponent == null) {
if (graphInfo != null) {
return graphInfo.getZoom();
}
return 1d;
}
return GraphViewerUtils.getGraphScale(getPrimaryGraphViewer());
}
/*
* Sets the entire view to be the given message instead of a graph. This differs from
* setStatusMessage() in that the status is overlayed on the graph.
*/
public void showErrorView(String errorMessage) {
stopAllAnimation();
this.graph = null;
removeSatellite();
viewContentPanel.removeAll();
viewContentPanel.paintImmediately(viewContentPanel.getBounds());
JLabel messageLabel = new JLabel(errorMessage);
Font font = messageLabel.getFont();
messageLabel.setFont(font.deriveFont(22f)); // make a bit bigger for readability
messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
messageLabel.setFocusable(true); // we have to have something focusable in our provider
viewContentPanel.add(messageLabel, BorderLayout.NORTH);
viewContentPanel.validate();
disposeViewer();
}
/**
* Sets a message to be painted on the viewer. This is useful to show a text message to the
* user. Passing null will clear the message.
*
* @param message the status message
*/
public void setStatusMessage(String message) {
if (graphComponent != null) {
graphComponent.setStatusMessage(message);
}
}
public GraphComponent<V, E, G> getGraphComponent() {
return graphComponent;
}
/**
* Returns whether the satellite intended to be visible. If this component is built, then
* a result of true means that the satellite is showing. If the component is not yet
* built, then a result of true means that the satellite will be made visible when the
* component is built.
*
* @return true if visible
*/
public boolean isSatelliteVisible() {
return showSatellite;
}
public void setSatelliteVisible(boolean visible) {
if (showSatellite == visible) {
return; // nothing to do
}
this.showSatellite = visible;
if (graphComponent != null) {
graphComponent.setSatelliteVisible(visible);
}
}
public void setSatelliteDocked(boolean docked) {
if (satelliteDocked == docked) {
return; // nothing to do
}
this.satelliteDocked = docked;
if (graphComponent == null) {
return;
}
graphComponent.setSatelliteDocked(docked);
if (!docked) {
undockedSatelliteContentPanel.removeAll();
undockedSatelliteContentPanel.add(graphComponent.getSatelliteContentComponent());
undockedSatelliteContentPanel.validate();
}
}
/**
* Returns whether the satellite intended to be docked. If this component is built, then
* a result of true means that the satellite is docked. If the component is not yet
* built, then a result of true means that the satellite will be made docked when the
* component is built.
*
* @return true if visible
*/
public boolean isSatelliteDocked() {
return satelliteDocked;
}
public void setPopupsVisible(boolean visible) {
this.showPopups = visible;
if (graphComponent != null) {
graphComponent.setPopupsVisible(visible);
}
}
public boolean arePopupsEnabled() {
return showPopups;
}
public JComponent getUndockedSatelliteComponent() {
return undockedSatelliteContentPanel;
}
public boolean isSatelliteComponent(Component c) {
if (graphComponent != null) {
return graphComponent.isSatelliteComponent(c);
}
return false;
}
public void setVertexHoverPathHighlightMode(PathHighlightMode mode) {
this.vertexHoverHighlightMode = mode;
if (graphComponent != null) {
graphComponent.setVertexHoverPathHighlightMode(mode);
}
}
public void setVertexFocusPathHighlightMode(PathHighlightMode mode) {
this.vertexFocusHighlightMode = mode;
if (graphComponent != null) {
graphComponent.setVertexFocusPathHighlightMode(mode);
}
}
public PathHighlightMode getVertexFocusPathHighlightMode() {
return vertexFocusHighlightMode;
}
public PathHighlightMode getVertexHoverPathHighlightMode() {
return vertexHoverHighlightMode;
}
public void setTooltipProvider(VertexTooltipProvider<V, E> provider) {
this.tooltipProvider = provider;
if (graphComponent != null) {
GraphViewer<V, E> viewer = graphComponent.getPrimaryViewer();
viewer.setVertexTooltipProvider(provider);
}
}
public void zoomOutGraph() {
VisualizationViewer<V, E> primaryViewer = getPrimaryGraphViewer();
scaler.scale(primaryViewer, ZOOM_OUT_AMOUNT, primaryViewer.getCenter());
}
public void zoomInGraph() {
VisualizationViewer<V, E> primaryViewer = getPrimaryGraphViewer();
scaler.scale(primaryViewer, ZOOM_IN_AMOUNT, primaryViewer.getCenter());
}
public void zoomToVertex(V v) {
VisualGraphViewUpdater<V, E> updater = getViewUpdater();
updater.zoomInCompletely(v);
}
public void zoomToWindow() {
VisualGraphViewUpdater<V, E> updater = getViewUpdater();
updater.fitGraphToViewerNow();
}
public VisualGraphViewUpdater<V, E> getViewUpdater() {
if (graphComponent == null) {
return null;
}
GraphViewer<V, E> viewer = getPrimaryGraphViewer();
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
return updater;
}
public Point getVertexPointInViewSpace(V v) {
return GraphViewerUtils.getPointInViewSpaceForVertex(getPrimaryGraphViewer(), v);
}
public Point translatePointFromVertexToViewSpace(V v, Point p) {
return GraphViewerUtils.translatePointFromVertexRelativeSpaceToViewSpace(
getPrimaryGraphViewer(), v, p);
}
public Rectangle translateRectangleFromVertexToViewSpace(V v, Rectangle r) {
return GraphViewerUtils.translateRectangleFromVertexRelativeSpaceToViewSpace(
getPrimaryGraphViewer(), v, r);
}
public MouseEvent translateMouseEventFromVertexToViewSpace(V v, MouseEvent e) {
Point viewerPoint = translatePointFromVertexToViewSpace(v, e.getPoint());
VisualizationViewer<V, E> newSource = getPrimaryGraphViewer();
return new MouseEvent(newSource, e.getID(), e.getWhen(), e.getModifiers(),
(int) viewerPoint.getX(), (int) viewerPoint.getY(), e.getClickCount(),
e.isPopupTrigger(), e.getButton());
}
public boolean isScaledPastInteractionThreshold() {
if (graphComponent == null) {
// I think this is some sort of timing issue
return true;// not sure what to return here...default to true?
}
return GraphViewerUtils.isScaledPastVertexInteractionThreshold(getPrimaryGraphViewer());
}
protected void maybeTwinkleVertex(V twinkleVertex, boolean doTwinkle) {
if (!doTwinkle) {
return;
}
graphComponent.twinkleVertex(twinkleVertex);
}
public void requestFocus() {
viewPanel.requestFocus();
}
public void repaint() {
viewPanel.repaint();
}
public V getFocusedVertex() {
if (graph == null) {
return null;
}
return graph.getFocusedVertex();
}
public Set<V> getSelectedVertices() {
if (graph == null) {
return Collections.emptySet();
}
return graph.getSelectedVertices();
}
public LayoutProvider<V, E, G> getLayoutProvider() {
return layoutProvider;
}
/**
* Effectively clears this display. This method is not called dispose, as that implies
* the end of an object's lifecycle. This object can be re-used after this method is
* called.
*/
public void cleanup() {
disposeViewer();
// do not do this, the component gets re-used between graph loads--must keep the listener
// clientSatelliteListener = Optional.empty();
}
protected void disposeViewer() {
if (graphComponent != null) {
graphComponent.dispose();
graphComponent = null;
}
removeSatellite();
}
}

View file

@ -0,0 +1,446 @@
/* ###
* 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;
import static ghidra.graph.viewer.GraphViewerUtils.*;
import java.awt.*;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Objects;
import com.google.common.base.Function;
import edu.uci.ics.jung.visualization.*;
import ghidra.generic.function.Callback;
import ghidra.graph.VisualGraph;
import ghidra.graph.job.*;
import ghidra.graph.viewer.edge.routing.BasicEdgeRouter;
import ghidra.util.Msg;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.AssertException;
import ghidra.util.task.BusyListener;
/**
* This is the class through which operations travel that manipulate the view and graph <b>while
* plugged-in to the UI</b>. (Setup and tear down operations performed before the view
* or graph are visible need not pass through this class.) This class is responsible for
* controlling how to display view and graph changes, including whether to animate.
*
* <P>The animations are categorized into those that mutate the graph and those that are just
* display animations (like hover animations).
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class VisualGraphViewUpdater<V extends VisualVertex, E extends VisualEdge<V>> {
@SuppressWarnings("unused") // may be useful in the future
private VisualGraph<V, E> graph;
private GraphJobRunner jobRunner = new GraphJobRunner();
// TODO merge the two animators into a 'non-mutating, concurrent' job runner
private static AbstractAnimator edgeHoverAnimator;
private static TwinkleVertexAnimator<?, ?> vertexTwinkleAnimator;
private GraphViewer<V, E> primaryViewer;
// may be null if a graph does not use a satellite
private SatelliteGraphViewer<V, E> satelliteViewer;
private WeakSet<Callback> jobStartedListeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
public VisualGraphViewUpdater(GraphViewer<V, E> primaryViewer, VisualGraph<V, E> graph) {
this.primaryViewer = Objects.requireNonNull(primaryViewer);
this.graph = Objects.requireNonNull(graph);
}
protected void setSatelliteViewer(SatelliteGraphViewer<V, E> satelliteViewer) {
this.satelliteViewer = satelliteViewer;
}
/**
* Add a listener to be notified when a job is started. Jobs often, but not always, mutate
* the underlying graph. For this reason, other tasks that use the graph may want to not
* do their work while a job is running.
*
* @param c the listener
*/
public void addJobScheduledListener(Callback c) {
jobStartedListeners.add(c);
}
public boolean isAnimationEnabled() {
return primaryViewer.getOptions().useAnimation();
}
public void dispose() {
// stop all mutations and animations
jobRunner.dispose();
}
/**
* Fits the graph into both the primary and satellite views
*/
public void fitAllGraphsToViewsNow() {
scheduleViewChangeJob(new FitGraphToViewJob<>(primaryViewer, satelliteViewer));
}
public void fitGraphToViewerNow() {
fitGraphToViewerNow(primaryViewer);
}
public void fitGraphToViewerNow(VisualizationServer<V, E> theViewer) {
scheduleViewChangeJob(new FitGraphToViewJob<>(theViewer));
}
/**
* Will schedule the fitting work to happen now if now work is being done, or later otherwis
*/
public void fitGraphToViewerLater() {
jobRunner.setFinalJob(new FitGraphToViewJob<>(primaryViewer));
}
public void fitGraphToViewerLater(VisualizationServer<V, E> theViewer) {
jobRunner.setFinalJob(new FitGraphToViewJob<>(theViewer));
}
public void zoomInCompletely() {
zoomInCompletely(null);
}
public void zoomInCompletely(V centerOnVertex) {
setGraphScale(1.0);
if (centerOnVertex == null) {
return;
}
moveVertexToCenterWithoutAnimation(centerOnVertex);
}
public void moveVertexToCenterTopWithoutAnimation(V vertex) {
// Note: it is implied that any move *without* animation is a signal to cancel
// all animation, as it is usually in response to a major structural change in the graph
stopAllAnimation();
Point2D.Double desiredOffsetPoint =
getVertexOffsetFromLayoutCenterTop(primaryViewer, vertex);
double dx = desiredOffsetPoint.getX();
double dy = desiredOffsetPoint.getY();
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
multiLayerTransformer.getTransformer(Layer.LAYOUT).translate(dx, dy);
primaryViewer.repaint();
}
public void moveVertexToCenterWithoutAnimation(V vertex) {
// Note: it is implied that any move *without* animation is a signal to cancel
// all animation, as it is usually in response to a major structural change in the graph
stopAllAnimation();
Point2D.Double desiredOffsetPoint = getVertexOffsetFromLayoutCenter(primaryViewer, vertex);
double dx = desiredOffsetPoint.getX();
double dy = desiredOffsetPoint.getY();
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
multiLayerTransformer.getTransformer(Layer.LAYOUT).translate(dx, dy);
primaryViewer.repaint();
}
/*
moveVertexToCenterLater(vertex);
moveVertexToCenterAnimated(vertex);
*/
public void moveVertexToCenterWithAnimation(V vertex) {
moveVertexToCenterWithAnimation(vertex, null);
}
public void moveVertexToCenterWithAnimation(V vertex, BusyListener callbackListener) {
MoveVertexToCenterAnimatorFunctionGraphJob<V, E> job =
new MoveVertexToCenterAnimatorFunctionGraphJob<>(primaryViewer, vertex,
isAnimationEnabled());
job.setBusyListener(callbackListener);
scheduleViewChangeJob(job);
}
public void moveVertexToCenterTopWithAnimation(V vertex) {
moveVertexToCenterTopWithAnimation(vertex, null);
}
public void moveVertexToCenterTopWithAnimation(V vertex, BusyListener callbackListener) {
MoveVertexToCenterTopAnimatorFunctionGraphJob<V, E> job =
new MoveVertexToCenterTopAnimatorFunctionGraphJob<>(primaryViewer, vertex,
isAnimationEnabled());
job.setBusyListener(callbackListener);
scheduleViewChangeJob(job);
}
public void moveViewerLocationWithoutAnimation(Point translation) {
// Note: it is implied that any move *without* animation is a signal to cancel
// all animation, as it is usually in response to a major structural change in the graph
stopAllAnimation();
double dx = translation.x;
double dy = translation.y;
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
multiLayerTransformer.getTransformer(Layer.LAYOUT).translate(dx, dy);
primaryViewer.repaint();
}
public void centerViewSpacePointWithAnimation(Point point) {
scheduleViewChangeJob(new MoveViewToViewSpacePointAnimatorFunctionGraphJob<>(primaryViewer,
point, isAnimationEnabled()));
}
public void centerViewSpacePointWithoutAnimation(Point point) {
Point pointInLayoutSpace = translatePointFromViewSpaceToLayoutSpace(point, primaryViewer);
centerLayoutSpacePointWithoutAnimation(pointInLayoutSpace);
}
public void centerLayoutSpacePointWithoutAnimation(Point point) {
// Note: it is implied that any move *without* animation is a signal to cancel
// all animation, as it is usually in response to a major structural change in the graph
stopAllAnimation();
Point2D.Double translationOffset = getOffsetFromCenterInLayoutSpace(primaryViewer, point);
double dx = translationOffset.getX();
double dy = translationOffset.getY();
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
multiLayerTransformer.getTransformer(Layer.LAYOUT).translate(dx, dy);
primaryViewer.repaint();
}
public void setLayoutSpacePointWithoutAnimation(Point2D point) {
// Note: it is implied that any move *without* animation is a signal to cancel
// all animation, as it is usually in response to a major structural change in the graph
stopAllAnimation();
double dx = point.getX();
double dy = point.getY();
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
multiLayerTransformer.getTransformer(Layer.LAYOUT).translate(dx, dy);
primaryViewer.repaint();
}
public void setLayoutSpacePointWithAnimation(Point point) {
scheduleViewChangeJob(new MoveViewToLayoutSpacePointAnimatorFunctionGraphJob<>(
primaryViewer, point, isAnimationEnabled()));
}
public void ensureVertexVisible(V vertex, Rectangle area) {
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
Function<? super V, Shape> transformer = renderContext.getVertexShapeTransformer();
Shape shape = transformer.apply(vertex);
Rectangle bounds = shape.getBounds();
ensureVertexAreaVisible(vertex, bounds, null);
}
/*
* Makes sure that the given rectangle is not outside of the primary viewer's view and that
* the area is not occluded by the satellite viewer.
*/
public void ensureVertexAreaVisible(V vertex, Rectangle area, BusyListener callbackListener) {
Objects.requireNonNull(vertex, "Vertex cannot be null");
Objects.requireNonNull(area, "Area rectangle cannot be null");
EnsureAreaVisibleAnimatorFunctionGraphJob<V, E> job =
new EnsureAreaVisibleAnimatorFunctionGraphJob<>(primaryViewer, satelliteViewer, vertex,
area, isAnimationEnabled());
job.setBusyListener(callbackListener);
scheduleViewChangeJob(job);
}
public void updateEdgeShapes(Collection<E> edges) {
if (!layoutUsesEdgeArticulations(primaryViewer.getGraphLayout())) {
return;
}
// ArticulatedEdgeRouter<V, E> edgeRouter = new ArticulatedEdgeRouter<V, E>(viewer, edges);
BasicEdgeRouter<V, E> edgeRouter = new BasicEdgeRouter<>(primaryViewer, edges);
edgeRouter.route();
}
public void setGraphPerspective(GraphPerspectiveInfo<V, E> graphInfo) {
/*
Current Issues (see SCR 9208):
-the given data is not created correctly--the memento is using location information
from when the navigation takes place, which may be after the user has moved the
graph (panned it by hand). So, we really need to record the info at the point when
the user first clicks or when the location is first set.
-How to handle the case where the user moves vertices that are on the navigation stack?
--Use this algorithm and then ensure that the cursor is on the screen.
*/
if (graphInfo == null) {
return;
}
// Note: Using this method implies a major structural change in the graph
stopAllAnimation();
if (!graphInfo.isRestoreZoom()) {
// note: if we want to support this, then we will have to adjust the translate
// coordinates based upon the differences in zoom.
Msg.error(this, "Restoring the view coordinates without " +
"restoring the zoom is currently not supported.", new AssertException());
}
RenderContext<V, E> renderContext = primaryViewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
// restore the current transform before we
setGraphScale(graphInfo.getZoom());
Point layoutPoint = graphInfo.getLayoutTranslateCoordinates();
multiLayerTransformer.getTransformer(Layer.LAYOUT).setTranslate(layoutPoint.x,
layoutPoint.y);
Point viewPoint = graphInfo.getViewTranslateCoordinates();
multiLayerTransformer.getTransformer(Layer.VIEW).setTranslate(viewPoint.x, viewPoint.y);
}
public void twinkeVertex(V vertex) {
if (!isScaledPastVertexInteractionThreshold(primaryViewer)) {
return;
}
if (vertexTwinkleAnimator != null) {
if (vertexTwinkleAnimator.getVertex() == vertex) {
return; // let the current twinkle just finish
}
vertexTwinkleAnimator.stop();
}
vertexTwinkleAnimator =
new TwinkleVertexAnimator<>(primaryViewer, vertex, isAnimationEnabled());
vertexTwinkleAnimator.start();
}
public void setGraphScale(double scale) {
stopAllAnimation();
GraphViewerUtils.setGraphScale(primaryViewer, scale);
}
public void animateEdgeHover() {
if (edgeHoverAnimator != null) {
edgeHoverAnimator.stop();
}
edgeHoverAnimator =
new EdgeHoverAnimator<>(primaryViewer, satelliteViewer, isAnimationEnabled());
edgeHoverAnimator.start();
}
/**
* Returns true if this updater is performing any animations or running any jobs that can
* mutate the graph or view
*
* @return true if busy
*/
public boolean isBusy() {
if (edgeHoverAnimator != null) {
if (!edgeHoverAnimator.hasFinished()) {
return true;
}
}
if (vertexTwinkleAnimator != null) {
if (!vertexTwinkleAnimator.hasFinished()) {
return true;
}
}
boolean busy = jobRunner.isBusy();
return busy;
}
/**
* Returns true if this updater is running any jobs that can mutate the graph or view
*
* @return true if busy
*/
public boolean isMutatingGraph() {
boolean busy = jobRunner.isBusy();
return busy;
}
//==================================================================================================
// Animation Methods
//==================================================================================================
public void scheduleViewChangeJob(GraphJob job) {
jobStartedListeners.forEach(l -> l.call());
stopAllNonMutativeAnimation();
jobRunner.schedule(job);
}
public void stopEdgeHoverAnimation() {
if (edgeHoverAnimator != null) {
edgeHoverAnimator.stop();
edgeHoverAnimator = null;
}
}
private void stopVertexTwinkleAnimation() {
if (vertexTwinkleAnimator != null) {
vertexTwinkleAnimator.stop();
vertexTwinkleAnimator = null;
}
}
public void stopAllAnimation() {
stopAllNonMutativeAnimation();
jobRunner.finishAllJobs();
}
protected void stopAllNonMutativeAnimation() {
stopEdgeHoverAnimation();
stopVertexTwinkleAnimation();
}
}

View file

@ -0,0 +1,150 @@
/* ###
* 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;
import java.awt.Component;
import java.awt.geom.Point2D;
import javax.swing.JComponent;
import ghidra.graph.GVertex;
/**
* A vertex that contains properties and state related to a user interface.
*
* <P><U>equals() and hashCode()</U> - The graph API allows for cloning of layouts. For this
* to correctly copy layout locations, each edge must override <code>equals</code> and
* <code>hashCode</code> in order to properly find edges across graphs.
*/
public interface VisualVertex extends GVertex {
/**
* Returns the component of this vertex. This is used for rendering and interaction
* with the user.
*
* @return the component of this vertex
*/
public JComponent getComponent();
/**
* Sets this vertex to be focused. This differs from being selected in that multiple
* vertices in a graph can be selected, but only one can be the focused vertex.
*
* @param focused true to focus; false to be marked as not focused
*/
public void setFocused(boolean focused);
/**
* Returns true if this vertex is focused (see {@link #setFocused(boolean)}
* @return true if focused
*/
public boolean isFocused();
/**
* Sets this vertex selected
*
* @param selected true to select this vertex; false to de-select this vertex
*/
public void setSelected(boolean selected);
/**
* Returns true if this vertex is selected
*
* @return true if this vertex is selected
*/
public boolean isSelected();
/**
* Sets this vertex to be hovered
*
* @param hovered true to be marked as hovered; false to be marked as not hovered
*/
public void setHovered(boolean hovered);
/**
* Returns true if this vertex is being hovered by the mouse
*
* @return true if this vertex is being hovered by the mouse
*/
public boolean isHovered();
/**
* Sets the location of this vertex in the view
*
* @param p the location of this vertex in the view
*/
public void setLocation(Point2D p);
/**
* Returns the location of this vertex in the view
*
* @return the location of this vertex in the view
*/
public Point2D getLocation();
/**
* Returns true if the given component of this vertex is grabbable, which means that
* mouse drags on that component will move the vertex.
*
* <P>This is used to differentiate components within a vertex that should receive mouse
* events versus those components that will not be given mouse events.
*
* @param c the component
* @return true if the component is grabbable
*/
public boolean isGrabbable(Component c);
/**
* A dispose method that should be called when a vertex is reclaimed, never again to be
* used in a graph or display
*/
public void dispose();
//==================================================================================================
// Rendering Methods (these could be refactored into another object in the future)
//==================================================================================================
/**
* Sets the emphasis value for this vertex. A value of 0 indicates no emphasis.
*
* @param emphasisLevel the emphasis
*/
public void setEmphasis(double emphasisLevel);
/**
* Returns the emphasis value of this vertex. 0 if not emphasized.
*
* @return the emphasis value of this vertex.
*/
public double getEmphasis();
/**
* Set the alpha, which determines how much of the vertex is visible/see through. 0 is
* completely transparent. This attribute allows transitional for animations.
*
* @param the alpha value
*/
public void setAlpha(double alpha);
/**
* Get the alpha, which determines how much of the vertex is visible/see through. 0 is
* completely transparent. This attribute allows transitional for animations.
*
* @return the alpha value
*/
public double getAlpha();
}

View file

@ -0,0 +1,44 @@
/* ###
* 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.actions;
import docking.ActionContext;
import docking.ComponentProvider;
import ghidra.graph.VisualGraph;
/**
* Context for {@link VisualGraph}s
*/
public class VgActionContext extends ActionContext implements VisualGraphActionContext {
public VgActionContext(ComponentProvider provider) {
this(provider, null);
}
public VgActionContext(ComponentProvider provider, Object contextObject) {
super(provider, null);
}
/**
* Returns true actions that manipulate the satellite viewer should be enabled for this context
* @return true actions that manipulate the satellite viewer should be enabled for this context
*/
@Override
public boolean shouldShowSatelliteActions() {
// these actions should be available generically; subclasses may override to return false
return true;
}
}

View file

@ -0,0 +1,29 @@
/* ###
* 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.actions;
import docking.ComponentProvider;
import ghidra.graph.VisualGraph;
/**
* Context for {@link VisualGraph}'s satellite viewer
*/
public class VgSatelliteContext extends VgActionContext {
public VgSatelliteContext(ComponentProvider provider) {
super(provider);
}
}

View file

@ -0,0 +1,46 @@
/* ###
* 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.actions;
import docking.ComponentProvider;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.VisualVertex;
/**
* Context for a {@link VisualGraph} when a vertex is selected
*
* @param <V> the vertex type
*/
public class VgVertexContext<V extends VisualVertex> extends VgActionContext
implements VisualGraphVertexActionContext<V> {
private V v;
public VgVertexContext(ComponentProvider provider, V v) {
super(provider);
this.v = v;
}
@Override
public V getVertex() {
return v;
}
@Override
public boolean shouldShowSatelliteActions() {
return false; // not satellite actions when we are over a vertex
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.actions;
import ghidra.graph.VisualGraph;
/**
* Action context for {@link VisualGraph}s
*/
public interface VisualGraphActionContext {
/**
* Returns true actions that manipulate the satellite viewer should be enabled for this context
* @return true actions that manipulate the satellite viewer should be enabled for this context
*/
public default boolean shouldShowSatelliteActions() {
// these actions should be available generically; subclasses may override to return false
return true;
}
}

View file

@ -0,0 +1,20 @@
/* ###
* 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.actions;
public interface VisualGraphContextMarker {
// marker interface that widgets can implement for identification for action context
}

View file

@ -0,0 +1,21 @@
/* ###
* 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.actions;
public interface VisualGraphSatelliteActionContext extends VisualGraphActionContext {
// marker interface
}

View file

@ -0,0 +1,39 @@
/* ###
* 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.actions;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.VisualVertex;
/**
* Context for a {@link VisualGraph} when a vertex is selected
*
* @param <V> the vertex type
*/
public interface VisualGraphVertexActionContext<V extends VisualVertex>
extends VisualGraphActionContext {
public V getVertex();
/**
* Returns true actions that manipulate the satellite viewer should be enabled for this context
* @return true actions that manipulate the satellite viewer should be enabled for this context
*/
@Override
public default boolean shouldShowSatelliteActions() {
// no satellite viewer actions when on a vertex
return false;
}
}

View file

@ -0,0 +1,153 @@
/* ###
* 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.edge;
import java.awt.geom.Point2D;
import java.util.*;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
/**
* An implementation of {@link VisualEdge} that implements the base interface so subclasses
* do not have to.
*
* @param <V> the vertex type
*/
public abstract class AbstractVisualEdge<V extends VisualVertex> implements VisualEdge<V> {
private V start;
private V end;
private boolean selected;
private boolean inActivePath;
private double alpha = 1.0;
private double emphasis;
private List<Point2D> articulations = new ArrayList<>();
public AbstractVisualEdge(V start, V end) {
this.start = start;
this.end = end;
}
@Override
public V getStart() {
return start;
}
@Override
public V getEnd() {
return end;
}
@Override
public void setSelected(boolean selected) {
this.selected = selected;
}
@Override
public boolean isSelected() {
return selected;
}
@Override
public void setInActivePath(boolean inActivePath) {
this.inActivePath = inActivePath;
}
@Override
public boolean isInActivePath() {
return inActivePath;
}
@Override
public List<Point2D> getArticulationPoints() {
return Collections.unmodifiableList(articulations);
}
@Override
public void setArticulationPoints(List<Point2D> points) {
this.articulations = new ArrayList<>(points);
}
@Override
public void setEmphasis(double emphasisLevel) {
this.emphasis = emphasisLevel;
}
@Override
public double getEmphasis() {
return emphasis;
}
@Override
public void setAlpha(double alpha) {
this.alpha = alpha;
}
@Override
public double getAlpha() {
return alpha;
}
@Override
public String toString() {
return "[" + start + ", " + end + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((end == null) ? 0 : end.hashCode());
result = prime * result + ((start == null) ? 0 : start.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
AbstractVisualEdge<?> other = (AbstractVisualEdge<?>) obj;
if (end == null) {
if (other.end != null) {
return false;
}
}
else if (!end.equals(other.end)) {
return false;
}
if (start == null) {
if (other.start != null) {
return false;
}
}
else if (!start.equals(other.start)) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,67 @@
/* ###
* 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.edge;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.LayoutDecorator;
import edu.uci.ics.jung.visualization.RenderContext;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import ghidra.graph.viewer.layout.VisualGraphLayout;
/**
* A class to override the default edge label placement. This class is called a renderer because
* the parent class is. However, it is not a renderer in the sense that it's job is to paint
* the contents, like in Java when you provide a cell rendering component, but rather, it uses
* such a component. Further, the job of this class is to position said component and then to
* have it paint its contents.
* <p>
* Normally we would just set our custom renderer on the {@link RenderContext} at construction
* time, like we do with the other rendering classes, but not such method is provided.
*/
public class BasicEdgeLabelRenderer<V extends VisualVertex, E extends VisualEdge<V>>
extends edu.uci.ics.jung.visualization.renderers.BasicEdgeLabelRenderer<V, E> {
@Override
public void labelEdge(RenderContext<V, E> rc, Layout<V, E> layout, E e, String label) {
// TODO delete this class
// FGLayout functionGraphLayout = getFunctionGraphLayout(layout);
// if (functionGraphLayout != null) {
// EdgeLabel<FunctionGraphVertex, FunctionGraphEdge> overridingRenderer =
// functionGraphLayout.getEdgeLabelRenderer();
// if (overridingRenderer != null) {
// overridingRenderer.labelEdge(rc, layout, e, label);
// return;
// }
// }
super.labelEdge(rc, layout, e, label);
}
private VisualGraphLayout<V, E> getFunctionGraphLayout(Layout<V, E> layout) {
if (layout instanceof LayoutDecorator) {
LayoutDecorator<V, E> layoutDecorator = (LayoutDecorator<V, E>) layout;
layout = layoutDecorator.getDelegate();
}
if (layout instanceof VisualGraphLayout) {
return (VisualGraphLayout<V, E>) layout;
}
return null;
}
}

View file

@ -0,0 +1,87 @@
/* ###
* 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.edge;
import java.util.*;
import ghidra.graph.*;
import ghidra.graph.viewer.*;
import ghidra.util.task.SwingRunnable;
import ghidra.util.task.TaskMonitor;
/**
* A task to find all the loops in a graph.
*
* @param <V> the vertex type
* @param <E> the edge type
* @param <G> the graph type
*/
//@formatter:off
public class InitializeCircuitsRunnable<V extends VisualVertex,
E extends VisualEdge<V>,
G extends VisualGraph<V, E>>
implements SwingRunnable {
//@formatter:on
private final VisualGraph<V, E> graph;
private final Set<E> allCircuitResults;
private final Map<V, Set<E>> circuitFlowResults;
private VisualGraphView<V, E, G> view;
public InitializeCircuitsRunnable(VisualGraphView<V, E, G> view, VisualGraph<V, E> graph) {
this.view = Objects.requireNonNull(view);
this.graph = graph;
this.allCircuitResults = new HashSet<>();
this.circuitFlowResults = new HashMap<>();
}
@Override
public void monitoredRun(TaskMonitor monitor) {
monitor.setMessage("Finding all loops");
Set<Set<V>> strongs = GraphAlgorithms.getStronglyConnectedComponents(graph);
for (Set<V> vertices : strongs) {
if (vertices.size() == 1) {
continue;
}
GDirectedGraph<V, E> subGraph = GraphAlgorithms.createSubGraph(graph, vertices);
Collection<E> edges = subGraph.getEdges();
allCircuitResults.addAll(edges);
HashSet<E> asSet = new HashSet<>(edges);
Collection<V> subVertices = subGraph.getVertices();
for (V v : subVertices) {
circuitFlowResults.put(v, asSet);
}
}
}
@Override
public void swingRun(boolean isCancelled) {
if (isCancelled) {
return;
}
// TODO delete this class...now!
// GraphViewer<V, E> viewer = view.getPrimaryGraphViewer();
// VisualGraphPathHighlighter<V, E> pathHighlighter = viewer.getPathHighlighter();
// pathHighlighter.setEdgeCircuits(allCircuitResults, circuitFlowResults);
// view.repaint();
}
}

View file

@ -0,0 +1,27 @@
/* ###
* 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.edge;
public interface PathHighlightListner {
/**
* Called when the a path is highlighted.
*
* @param hoverChange true if the change path is hover change; false if the changed path
* is a selection change
*/
public void pathHighlightChanged(boolean hoverChange);
}

View file

@ -0,0 +1,28 @@
/* ###
* 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.edge;
/**
* A simple boolean supplier that signals if path highlighting work should not take place
*/
public interface PathHighlighterWorkPauser {
/**
* True if work should not happen; false for normal path highlighting operations
* @return if work should not happen
*/
public boolean isPaused();
}

View file

@ -0,0 +1,461 @@
/* ###
* 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.edge;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import javax.swing.JComponent;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.renderers.*;
import edu.uci.ics.jung.visualization.transform.LensTransformer;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import edu.uci.ics.jung.visualization.transform.shape.GraphicsDecorator;
import edu.uci.ics.jung.visualization.util.VertexShapeFactory;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
import ghidra.graph.viewer.vertex.VertexShapeProvider;
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
/**
* Edge render for the {@link VisualGraph} system
*
* <h3><center>Implementation Notes</center></h3>
*
* <h4>Jung Vertex/Edge Rendering</h4>
* <p>Jung creates shapes for vertices (see {@link VertexShapeFactory}) that are centered. They
* do this by getting the width/height of the shape and then creating an x/y value that is
* half of the width and height, respectively. This has the effect of the vertex appearing
* centered over its connected edge. We mimic that with our
* {@link VisualGraphVertexShapeTransformer} so that our edge rendering code is similar to
* Jung's.
* <p>If we ever decide instead to not center our shapes, then this renderer would have to be
* updated to itself center the edge shape created herein, like this:
* <pre>
* Rectangle b1 = s1.getBounds();
* Rectangle b2 = s2.getBounds();
*
* // translate the edge to be centered in the vertex
* int w1 = b1.width >> 1;
* int h1 = b1.height >> 1;
* int w2 = b2.width >> 1;
* int h2 = b2.height >> 1;
*
* float tx1 = x1 + w1;
* float ty1 = y1 + h1;
* float tx2 = x2 + w2;
* float ty2 = y2 + h2;
* Shape edgeShape = getEdgeShape(rc, graph, e, tx1, ty1, tx2, ty2, isLoop, xs1);
* </pre>
* <p>Also, there are other spots in the system where we account for this center that would
* have to be changed, such as the {@link AbstractVisualGraphLayout}, which needs the centering
* offsets to handle vertex clipping.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public abstract class VisualEdgeRenderer<V extends VisualVertex, E extends VisualEdge<V>>
extends BasicEdgeRenderer<V, E> {
private static final float HOVERED_STROKE_WIDTH = 8.0f;
private static final float SELECTED_STROKE_WIDTH = 4.0f;
private static final float EMPHASIZED_STOKE_WIDTH = SELECTED_STROKE_WIDTH + 3.0f;
private float dashingPatternOffset;
private Color baseColor = Color.BLACK;
private Color highlightColor = Color.GRAY;
/**
* Sets the offset value for painting dashed lines. This allows clients to animate the
* lines being drawn for edges in the edge direction.
*
* @param dashingPatterOffset the offset value
*/
// TODO this method is too specific for this interface. It paints a special view when
// the edge is part of a given path. This should probably be part of a subclass
public void setDashingPatternOffset(float dashingPatterOffset) {
this.dashingPatternOffset = dashingPatterOffset;
}
public void setBaseColor(Color color) {
this.baseColor = color;
}
public Color getBaseColor(Graph<V, E> g, E e) {
return baseColor;
}
public void setHighlightColor(Color highlightColor) {
this.highlightColor = highlightColor;
}
public Color getHighlightColor(Graph<V, E> g, E e) {
return highlightColor;
}
// template method
protected boolean isInActivePath(E e) {
return e.isInActivePath();
}
// template method
protected boolean isSelected(E e) {
return e.isSelected();
}
// template method
protected boolean isEmphasiszed(E e) {
return e.getEmphasis() != 0;
}
@Override
public void drawSimpleEdge(RenderContext<V, E> rc, Layout<V, E> layout, E e) {
GraphicsDecorator gDecorator = rc.getGraphicsContext();
Graphics2D graphicsCopy = (Graphics2D) gDecorator.create();
GraphicsDecorator g = new GraphicsDecorator(graphicsCopy);
double alpha = e.getAlpha();
if (alpha < 1D) {
g.setComposite(
AlphaComposite.getInstance(AlphaComposite.SrcOver.getRule(), (float) alpha));
}
Graph<V, E> graph = layout.getGraph();
Pair<V> endpoints = graph.getEndpoints(e);
V v1 = endpoints.getFirst();
V v2 = endpoints.getSecond();
float scalex = (float) g.getTransform().getScaleX();
float scaley = (float) g.getTransform().getScaleY();
boolean isActive = isInActivePath(e);
boolean isSelected = isSelected(e);
boolean isEmphasized = isEmphasiszed(e);
Color hoveredColor = getHighlightColor(graph, e);
Color selectedColor = getHighlightColor(graph, e).darker();
float scale = StrictMath.min(scalex, scaley);
Point2D p1 = layout.apply(v1);
Point2D p2 = layout.apply(v2);
MultiLayerTransformer multiLayerTransformer = rc.getMultiLayerTransformer();
p1 = multiLayerTransformer.transform(Layer.LAYOUT, p1);
p2 = multiLayerTransformer.transform(Layer.LAYOUT, p2);
float x1 = (float) p1.getX();
float y1 = (float) p1.getY();
float x2 = (float) p2.getX();
float y2 = (float) p2.getY();
boolean isLoop = v1.equals(v2);
Rectangle deviceRectangle = null;
JComponent vv = rc.getScreenDevice();
if (vv != null) {
Dimension d = vv.getSize();
deviceRectangle = new Rectangle(0, 0, d.width, d.height);
}
Shape vs1 = getCompactShape(rc, layout, v1);
Shape edgeShape = getEdgeShape(rc, graph, e, x1, y1, x2, y2, isLoop, vs1);
MutableTransformer vt = multiLayerTransformer.getTransformer(Layer.VIEW);
if (vt instanceof LensTransformer) {
vt = ((LensTransformer) vt).getDelegate();
}
Context<Graph<V, E>, E> context = Context.<Graph<V, E>, E> getInstance(graph, e);
boolean edgeHit = vt.transform(edgeShape).intersects(deviceRectangle);
if (edgeHit) {
Paint oldPaint = g.getPaint();
// get Paints for filling and drawing
// (filling is done first so that drawing and label use same Paint)
Paint fillPaint = rc.getEdgeFillPaintTransformer().apply(e);
BasicStroke selectedStroke = getSelectedStroke(e, scale);
BasicStroke hoverStroke = getHoveredStroke(e, scale);
BasicStroke empahsisStroke = getEmphasisStroke(e, scale);
if (fillPaint != null) {
if (isActive) {
Stroke saveStroke = g.getStroke();
g.setPaint(hoveredColor);
g.setStroke(hoverStroke);
g.fill(edgeShape);
g.setStroke(saveStroke);
}
if (isSelected) {
Stroke saveStroke = g.getStroke();
g.setPaint(selectedColor);
g.setStroke(selectedStroke);
g.fill(edgeShape);
g.setStroke(saveStroke);
}
g.setPaint(fillPaint);
g.fill(edgeShape);
}
Paint drawPaint = rc.getEdgeDrawPaintTransformer().apply(e);
if (drawPaint != null) {
if (isEmphasized) {
Stroke saveStroke = g.getStroke();
g.setPaint(drawPaint);
g.setStroke(empahsisStroke);
g.draw(edgeShape);
g.setStroke(saveStroke);
}
if (isActive) {
Stroke saveStroke = g.getStroke();
g.setPaint(hoveredColor);
g.setStroke(hoverStroke);
g.draw(edgeShape);
g.setStroke(saveStroke);
}
if (isSelected) {
Stroke saveStroke = g.getStroke();
g.setPaint(selectedColor);
g.setStroke(selectedStroke);
g.draw(edgeShape);
g.setStroke(saveStroke);
}
g.setPaint(drawPaint);
g.draw(edgeShape);
// debug - draw a box around the edge
//Rectangle shapeBounds = edgeShape.getBounds();
//g.setPaint(Color.ORANGE);
//g.draw(shapeBounds);
}
Predicate<Context<Graph<V, E>, E>> predicate = rc.getEdgeArrowPredicate();
boolean drawArrow = predicate.apply(context);
if (drawArrow) {
Stroke new_stroke = rc.getEdgeArrowStrokeTransformer().apply(e);
Stroke old_stroke = g.getStroke();
if (new_stroke != null) {
g.setStroke(new_stroke);
}
Shape vs2 = getVertexShapeForArrow(rc, layout, v2); // end vertex
boolean arrowHit = vt.transform(vs2).intersects(deviceRectangle);
Paint arrowFillPaint = rc.getArrowFillPaintTransformer().apply(e);
Paint arrowDrawPaint = rc.getArrowDrawPaintTransformer().apply(e);
if (arrowHit) {
EdgeArrowRenderingSupport<V, E> arrowRenderingSupport =
new BasicEdgeArrowRenderingSupport<>();
AffineTransform at =
arrowRenderingSupport.getArrowTransform(rc, edgeShape, vs2);
if (at == null) {
return;
}
Shape arrow = rc.getEdgeArrowTransformer().apply(context);
arrow = scaleArrowForBetterVisibility(rc, arrow);
arrow = at.createTransformedShape(arrow);
if (isEmphasized) {
Stroke saveStroke = g.getStroke();
g.setPaint(arrowDrawPaint);
g.setStroke(empahsisStroke);
g.fill(arrow);
g.draw(arrow);
g.setStroke(saveStroke);
}
if (isActive) {
Stroke saveStroke = g.getStroke();
g.setPaint(hoveredColor);
g.setStroke(hoverStroke);
g.fill(arrow);
g.draw(arrow);
g.setStroke(saveStroke);
}
if (isSelected) {
Stroke saveStroke = g.getStroke();
g.setPaint(selectedColor);
g.setStroke(selectedStroke);
g.fill(arrow);
g.draw(arrow);
g.setStroke(saveStroke);
}
g.setPaint(arrowFillPaint);
g.fill(arrow);
g.setPaint(arrowDrawPaint);
g.draw(arrow);
}
// restore paint and stroke
if (new_stroke != null) {
g.setStroke(old_stroke);
}
}
// restore old paint
g.setPaint(oldPaint);
}
}
protected Shape getVertexShapeForArrow(RenderContext<V, E> rc, Layout<V, E> layout, V v) {
// we use the default shape (the full shape) for arrow detection
return getFullShape(rc, layout, v);
}
/**
* Returns the edge shape for the given points
*
* @param rc the render context for the graph
* @param graph the graph
* @param e the edge to shape
* @param x1 the start vertex point x
* @param y1 the start vertex point y
* @param x2 the end vertex point x
* @param y2 the end vertex point y
* @param isLoop true if the start == end, which is a self-loop
* @param vertexShape the vertex shape (used in the case of a loop to draw a circle from the
* shape to itself)
* @return the edge shape
*/
public abstract Shape getEdgeShape(RenderContext<V, E> rc, Graph<V, E> graph, E e, float x1,
float y1, float x2, float y2, boolean isLoop, Shape vertexShape);
private BasicStroke getHoveredStroke(E e, float scale) {
float width = HOVERED_STROKE_WIDTH / (float) Math.pow(scale, .80);
return new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 0f,
new float[] { width * 1, width * 2 }, width * 3 * dashingPatternOffset);
}
private BasicStroke getSelectedStroke(E e, float scale) {
float width = SELECTED_STROKE_WIDTH / (float) Math.pow(scale, .80);
return new BasicStroke(width);
}
private BasicStroke getEmphasisStroke(E e, float scale) {
double emphasisRatio = e.getEmphasis(); // this value is 0 when no emphasis
float fullEmphasis = EMPHASIZED_STOKE_WIDTH;
float emphasis = (float) (fullEmphasis * emphasisRatio);
float width = emphasis / (float) Math.pow(scale, .80);
return new BasicStroke(width);
}
private Shape scaleArrowForBetterVisibility(RenderContext<V, E> rc, Shape arrow) {
MultiLayerTransformer multiLayerTransformer = rc.getMultiLayerTransformer();
MutableTransformer viewTransformer = multiLayerTransformer.getTransformer(Layer.VIEW);
double scaleX2 = viewTransformer.getScaleX();
double scaleY2 = viewTransformer.getScaleY();
// make the arrow bigger so that later when the view scale is applied, the shape does
// not get as small as fast as the graph
AffineTransform fine = AffineTransform.getScaleInstance(1 / Math.pow(scaleX2, .68),
1 / Math.pow(scaleY2, .68));
return fine.createTransformedShape(arrow);
}
/**
* Uses the render context to create a compact shape for the given vertex
*
* @param rc the render context
* @param layout the layout
* @param vertex the vertex
* @return the vertex shape
* @see VertexShapeProvider#getFullShape()
*/
public Shape getFullShape(RenderContext<V, E> rc, Layout<V, E> layout, V vertex) {
Function<? super V, Shape> vertexShaper = rc.getVertexShapeTransformer();
Shape shape = null;
if (vertexShaper instanceof VisualGraphVertexShapeTransformer) {
@SuppressWarnings("unchecked")
VisualGraphVertexShapeTransformer<V> vgShaper =
(VisualGraphVertexShapeTransformer<V>) vertexShaper;
// use the viewable shape here, as it is visually pleasing
shape = vgShaper.transformToFullShape(vertex);
}
else {
shape = vertexShaper.apply(vertex);
}
return transformFromLayoutToView(rc, layout, vertex, shape);
}
/**
* Uses the render context to create a compact shape for the given vertex
*
* @param rc the render context
* @param layout the layout
* @param vertex the vertex
* @return the vertex shape
* @see VertexShapeProvider#getCompactShape()
*/
protected Shape getCompactShape(RenderContext<V, E> rc, Layout<V, E> layout, V vertex) {
Function<? super V, Shape> vertexShaper = rc.getVertexShapeTransformer();
Shape shape = null;
if (vertexShaper instanceof VisualGraphVertexShapeTransformer) {
@SuppressWarnings("unchecked")
VisualGraphVertexShapeTransformer<V> vgShaper =
(VisualGraphVertexShapeTransformer<V>) vertexShaper;
// use the viewable shape here, as it is visually pleasing
shape = vgShaper.transformToCompactShape(vertex);
}
else {
shape = vertexShaper.apply(vertex);
}
return transformFromLayoutToView(rc, layout, vertex, shape);
}
protected Shape transformFromLayoutToView(RenderContext<V, E> rc, Layout<V, E> layout, V vertex,
Shape shape) {
Point2D p = layout.apply(vertex);
MultiLayerTransformer multiLayerTransformer = rc.getMultiLayerTransformer();
p = multiLayerTransformer.transform(Layer.LAYOUT, p);
float x = (float) p.getX();
float y = (float) p.getY();
// create a transform that translates to the location of
// the vertex to be rendered
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
return xform.createTransformedShape(shape);
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.edge;
import java.awt.Shape;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.RenderContext;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
/**
* A renderer designed to override default edge rendering to NOT paint emphasizing effects. We
* do this because space is limited in the satellite and because this rendering can take excess
* processing time.
*/
public class VisualGraphEdgeSatelliteRenderer<V extends VisualVertex, E extends VisualEdge<V>>
extends VisualEdgeRenderer<V, E> {
private final VisualEdgeRenderer<V, E> rendererDelegate;
public VisualGraphEdgeSatelliteRenderer(VisualEdgeRenderer<V, E> delegate) {
this.rendererDelegate = delegate;
}
@Override
protected boolean isInActivePath(E e) {
return false;
}
@Override
protected boolean isSelected(E e) {
return false;
}
@Override
protected boolean isEmphasiszed(E e) {
return false;
}
@Override
public Shape getEdgeShape(RenderContext<V, E> rc, Graph<V, E> graph, E e, float x1, float y1,
float x2, float y2, boolean isLoop, Shape vertexShape) {
return rendererDelegate.getEdgeShape(rc, graph, e, x1, y1, x2, y2, isLoop, vertexShape);
}
@Override
protected Shape getVertexShapeForArrow(RenderContext<V, E> rc, Layout<V, E> layout, V v) {
// we use the default shape (the full shape) for arrow detection
return getCompactShape(rc, layout, v);
}
}

View file

@ -0,0 +1,48 @@
/* ###
* 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.edge;
import java.awt.BasicStroke;
import java.awt.Stroke;
import com.google.common.base.Function;
import edu.uci.ics.jung.visualization.picking.PickedInfo;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
public class VisualGraphEdgeStrokeTransformer<V extends VisualVertex, E extends VisualEdge<V>>
implements Function<E, Stroke> {
private static final Stroke BASIC_STROKE = new BasicStroke(1);
private final Stroke HEAVY_STROKE;
private final PickedInfo<E> pickedInfo;
public VisualGraphEdgeStrokeTransformer(PickedInfo<E> pickedInfo, int pickedStrokeSize) {
HEAVY_STROKE = new BasicStroke(pickedStrokeSize);
this.pickedInfo = pickedInfo;
}
@Override
public Stroke apply(E e) {
if (pickedInfo.isPicked(e)) {
return HEAVY_STROKE;
}
return BASIC_STROKE;
}
}

View file

@ -0,0 +1,972 @@
/* ###
* 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.edge;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import docking.DockingWindowManager;
import generic.concurrent.GThreadPool;
import ghidra.generic.function.Callback;
import ghidra.graph.*;
import ghidra.graph.algo.ChkDominanceAlgorithm;
import ghidra.graph.algo.ChkPostDominanceAlgorithm;
import ghidra.graph.graphs.GroupingVisualGraph;
import ghidra.graph.viewer.*;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.CallbackAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
/**
* A class that calculates flow between vertices and then triggers that flow to be painted
* in the UI.
*
* <P><B><U>Threading Policy:</U></B> Some operations use algorithms that slow down, depending
* upon the graph size. Further, some of these algorithms may not even complete. To keep the
* graph responsive, this class will perform its work <I>in the future</I>. The work we
* wish to do is further complicated by these requirements:
* <UL>
* <LI>Some data should be calculated only as needed, to avoid excessive work</LI>
* <LI>Many tasks depend on data to be calculated before they can perform their algorithm</LI>
* <LI>Results must be cached for speed, but may cleared as the graph is mutated</LI>
* <LI>Algorithms must not block the UI thread</LI>
* <LI>Related actions (i.e., hover vs. selection) should cancel any pending action, but not
* unrelated actions (e.g., a new hover request should cancel a pending hover update)
* </UL>
*
* Based on these requirements, we need to use multi-threading. Further complicating the need
* for multi-threading is that some operations depending on lazy-loaded data. Finally, we
* have different types of actions, hovering vs. selecting a vertex, which should override
* previous related requests. To accomplish this we use:
* <UL>
* <LI>{@link CompletableFuture} - to lazy-load and cache required algorithm data</LI>
* <LI>{@link RunManager}s - to queue requests so that new requests cancel old ones. A
* different Run Manager is used for each type of request.</LI>
* </UL>
*
* <P><B><U>Naming Conventions:</U></B> There are many methods in this class, called from
* different threads. For simplicity, we use the following conventions:
* <UL>
* <LI><CODE>fooAsync</CODE> - methods ending in <B>Async</B> indicate that they are to be
* called from a background thread.</LI>
* <LI><CODE>fooSwing</CODE> - methods ending in <B>Swing</B> indicate that they are to be
* called from the Swing thread.</LI>
* <LI>*All public methods are assumed to be called on the Swing thread</LI>
* </UL>
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class VisualGraphPathHighlighter<V extends VisualVertex, E extends VisualEdge<V>> {
// Note: dominance is usually calculated in less than a second; if the timeout is reached,
// then we have a degenerate case or too large a graph
private static final int ALGORITHM_TIMEOUT = 5;
private PathHighlightMode vertexFocusMode = PathHighlightMode.OFF;
private PathHighlightMode vertexHoverMode = PathHighlightMode.OFF;
private Map<V, Set<E>> forwardFlowEdgeCache = new HashMap<>();
private Map<V, Set<E>> reverseFlowEdgeCache = new HashMap<>();
private Map<V, Set<E>> forwardScopedFlowEdgeCache = new HashMap<>();
private Map<V, Set<E>> reverseScopedFlowEdgeCache = new HashMap<>();
private VisualGraph<V, E> graph;
private RunManager hoverRunManager =
new RunManager(GraphViewerUtils.GRAPH_DECORATOR_THREAD_POOL_NAME, null);
private RunManager focusRunManager =
new RunManager(GraphViewerUtils.GRAPH_DECORATOR_THREAD_POOL_NAME, null);
private CompletableFuture<ChkDominanceAlgorithm<V, E>> dominanceFuture;
private CompletableFuture<ChkDominanceAlgorithm<V, E>> postDominanceFuture;
private CompletableFuture<Circuits> circuitFuture;
private PathHighlightListner listener = isHover -> {
// stub
};
private PathHighlighterWorkPauser workPauser = () -> false;
private SwingUpdateManager focusedVertexUpdater =
new SwingUpdateManager(() -> doUpdateFocusedVertex());
public VisualGraphPathHighlighter(VisualGraph<V, E> graph, PathHighlightListner listener) {
this.graph = graph;
if (listener != null) {
this.listener = listener;
}
}
/**
* Sets the callback that signals when this path highlighter should not be performing any
* work
*
* @param pauser the callback that returns a boolean of true when this class should pause
* its work.
*/
public void setWorkPauser(PathHighlighterWorkPauser pauser) {
if (pauser != null) {
this.workPauser = pauser;
}
}
private Executor getGraphExecutor() {
GThreadPool pool =
GThreadPool.getSharedThreadPool(GraphViewerUtils.GRAPH_DECORATOR_THREAD_POOL_NAME);
return pool.getExecutor();
}
private CompletableFuture<ChkDominanceAlgorithm<V, E>> lazyCreateDominaceFuture() {
// lazy-load
if (dominanceFuture != null) {
return dominanceFuture;
}
// we use an executor to restrict thread usage by the Graph API
Executor executor = getGraphExecutor();
dominanceFuture = CompletableFuture.supplyAsync(() -> {
// this operation is fast enough that it shouldn't timeout, but just in case...
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
TimeUnit.SECONDS, new TaskMonitorAdapter(true));
try {
// note: calling the constructor performs the work
return new ChkDominanceAlgorithm<>(graph, timeoutMonitor);
}
catch (CancelledException e) {
// shouldn't happen
Msg.debug(VisualGraphPathHighlighter.this,
"Domiance calculation timed-out for " + graph.getClass().getSimpleName());
}
return null;
}, executor);
return dominanceFuture;
}
private CompletableFuture<ChkDominanceAlgorithm<V, E>> lazyCreatePostDominanceFuture() {
// lazy-load
if (postDominanceFuture != null) {
return postDominanceFuture;
}
Executor executor = getGraphExecutor();
postDominanceFuture = CompletableFuture.supplyAsync(() -> {
// this operation is fast enough that it shouldn't timeout, but just in case...
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
TimeUnit.SECONDS, new TaskMonitorAdapter(true));
try {
// note: calling the constructor performs the work
return new ChkPostDominanceAlgorithm<>(graph, timeoutMonitor);
}
catch (CancelledException e) {
// shouldn't happen
Msg.debug(VisualGraphPathHighlighter.this,
"Post-domiance calculation timed-out for " + graph.getClass().getSimpleName());
}
return null;
}, executor);
return postDominanceFuture;
}
private CompletableFuture<VisualGraphPathHighlighter<V, E>.Circuits> lazyCreateCircuitFuture() {
// lazy-load
if (circuitFuture != null) {
return circuitFuture;
}
Executor executor = getGraphExecutor();
circuitFuture = CompletableFuture.supplyAsync(() -> {
// this operation is fast enough that it shouldn't timeout, but just in case...
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
TimeUnit.SECONDS, new TaskMonitorAdapter(true));
Circuits circuits = calculateCircuitsAsync(timeoutMonitor);
if (!circuits.complete) {
setStatusTextSwing("Unable to calculate all loops - timed-out");
}
return circuits;
}, executor);
return circuitFuture;
}
private void setStatusTextSwing(String message) {
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
if (dwm != null) {
dwm.setStatusText(message);
}
}
/**
* Signals to this path highlighter that it should stop all background jobs
*/
public void stop() {
hoverRunManager.cancelAllRunnables();
focusRunManager.cancelAllRunnables();
if (dominanceFuture != null) {
dominanceFuture.cancel(true);
}
if (postDominanceFuture != null) {
postDominanceFuture.cancel(true);
}
if (circuitFuture != null) {
circuitFuture.cancel(true);
}
}
public void dispose() {
hoverRunManager.dispose();
focusRunManager.dispose();
clearCacheSwing();
}
public boolean isBusy() {
return hoverRunManager.isInProgress() || focusRunManager.isInProgress();
}
public PathHighlightMode getVertexHoverPathHighlightMode() {
return vertexHoverMode;
}
public PathHighlightMode getVertexFocusPathHighlightMode() {
return vertexFocusMode;
}
public void setVertexFocusMode(PathHighlightMode mode) {
this.vertexFocusMode = Objects.requireNonNull(mode);
V focusedVertex = graph.getFocusedVertex();
setFocusedVertex(focusedVertex);
}
public void setVertexHoverMode(PathHighlightMode mode) {
this.vertexHoverMode = Objects.requireNonNull(mode);
}
public void setHoveredVertex(V hoveredVertex) {
if (workPauser.isPaused()) {
return; // hovers a transient, no need to remember the request
}
clearHoveredEdgesSwing();
if (hoveredVertex == null) {
return;
}
switch (vertexHoverMode) {
case IN:
setInHoveredEdgesSwing(hoveredVertex);
break;
case OUT:
setOutHoveredEdgesSwing(hoveredVertex);
break;
case INOUT:
setInOutHoveredEdgesSwing(hoveredVertex);
break;
case CYCLE:
setVertexCycleHoveredEdgesSwing(hoveredVertex);
break;
case SCOPED_FORWARD:
setForwardScopedFlowHoveredEdgesSwing(hoveredVertex);
break;
case SCOPED_REVERSE:
setReverseScopedFlowHoveredEdgesSwing(hoveredVertex);
break;
case PATH:
V focusedVertex = graph.getFocusedVertex();
if (focusedVertex != null) {
setVertexToVertexPathHoveredEdgesSwing(focusedVertex, hoveredVertex);
}
break;
case OFF:
default:
break;
}
}
public void setFocusedVertex(V focusedVertex) {
if (workPauser.isPaused()) {
focusedVertexUpdater.updateLater(); // redo this later when work is not paused
return;
}
clearFocusedEdgesSwing();
if (vertexFocusMode == PathHighlightMode.ALLCYCLE) {
setAllCycleFocusedEdgesSwing();
return;
}
if (focusedVertex == null) {
return;
}
switch (vertexFocusMode) {
case IN:
setInFocusedEdges(focusedVertex);
break;
case OUT:
setOutFocusedEdgesSwing(focusedVertex);
break;
case INOUT:
setInOutFocusedEdgesSwing(focusedVertex);
break;
case SCOPED_FORWARD:
setForwardScopedFlowFocusedEdgesSwing(focusedVertex);
break;
case SCOPED_REVERSE:
setReverseScopedFlowFocusedEdgesSwing(focusedVertex);
break;
case CYCLE:
setVertexCycleFocusedEdgesSwing(focusedVertex);
break;
case OFF:
default:
break;
}
}
private void doUpdateFocusedVertex() {
V focusedVertex = graph.getFocusedVertex();
setFocusedVertex(focusedVertex);
}
private void clearHoveredEdgesSwing() {
for (E edge : graph.getEdges()) {
edge.setInActivePath(false);
}
}
private void clearFocusedEdgesSwing() {
for (E edge : graph.getEdges()) {
edge.setSelected(false);
}
}
public void clearEdgeCache() {
//
// This call to clear the cache happens due to graph vertex mutations. The client does
// not want outdated edge information that points to removed vertices hanging around.
// However, the loop information is calculated not on-the-fly, but when the graph is
// first loaded. Thus, clearing that will trigger the graph to lose all loop info. To
// avoid this, we will update the loop info to reflect the current graph vertex state.
//
Set<E> newAllCircuitFlowEdgeCache = new HashSet<>();
Map<V, Set<E>> newCircuitFlowEdgeCache = new HashMap<>();
accumulateCircuitEdgesForCurrentStateOfGraphSwing(newAllCircuitFlowEdgeCache,
newCircuitFlowEdgeCache);
clearCacheSwing();
setEdgeCircuitsSwing(newAllCircuitFlowEdgeCache, newCircuitFlowEdgeCache);
}
//==================================================================================================
// Swing Thread Methods
//==================================================================================================
private void accumulateCircuitEdgesForCurrentStateOfGraphSwing(Set<E> newAllCircuits,
Map<V, Set<E>> newCircuitsByVertex) {
CompletableFuture<Circuits> f = circuitFuture;
if (f == null || !f.isDone() || f.isCancelled()) {
return; // no circuits yet calculated
}
Circuits circuits = getAsync(circuitFuture); // non-blocking, since we checked above
accumulateAllCircuitsSwing(circuits, newAllCircuits);
accumulateVertexCircuitsSwing(circuits, newCircuitsByVertex);
}
private void accumulateAllCircuitsSwing(Circuits circuits, Set<E> results) {
Set<E> edges = circuits.allCircuits;
for (E e : edges) {
E currentEdge = ensureEdgeUpToDateSwing(e);
if (currentEdge != null) {
// edge is null when the old edge endpoints have both been grouped
results.add(currentEdge);
}
}
}
private void accumulateVertexCircuitsSwing(Circuits circuits, Map<V, Set<E>> results) {
Map<V, Set<E>> circuitsByVertex = circuits.circuitsByVertex;
Set<Entry<V, Set<E>>> entrySet = circuitsByVertex.entrySet();
for (Entry<V, Set<E>> entry : entrySet) {
V v = entry.getKey();
if (!graph.containsVertex(v)) {
V newVertex = findMatchingVertexSwing(v);
if (newVertex == null) {
// this can happen during grouping operations
continue;
}
v = newVertex;
}
HashSet<E> newEdgeSet = new HashSet<>();
Set<E> oldEdgeSet = entry.getValue();
for (E e : oldEdgeSet) {
E currentEdge = ensureEdgeUpToDateSwing(e);
if (currentEdge != null) {
// edge is null when the old edge endpoints have both been grouped
newEdgeSet.add(currentEdge);
}
}
results.put(v, newEdgeSet);
}
}
private E ensureEdgeUpToDateSwing(E edge) {
V start = edge.getStart();
V end = edge.getEnd();
// a 'contains' lookup is faster than the search required by the 'find' below
boolean containsStart = graph.containsVertex(start);
boolean containsDestination = graph.containsVertex(end);
if (containsStart && containsDestination) {
return edge;
}
// At least one of the vertices is no longer in the graph. Find the equivalent and
// then get the new edge.
V newStart = findMatchingVertexSwing(start);
V newEnd = findMatchingVertexSwing(end);
return graph.findEdge(newStart, newEnd);
}
private V findMatchingVertexSwing(V v) {
if (!(graph instanceof GroupingVisualGraph)) {
return v;
}
V matchingVertex = ((GroupingVisualGraph<V, E>) graph).findMatchingVertex(v);
return matchingVertex;
}
private void clearCacheSwing() {
forwardFlowEdgeCache.clear();
reverseFlowEdgeCache.clear();
forwardScopedFlowEdgeCache.clear();
reverseScopedFlowEdgeCache.clear();
disposeSwing(circuitFuture, Circuits::clear);
disposeSwing(dominanceFuture, ChkDominanceAlgorithm::clear);
disposeSwing(postDominanceFuture, ChkDominanceAlgorithm::clear);
// reset these to compensate for new or removed vertices (but not the circuits,
// as they are slow and we can recalculate them)
dominanceFuture = null;
postDominanceFuture = null;
}
private void setInFocusedEdges(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
}
private void setOutFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
}
private void setForwardScopedFlowFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
}
private void setReverseScopedFlowFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
}
private void setInOutFocusedEdgesSwing(V vertex) {
//
// Select ins and outs, one after the other.
//
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
focusRunManager.runNow(new SelectRunnable(inSupplier), null);
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
focusRunManager.runNext(new SelectRunnable(outSupplier), null);
}
private void setVertexCycleFocusedEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
focusRunManager.runNow(new SelectRunnable(supplier), null);
}
private void setAllCycleFocusedEdgesSwing() {
Supplier<Set<E>> supplier = () -> getAllCircuitFlowEdgesAsync();
focusRunManager.runNow(new SelectRunnable(supplier), null);
}
private void setInHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
}
private void setOutHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
}
private void setForwardScopedFlowHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getForwardScopedFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
}
private void setReverseScopedFlowHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getReverseScopedFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
}
private void setInOutHoveredEdgesSwing(V vertex) {
//
// Select ins and outs, one after the other.
//
Supplier<Set<E>> inSupplier = () -> getReverseFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(inSupplier), null);
Supplier<Set<E>> outSupplier = () -> getForwardFlowEdgesForVertexAsync(vertex);
hoverRunManager.runNext(new HoverRunnable(outSupplier), null);
}
private void setVertexCycleHoveredEdgesSwing(V vertex) {
Supplier<Set<E>> supplier = () -> getCircuitEdgesAsync(vertex);
hoverRunManager.runNow(new HoverRunnable(supplier), null);
}
private void setVertexToVertexPathHoveredEdgesSwing(V start, V end) {
Callback callback = () -> calculatePathsBetweenVerticesAsync(start, end);
focusRunManager.runNow(new SlowHoverRunnable(callback), null);
}
private void selectSwing(Collection<E> edges) {
edges.forEach(e -> e.setSelected(true));
listener.pathHighlightChanged(false);
}
private void activateSwing(Collection<E> edges) {
edges.forEach(e -> e.setInActivePath(true));
listener.pathHighlightChanged(true);
}
private void setEdgeCircuitsSwing(Set<E> allCircuitResults, Map<V, Set<E>> circuitFlowResults) {
// update the focus mode and then repaint the graph now that we have the needed data
if (vertexFocusMode == PathHighlightMode.ALLCYCLE) {
setAllCycleFocusedEdgesSwing();
}
else if (vertexFocusMode == PathHighlightMode.CYCLE) {
V focused = graph.getFocusedVertex();
setVertexCycleFocusedEdgesSwing(focused);
}
}
private <T> void disposeSwing(CompletableFuture<T> cf, Consumer<T> clearer) {
if (cf == null) {
// never loaded
return;
}
if (!cf.isDone()) {
cf.cancel(true);
return;
}
if (!cf.isCompletedExceptionally()) {
// clear the contents of the future, as it is acting like a cache
clearer.accept(cf.getNow(null));
}
}
//==================================================================================================
// Asynchronous Methods (expected to be called in the background)
//==================================================================================================
private Set<E> getForwardScopedFlowEdgesForVertexAsync(V v) {
if (v == null) {
return null;
}
Set<E> flowEdges = forwardScopedFlowEdgeCache.get(v);
if (flowEdges == null) {
flowEdges = findForwardScopedFlowAsync(v);
forwardScopedFlowEdgeCache.put(v, flowEdges);
}
return Collections.unmodifiableSet(flowEdges);
}
private Set<E> getForwardFlowEdgesForVertexAsync(V v) {
return getFlowEdgesForVertexAsync(true, forwardFlowEdgeCache, v);
}
private Set<E> getReverseFlowEdgesForVertexAsync(V v) {
return getFlowEdgesForVertexAsync(false, reverseFlowEdgeCache, v);
}
private Set<E> getFlowEdgesForVertexAsync(boolean isForward, Map<V, Set<E>> cache, V v) {
if (v == null) {
return null;
}
Set<E> flowEdges = cache.get(v);
if (flowEdges == null) {
flowEdges = new HashSet<>();
Set<E> pathsToVertex = GraphAlgorithms.getEdgesFrom(graph, v, isForward);
flowEdges.addAll(pathsToVertex);
cache.put(v, flowEdges);
}
return Collections.unmodifiableSet(flowEdges);
}
private Set<E> getAllCircuitFlowEdgesAsync() {
CompletableFuture<Circuits> future = lazyCreateCircuitFuture();
Circuits circuits = getAsync(future); // blocking operation
if (circuits == null) {
return Collections.emptySet(); // can happen during dispose
}
return Collections.unmodifiableSet(circuits.allCircuits);
}
private Set<E> getReverseScopedFlowEdgesForVertexAsync(V v) {
if (v == null) {
return null;
}
Set<E> flowEdges = reverseScopedFlowEdgeCache.get(v);
if (flowEdges == null) {
flowEdges = findReverseScopedFlowAsync(v);
reverseScopedFlowEdgeCache.put(v, flowEdges);
}
return Collections.unmodifiableSet(flowEdges);
}
private Set<E> getCircuitEdgesAsync(V v) {
if (v == null) {
return null;
}
CompletableFuture<Circuits> future = lazyCreateCircuitFuture();
Circuits circuits = getAsync(future); // blocking operation
if (circuits == null) {
return Collections.emptySet(); // can happen during dispose
}
Set<E> set = circuits.circuitsByVertex.get(v);
if (set == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(set);
}
private <T> T getAsync(CompletableFuture<T> cf) {
try {
T t = cf.get(); // blocking
return t;
}
catch (InterruptedException e) {
Msg.trace(VisualGraphPathHighlighter.this, "Unable to complete the future", e);
}
catch (ExecutionException e) {
Msg.debug(VisualGraphPathHighlighter.this, "Unable to complete the future", e);
}
return null;
}
private Circuits calculateCircuitsAsync(TaskMonitor monitor) {
Circuits result = new Circuits();
monitor.setMessage("Finding all loops");
Set<Set<V>> strongs = GraphAlgorithms.getStronglyConnectedComponents(graph);
for (Set<V> vertices : strongs) {
if (monitor.isCancelled()) {
return result;
}
if (vertices.size() == 1) {
continue;
}
GDirectedGraph<V, E> subGraph = GraphAlgorithms.createSubGraph(graph, vertices);
Collection<E> edges = subGraph.getEdges();
result.allCircuits.addAll(edges);
HashSet<E> asSet = new HashSet<>(edges);
Collection<V> subVertices = subGraph.getVertices();
for (V v : subVertices) {
if (monitor.isCancelled()) {
return result;
}
result.circuitsByVertex.put(v, asSet);
}
}
result.complete = true;
return result;
}
private List<E> pathToEdgesAsync(List<V> path) {
List<E> results = new ArrayList<>();
Iterator<V> it = path.iterator();
V from = it.next();
while (it.hasNext()) {
V to = it.next();
E e = graph.findEdge(from, to);
results.add(e);
from = to;
}
return results;
}
private Set<E> findForwardScopedFlowAsync(V v) {
CompletableFuture<ChkDominanceAlgorithm<V, E>> future = lazyCreateDominaceFuture();
try {
ChkDominanceAlgorithm<V, E> dominanceAlgorithm = getAsync(future);
if (dominanceAlgorithm != null) { // null implies timeout
Set<V> dominated = dominanceAlgorithm.getDominated(v);
return GraphAlgorithms.retainEdges(graph, dominated);
}
}
catch (Exception e) {
// handled below
}
// use the empty set so we do not repeatedly attempt to calculate these paths
return Collections.emptySet();
}
private Set<E> findReverseScopedFlowAsync(V v) {
CompletableFuture<ChkDominanceAlgorithm<V, E>> future = lazyCreatePostDominanceFuture();
try {
ChkDominanceAlgorithm<V, E> postDominanceAlgorithm = getAsync(future);
if (postDominanceAlgorithm != null) { // null implies timeout
Set<V> dominated = postDominanceAlgorithm.getDominated(v);
return GraphAlgorithms.retainEdges(graph, dominated);
}
}
catch (Exception e) {
// handled below
}
// use the empty set so we do not repeatedly attempt to calculate these paths
return Collections.emptySet();
}
private void calculatePathsBetweenVerticesAsync(V v1, V v2) {
CallbackAccumulator<List<V>> accumulator = new CallbackAccumulator<>(path -> {
Collection<E> edges = pathToEdgesAsync(path);
SystemUtilities.runSwingLater(() -> activateSwing(edges));
});
TaskMonitor timeoutMonitor = TimeoutTaskMonitor.timeoutIn(ALGORITHM_TIMEOUT,
TimeUnit.SECONDS, new TaskMonitorAdapter(true));
try {
GraphAlgorithms.findPaths(graph, v1, v2, accumulator, timeoutMonitor);
}
catch (ConcurrentModificationException e) {
// TODO temp fix for 8.0.
// This exception can happen when the current graph is being mutated off of the
// Swing thread, such as when grouping and ungrouping. For now, squash the
// problem, as it is only a UI feature. Post-"big graph branch merge", update
// how we schedule this task in relation to background graph jobs (maybe just make
// this task a job)
}
catch (CancelledException e) {
SystemUtilities.runSwingLater(
() -> setStatusTextSwing("Path computation halted by user or timeout.\n" +
"Paths shown in graph are not complete!"));
}
}
//==================================================================================================
// Inner Classes
//==================================================================================================
/**
* A simple class to hold loops and success status
*/
private class Circuits {
// this is false when the circuit finding takes too long
private boolean complete;
private Set<E> allCircuits = new HashSet<>();
private Map<V, Set<E>> circuitsByVertex = new HashMap<>();
void clear() {
allCircuits.clear();
circuitsByVertex.clear();
}
@Override
public String toString() {
//@formatter:off
return "{\n" +
"\tall circuits: " + allCircuits + "\n" +
"\tby vertex: " + circuitsByVertex + "\n" +
"}";
//@formatter:on
}
}
/**
* A class to handle off-loading the calculation of edges to be hovered. The results will
* then be used to update the UI.
*/
private class HoverRunnable implements SwingRunnable {
private Supplier<Set<E>> edgeSupplier;
private Set<E> edges;
HoverRunnable(Supplier<Set<E>> edgeSupplier) {
this.edgeSupplier = edgeSupplier;
}
@Override
public void monitoredRun(TaskMonitor monitor) {
try {
edges = edgeSupplier.get();
}
catch (CancellationException e) {
// this can happen as our cache is getting cleared
monitor.cancel();
}
}
@Override
public void swingRun(boolean isCancelled) {
if (isCancelled) {
return;
}
activateSwing(edges);
}
}
/**
* A class to handle off-loading the calculation of edges to be focused/selected.
* The results will then be used to update the UI.
*/
private class SelectRunnable implements SwingRunnable {
private Supplier<Set<E>> edgeSupplier;
private Set<E> edges;
SelectRunnable(Supplier<Set<E>> edgeSupplier) {
this.edgeSupplier = edgeSupplier;
}
@Override
public void monitoredRun(TaskMonitor monitor) {
try {
edges = edgeSupplier.get();
}
catch (CancellationException e) {
// this can happen as our cache is getting cleared
monitor.cancel();
}
}
@Override
public void swingRun(boolean isCancelled) {
if (isCancelled) {
return;
}
selectSwing(edges);
}
}
/**
* A class meant to run in the hover RunManager that is slow or open-ended. Work will
* be performed as long as possible, updating results along the way.
*/
private class SlowHoverRunnable implements MonitoredRunnable {
private Callback callback;
SlowHoverRunnable(Callback callback) {
this.callback = callback;
}
@Override
public void monitoredRun(TaskMonitor monitor) {
try {
callback.call();
}
catch (CancellationException e) {
// this can happen as our cache is getting cleared
monitor.cancel();
}
}
}
}

View file

@ -0,0 +1,353 @@
/* ###
* 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.edge.routing;
import static ghidra.graph.viewer.GraphViewerUtils.*;
import java.awt.*;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.collections4.*;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.VisualizationServer;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import ghidra.graph.viewer.renderer.DebugShape;
class ArticulatedEdgeRouter<V extends VisualVertex, E extends VisualEdge<V>>
extends BasicEdgeRouter<V, E> {
private Shape spaceBetweenEndPointsShape; // layout space
private Map<V, Rectangle> cachedVertexBoundsMap;
private static final AtomicInteger debugCounter = new AtomicInteger(1);
ArticulatedEdgeRouter(VisualizationServer<V, E> viewer, Collection<E> edges) {
super(viewer, edges);
this.viewer = viewer;
this.edges = edges;
}
@Override
public void route() {
debugCounter.set(debugCounter.incrementAndGet());
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
for (E edge : edges) {
Shape edgeShape = getEdgeShapeInGraphSpace(viewer, edge);
if (!isOccluded(edge, edgeShape)) {
DebugShape<V, E> debugShape = new DebugShape<>(viewer, debugCounter, "Default",
getEdgeShapeInGraphSpace(viewer, edge), getPhantomEdgeColor(edge, true));
viewer.addPostRenderPaintable(debugShape);
List<Point2D> articulations = edge.getArticulationPoints();
if (!articulations.isEmpty()) {
articulations = removeBadlyAngledArticulations(edge, articulations);
edge.setArticulationPoints(articulations);
continue;
}
}
Pair<V> endpoints = graph.getEndpoints(edge);
V start = endpoints.getFirst();
V end = endpoints.getSecond();
if (start == end) {
continue; // self-loop
}
Point2D startPoint = layout.apply(start);
Point2D endPoint = layout.apply(end);
Shape boxBetweenVertices = createRectangle(startPoint, endPoint);
Shape xBox = translateShapeFromLayoutSpaceToGraphSpace(boxBetweenVertices, viewer);
spaceBetweenEndPointsShape = constrictToVerticesInsideShape(xBox, start, end);
DebugShape<V, E> debugShape = new DebugShape<>(viewer, debugCounter, "Restricted Box",
translateShapeFromLayoutSpaceToGraphSpace(spaceBetweenEndPointsShape, viewer),
getRoutingBoxColor(edge));
viewer.addPostRenderPaintable(debugShape);
// Shape line = createLineEdge(viewer, start, end, edge);
// if (!isOccluded(viewer, edge, line)) {
// edge.setLayoutArticulationPoints(new ArrayList<Point2D>());
// continue;
// }
Shape routedShape = createRoutedTwoPointShape(start, end, edge, true);
debugShape = new DebugShape<>(viewer, debugCounter, "Left Edge", routedShape,
getPhantomEdgeColor(edge, true));
viewer.addPostRenderPaintable(debugShape);
List<Point2D> articulations = getArticulations(routedShape);
if (!isOccluded(edge, routedShape)) {
articulations = removeBadlyAngledArticulations(edge, articulations);
edge.setArticulationPoints(articulations);
continue;
}
// TODO: add a loop here to try moving the articulations out a bit
routedShape = createRoutedTwoPointShape(start, end, edge, false);
debugShape = new DebugShape<>(viewer, debugCounter, "Right edge", routedShape,
getPhantomEdgeColor(edge, false));
viewer.addPostRenderPaintable(debugShape);
articulations = getArticulations(routedShape);
if (!isOccluded(edge, routedShape)) {
articulations = removeBadlyAngledArticulations(edge, articulations);
edge.setArticulationPoints(articulations);
continue;
}
// do the basic--just a default edge line
edge.setArticulationPoints(new ArrayList<Point2D>());
}
}
private Shape constrictToVerticesInsideShape(Shape boundingShape, V start, V end) {
Set<V> vertices = new HashSet<>();
Map<V, Rectangle> vertexBoundsMap = getVertexBounds();
Set<Entry<V, Rectangle>> entrySet = vertexBoundsMap.entrySet();
for (Entry<V, Rectangle> entry : entrySet) {
V v = entry.getKey();
Rectangle vertexBounds = getVertexBoundsInGraphSpace(viewer, v);
if (boundingShape.intersects(vertexBounds)) {
vertices.add(v);
}
}
vertices.remove(start); // don't include the edges vertices in the shape
vertices.remove(end);
if (vertices.isEmpty()) {
boundingShape.getBounds().setSize(0, 0);
return boundingShape;
}
return getBoundsForVerticesInLayoutSpace(viewer, vertices);
}
private Rectangle createRectangle(Point2D startPoint, Point2D endPoint) {
double smallestX = Math.min(startPoint.getX(), endPoint.getX());
double smallestY = Math.min(startPoint.getY(), endPoint.getY());
double largestX = Math.max(startPoint.getX(), endPoint.getX());
double largestY = Math.max(startPoint.getY(), endPoint.getY());
int width = (int) (largestX - smallestX);
int height = (int) (largestY - smallestY);
return new Rectangle((int) smallestX, (int) smallestY, width, height);
}
private void moveArticulationsAroundVertices(Set<V> vertices, E edge, boolean goLeft) {
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
Pair<V> endpoints = graph.getEndpoints(edge);
V start = endpoints.getFirst();
V end = endpoints.getSecond();
Point2D startPoint = layout.apply(start);
Point2D endPoint = layout.apply(end);
// Rectangle bounds = getBoundsForVerticesInLayoutSpace(viewer, vertices);
// paint the shape
// Color color = getIntersectingBoxColor(edge);
// String name = goLeft ? "Left" : "Right";
// DebugShape<V, E> debugShape =
// new DebugShape<V, E>(viewer, debugCounter, name,
// translateShapeFromLayoutSpaceToGraphSpace(bounds, viewer), color);
// viewer.addPostRenderPaintable(debugShape);
Rectangle bounds = spaceBetweenEndPointsShape.getBounds();
int padding = 20;
int x = goLeft ? bounds.x : bounds.x + bounds.width;
x += goLeft ? -padding : padding;
Point2D top = new Point2D.Double(x, bounds.y - padding);
Point2D bottom = new Point2D.Double(x, bounds.y + bounds.height + padding);
if (startPoint.getY() > endPoint.getY()) {
// swap the top and bottom points, as our source vertex is below the destination
Point2D newTop = bottom;
bottom = top;
top = newTop;
}
List<Point2D> articulationPoints = new ArrayList<>();
articulationPoints.add(top);
articulationPoints.add(bottom);
edge.setArticulationPoints(articulationPoints);
}
// the edge is cloned from the given edge--safe
private Shape createRoutedTwoPointShape(V start, V end, E edge, boolean goLeft) {
Set<E> edgesSet = new HashSet<>();
edgesSet.add(edge);
Map<E, Set<V>> occludedEdges = getOccludedEdges(edgesSet);
Set<V> intersectingVertices = occludedEdges.get(edge);
if (intersectingVertices.isEmpty()) {
// not sure what to do yet, just draw a straight line
return createLineEdge(start, end, edge);
}
E newEdge = (E) edge.cloneEdge(edge.getStart(), edge.getEnd());
moveArticulationsAroundVertices(intersectingVertices, newEdge, goLeft);
return getEdgeShapeInGraphSpace(viewer, newEdge);
}
/**
* Returns a mapping edges to vertices that touch them.
*
* @param viewer the viewer containing the graph
* @param edgeCollection the edges to check for occlusion
* @return a mapping of occluded edges (a subset of the provided edges) to those vertices that
* occlude them.
*/
private Map<E, Set<V>> getOccludedEdges(Collection<E> edgeCollection) {
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
Set<V> prototype = new HashSet<>();
Factory<Set<V>> factory = FactoryUtils.prototypeFactory(prototype);
Map<E, Set<V>> map = MapUtils.lazyMap(new HashMap<E, Set<V>>(), factory);
Map<V, Rectangle> vertexBoundsMap = getVertexBounds();
Set<Entry<V, Rectangle>> entrySet = vertexBoundsMap.entrySet();
for (Entry<V, Rectangle> entry : entrySet) {
V v = entry.getKey();
Rectangle vertexBounds = getVertexBoundsInGraphSpace(viewer, v);
for (E edge : edgeCollection) {
Shape edgeShape = getEdgeShapeInGraphSpace(viewer, edge);
Pair<V> endpoints = graph.getEndpoints(edge);
if (v == endpoints.getFirst() || v == endpoints.getSecond()) {
// do we ever care if an edge is occluded by its own vertices?
continue;
}
if (edgeShape.intersects(vertexBounds)) {
Set<V> set = map.get(edge);
set.add(v);
}
}
}
return map;
}
private Map<V, Rectangle> getVertexBounds() {
if (cachedVertexBoundsMap != null) {
return cachedVertexBoundsMap;
}
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
Collection<V> vertices = graph.getVertices();
Map<V, Rectangle> map = new HashMap<>();
for (V v : vertices) {
Rectangle vertexBounds = getVertexBoundsInGraphSpace(viewer, v);
map.put(v, vertexBounds);
}
cachedVertexBoundsMap = map;
return map;
}
private List<Point2D> getArticulations(Shape shape) {
PathIterator pathIterator = shape.getPathIterator(null);
List<Point2D> articulations = new ArrayList<>();
double[] coords = new double[6];
// Note: this extraction is based upon a two articulation edge!
pathIterator.next(); // skip the first value, the start vertex value
// 2nd element is the first articulation
pathIterator.currentSegment(coords);
Point2D.Double pathPoint = new Point2D.Double(coords[0], coords[1]);
Point layoutSpacePoint = translatePointFromGraphSpaceToLayoutSpace(pathPoint, viewer);
articulations.add(layoutSpacePoint);
// 3rd element is the second articulation
pathIterator.next();
pathIterator.currentSegment(coords);
pathPoint = new Point2D.Double(coords[0], coords[1]);
layoutSpacePoint = translatePointFromGraphSpaceToLayoutSpace(pathPoint, viewer);
articulations.add(layoutSpacePoint);
return articulations;
}
private Shape createLineEdge(V start, V end, E edge) {
edge.setArticulationPoints(new ArrayList<Point2D>()); // clear the points--straight line
return getEdgeShapeInGraphSpace(viewer, edge);
}
private Color getRoutingBoxColor(E edge) {
if (isTrueEdge(edge)) {
return Color.MAGENTA;
}
return Color.ORANGE;
}
//
// private Color getIntersectingBoxColor(E edge) {
// if (isTrueEdge(edge)) {
// return Color.RED;
// }
// return Color.PINK;
// }
private Color getPhantomEdgeColor(E edge, boolean isLeft) {
if (isLeft) {
if (isTrueEdge(edge)) {
return new Color(0x999900);
}
return new Color(0x009900);
}
if (isTrueEdge(edge)) {
return new Color(0x3300CC);
}
return new Color(0x3399FF);
}
private boolean isTrueEdge(E edge) {
return true;
// return edge.getFlowType().isJump(); // a jump is a 'true' edge
}
}

View file

@ -0,0 +1,127 @@
/* ###
* 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.edge.routing;
import static ghidra.graph.viewer.GraphViewerUtils.getVertexBoundsInGraphSpace;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.util.*;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Pair;
import edu.uci.ics.jung.visualization.VisualizationServer;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
public class BasicEdgeRouter<V extends VisualVertex, E extends VisualEdge<V>> {
protected VisualizationServer<V, E> viewer;
protected Collection<E> edges = null;
public BasicEdgeRouter(VisualizationServer<V, E> viewer, Collection<E> edges) {
this.viewer = viewer;
this.edges = edges;
}
public void route() {
for (E edge : edges) {
List<Point2D> articulations = edge.getArticulationPoints();
if (articulations.isEmpty()) {
continue; // nothing to do
}
articulations = removeBadlyAngledArticulations(edge, articulations);
edge.setArticulationPoints(articulations);
// TODO: not sure if we want to test for occlusion here too
// Shape edgeShape = getEdgeShapeInGraphSpace(viewer, edge);
// if (!isOccluded(edge, edgeShape)) {
//
// }
}
}
protected boolean isOccluded(E edge, Shape graphSpaceShape) {
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
Collection<V> vertices = graph.getVertices();
for (V vertex : vertices) {
Rectangle vertexBounds = getVertexBoundsInGraphSpace(viewer, vertex);
Pair<V> endpoints = graph.getEndpoints(edge);
if (vertex == endpoints.getFirst() || vertex == endpoints.getSecond()) {
// do we ever care if an edge is occluded by its own vertices?
continue;
}
if (graphSpaceShape.intersects(vertexBounds)) {
return true;
}
}
return false;
}
protected List<Point2D> removeBadlyAngledArticulations(E edge, List<Point2D> articulations) {
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
Pair<V> endpoints = graph.getEndpoints(edge);
V start = endpoints.getFirst();
V end = endpoints.getSecond();
Point2D startPoint = layout.apply(start);
Point2D endPoint = layout.apply(end);
if (startPoint.getY() > endPoint.getY()) {
// swap the top and bottom points, as our source vertex is below the destination
Point2D newStart = endPoint;
endPoint = startPoint;
startPoint = newStart;
}
List<Point2D> newList = new ArrayList<>();
for (Point2D articulation : articulations) {
double deltaY = articulation.getY() - startPoint.getY();
double deltaX = articulation.getX() - startPoint.getX();
double theta = Math.atan2(deltaY, deltaX);
double degrees = theta * 180 / Math.PI;
if (degrees < 0 || degrees > 180) {
continue;
}
deltaY = endPoint.getY() - articulation.getY();
deltaX = endPoint.getX() - articulation.getX();
theta = Math.atan2(deltaY, deltaX);
degrees = theta * 180 / Math.PI;
if (degrees < 0 || degrees > 180) {
continue;
}
newList.add(articulation);
}
return newList;
}
}

View file

@ -0,0 +1,248 @@
/* ###
* 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.event.mouse;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.Objects;
import javax.swing.*;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.picking.PickedState;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.event.picking.GPickedState;
/**
* A class that knows how and where a given vertex was clicked. Further, this class knows how
* to get clicked components within a given vertex.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class VertexMouseInfo<V extends VisualVertex, E extends VisualEdge<V>> {
private static final Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
private static final Cursor HAND_CURSOR = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
private final MouseEvent originalMouseEvent;
private final GraphViewer<V, E> viewer;
// TODO make these private if the subclass goes away
protected final V vertex;
private MouseEvent translatedMouseEvent;
protected Component mousedDestinationComponent;
public VertexMouseInfo(MouseEvent originalMouseEvent, V vertex, Point2D vertexBasedClickPoint,
GraphViewer<V, E> viewer) {
this.originalMouseEvent = Objects.requireNonNull(originalMouseEvent);
this.vertex = Objects.requireNonNull(vertex);
this.viewer = Objects.requireNonNull(viewer);
JComponent component = vertex.getComponent();
Component deepestComponent = SwingUtilities.getDeepestComponentAt(component,
(int) vertexBasedClickPoint.getX(), (int) vertexBasedClickPoint.getY());
setClickedComponent(deepestComponent, vertexBasedClickPoint);
}
public boolean isScaledPastInteractionThreshold() {
RenderContext<V, E> renderContext = viewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = renderContext.getMultiLayerTransformer();
MutableTransformer viewTransformer = multiLayerTransformer.getTransformer(Layer.VIEW);
double scale = viewTransformer.getScale();
return scale < GraphViewerUtils.INTERACTION_ZOOM_THRESHOLD;
}
public Cursor getCursorForClickedComponent() {
if (isGrabArea()) {
return HAND_CURSOR;
}
if (!isVertexSelected()) {
return HAND_CURSOR;
}
return DEFAULT_CURSOR;
}
public boolean isGrabArea() {
// subclasses can override to specify areas of the vertex that they can click in order
// to edit and perform keyboard operations
if (isButtonClick()) {
return false;
}
return vertex.isGrabbable(getClickedComponent());
}
public boolean isButtonClick() {
Component clickedComponent = getClickedComponent();
if (clickedComponent instanceof JButton) {
return true;
}
return false;
}
public boolean isVertexSelected() {
PickedState<V> pickedVertexState = viewer.getPickedVertexState();
return pickedVertexState.isPicked(vertex);
}
/**
* Selects, or 'pick's the given vertex.
*
* @param addToSelection true signals to add the given vertex to the set of selected vertices;
* false signals to clear the existing selected vertices before selecting
* the given vertex
*/
public void selectVertex(boolean addToSelection) {
// when the user manually clicks a vertex, we no longer want an edge selected
PickedState<E> pickedEdgeState = viewer.getPickedEdgeState();
pickedEdgeState.clear();
if (isVertexSelected()) {
return;
}
GPickedState<V> pickedState = viewer.getGPickedVertexState();
pickedState.pickToSync(vertex, addToSelection);
}
Component getVertexComponent() {
return vertex.getComponent();
}
public Component getClickedComponent() {
return mousedDestinationComponent;
}
public GraphViewer<V, E> getViewer() {
return viewer;
}
public V getVertex() {
return vertex;
}
public Point getDeepestComponentBasedClickPoint() {
return translatedMouseEvent.getPoint();
}
/**
* You can use this method to override which Java component will get the forwarded event. By
* default, the mouse info will forward the event to the component that is under the point in
* the event.
* @param clickedComponent the component that was clicked
* @param vertexBasedPoint the point, relative to the vertex's coordinates
*/
public void setClickedComponent(Component clickedComponent, Point2D vertexBasedPoint) {
this.mousedDestinationComponent = clickedComponent;
Point componentPoint =
new Point((int) vertexBasedPoint.getX(), (int) vertexBasedPoint.getY());
// default values...
Component newEventSource = vertex.getComponent();
Point pointInClickedComponentCoordinates = componentPoint;
if (clickedComponent != null) {
// the component can be null when it hasn't been shown yet, like in fast rendering
newEventSource = clickedComponent;
pointInClickedComponentCoordinates =
SwingUtilities.convertPoint(getVertexComponent(), componentPoint, clickedComponent);
}
translatedMouseEvent = createMouseEventFromSource(newEventSource, originalMouseEvent,
pointInClickedComponentCoordinates);
}
public Object getEventSource() {
return originalMouseEvent.getSource();
}
public MouseEvent getOriginalMouseEvent() {
return originalMouseEvent;
}
public MouseEvent getTranslatedMouseEvent() {
return translatedMouseEvent;
}
public void forwardEvent() {
if (mousedDestinationComponent == null) {
return;
}
mousedDestinationComponent.dispatchEvent(translatedMouseEvent);
if (!isPopupClick()) {
// don't consume popup because we want DockableComponent to get the event also to popup
originalMouseEvent.consume();
}
}
public void simulateMouseEnteredEvent() {
if (mousedDestinationComponent == null) {
return;
}
MouseEvent mouseEnteredEvent = createMouseEnteredEvent();
mousedDestinationComponent.dispatchEvent(mouseEnteredEvent);
viewer.repaint();
}
public void simulateMouseExitedEvent() {
if (mousedDestinationComponent == null) {
return;
}
MouseEvent mouseExitedEvent = createMouseExitedEvent();
mousedDestinationComponent.dispatchEvent(mouseExitedEvent);
viewer.repaint();
}
private MouseEvent createMouseEnteredEvent() {
return new MouseEvent(mousedDestinationComponent, MouseEvent.MOUSE_ENTERED,
System.currentTimeMillis(), 0, 0, 0, 0, false);
}
private MouseEvent createMouseExitedEvent() {
return new MouseEvent(mousedDestinationComponent, MouseEvent.MOUSE_EXITED,
System.currentTimeMillis(), 0, 0, 0, 0, false);
}
private MouseEvent createMouseEventFromSource(Component source, MouseEvent progenitor,
Point2D clickPoint) {
return new MouseEvent(source, progenitor.getID(), progenitor.getWhen(),
progenitor.getModifiers() | progenitor.getModifiersEx(), (int) clickPoint.getX(),
(int) clickPoint.getY(), progenitor.getClickCount(), progenitor.isPopupTrigger(),
progenitor.getButton());
}
public boolean isPopupClick() {
return getOriginalMouseEvent().getButton() == MouseEvent.BUTTON3;
}
@Override
public String toString() {
//@formatter:off
return "{\n" +
"\tvertex: " + vertex + ",\n"+
"\tclickedComponent: " + mousedDestinationComponent+ ",\n"+
"\tevent: " + originalMouseEvent + ",\n"+
"\ttranslatedEvent: " + translatedMouseEvent + "\n"+
"}";
//@formatter:on
}
}

View file

@ -0,0 +1,59 @@
/* ###
* 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.event.mouse;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
/**
* Creates tooltips for a given vertex.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public interface VertexTooltipProvider<V, E> {
/**
* Returns a tooltip component for the given vertex
*
* <p>This is used when the vertex is scaled too far for the user to see individual
* vertex subcomponents.
*
* @param v the vertex
* @return a tooltip component
*/
public JComponent getTooltip(V v);
/**
* Returns a tooltip component for the given vertex and edge. This is used to create
* an edge tooltip, allowing for vertex data to appear in the tip.
*
* @param v the vertex
* @param e the edge for
* @return a tooltip component
*/
public JComponent getTooltip(V v, E e);
/**
* Returns a tooltip string for the given vertex and mouse event
*
* @param v the vertex
* @param e the mouse event
* @return the tooltip text
*/
public String getTooltipText(V v, MouseEvent e);
}

View file

@ -0,0 +1,228 @@
/* ###
* 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.event.mouse;
import java.awt.Cursor;
import java.awt.event.*;
import java.awt.geom.Point2D;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import edu.uci.ics.jung.visualization.picking.PickedState;
import ghidra.graph.viewer.*;
/**
* Usage Notes:
* <ul>
* <li>We clear state on mouseReleased() and mouseExited(), since we will get
* at least one of those calls</li>
* </ul>
*/
//@formatter:off
public abstract class VisualGraphAbstractGraphMousePlugin<V extends VisualVertex,
E extends VisualEdge<V>>
extends AbstractGraphMousePlugin
implements MouseListener, MouseMotionListener, VisualGraphMousePlugin<V, E> {
//@formatter:on
protected boolean isHandlingMouseEvents;
protected V selectedVertex;
protected E selectedEdge;
public VisualGraphAbstractGraphMousePlugin() {
this(InputEvent.BUTTON1_MASK);
}
public VisualGraphAbstractGraphMousePlugin(int selectionModifiers) {
super(selectionModifiers);
}
protected boolean checkForVertex(MouseEvent e) {
if (!checkModifiers(e)) {
selectedVertex = null;
return false;
}
VisualizationViewer<V, E> vv = getViewer(e);
GraphElementAccessor<V, E> pickSupport = vv.getPickSupport();
Layout<V, E> layout = vv.getGraphLayout();
if (pickSupport == null) {
return false;
}
// p is the screen point for the mouse event
Point2D p = e.getPoint();
selectedVertex = pickSupport.getVertex(layout, p.getX(), p.getY());
if (selectedVertex == null) {
return false;
}
e.consume();
return true;
}
protected boolean checkForEdge(MouseEvent e) {
if (!checkModifiers(e) || isOverVertex(e)) {
selectedEdge = null;
return false;
}
VisualizationViewer<V, E> vv = getViewer(e);
GraphElementAccessor<V, E> pickSupport = vv.getPickSupport();
Layout<V, E> layout = vv.getGraphLayout();
if (pickSupport == null) {
return false;
}
// p is the screen point for the mouse event
Point2D p = e.getPoint();
selectedEdge = pickSupport.getEdge(layout, p.getX(), p.getY());
if (selectedEdge == null) {
return false;
}
e.consume();
isHandlingMouseEvents = true;
return true;
}
protected boolean pickVertex(V vertex, VisualizationViewer<V, E> viewer) {
PickedState<V> pickedVertexState = viewer.getPickedVertexState();
if (pickedVertexState == null) {
return false;
}
if (pickedVertexState.isPicked(vertex) == false) {
pickedVertexState.clear();
pickedVertexState.pick(vertex, true);
}
return true;
}
protected boolean pickEdge(E edge, VisualizationViewer<V, E> viewer) {
PickedState<E> pickedVertexState = viewer.getPickedEdgeState();
if (pickedVertexState == null) {
return false;
}
if (pickedVertexState.isPicked(edge) == false) {
pickedVertexState.clear();
pickedVertexState.pick(edge, true);
}
return true;
}
protected boolean isOverVertex(MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
return (GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint()) != null);
}
protected boolean isOverEdge(MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
E edge = GraphViewerUtils.getEdgeFromPointInViewSpace(viewer, e.getPoint());
if (edge == null) {
return false;
}
return !isOverVertex(e);
}
protected void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
viewer.setCursor(newCursor);
}
protected boolean shouldShowCursor(MouseEvent e) {
return isOverVertex(e); // default to showing cursor over vertices
}
@Override
public void mousePressed(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
// override this method to do stuff
}
@Override
public void mouseClicked(MouseEvent e) {
if (!isHandlingMouseEvents) {
return;
}
e.consume();
resetState();
}
protected void resetState() {
isHandlingMouseEvents = false;
selectedVertex = null;
selectedEdge = null;
}
@Override
public void mouseDragged(MouseEvent e) {
if (!isHandlingMouseEvents) {
return;
}
e.consume();
resetState();
}
@Override
public void mouseMoved(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
// only "turn on" the cursor; resetting is handled elsewhere (in the mouse driver)
if (shouldShowCursor(e)) {
installCursor(cursor, e);
e.consume();
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
if (shouldShowCursor(e)) {
installCursor(cursor, e);
}
}
@Override
public void mouseEntered(MouseEvent e) {
if (shouldShowCursor(e)) {
installCursor(cursor, e);
e.consume();
}
}
@Override
public void mouseExited(MouseEvent e) {
installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e);
}
}

View file

@ -0,0 +1,109 @@
/* ###
* 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.event.mouse;
import java.awt.Cursor;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AnimatedPickingGraphMousePlugin;
import ghidra.graph.viewer.*;
/**
* A mouse handler to center a vertex when the header is double-clicked.
*/
public class VisualGraphAnimatedPickingGraphMousePlugin<V extends VisualVertex, E extends VisualEdge<V>>
extends AnimatedPickingGraphMousePlugin<V, E> implements VisualGraphMousePlugin<V, E> {
private boolean isHandlingMouseEvents;
public VisualGraphAnimatedPickingGraphMousePlugin() {
super(InputEvent.BUTTON1_MASK);
this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
}
@Override
public void mousePressed(MouseEvent e) {
if (e.getClickCount() != 2) {
return;
}
super.mousePressed(e);
if (vertex == null) {
return; // no vertex clicked, nothing to do
}
isHandlingMouseEvents = true;
}
@Override
public void mouseClicked(MouseEvent e) {
if (!checkModifiers(e) || !isHandlingMouseEvents) {
return;
}
isHandlingMouseEvents = false;
GraphViewer<V, E> viewer = getGraphViewer(e);
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
updater.moveVertexToCenterWithAnimation(vertex);
e.consume();
vertex = null;
}
@Override
public void mouseDragged(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
}
@Override
public void mouseMoved(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
if (isOverVertex(e)) {
installCursor(cursor, e);
e.consume();
}
}
@SuppressWarnings("unchecked")
private boolean isOverVertex(MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
return (GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint()) != null);
}
@SuppressWarnings("unchecked")
private void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
viewer.setCursor(newCursor);
}
/*
* Override subclass method to translate the master view instead of this satellite view
*/
@Override
public void mouseReleased(MouseEvent e) {
if (isHandlingMouseEvents) {
e.consume();
}
}
}

View file

@ -0,0 +1,45 @@
/* ###
* 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.event.mouse;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
public class VisualGraphCursorRestoringGraphMousePlugin<V, E> extends AbstractGraphMousePlugin
implements MouseMotionListener {
public VisualGraphCursorRestoringGraphMousePlugin() {
super(0);
}
public void mouseDragged(MouseEvent e) {
// don't care
}
public void mouseMoved(MouseEvent e) {
installCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR), e);
}
@SuppressWarnings("unchecked")
private void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
viewer.setCursor(newCursor);
}
}

View file

@ -0,0 +1,90 @@
/* ###
* 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.event.mouse;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.picking.PickedState;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.event.picking.GPickedState;
public class VisualGraphEdgeSelectionGraphMousePlugin<V extends VisualVertex, E extends VisualEdge<V>>
extends VisualGraphAbstractGraphMousePlugin<V, E> {
public VisualGraphEdgeSelectionGraphMousePlugin() {
this.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
}
@Override
public void mousePressed(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
if (e.getClickCount() != 2) {
return;
}
checkForEdge(e); // this will select an edge if we can and store off the edge
}
@Override
public void mouseClicked(MouseEvent e) {
if (!isHandlingMouseEvents) {
return;
}
E edgeReference = selectedEdge; // grab a copy before we reset our state
e.consume();
resetState();
// on double-clicks we go to the vertex in the current edge direction unless that vertex
// is already selected, then we go to the other vertex
GraphViewer<V, E> viewer = getGraphViewer(e);
PickedState<V> pickedVertexState = viewer.getPickedVertexState();
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
V destination = graph.getDest(edgeReference);
if (!pickedVertexState.isPicked(destination)) {
pickAndShowVertex(destination, pickedVertexState, viewer);
return;
}
// the destination was picked, go the other direction
V source = graph.getSource(edgeReference);
pickAndShowVertex(source, pickedVertexState, viewer);
}
private void pickAndShowVertex(V vertex, PickedState<V> pickedVertexState,
GraphViewer<V, E> viewer) {
GPickedState<V> pickedStateWrapper = (GPickedState<V>) pickedVertexState;
pickedStateWrapper.pickToActivate(vertex);
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
updater.moveVertexToCenterWithAnimation(vertex);
}
@Override
protected boolean shouldShowCursor(MouseEvent e) {
return isOverEdge(e);
}
}

View file

@ -0,0 +1,310 @@
/* ###
* 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.event.mouse;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JComponent;
import docking.DockingUtils;
import docking.ToolTipManager;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import ghidra.graph.viewer.*;
//@formatter:off
public class VisualGraphEventForwardingGraphMousePlugin<V extends VisualVertex,
E extends VisualEdge<V>>
extends AbstractGraphMousePlugin
implements MouseListener, MouseMotionListener, VisualGraphMousePlugin<V, E> {
//@formatter:on
private static final Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
private VertexMouseInfo<V, E> mousePressedInfo;
private VertexMouseInfo<V, E> currentMouseEnteredInfo;
private boolean isHandlingEvent = false;
// TODO for deprecated usage note, see the VisualGraphMousePlugin interface
public VisualGraphEventForwardingGraphMousePlugin() {
this(InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK);
}
public VisualGraphEventForwardingGraphMousePlugin(int modifiers) {
super(modifiers);
this.cursor = DEFAULT_CURSOR;
}
@Override
public boolean checkModifiers(MouseEvent e) {
int eventModifiers = e.getModifiers();
eventModifiers = turnOffControlKey(eventModifiers);
return ((eventModifiers & getModifiers()) == eventModifiers);
}
private int turnOffControlKey(int eventModifiers) {
return eventModifiers & (~DockingUtils.CONTROL_KEY_MODIFIER_MASK_DEPRECATED);
}
private boolean isControlClick(MouseEvent e) {
int allModifiers = e.getModifiers();
int osSpecificMask = DockingUtils.CONTROL_KEY_MODIFIER_MASK_DEPRECATED;
return (allModifiers & osSpecificMask) == osSpecificMask;
// can't use this until we fix the old modifiers usage
// boolean controlDown = DockingUtils.isControlModifier(e);
// return controlDown;
}
@Override
public void mousePressed(MouseEvent e) {
mousePressedInfo = null;
isHandlingEvent = false;
if (!checkModifiers(e)) {
return;
}
VertexMouseInfo<V, E> vertexMouseInfo = getTranslatedMouseInfo(e);
if (vertexMouseInfo == null) {
return;
}
if (vertexMouseInfo.isScaledPastInteractionThreshold()) {
return;
}
updateCursor(vertexMouseInfo);
if (allowHeaderClickThroughToLowerLevelMouseHandlers(vertexMouseInfo)) {
// let the follow-on mouse processors, well, process (this allows dragging to happen)
return;
}
isHandlingEvent = true;
mousePressedInfo = vertexMouseInfo;
// When clicking a button on the header, we do not want to clear the other selected
// vertices, as some of the buttons (like grouping) work on multiple selected vertices.
// Further, if a user wants to click a button to work on the selection, then we don't
// want to clear that selection as they click the button.
// boolean addToSelection = isHeaderButtonClick(vertexMouseInfo) || isControlClick(e);
boolean addToSelection = isControlClick(e);
vertexMouseInfo.selectVertex(addToSelection); // make sure the pick state is in synch
vertexMouseInfo.forwardEvent();
}
private VertexMouseInfo<V, E> getTranslatedMouseInfo(MouseEvent e) {
GraphViewer<V, E> viewer = getGraphViewer(e);
return GraphViewerUtils.convertMouseEventToVertexMouseEvent(viewer, e);
}
private boolean allowHeaderClickThroughToLowerLevelMouseHandlers(VertexMouseInfo<V, E> info) {
if (info.isPopupClick()) {
return false;
}
return info.isGrabArea();
}
@Override
public void mouseReleased(MouseEvent e) {
isHandlingEvent = false;
if (mousePressedInfo != null) {
VertexMouseInfo<V, E> mouseReleasedMouseInfo = getTranslatedMouseInfo(e);
if (mouseReleasedMouseInfo == null) {
// NOTE: getting here implies we had a mousePressed() inside of a vertex, but the
// mouseReleased() is outside of that vertex (like during a drag operation). In
// that case, we want to consume the event, so that other mouse listeners (like
// the vertex picking listener) do not do unexpected things (like deselecting a
// vertex after a drag operation).
handleMouseEventAfterLeavingVertex(e, mousePressedInfo);
return;
}
// Compare the mouse pressed Java component with the mouse released Java
// component. If they are different, then forward the released event to the
// original, mouse pressed component. This helps fix issues where dragging started
// in the contents of a vertex, but ended on the header.
Component pressedComponent = mousePressedInfo.getClickedComponent();
Component releasedComponent = mouseReleasedMouseInfo.getClickedComponent();
if (pressedComponent != releasedComponent) {
handleMouseEventAfterLeavingVertex(e, mousePressedInfo);
return;
}
if (mouseReleasedMouseInfo.isScaledPastInteractionThreshold()) {
return;
}
mouseReleasedMouseInfo.forwardEvent();
isHandlingEvent = true;
}
}
@Override
public void mouseDragged(MouseEvent e) {
isHandlingEvent = false;
if (mousePressedInfo != null) {
isHandlingEvent = true;
VertexMouseInfo<V, E> mouseDraggedMouseInfo = getTranslatedMouseInfo(e);
if (mouseDraggedMouseInfo == null) {
handleMouseEventAfterLeavingVertex(e, mousePressedInfo);
return;
}
else if (mousePressedInfo != mouseDraggedMouseInfo) {
// don't allow dragging from one vertex into another
handleMouseEventAfterLeavingVertex(e, mousePressedInfo);
return;
}
if (mouseDraggedMouseInfo.isScaledPastInteractionThreshold()) {
return;
}
mouseDraggedMouseInfo.forwardEvent();
mouseDraggedMouseInfo.getViewer().repaint();
return;
}
ToolTipManager.sharedInstance().hideTipWindow();
}
/*
* The user has initiated a mouse operation, going from inside a vertex to outside the vertex,
* and we want to make sure that event are still given to the original vertex (this allows
* operations like dragging inside of vertices to work as expected)
*/
private void handleMouseEventAfterLeavingVertex(MouseEvent e,
VertexMouseInfo<V, E> startMouseInfo) {
if (startMouseInfo.isScaledPastInteractionThreshold()) {
return;
}
// create a mouse info for the current event
GraphViewer<V, E> viewer = startMouseInfo.getViewer();
V vertex = startMouseInfo.getVertex();
Point vertexRelativePoint =
GraphViewerUtils.translatePointFromViewSpaceToVertexRelativeSpace(viewer, e.getPoint(),
vertex);
VertexMouseInfo<V, E> currentDraggedInfo =
viewer.createVertexMouseInfo(e, vertex, vertexRelativePoint);
currentDraggedInfo.setClickedComponent(startMouseInfo.getClickedComponent(),
vertexRelativePoint);
currentDraggedInfo.forwardEvent();
viewer.repaint();
}
@Override
public void mouseClicked(MouseEvent e) {
isHandlingEvent = false;
if (mousePressedInfo != null) {
isHandlingEvent = true;
repaintVertex(e, mousePressedInfo);
VertexMouseInfo<V, E> mouseClickedMouseInfo = getTranslatedMouseInfo(e);
if (mouseClickedMouseInfo == null) {
return;
}
if (mouseClickedMouseInfo.isScaledPastInteractionThreshold()) {
return;
}
mouseClickedMouseInfo.forwardEvent();
}
mousePressedInfo = null;
}
private void repaintVertex(MouseEvent e, VertexMouseInfo<V, E> mouseInfo) {
GraphViewer<V, E> viewer = getGraphViewer(e);
viewer.repaint();
}
@Override
public void mouseEntered(MouseEvent e) {
setDefaultCursor(e);
}
@Override
public void mouseExited(MouseEvent e) {
setDefaultCursor(e);
}
@Override
public void mouseMoved(MouseEvent e) {
isHandlingEvent = false;
VertexMouseInfo<V, E> mouseMovedMouseInfo = getTranslatedMouseInfo(e);
if (mouseMovedMouseInfo == null) {
triggerMouseExited(currentMouseEnteredInfo, mouseMovedMouseInfo);
setDefaultCursor(e);
currentMouseEnteredInfo = null;
return;
}
if (mouseMovedMouseInfo.isScaledPastInteractionThreshold()) {
return;
}
isHandlingEvent = true;
triggerMouseExited(currentMouseEnteredInfo, mouseMovedMouseInfo);
triggerMouseEntered(currentMouseEnteredInfo, mouseMovedMouseInfo);
currentMouseEnteredInfo = mouseMovedMouseInfo;
mouseMovedMouseInfo.forwardEvent();
}
private void triggerMouseExited(VertexMouseInfo<V, E> currentInfo,
VertexMouseInfo<V, E> newInfo) {
if (currentInfo == null) {
return;
}
if (newInfo == null) {
currentInfo.simulateMouseExitedEvent(); // different infos, send the event
}
else if (newInfo.getClickedComponent() != currentInfo.getClickedComponent()) {
currentInfo.simulateMouseExitedEvent(); // different infos, send the event
}
}
private void triggerMouseEntered(VertexMouseInfo<V, E> currentInfo,
VertexMouseInfo<V, E> newInfo) {
if (currentInfo == null ||
(newInfo.getClickedComponent() != currentInfo.getClickedComponent())) {
newInfo.simulateMouseEnteredEvent();
}
updateCursor(newInfo);
}
private void updateCursor(VertexMouseInfo<V, E> info) {
if (!isHandlingEvent) {
return;
}
JComponent c = (JComponent) info.getEventSource();
c.setCursor(info.getCursorForClickedComponent());
}
private void setDefaultCursor(MouseEvent e) {
if (!isHandlingEvent) {
return;
}
JComponent c = (JComponent) e.getSource();
c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}

View file

@ -0,0 +1,143 @@
/* ###
* 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.event.mouse;
import java.awt.event.*;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.edge.VisualGraphPathHighlighter;
/**
* A mouse plugin to handle vertex hovers, to include animating paths in the graph, based
* upon the current {@link PathHighlightMode}.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class VisualGraphHoverMousePlugin<V extends VisualVertex, E extends VisualEdge<V>>
extends AbstractGraphMousePlugin
implements MouseMotionListener, MouseListener, VisualGraphMousePlugin<V, E> {
private final GraphComponent<V, E, ?> graphComponent;
private final VisualGraphPathHighlighter<V, E> pathHighlighter;
private final VisualGraph<V, E> graph;
// Note: we used to have code that would differentiate between the hovered and 'other'
// viewer, which would be the primary viewer and the satellite viewer, depending
// upon how this class was created. We currently don't need to know the difference,
// but it may be needed in the future.
private final VisualizationViewer<V, E> sourceViewer;
private final VisualizationViewer<V, E> otherViewer;
private V hoveredVertex;
public VisualGraphHoverMousePlugin(GraphComponent<V, E, ?> graphComponent,
VisualizationViewer<V, E> viewer, VisualizationViewer<V, E> otherViewer) {
super(0);
this.graphComponent = graphComponent;
this.pathHighlighter = graphComponent.getPathHighlighter();
this.graph = graphComponent.getGraph();
this.sourceViewer = viewer;
this.otherViewer = otherViewer;
}
@Override
public void mouseMoved(MouseEvent e) {
updateMouseHovers(e);
}
private void updateMouseHovers(MouseEvent e) {
if (graphComponent.isUninitialized()) {
return;
}
GraphViewer<V, E> viewer = getGraphViewer(e);
V newHoveredVertex = GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint());
if (newHoveredVertex == hoveredVertex) {
return;
}
updateMouseHoversForVertex(viewer, newHoveredVertex);
}
private void updateMouseHoversForVertex(GraphViewer<V, E> viewer, V newHoveredVertex) {
VisualGraphViewUpdater<V, E> updater = getViewUpdater(viewer);
updater.stopEdgeHoverAnimation();
setHovered(hoveredVertex, false);
hoveredVertex = newHoveredVertex;
setHovered(hoveredVertex, true);
setupHoverEdgesForVertex(newHoveredVertex);
}
private void setHovered(V v, boolean hovered) {
if (v != null) {
v.setHovered(hovered);
}
}
private void setupHoverEdgesForVertex(V newHoveredVertex) {
if (graph.getEdgeCount() == 0) {
return; // no edges to animate
}
pathHighlighter.setHoveredVertex(newHoveredVertex);
repaint();
}
private void repaint() {
sourceViewer.repaint();
otherViewer.repaint();
}
@Override
public void mouseExited(MouseEvent e) {
// don't care
}
@Override
public void mouseDragged(MouseEvent e) {
VisualGraphViewUpdater<V, E> updater = getViewUpdater(e);
updater.stopEdgeHoverAnimation();
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
return;
}
updateMouseHovers(e);
}
@Override
public void mouseEntered(MouseEvent e) {
// don't care
}
@Override
public void mousePressed(MouseEvent e) {
// don't care
}
@Override
public void mouseClicked(MouseEvent e) {
// handled by dragged and released
}
}

View file

@ -0,0 +1,111 @@
/* ###
* 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.event.mouse;
import java.awt.event.MouseEvent;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.*;
/**
* An interface to provide a common set of methods for classes that could not otherwise
* extend an abstract class. This interface signals that the implementer is a {@link VisualGraph}
* mouse plugin.
*
* <P>Note: The implementors of this interface still use the deprecated
* {@link MouseEvent#getModifiers()} method, since many of those classes extends from
* 3rd-party classes that still use them, such as {@link PickingGraphMousePlugin}. We will need
* to update the library (if/when possible), or rewrite our code so that it does not use the
* old 3rd-party algorithms.
*
* @param <V> the vertex
* @param <E> the edge
*/
public interface VisualGraphMousePlugin<V extends VisualVertex, E extends VisualEdge<V>> {
public default VisualizationViewer<V, E> getViewer(MouseEvent e) {
GraphViewer<V, E> viewer = getGraphViewer(e);
return viewer;
}
/**
* Returns the <b>primary/master</b> graph viewer.
*
* @param e the mouse event from which to get the viewer
* @return the viewer
*/
@SuppressWarnings("unchecked")
public default GraphViewer<V, E> getGraphViewer(MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
// is this the satellite viewer?
if (viewer instanceof SatelliteGraphViewer) {
return (GraphViewer<V, E>) ((SatelliteGraphViewer<V, E>) viewer).getMaster();
}
if (viewer instanceof GraphViewer) {
GraphViewer<V, E> graphViewer = (GraphViewer<V, E>) viewer;
return graphViewer;
}
throw new IllegalStateException("Do not have a master or satellite GraphViewer");
}
/**
* Returns the satellite graph viewer. This assumes that the mouse event originated from
* the satellite viewer.
*
* @param e the mouse event from which to get the viewer
* @return the viewer
*/
@SuppressWarnings("unchecked")
public default SatelliteGraphViewer<V, E> getSatelliteGraphViewer(MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
// is this the satellite viewer?
if (viewer instanceof SatelliteGraphViewer) {
return (SatelliteGraphViewer<V, E>) viewer;
}
throw new IllegalStateException("Do not have a satellite GraphViewer");
}
/**
* Returns the updater that is used to modify the primary graph viewer.
*
* @param e the mouse event from which to get the viewer
* @return the updater
*/
public default VisualGraphViewUpdater<V, E> getViewUpdater(MouseEvent e) {
GraphViewer<V, E> viewer = getGraphViewer(e);
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
return updater;
}
/**
* Returns the updater that is used to modify the primary graph viewer.
*
* @param viewer the viewer
* @return the updater
*/
public default VisualGraphViewUpdater<V, E> getViewUpdater(GraphViewer<V, E> viewer) {
VisualGraphViewUpdater<V, E> updater = viewer.getViewUpdater();
return updater;
}
}

View file

@ -0,0 +1,183 @@
/* ###
* 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.event.mouse;
import java.awt.Color;
import java.awt.Point;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.util.Objects;
import docking.DockingUtils;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.control.AbstractGraphMousePlugin;
import edu.uci.ics.jung.visualization.transform.MutableTransformer;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.renderer.*;
/**
* A simple plugin that allows clients to be notified of mouse events before any of the other
* mouse plugins.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
//@formatter:off
public class VisualGraphMouseTrackingGraphMousePlugin<V extends VisualVertex,
E extends VisualEdge<V>>
extends AbstractGraphMousePlugin
implements MouseListener, MouseMotionListener, VisualGraphMousePlugin<V, E> {
//@formatter:on
private MouseDebugPaintable paintable = new MouseDebugPaintable();
private GraphViewer<V, E> viewer;
private Point dragEnd;
private MouseDraggedPaintableShape dragShape;
private MouseDraggedLinePaintableShape dragLineShape;
private int mouseMovedCount;
public VisualGraphMouseTrackingGraphMousePlugin(GraphViewer<V, E> viewer) {
super(InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK);
this.viewer = Objects.requireNonNull(viewer);
viewer.addPostRenderPaintable(paintable);
}
@Override
public boolean checkModifiers(MouseEvent e) {
int eventModifiers = e.getModifiers();
eventModifiers = turnOffControlKey(eventModifiers);
return ((eventModifiers & getModifiers()) == eventModifiers);
}
private int turnOffControlKey(int eventModifiers) {
return eventModifiers & (~DockingUtils.CONTROL_KEY_MODIFIER_MASK);
}
@Override
public void mouseDragged(MouseEvent e) {
RenderContext<?, ?> rc = viewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = rc.getMultiLayerTransformer();
MutableTransformer layoutXformer = multiLayerTransformer.getTransformer(Layer.LAYOUT);
AffineTransform layoutXform = layoutXformer.getTransform();
double tx = layoutXform.getTranslateX();
double ty = layoutXform.getTranslateY();
Point p = e.getPoint();
Point gp = GraphViewerUtils.translatePointFromViewSpaceToGraphSpace(p, viewer);
dragEnd = gp;
Point gDown = GraphViewerUtils.translatePointFromViewSpaceToGraphSpace(down, viewer);
if (dragShape == null) {
dragShape = new MouseDraggedPaintableShape(gDown, gp, tx, ty);
paintable.addShape(dragShape, viewer);
}
else {
dragShape.setPoints(gDown, dragEnd);
}
int offset = 20;
Point downOver = new Point(down.x + offset, down.y + offset);
Point pOver = new Point(p.x + offset, p.y + offset);
Point gpOver = GraphViewerUtils.translatePointFromViewSpaceToGraphSpace(pOver, viewer);
if (dragLineShape == null) {
Point gDownOver =
GraphViewerUtils.translatePointFromViewSpaceToGraphSpace(downOver, viewer);
dragLineShape = new MouseDraggedLinePaintableShape(gDownOver, gpOver, tx, ty);
paintable.addShape(dragLineShape, viewer);
}
else {
dragLineShape.addPoint(gpOver);
}
viewer.repaint();
}
@Override
public void mouseMoved(MouseEvent e) {
// we get a lot of these events, so don't record them all
if (++mouseMovedCount % 5 == 0) {
addPointMousePaintable(e, new Color(0, 255, 0, 127)); // greenish
}
}
@Override
public void mouseClicked(MouseEvent e) {
int button = e.getButton();
if (button == MouseEvent.BUTTON2) {
paintable.clear();
return;
}
addPointMousePaintable(e, Color.ORANGE);
}
private void addPointMousePaintable(MouseEvent e, Color color) {
Point p = e.getPoint();
Point gp = GraphViewerUtils.translatePointFromViewSpaceToGraphSpace(p, viewer);
RenderContext<?, ?> rc = viewer.getRenderContext();
MultiLayerTransformer multiLayerTransformer = rc.getMultiLayerTransformer();
MutableTransformer layoutXformer = multiLayerTransformer.getTransformer(Layer.LAYOUT);
AffineTransform layoutXform = layoutXformer.getTransform();
double tx = layoutXform.getTranslateX();
double ty = layoutXform.getTranslateY();
MouseClickedPaintableShape ps = new MouseClickedPaintableShape(gp, tx, ty, color);
paintable.addShape(ps, viewer);
viewer.repaint();
}
@Override
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
down = p;
}
@Override
public void mouseReleased(MouseEvent e) {
down = null;
dragEnd = null;
if (dragShape != null) {
dragShape.shapeFinished();
dragShape = null;
}
if (dragLineShape != null) {
dragLineShape.shapeFinished();
dragLineShape = null;
}
viewer.repaint();
}
@Override
public void mouseEntered(MouseEvent e) {
// stub
}
@Override
public void mouseExited(MouseEvent e) {
// stub
}
}

View file

@ -0,0 +1,201 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* 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.event.mouse;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import docking.DockingUtils;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.control.PickingGraphMousePlugin;
import edu.uci.ics.jung.visualization.picking.PickedState;
import ghidra.graph.viewer.*;
public class VisualGraphPickingGraphMousePlugin<V extends VisualVertex, E extends VisualEdge<V>>
extends PickingGraphMousePlugin<V, E> implements VisualGraphMousePlugin<V, E> {
// ALERT: -this class was created because mouseDragged() has a bug that generates a NPE
// -also, mousePressed() has a bug in that it does not check the modifiers when the method is entered
// TODO for deprecated usage note, see the VisualGraphMousePlugin interface
public VisualGraphPickingGraphMousePlugin() {
super(InputEvent.BUTTON1_MASK,
InputEvent.BUTTON1_MASK | DockingUtils.CONTROL_KEY_MODIFIER_MASK_DEPRECATED);
}
@Override
public boolean checkModifiers(MouseEvent e) {
if (e.getModifiers() == addToSelectionModifiers) {
return true;
}
return super.checkModifiers(e);
}
@Override
public void mousePressed(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
super.mousePressed(e);
}
@Override
public void mouseDragged(MouseEvent e) {
if (locked) {
return;
}
GraphViewer<V, E> viewer = getGraphViewer(e);
if (vertex != null) {
dragVertices(e, viewer);
}
else {
increaseDragRectangle(e);
}
viewer.repaint();
}
private void increaseDragRectangle(MouseEvent e) {
Point2D out = e.getPoint();
int theModifiers = e.getModifiers();
if (theModifiers == addToSelectionModifiers || theModifiers == modifiers) {
if (down != null) {
rect.setFrameFromDiagonal(down, out);
}
}
}
private void dragVertices(MouseEvent e, GraphViewer<V, E> viewer) {
Point p = e.getPoint();
RenderContext<V, E> context = viewer.getRenderContext();
MultiLayerTransformer xformer = context.getMultiLayerTransformer();
Point2D layoutPoint = xformer.inverseTransform(p);
Point2D layoutDown = xformer.inverseTransform(down);
Layout<V, E> layout = viewer.getGraphLayout();
double dx = layoutPoint.getX() - layoutDown.getX();
double dy = layoutPoint.getY() - layoutDown.getY();
PickedState<V> ps = viewer.getPickedVertexState();
for (V v : ps.getPicked()) {
Point2D vertexPoint = layout.apply(v);
vertexPoint.setLocation(vertexPoint.getX() + dx, vertexPoint.getY() + dy);
layout.setLocation(v, vertexPoint);
updatedArticulatedEdges(viewer, v);
}
down = p;
e.consume();
}
private void updatedArticulatedEdges(GraphViewer<V, E> viewer, V v) {
Layout<V, E> layout = viewer.getGraphLayout();
Graph<V, E> graph = layout.getGraph();
Collection<E> edges = graph.getIncidentEdges(v);
VisualGraphViewUpdater<V, E> updater = getViewUpdater(viewer);
updater.updateEdgeShapes(edges);
}
@Override
public void mouseMoved(MouseEvent e) {
if (isOverVertex(e)) {
installCursor(cursor, e);
e.consume();
}
}
private boolean isOverVertex(MouseEvent e) {
VisualizationViewer<V, E> viewer = getViewer(e);
return (GraphViewerUtils.getVertexFromPointInViewSpace(viewer, e.getPoint()) != null);
}
@SuppressWarnings("unchecked")
private void installCursor(Cursor newCursor, MouseEvent e) {
VisualizationViewer<V, E> viewer = (VisualizationViewer<V, E>) e.getSource();
viewer.setCursor(newCursor);
}
/* Pretty sure we don't need this now that we update the vertex locations directly. This
was old code that pre-existed the preferred method for updating vertex locations. Once
all tests are passing, and selecting edges of previously dragged vertices still works,
the delete this code.
private void updateVertexLocationToCompensateForDraggingWorkaround(double dx, double dy, V v) {
Point2D original = v.getLocation();
original.setLocation(original.getX() + dx, original.getY() + dy);
v.setLocation(original);
}
*/
@Override
public void mouseReleased(MouseEvent e) {
if (!checkModifiers(e)) {
return;
}
// We overrode this method here to clear the picked state of edges and vertices if we
// ever get a released event when the user is clicking somewhere that is not an edge or
// vertex
if (!isDragging() && vertex == null && edge == null) {
maybeClearPickedState(e);
}
super.mouseReleased(e);
}
private boolean isDragging() {
Rectangle2D frame = rect.getFrame();
return frame.getHeight() > 0;
}
@SuppressWarnings("unchecked")
private void maybeClearPickedState(MouseEvent event) {
VisualizationViewer<V, E> vv = (VisualizationViewer<V, E>) event.getSource();
PickedState<V> pickedVertexState = vv.getPickedVertexState();
PickedState<E> pickedEdgeState = vv.getPickedEdgeState();
if (pickedEdgeState == null || pickedVertexState == null) {
return;
}
GraphElementAccessor<V, E> pickSupport = vv.getPickSupport();
Layout<V, E> layout = vv.getGraphLayout();
Point2D mousePoint = event.getPoint();
V v = pickSupport.getVertex(layout, mousePoint.getX(), mousePoint.getY());
if (v != null) {
return;
}
E e = pickSupport.getEdge(layout, mousePoint.getX(), mousePoint.getY());
if (e != null) {
return;
}
pickedEdgeState.clear();
pickedVertexState.clear();
}
}

View file

@ -0,0 +1,236 @@
/* ###
* 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.event.mouse;
import java.awt.event.*;
import java.util.concurrent.CopyOnWriteArrayList;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.GraphMousePlugin;
import ghidra.graph.viewer.VisualEdge;
import ghidra.graph.viewer.VisualVertex;
import ghidra.util.Msg;
/**
* This is the class that controls which mouse plugins get installed into the graph.
*
* @param <V> the vertex type
* @param <E> the edge type
*/
public class VisualGraphPluggableGraphMouse<V extends VisualVertex, E extends VisualEdge<V>>
implements VisualizationViewer.GraphMouse {
protected CopyOnWriteArrayList<GraphMousePlugin> mousePlugins = new CopyOnWriteArrayList<>();
public VisualGraphPluggableGraphMouse() {
addPlugins();
}
protected void addPlugins() {
//
// Note: the order of these additions matters, as an event will flow to each plugin until
// it is handled.
//
// passes events to the Ghidra components
add(new VisualGraphEventForwardingGraphMousePlugin<V, E>());
// edge and vertex picking
add(new VisualGraphEdgeSelectionGraphMousePlugin<V, E>());
// add( new VisualGraphAnimatedPickingGraphMousePlugin<V, E>() );
add(new VisualGraphZoomingPickingGraphMousePlugin<V, E>());
// zooming and alternate mouse wheel operation--panning
add(new VisualGraphScalingGraphMousePlugin<V, E>());
add(new VisualGraphScrollWheelPanningPlugin<V, E>());
// the grab/pan feature
add(new VisualGraphTranslatingGraphMousePlugin<V, E>());
// ...more picking (dragging an area and single node picking)
add(new VisualGraphPickingGraphMousePlugin<V, E>());
// cursor cleanup
add(new VisualGraphCursorRestoringGraphMousePlugin<V, E>());
}
/**
* Places the given plugin at the front of the list
*
* @param p the mouse plugin to prepend
*/
public void prepend(GraphMousePlugin p) {
if (mousePlugins.contains(p)) {
mousePlugins.remove(p);
}
mousePlugins.add(0, p);
}
public void add(GraphMousePlugin p) {
if (mousePlugins.contains(p)) {
mousePlugins.remove(p);
}
mousePlugins.add(p);
}
public void remove(GraphMousePlugin p) {
mousePlugins.remove(p);
}
public void dispose() {
mousePlugins.clear();
}
private void trace(String s) {
Msg.trace(this, s);
}
private void trace(String s, MouseEvent e) {
Msg.trace(this, "click count = " + e.getClickCount() + " - " + s);
}
@Override
public void mouseClicked(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseClicked() on " + p, e);
((MouseListener) p).mouseClicked(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mousePressed(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mousePressed() on " + p, e);
((MouseListener) p).mousePressed(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseReleased() on " + p, e);
((MouseListener) p).mouseReleased(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mouseEntered(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseEntered() on " + p, e);
((MouseListener) p).mouseEntered(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mouseExited(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseListener)) {
continue;
}
trace("mouseExited() on " + p, e);
((MouseListener) p).mouseExited(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mouseDragged(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseMotionListener)) {
continue;
}
trace("mouseDragged() on " + p, e);
((MouseMotionListener) p).mouseDragged(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mouseMoved(MouseEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseMotionListener)) {
continue;
}
trace("mouseMoved() on " + p, e);
((MouseMotionListener) p).mouseMoved(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
for (GraphMousePlugin p : mousePlugins) {
if (!(p instanceof MouseWheelListener)) {
continue;
}
trace("mouseWheelMoved() on " + p, e);
((MouseWheelListener) p).mouseWheelMoved(e);
if (e.isConsumed()) {
trace("\tconsumed");
return;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more