1 | /**
|
---|
2 | *#########################################################################
|
---|
3 | *
|
---|
4 | * A component of the Gatherer application, part of the Greenstone digital
|
---|
5 | * library suite from the New Zealand Digital Library Project at the
|
---|
6 | * University of Waikato, New Zealand.
|
---|
7 | *
|
---|
8 | * Author: John Thompson, Greenstone Digital Library, University of Waikato
|
---|
9 | *
|
---|
10 | * Copyright (C) 1999 New Zealand Digital Library Project
|
---|
11 | *
|
---|
12 | * This program is free software; you can redistribute it and/or modify
|
---|
13 | * it under the terms of the GNU General Public License as published by
|
---|
14 | * the Free Software Foundation; either version 2 of the License, or
|
---|
15 | * (at your option) any later version.
|
---|
16 | *
|
---|
17 | * This program is distributed in the hope that it will be useful,
|
---|
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
20 | * GNU General Public License for more details.
|
---|
21 | *
|
---|
22 | * You should have received a copy of the GNU General Public License
|
---|
23 | * along with this program; if not, write to the Free Software
|
---|
24 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
---|
25 | *########################################################################
|
---|
26 | */
|
---|
27 | package org.greenstone.gatherer.util;
|
---|
28 |
|
---|
29 | import java.lang.Runnable;
|
---|
30 | import javax.swing.SwingUtilities;
|
---|
31 | import javax.swing.tree.*;
|
---|
32 | import org.greenstone.gatherer.DebugStream;
|
---|
33 | import org.greenstone.gatherer.metadata.FilenameEncoding;
|
---|
34 | import org.greenstone.gatherer.collection.CollectionTreeNode;
|
---|
35 |
|
---|
36 | /** Due to the TreeModel objects not having any synchronization, certain assumptions, such as the model state remaining constant during a repaint, don't always hold - especially given that I'm changing the tree model on a different thread. In order to get around this I will use the latest swing paradigm wherein you flag a section of code to be executed by the AWT GUI Event queue, as soon as other gui tasks have finished. This way I shouldn't have tree redraws throwing NPEs because the array size of the children of a certain node has changed -while- the repaint call was made, i.e. repaint() calls getChildCount() = 13, removeNodeFromParent() called, repaint calls getChildAt(12) = ArrayIndexOutOfBoundsException.
|
---|
37 | * @author John Thompson, Greenstone Digital Library, University of Waikato
|
---|
38 | * @version 2.3c
|
---|
39 | */
|
---|
40 | public class SynchronizedTreeModelTools {
|
---|
41 | /** Adds an insertNodeInto model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above. */
|
---|
42 | static final public Runnable insertNodeInto(DefaultTreeModel model, MutableTreeNode parent, MutableTreeNode target_node) {
|
---|
43 | return insertNodeInto(model, parent, target_node, true);
|
---|
44 | }
|
---|
45 |
|
---|
46 | static final public Runnable insertNodeInto(final DefaultTreeModel model, final MutableTreeNode parent, final MutableTreeNode target_node, final boolean wait_allowed) {
|
---|
47 | final Runnable doInsertNodeInto = new Runnable() {
|
---|
48 | public void run() {
|
---|
49 | ///ystem.err.print("Running task... ");
|
---|
50 | DebugStream.println("insertNodeInto(" + model + ", " + parent + ", " + target_node + ", " + wait_allowed);
|
---|
51 | int index = -1;
|
---|
52 | int pos = 0;
|
---|
53 | while(index == -1 && pos < parent.getChildCount()) {
|
---|
54 | TreeNode node = parent.getChildAt(pos);
|
---|
55 | int result = 0;
|
---|
56 | ///ystem.err.println("Compare " + target_node + " to " + node);
|
---|
57 | if((target_node.isLeaf() && node.isLeaf()) || (!target_node.isLeaf() && !node.isLeaf())) {
|
---|
58 | result = target_node.toString().toLowerCase().compareTo(node.toString().toLowerCase());
|
---|
59 | }
|
---|
60 | else if(target_node.isLeaf()) {
|
---|
61 | result = 1;
|
---|
62 | }
|
---|
63 | else {
|
---|
64 | result = -1;
|
---|
65 | }
|
---|
66 | if(result > 0) {
|
---|
67 | ///ystem.err.println("Keep searching...");
|
---|
68 | pos++;
|
---|
69 | }
|
---|
70 | else {
|
---|
71 | ///ystem.err.println("Found!");
|
---|
72 | index = pos;
|
---|
73 | }
|
---|
74 | }
|
---|
75 | if(index == -1) {
|
---|
76 | index = parent.getChildCount();
|
---|
77 | }
|
---|
78 | model.insertNodeInto(target_node, parent, index);
|
---|
79 | }
|
---|
80 | };
|
---|
81 | ///ystem.err.print("Queuing Task... ");
|
---|
82 | try {
|
---|
83 | if(wait_allowed && !SwingUtilities.isEventDispatchThread()) {
|
---|
84 | ///ystem.err.print("In another thread - invoke and wait... ");
|
---|
85 | SwingUtilities.invokeAndWait(doInsertNodeInto);
|
---|
86 | }
|
---|
87 | else {
|
---|
88 | ///ystem.err.print("In Event Thread or wait not allowed - invoke later... ");
|
---|
89 | SwingUtilities.invokeLater(doInsertNodeInto);
|
---|
90 | }
|
---|
91 | }
|
---|
92 | catch (Exception exception) {
|
---|
93 | DebugStream.printStackTrace(exception);
|
---|
94 | }
|
---|
95 | ///ystem.err.print("Added Task... ");
|
---|
96 | return doInsertNodeInto;
|
---|
97 | }
|
---|
98 |
|
---|
99 |
|
---|
100 | /** Adds a removeNodeFromParent model update onto the AWT Event queue. This gets around the lack of synchronization illustrated above.
|
---|
101 | * @param model The <strong>GTreeModel</strong> we want to remove the node from.
|
---|
102 | * @param target_node The <strong>GTreeNode</strong> to remove.
|
---|
103 | */
|
---|
104 | static final public void removeNodeFromParent(final DefaultTreeModel model, final MutableTreeNode target_node) {
|
---|
105 | ///ystem.err.println("Remove " + target_node + " from parent in model " + model);
|
---|
106 | final Runnable doRemoveNodeFromParent = new Runnable() {
|
---|
107 | public void run() {
|
---|
108 | // If we're dealing with a collection tree node, it may have
|
---|
109 | // gs.FilenameEncoding assigned, so we remove its entry from the map.
|
---|
110 | // Needs to be done here because the tree is constantly changing
|
---|
111 | // when nodes are being removed, renamed and deleted, and this
|
---|
112 | // affects lookup queries sent to the map.
|
---|
113 | // Don't need to do a recursive reset on this coltreenode, because
|
---|
114 | // Delete/Move/Rename FileJobs were created for *each* node
|
---|
115 | if(target_node instanceof CollectionTreeNode) {
|
---|
116 | CollectionTreeNode colNode = (CollectionTreeNode)target_node;
|
---|
117 | FilenameEncoding.map.remove(colNode.getURLEncodedFilePath());
|
---|
118 | }
|
---|
119 |
|
---|
120 | model.removeNodeFromParent(target_node);
|
---|
121 | }
|
---|
122 | };
|
---|
123 | try {
|
---|
124 | //SwingUtilities.invokeLater(doRemoveNodeFromParent);
|
---|
125 | SwingUtilities.invokeAndWait(doRemoveNodeFromParent);
|
---|
126 | }
|
---|
127 | catch (Exception exception) {
|
---|
128 | DebugStream.printStackTrace(exception);
|
---|
129 | }
|
---|
130 | // If we've thrown an error because we tried to invoke the runnable task and wait, when we are in the AWTEvent thread already, then try agin but with an invoke later.
|
---|
131 | catch (java.lang.Error error) {
|
---|
132 | if(error.toString().equals("java.lang.Error: Cannot call invokeAndWait from the event dispatcher thread")) {
|
---|
133 | SwingUtilities.invokeLater(doRemoveNodeFromParent);
|
---|
134 | }
|
---|
135 | }
|
---|
136 | }
|
---|
137 | }
|
---|