GP-5258 - Fixed a Symbol Tree bug that caused an empty tree when showing the tree with a filter applied

This commit is contained in:
dragonmacher 2025-01-13 16:41:36 -05:00
parent d87b514baa
commit 9ae412a0b3
5 changed files with 60 additions and 39 deletions

View file

@ -150,10 +150,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
private SymbolGTree createTree(SymbolTreeRootNode rootNode) { private SymbolGTree createTree(SymbolTreeRootNode rootNode) {
if (tree != null) { if (tree != null) {
GTreeNode oldRootNode = tree.getModelRoot();
tree.setRootNode(rootNode); tree.setRootNode(rootNode);
oldRootNode.removeAll();// assist in cleanup a bit
return tree; return tree;
} }

View file

@ -253,6 +253,10 @@ abstract class CoreGTreeNode implements Cloneable {
} }
public void dispose() { public void dispose() {
disconnect(true);
}
void disconnect(boolean dispose) {
List<GTreeNode> oldChildren; List<GTreeNode> oldChildren;
synchronized (this) { synchronized (this) {
oldChildren = children; oldChildren = children;
@ -262,7 +266,10 @@ abstract class CoreGTreeNode implements Cloneable {
if (oldChildren != null) { if (oldChildren != null) {
for (GTreeNode node : oldChildren) { for (GTreeNode node : oldChildren) {
node.dispose(); node.disconnect(dispose);
if (dispose) {
node.dispose();
}
} }
oldChildren.clear(); oldChildren.clear();
} }
@ -270,23 +277,13 @@ abstract class CoreGTreeNode implements Cloneable {
/** /**
* This is used to dispose filtered "clone" nodes. When a filter is applied to the tree, * This is used to dispose filtered "clone" nodes. When a filter is applied to the tree,
* the nodes that matched are "shallow" cloned, so when the filter is removed, we don't * the nodes that matched are "shallow" cloned. This is effectively a shallow dispose that will
* want to do a full dispose on the nodes, just clean up the parent-child references. * clean up any children and disconnect this node from the true, but will *not* call dispose(),
* which would affect the original node from which this node was cloned.
*/ */
final void disposeClones() { final void disposeClone() {
List<GTreeNode> oldChildren; // Do not dispose, as that will affect the clone's source too
synchronized (this) { disconnect(false);
oldChildren = children;
children = null;
parent = null;
}
if (oldChildren != null) {
for (GTreeNode node : oldChildren) {
node.disposeClones();
}
oldChildren.clear();
}
} }
/** /**

View file

@ -285,12 +285,12 @@ public class GTree extends JPanel implements BusyListener {
realModelRootNode.dispose(); realModelRootNode.dispose();
} }
// if there is a filter applied, clean up the filtered nodes. Note that filtered nodes // If there is a filter applied, clean up the filtered nodes. Note that filtered nodes
// are expected to be shallow clones of the model nodes, so we don't want to call full // are expected to be shallow clones of the model nodes, so we don't want to call full
// dispose on the filtered nodes because internal clean-up should happen when the // dispose on the filtered nodes because internal clean-up should happen when the
// model nodes are disposed. The disposeClones just breaks the child-parent ties. // model nodes are disposed. The disposeClone() just breaks the child-parent ties.
if (realViewRootNode != null && realViewRootNode != realModelRootNode) { if (realViewRootNode != null && realViewRootNode != realModelRootNode) {
realViewRootNode.disposeClones(); realViewRootNode.disposeClone();
} }
filterProvider.dispose(); filterProvider.dispose();
@ -847,23 +847,40 @@ public class GTree extends JPanel implements BusyListener {
Swing.runIfSwingOrRunLater(() -> { Swing.runIfSwingOrRunLater(() -> {
worker.clearAllJobs(); worker.clearAllJobs();
rootNode.setParent(rootParent); rootNode.setParent(rootParent);
GTreeNode oldModelRoot = realModelRootNode;
GTreeNode oldViewRoot = realViewRootNode;
realModelRootNode = rootNode; realModelRootNode = rootNode;
realViewRootNode = rootNode; realViewRootNode = rootNode;
GTreeNode oldRoot; swingSetModelRootNode(rootNode);
oldRoot = swingSetModelRootNode(rootNode);
oldRoot.dispose(); disposeOldRoots(oldModelRoot, oldViewRoot);
if (filter != null) { if (filter != null) {
filterUpdateManager.update(); filterUpdateManager.update();
} }
}); });
} }
private void disposeOldRoots(GTreeNode oldModelRoot, GTreeNode oldViewRoot) {
if (oldModelRoot != null && oldModelRoot != realModelRootNode) {
oldModelRoot.dispose();
}
// If there is a filter applied, clean up the filtered nodes. Note that filtered nodes
// are expected to be shallow clones of the model nodes, so we don't want to call full
// dispose on the filtered nodes because internal clean-up should happen when the
// model nodes are disposed. The disposeClone() just breaks the child-parent ties.
if (oldViewRoot != null && oldViewRoot != realViewRootNode) {
oldViewRoot.disposeClone(); // safe to call even if we disposed the same node above
}
}
void swingSetFilteredRootNode(GTreeNode filteredRootNode) { void swingSetFilteredRootNode(GTreeNode filteredRootNode) {
filteredRootNode.setParent(rootParent); filteredRootNode.setParent(rootParent);
realViewRootNode = filteredRootNode; realViewRootNode = filteredRootNode;
GTreeNode currentRoot = swingSetModelRootNode(filteredRootNode); GTreeNode currentRoot = swingSetModelRootNode(filteredRootNode);
if (currentRoot != realModelRootNode) { if (currentRoot != realModelRootNode) {
currentRoot.disposeClones(); currentRoot.disposeClone();
} }
} }
@ -871,7 +888,7 @@ public class GTree extends JPanel implements BusyListener {
realViewRootNode = realModelRootNode; realViewRootNode = realModelRootNode;
GTreeNode currentRoot = swingSetModelRootNode(realModelRootNode); GTreeNode currentRoot = swingSetModelRootNode(realModelRootNode);
if (currentRoot != realModelRootNode && currentRoot != null) { if (currentRoot != realModelRootNode && currentRoot != null) {
currentRoot.disposeClones(); currentRoot.disposeClone();
} }
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -53,4 +53,9 @@ class GTreeRootParentNode extends GTreeNode {
public boolean isLeaf() { public boolean isLeaf() {
return false; return false;
} }
@Override
public String toString() {
return "GTree Root Parent Node";
}
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -125,20 +125,25 @@ public class GTreeModel implements TreeModel {
"GTreeModel.fireNodeStructuredChanged() must be " + "called from the AWT thread"); "GTreeModel.fireNodeStructuredChanged() must be " + "called from the AWT thread");
// If the tree is filtered and this is called on the original node, we have to // If the tree is filtered and this is called on the original node, we have to
// translate the node to a view node (one the jtree knows). // translate the node to a view node (one the tree knows).
GTreeNode viewNode = convertToViewNode(changedNode); GTreeNode viewNode = convertToViewNode(changedNode);
if (viewNode == null) { if (viewNode == null) {
return; return;
} }
if (viewNode != changedNode) { if (viewNode != changedNode) {
// This means we are filtered and since the original node's children are invalid, // The only time this can happen is when the tree is filtered. In this case, the
// then the filtered children are invalid also. So clear out the children by // view node will be a clone of the changed node. We need to update the cloned
// setting an empty list as we don't want to trigger the node to regenerate its // node to signal that the children have changed. If we set the children to null,
// children which happens if you set the children to null. // then they will get reloaded when we fire the event. Instead, if we set the
// children to the empty list, the node will simply think there are no children and
// it will not get reloaded.
// //
// This won't cause a second event to the jtree because we are protected // After the events have been fired, there will eventually be a refilter operation
// by the isFiringNodeStructureChanged variable // to update the view node with the correct children for the active filter.
//
// Note: this won't cause a second event to the tree because we are protected by
// the isFiringNodeStructureChanged flag at the top of this method.
viewNode.setChildren(Collections.emptyList()); viewNode.setChildren(Collections.emptyList());
} }