[6096] | 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.*;
|
---|
[8236] | 32 | import org.greenstone.gatherer.DebugStream;
|
---|
[23433] | 33 | import org.greenstone.gatherer.metadata.FilenameEncoding;
|
---|
| 34 | import org.greenstone.gatherer.collection.CollectionTreeNode;
|
---|
[8236] | 35 |
|
---|
[6096] | 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 |
|
---|
[6622] | 46 | static final public Runnable insertNodeInto(final DefaultTreeModel model, final MutableTreeNode parent, final MutableTreeNode target_node, final boolean wait_allowed) {
|
---|
[6096] | 47 | final Runnable doInsertNodeInto = new Runnable() {
|
---|
| 48 | public void run() {
|
---|
| 49 | ///ystem.err.print("Running task... ");
|
---|
[8236] | 50 | DebugStream.println("insertNodeInto(" + model + ", " + parent + ", " + target_node + ", " + wait_allowed);
|
---|
[6096] | 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()) {
|
---|
[11084] | 61 | result = 1;
|
---|
[6096] | 62 | }
|
---|
| 63 | else {
|
---|
[11084] | 64 | result = -1;
|
---|
[6096] | 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 | }
|
---|
[6212] | 92 | catch (Exception exception) {
|
---|
[8236] | 93 | DebugStream.printStackTrace(exception);
|
---|
[6096] | 94 | }
|
---|
| 95 | ///ystem.err.print("Added Task... ");
|
---|
| 96 | return doInsertNodeInto;
|
---|
| 97 | }
|
---|
[8577] | 98 |
|
---|
| 99 |
|
---|
[6096] | 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() {
|
---|
[23433] | 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);
|
---|
[6096] | 121 | }
|
---|
| 122 | };
|
---|
| 123 | try {
|
---|
[6212] | 124 | //SwingUtilities.invokeLater(doRemoveNodeFromParent);
|
---|
[6096] | 125 | SwingUtilities.invokeAndWait(doRemoveNodeFromParent);
|
---|
| 126 | }
|
---|
[6212] | 127 | catch (Exception exception) {
|
---|
[8236] | 128 | DebugStream.printStackTrace(exception);
|
---|
[6096] | 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 | }
|
---|