blob: d72f8879d5c7e6f64cf9d51d50e34a4b5baec8d7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.explorer.view;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.netbeans.junit.NbTestCase;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.RequestProcessor;
/**
* Tests for class BeanTreeViewTest
*/
public class BeanTreeViewTest extends NbTestCase {
public static Test suite() {
return GraphicsEnvironment.isHeadless() ? new TestSuite() : new TestSuite(BeanTreeViewTest.class);
}
private static final int NO_OF_NODES = 3;
static {
System.setProperty("netbeans.debug.heap", "no wait");
}
public BeanTreeViewTest(String name) {
super(name);
}
public void testOnlyChildRemoveCausesSelectionOfParent() throws Throwable {
ExplorerManager em = doChildRemovalTest("one", "one");
final List<Node> arr = Arrays.asList(em.getSelectedNodes());
assertEquals("One selected: " + arr, 1, arr.size());
assertEquals("Root selected", em.getRootContext(), arr.get(0));
}
public void testFirstChildRemovalCausesSelectionOfSibling() throws Throwable {
doChildRemovalTest("foo");
}
public void testSecondChildRemovalCausesSelectionOfSibling() throws Throwable {
doChildRemovalTest("bar");
}
public void testThirdChildRemovalCausesSelectionOfSibling() throws Throwable {
doChildRemovalTest("bla");
}
private static Object holder;
private void doChildRemovalTest(final String name) throws Throwable {
doChildRemovalTest(name, "foo", "bar", "bla");
}
private ExplorerManager doChildRemovalTest(final String name, final String... childrenNames) throws Throwable {
class AWTTst implements Runnable {
AbstractNode root = new AbstractNode(new Children.Array());
Node[] children;
{
List<Node> arr = new ArrayList<Node>();
for (String s : childrenNames) {
arr.add(createLeaf(s));
}
children = arr.toArray(new Node[0]);
}
Panel p;
BeanTreeView btv;
JFrame f;
JTree tree;
Node operateOn;
private void initUI() {
if (p == null) {
p = new Panel();
btv = new BeanTreeView();
f = new JFrame();
tree = btv.tree;
root.setName("test root");
root.getChildren().add(children);
p.getExplorerManager().setRootContext(root);
p.add(BorderLayout.CENTER, btv);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(BorderLayout.CENTER, p);
f.setVisible(true);
}
}
@Override
public void run() {
initUI();
for (int i = 0;; i++) {
if (name.equals(children[i].getName())) {
// this should select a sibling of the removed node
operateOn = children[i];
break;
}
}
try {
p.getExplorerManager().setSelectedNodes(new Node[]{operateOn});
} catch (PropertyVetoException e) {
fail("Unexpected PropertyVetoException from ExplorerManager.setSelectedNodes()");
}
TreePath[] paths = tree.getSelectionPaths();
assertNotNull("Before removal: one node should be selected, but there are none.", paths);
assertEquals("Before removal: one node should be selected, but there are none.", 1, paths.length);
assertEquals("Before removal: one node should be selected, but there are none.", operateOn, Visualizer.findNode(paths[0].getLastPathComponent()));
assertEquals("Before removal: one node should be selected, but there are none.", operateOn, Visualizer.findNode(tree.getAnchorSelectionPath().getLastPathComponent()));
// this should select a sibling of the removed node
root.getChildren().remove(new Node[]{operateOn});
assertNotNull("After removal: one node should be selected, but there are none.", tree.getSelectionPath());
children = null;
}
public void tryGc() {
WeakReference<Node> wref = new WeakReference<Node>(operateOn);
operateOn = null;
assertGC("Node should be released.", wref);
}
}
AWTTst awt = new AWTTst();
holder = awt;
try {
SwingUtilities.invokeAndWait(awt);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
awt.tryGc();
return awt.p.getExplorerManager();
}
public void testOnlyChildDestroyedCausesSelectionOfParent () throws Throwable {
// node.destroy called on the last selected node of the root node
ExplorerManager em = doChildDestroyTest ("one", Collections.singleton("one"), "one");
final List<Node> arr = Arrays.asList(em.getSelectedNodes());
assertEquals("One selected: " + arr, 1, arr.size());
assertEquals("Root selected", em.getRootContext(), arr.get(0));
}
public void testChildDestroyedMoreInSelection () throws Throwable {
// two children in selection, should not select parent
// the second child should be selected in the end
ExplorerManager em = doChildDestroyTest ("one", Arrays.asList("one", "two"), "one", "two");
final List<Node> arr = Arrays.asList(em.getSelectedNodes());
assertEquals("One node selected: " + arr, 1, arr.size());
assertEquals("second child selected", "two", arr.get(0).getName());
}
public void testChildDestroyedCausesNextChildSelected () throws Throwable {
// first selected child is destroyed => selection should move to the next child
ExplorerManager em = doChildDestroyTest ("one", Collections.singleton("one"), "one", "two");
final List<Node> arr = Arrays.asList(em.getSelectedNodes());
assertEquals("One selected: " + arr, 1, arr.size());
assertEquals("second child selected", "two", arr.get(0).getName());
}
private ExplorerManager doChildDestroyTest (final String name, final Collection<String> toSelect,
final String... childrenNames) throws Throwable {
class AWTTst implements Runnable {
Node[] children;
{
List<Node> arr = new ArrayList<Node>();
for (String s : childrenNames) {
arr.add(createLeaf(s));
}
children = arr.toArray(new Node[0]);
}
AbstractNode root = new AbstractNode(new RefreshableChildren());
Panel p;
BeanTreeView btv;
JFrame f;
JTree tree;
Node operateOn;
private void initUI() {
if (p == null) {
p = new Panel();
btv = new BeanTreeView();
f = new JFrame();
tree = btv.tree;
((RefreshableChildren) root.getChildren()).refreshKeys(childrenNames);
root.setName("test root");
p.getExplorerManager().setRootContext(root);
p.add(BorderLayout.CENTER, btv);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(BorderLayout.CENTER, p);
f.setVisible(true);
}
}
// children must be Children.Keys(lazy)
class RefreshableChildren extends Children.Keys<String> {
public RefreshableChildren () {
super(true);
}
@Override
protected Node[] createNodes (String key) {
Node n = null;
for (Node cand : children) {
if (cand.getName().equals(key)) {
n = cand;
break;
}
}
return new Node[] { n };
}
void refreshKeys (String[] keys) {
super.setKeys(keys);
}
}
@Override
public void run() {
initUI();
List<Node> selection = new ArrayList<Node>();
for (int i = 0; i < children.length; i++) {
if (name.equals(children[i].getName())) {
// this should select a sibling of the removed node
operateOn = children[i];
}
if (toSelect.contains(children[i].getName())) {
selection.add(children[i]);
}
}
try {
p.getExplorerManager().setSelectedNodes(selection.toArray(new Node[selection.size()]));
} catch (PropertyVetoException e) {
fail("Unexpected PropertyVetoException from ExplorerManager.setSelectedNodes()");
}
TreePath[] paths = tree.getSelectionPaths();
assertNotNull("Before removal: one node should be selected, but there are none.", paths);
assertEquals("Before removal: one node should be selected, but there are none.", selection.size(), paths.length);
if (selection.size() == 1) {
assertEquals("Before removal: one node should be selected, but there are none.", operateOn, Visualizer.findNode(paths[0].getLastPathComponent()));
assertEquals("Before removal: one node should be selected, but there are none.", operateOn, Visualizer.findNode(tree.getAnchorSelectionPath().getLastPathComponent()));
}
try {
// destroy the node
operateOn.destroy();
} catch (IOException ex) {
fail(ex.getMessage());
}
assertNotNull("After removal: one node should be selected, but there are none.", tree.getSelectionPath());
}
public void tryGc() throws InterruptedException, InvocationTargetException {
// somewhere around here it's time to remove the destroyed node from children
// the same as DataObject and FolderChildren work
EventQueue.invokeAndWait(new Runnable() {
@Override
public void run () {
List<String> newKeys = new ArrayList<String>(Arrays.asList(childrenNames));
newKeys.remove(name);
((RefreshableChildren) root.getChildren()).refreshKeys(newKeys.toArray(new String[newKeys.size()]));
children = null;
}
});
WeakReference<Node> wref = new WeakReference<Node>(operateOn);
operateOn = null;
RequestProcessor selectionProcessor;
int delay;
try {
Method getSelectionProcessorMethod = ExplorerManager.class.getDeclaredMethod("getSelectionProcessor");
getSelectionProcessorMethod.setAccessible(true);
selectionProcessor = (RequestProcessor) getSelectionProcessorMethod.invoke(null);
Field delayField = ExplorerManager.class.getDeclaredField("SELECTION_SYNC_DELAY");
delayField.setAccessible(true);
delay = (Integer) delayField.get(null);
// Wait for the task in selectionProcessor to start up:
Class ticTacClass = Class.forName(RequestProcessor.class.getName()+"$TickTac");
Field tickField = ticTacClass.getDeclaredField("TICK");
tickField.setAccessible(true);
Object tick = tickField.get(null);
if (tick != null) {
// Waiting for the tick queue to become empty (scheduled tasks removed)
Field queueField = ticTacClass.getDeclaredField("queue");
queueField.setAccessible(true);
Queue queue = (Queue) queueField.get(tick);
while (hasOwnersOf(tick, queue, selectionProcessor)) {
//System.err.println("Waiting for queue "+Integer.toHexString(System.identityHashCode(queue))+" to become empty... peek = "+queue.peek()+" is empty = "+queue.isEmpty());
Thread.sleep(2*delay); // Wait for the queue with scheduled tasks to become empty
}
}
} catch (IllegalAccessException ex) {
throw new InvocationTargetException(ex);
} catch (NoSuchMethodException ex) {
throw new InvocationTargetException(ex);
} catch (NoSuchFieldException ex) {
throw new InvocationTargetException(ex);
} catch (ClassNotFoundException ex) {
throw new InvocationTargetException(ex);
}
// Wait for the task, removed from tick queue, to be attached to a processor...
Thread.sleep(2*delay); // No reliable way :-(
// Wait for the task in selectionProcessor to finish
selectionProcessor.awaitTermination(60, TimeUnit.SECONDS);
assertGC("Node should be released.", wref);
}
private boolean hasOwnersOf(Object tick, Queue q, RequestProcessor rp) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException {
Class itemClass = Class.forName(RequestProcessor.class.getName()+"$Item");
Field ownerField = itemClass.getDeclaredField("owner");
ownerField.setAccessible(true);
synchronized (tick) {
for (Object o : q) {
Object ownerRP = ownerField.get(o);
if (rp.equals(ownerRP)) {
return true;
}
}
}
return false;
}
}
AWTTst awt = new AWTTst();
holder = awt;
try {
SwingUtilities.invokeAndWait(awt);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
awt.tryGc();
return awt.p.getExplorerManager();
}
public void testVisibleVisNodesAreNotGCed() throws InterruptedException, Throwable {
doTestVisibleVisNodesAreNotGCed(false);
}
public void testVisibleVisNodesAreNotGCedAfterCollapseExpand() throws InterruptedException, Throwable {
doTestVisibleVisNodesAreNotGCed(true);
}
public void doTestVisibleVisNodesAreNotGCed(final boolean collapseAndExpand) throws InterruptedException, Throwable {
class AWTTst implements Runnable {
AbstractNode root = new AbstractNode(new Children.Array());
Node[] children = {
createLeaf("foo"),
createLeaf("bar"),
createLeaf("bla")
};
VisualizerNode[] visNodes;
Panel p;
BeanTreeView btv;
JFrame f;
JTree tree;
private void initUI() {
if (p == null) {
p = new Panel();
btv = new BeanTreeView();
f = new JFrame();
tree = btv.tree;
root.setName("test root");
root.getChildren().add(children);
p.getExplorerManager().setRootContext(root);
p.add(BorderLayout.CENTER, btv);
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.getContentPane().add(BorderLayout.CENTER, p);
f.setVisible(true);
}
}
public void run() {
initUI();
try {
p.getExplorerManager().setSelectedNodes(children);
} catch (PropertyVetoException e) {
fail("Unexpected PropertyVetoException from ExplorerManager.setSelectedNodes()");
}
TreePath[] paths = tree.getSelectionPaths();
assertEquals("3 nodes should be selected.", 3, paths.length);
visNodes = new VisualizerNode[NO_OF_NODES];
for (int i = 0; i < visNodes.length; i++) {
visNodes[i] = (VisualizerNode) paths[i].getLastPathComponent();
}
try {
p.getExplorerManager().setSelectedNodes(new Node[0]);
} catch (PropertyVetoException e) {
fail("Unexpected PropertyVetoException from ExplorerManager.setSelectedNodes()");
}
paths = tree.getSelectionPaths();
if (paths != null && paths.length == 0) {
paths = null;
}
assertNull("Nothing should be selected: " + Arrays.toString(paths), paths);
if (collapseAndExpand) {
btv.collapseNode(root);
btv.expandNode(root);
}
}
public void checkNotGc() {
WeakReference<VisualizerNode> wref = new WeakReference<VisualizerNode>(visNodes[1]);
visNodes = null;
try {
EQFriendlyGC.assertGC("Node should be released.", wref);
} catch (AssertionFailedError e) {
return;
}
fail("should not be GC");
}
}
AWTTst awt = new AWTTst();
holder = awt;
try {
SwingUtilities.invokeAndWait(awt);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
awt.checkNotGc();
}
public void testSelectingRootDoesNotClearExploredContext() throws InterruptedException, Throwable {
class AWTTst implements Runnable {
AbstractNode root = new AbstractNode(new Children.Array());
VisualizerNode visNode;
Panel p;
BeanTreeView btv;
JFrame f;
JTree tree;
private void initUI() {
if (p == null) {
p = new Panel();
btv = new BeanTreeView();
f = new JFrame();
tree = btv.tree;
root.setName("test root");
p.getExplorerManager().setRootContext(root);
p.add(BorderLayout.CENTER, btv);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(BorderLayout.CENTER, p);
f.setVisible(true);
}
}
@Override
public void run() {
initUI();
try {
btv.selectionChanged(new Node[] { root }, p.getExplorerManager());
} catch (PropertyVetoException ex) {
fail(ex.getMessage());
}
assertSame("Root is explored", root, p.getExplorerManager().getExploredContext());
}
}
AWTTst awt = new AWTTst();
holder = awt;
try {
SwingUtilities.invokeAndWait(awt);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
public void testVisibleCollapsedNodesAreGCed() throws InterruptedException, Throwable {
class AWTTst implements Runnable {
AbstractNode root = new AbstractNode(new Children.Array());
Node[] children = {
createLeaf("foo"),
createLeaf("bar"),
createLeaf("bla")
};
VisualizerNode visNode;
Panel p;
BeanTreeView btv;
JFrame f;
JTree tree;
private void initUI() {
if (p == null) {
p = new Panel();
btv = new BeanTreeView();
f = new JFrame();
tree = btv.tree;
root.setName("test root");
root.getChildren().add(children);
p.getExplorerManager().setRootContext(root);
p.add(BorderLayout.CENTER, btv);
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.getContentPane().add(BorderLayout.CENTER, p);
f.setVisible(true);
}
}
public void run() {
initUI();
try {
p.getExplorerManager().setSelectedNodes(new Node[] {children[0]});
} catch (PropertyVetoException e) {
fail("Unexpected PropertyVetoException from ExplorerManager.setSelectedNodes()");
}
TreePath[] paths = tree.getSelectionPaths();
assertEquals("one node should be selected.", 1, paths.length);
visNode = (VisualizerNode) paths[0].getLastPathComponent();
try {
p.getExplorerManager().setSelectedNodes(new Node[0]);
} catch (PropertyVetoException e) {
fail("Unexpected PropertyVetoException from ExplorerManager.setSelectedNodes()");
}
paths = tree.getSelectionPaths();
if (paths != null && paths.length == 0) {
paths = null;
}
assertNull("Nothing should be selected: " + Arrays.toString(paths), paths);
btv.collapseNode(children[0].getParentNode());
}
public void checkGc() {
WeakReference<VisualizerNode> wref = new WeakReference<VisualizerNode>(visNode);
visNode = null;
assertGC("Collapsed - should be GCed.", wref);
}
}
AWTTst awt = new AWTTst();
holder = awt;
try {
SwingUtilities.invokeAndWait(awt);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
awt.checkGc();
}
private static Node createLeaf(String name) {
AbstractNode n = new AbstractNode(Children.LEAF);
n.setName(name);
return n;
}
private static class Panel extends JPanel
implements ExplorerManager.Provider {
private ExplorerManager em = new ExplorerManager();
public ExplorerManager getExplorerManager() {
return em;
}
}
}