Skip to content

Commit 5057d65

Browse files
committed
Merge remote-tracking branch 'origin/GT-3279_ghidravore_gtree_improvements'
2 parents 5e74ca6 + 51ec275 commit 5057d65

File tree

8 files changed

+303
-181
lines changed

8 files changed

+303
-181
lines changed

Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/OrganizationNode.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class OrganizationNode extends SymbolTreeNode {
4949
private OrganizationNode(List<GTreeNode> list, int max, int parentLevel, TaskMonitor monitor)
5050
throws CancelledException {
5151

52-
setChildren(computeChildren(list, max, this, parentLevel, monitor));
52+
doSetChildren(computeChildren(list, max, this, parentLevel, monitor));
5353

5454
GTreeNode child = getChild(0);
5555
baseName = child.getName().substring(0, getPrefixSizeForGrouping(getChildren(), 1) + 1);
@@ -128,8 +128,7 @@ private static List<GTreeNode> computeChildren(List<GTreeNode> list, int maxNode
128128
monitor.checkCanceled();
129129
String str = list.get(i).getName();
130130
if (stringsDiffer(prevStr, str, characterOffset)) {
131-
addNode(children, list, start, i - 1, maxNodes, characterOffset,
132-
monitor);
131+
addNode(children, list, start, i - 1, maxNodes, characterOffset, monitor);
133132
start = i;
134133
}
135134
prevStr = str;
@@ -144,16 +143,14 @@ private static boolean stringsDiffer(String s1, String s2, int diffLevel) {
144143
return true;
145144
}
146145
return s1.substring(0, diffLevel + 1)
147-
.compareToIgnoreCase(
148-
s2.substring(0, diffLevel + 1)) != 0;
146+
.compareToIgnoreCase(s2.substring(0, diffLevel + 1)) != 0;
149147
}
150148

151149
private static void addNode(List<GTreeNode> children, List<GTreeNode> list, int start, int end,
152-
int max, int diffLevel, TaskMonitor monitor)
153-
throws CancelledException {
150+
int max, int diffLevel, TaskMonitor monitor) throws CancelledException {
154151
if (end - start > 0) {
155-
children.add(new OrganizationNode(list.subList(start, end + 1), max, diffLevel,
156-
monitor));
152+
children.add(
153+
new OrganizationNode(list.subList(start, end + 1), max, diffLevel, monitor));
157154
}
158155
else {
159156
GTreeNode node = list.get(start);

Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/nodes/SymbolCategoryNode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ protected List<GTreeNode> getSymbols(SymbolType type, boolean globalOnly, TaskMo
7979
while (it.hasNext()) {
8080
Symbol s = it.next();
8181
monitor.incrementProgress(1);
82+
monitor.checkCanceled();
8283
if (s != null && (s.getSymbolType() == symbolType)) {
83-
monitor.checkCanceled();
8484
list.add(SymbolNode.createNode(s, program));
8585
}
8686
}

Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/CoreGTreeNode.java

Lines changed: 132 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,46 @@
1818
import java.util.*;
1919
import java.util.concurrent.CopyOnWriteArrayList;
2020

21+
import javax.swing.JTree;
22+
2123
import docking.widgets.tree.internal.InProgressGTreeNode;
2224
import ghidra.util.Swing;
25+
import ghidra.util.SystemUtilities;
2326

2427
/**
2528
* This class exists to help prevent threading errors in {@link GTreeNode} and subclasses,
2629
* by privately maintaining synchronous access to the parent and children of a node.
2730
* <P>
2831
* This implementation uses a {@link CopyOnWriteArrayList} to store its children. The theory is
2932
* that this will allow direct thread-safe access to the children without having to worry about
30-
* {@link ConcurrentModificationException}s. Also, the assumption is that accessing the children
31-
* will occur much more frequently than modifying the children. This should only be a problem if
32-
* a direct descendent of AbstractGTreeNode creates it children by calling
33+
* {@link ConcurrentModificationException}s while iterating the children. Also, the assumption
34+
* is that accessing the children will occur much more frequently than modifying the children.
35+
* This should only be a problem if a direct descendent of GTreeNode creates its children by calling
3336
* addNode many times. But in that case, the tree should be using Lazy or
3437
* SlowLoading nodes which always load into another list first and all the children will be set
3538
* on a node in a single operation.
3639
* <P>
37-
* Subclasses that need access to the children
38-
* can call the {@link #children()} method which will ensure that the children are
39-
* loaded (not null). Since this class uses a {@link CopyOnWriteArrayList}, subclasses that call
40-
* the {@link #children()} method can safely operate and iterate on the list they get back without
41-
* having to worry about getting a {@link ConcurrentModificationException}.
40+
* Subclasses that need access to the children can call the {@link #children()} method which will
41+
* ensure that the children are loaded (not null). Since this class uses a
42+
* {@link CopyOnWriteArrayList}, subclasses that call the {@link #children()} method can safely
43+
* iterate the list without having to worry about getting a {@link ConcurrentModificationException}.
4244
* <P>
4345
* This class uses synchronization to assure that the parent/children relationship is stable across
4446
* threads. To avoid deadlocks, the sychronization strategy is that if you have the lock on
45-
* a parent node, you can safely acquire the lock on any of its descendants, put never its
47+
* a parent node, you can safely acquire the lock on any of its descendants, but never its
4648
* ancestors. To facilitate this strategy, the {@link #getParent()} is not synchronized, but it
4749
* is made volatile to assure the current value is always used.
50+
* <P>
51+
* Except for the {@link #doSetChildren(List)} method, all other calls that mutate the
52+
* children must be called on the swing thread. The idea is that bulk operations can work efficiently
53+
* by avoiding constantly switching to the swing thread to mutate the tree. This works because
54+
* the bulk setting of the children generates a coarse "node structure changed" event, which causes the
55+
* underlying {@link JTree} to rebuild its internal cache of the tree. Individual add/remove operations
56+
* have to be done very carefully such that the {@link JTree} is always updated on one change before any
57+
* additional changes are done. This is why those operations are required to be done on the swing
58+
* thread, which combined with the fact that all mutate operations are synchronized, keeps the JTree
59+
* happy.
60+
*
4861
*/
4962
abstract class CoreGTreeNode implements Cloneable {
5063
// the parent is volatile to facilitate the synchronization strategy (see comments above)
@@ -118,6 +131,25 @@ protected final List<GTreeNode> children() {
118131
*/
119132
protected abstract List<GTreeNode> generateChildren();
120133

134+
/**
135+
* Sets the children of this node to the given list of child nodes and fires the appropriate
136+
* tree event to kick the tree to update the display. Note: This method must be called
137+
* from the swing thread because it will notify the underlying JTree.
138+
* @param childList the list of child nodes to assign as children to this node
139+
* @see #doSetChildren(List) if calling from a background thread.
140+
*/
141+
protected synchronized void doSetChildrenAndFireEvent(List<GTreeNode> childList) {
142+
doSetChildren(childList);
143+
doFireNodeStructureChanged();
144+
}
145+
146+
/**
147+
* Sets the children of this node to the given list of child nodes, but does not notify the
148+
* tree. This method does not have to be called from the swing thread. It is intended to be
149+
* used by background threads that want to populate all or part of the tree, but wait until
150+
* the bulk operations are completed before notifying the tree.
151+
* @param childList the list of child nodes to assign as children to this node
152+
*/
121153
protected synchronized void doSetChildren(List<GTreeNode> childList) {
122154
List<GTreeNode> oldChildren = children;
123155
children = null;
@@ -142,6 +174,57 @@ protected synchronized void doSetChildren(List<GTreeNode> childList) {
142174
}
143175
}
144176

177+
/**
178+
* Adds a node to this node's children. Must be called from the swing thread.
179+
* @param node the node to add as a child to this node
180+
*/
181+
protected synchronized void doAddNode(GTreeNode node) {
182+
children().add(node);
183+
node.setParent((GTreeNode) this);
184+
doFireNodeAdded(node);
185+
}
186+
187+
/**
188+
* Adds a node to this node's children at the given index and notifies the tree.
189+
* Must be called from the swing thread.
190+
* @param index the index at which to add the new node
191+
* @param node the node to add as a child to this node
192+
*/
193+
protected synchronized void doAddNode(int index, GTreeNode node) {
194+
List<GTreeNode> kids = children();
195+
int insertIndex = Math.min(kids.size(), index);
196+
kids.add(insertIndex, node);
197+
node.setParent((GTreeNode) this);
198+
doFireNodeAdded(node);
199+
}
200+
201+
/**
202+
* Removes the node from this node's children and notifies the tree. Must be called
203+
* from the swing thread.
204+
* @param node the node to remove
205+
*/
206+
protected synchronized void doRemoveNode(GTreeNode node) {
207+
List<GTreeNode> kids = children();
208+
int index = kids.indexOf(node);
209+
if (index >= 0) {
210+
kids.remove(index);
211+
node.setParent(null);
212+
doFireNodeRemoved(node, index);
213+
}
214+
}
215+
216+
/**
217+
* Adds the given nodes to this node's children. Must be called from the swing thread.
218+
* @param nodes the nodes to add to the children this node
219+
*/
220+
protected synchronized void doAddNodes(List<GTreeNode> nodes) {
221+
for (GTreeNode node : nodes) {
222+
node.setParent((GTreeNode) this);
223+
}
224+
children().addAll(nodes);
225+
doFireNodeStructureChanged();
226+
}
227+
145228
/**
146229
* Creates a clone of this node. The clone should contain a shallow copy of all the node's
147230
* attributes except that the parent and children are null.
@@ -157,7 +240,6 @@ public GTreeNode clone() throws CloneNotSupportedException {
157240
}
158241

159242
public void dispose() {
160-
161243
List<GTreeNode> oldChildren;
162244
synchronized (this) {
163245
oldChildren = children;
@@ -242,4 +324,44 @@ private boolean isInProgress(List<GTreeNode> childList) {
242324
return false;
243325
}
244326

327+
protected void doFireNodeAdded(GTreeNode newNode) {
328+
assertSwing();
329+
GTree tree = getTree();
330+
if (tree != null) {
331+
tree.getModel().fireNodeAdded((GTreeNode) this, newNode);
332+
tree.refilterLater(newNode);
333+
}
334+
}
335+
336+
protected void doFireNodeRemoved(GTreeNode removedNode, int index) {
337+
assertSwing();
338+
GTree tree = getTree();
339+
if (tree != null) {
340+
tree.getModel().fireNodeRemoved((GTreeNode) this, removedNode, index);
341+
}
342+
}
343+
344+
protected void doFireNodeStructureChanged() {
345+
assertSwing();
346+
GTree tree = getTree();
347+
if (tree != null) {
348+
tree.getModel().fireNodeStructureChanged((GTreeNode) this);
349+
tree.refilterLater();
350+
}
351+
}
352+
353+
protected void doFireNodeChanged() {
354+
assertSwing();
355+
GTree tree = getTree();
356+
if (tree != null) {
357+
tree.getModel().fireNodeDataChanged((GTreeNode) this);
358+
tree.refilterLater();
359+
}
360+
}
361+
362+
private void assertSwing() {
363+
SystemUtilities
364+
.assertThisIsTheSwingThread("tree events must be called from the AWT thread");
365+
}
366+
245367
}

0 commit comments

Comments
 (0)