18
18
import java .util .*;
19
19
import java .util .concurrent .CopyOnWriteArrayList ;
20
20
21
+ import javax .swing .JTree ;
22
+
21
23
import docking .widgets .tree .internal .InProgressGTreeNode ;
22
24
import ghidra .util .Swing ;
25
+ import ghidra .util .SystemUtilities ;
23
26
24
27
/**
25
28
* This class exists to help prevent threading errors in {@link GTreeNode} and subclasses,
26
29
* by privately maintaining synchronous access to the parent and children of a node.
27
30
* <P>
28
31
* This implementation uses a {@link CopyOnWriteArrayList} to store its children. The theory is
29
32
* 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
33
36
* addNode many times. But in that case, the tree should be using Lazy or
34
37
* SlowLoading nodes which always load into another list first and all the children will be set
35
38
* on a node in a single operation.
36
39
* <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}.
42
44
* <P>
43
45
* This class uses synchronization to assure that the parent/children relationship is stable across
44
46
* 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
46
48
* ancestors. To facilitate this strategy, the {@link #getParent()} is not synchronized, but it
47
49
* 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
+ *
48
61
*/
49
62
abstract class CoreGTreeNode implements Cloneable {
50
63
// the parent is volatile to facilitate the synchronization strategy (see comments above)
@@ -118,6 +131,25 @@ protected final List<GTreeNode> children() {
118
131
*/
119
132
protected abstract List <GTreeNode > generateChildren ();
120
133
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
+ */
121
153
protected synchronized void doSetChildren (List <GTreeNode > childList ) {
122
154
List <GTreeNode > oldChildren = children ;
123
155
children = null ;
@@ -142,6 +174,57 @@ protected synchronized void doSetChildren(List<GTreeNode> childList) {
142
174
}
143
175
}
144
176
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
+
145
228
/**
146
229
* Creates a clone of this node. The clone should contain a shallow copy of all the node's
147
230
* attributes except that the parent and children are null.
@@ -157,7 +240,6 @@ public GTreeNode clone() throws CloneNotSupportedException {
157
240
}
158
241
159
242
public void dispose () {
160
-
161
243
List <GTreeNode > oldChildren ;
162
244
synchronized (this ) {
163
245
oldChildren = children ;
@@ -242,4 +324,44 @@ private boolean isInProgress(List<GTreeNode> childList) {
242
324
return false ;
243
325
}
244
326
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
+
245
367
}
0 commit comments