| /* |
| * 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; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.VetoableChangeListener; |
| import java.lang.ref.Reference; |
| import java.lang.ref.WeakReference; |
| import java.util.LinkedList; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import org.netbeans.junit.NbTestCase; |
| import org.netbeans.junit.RandomlyFails; |
| import org.openide.nodes.AbstractNode; |
| import org.openide.nodes.Children; |
| import org.openide.nodes.Node; |
| |
| /** |
| * |
| * @author Jaroslav Tulach |
| */ |
| public class ExplorerManagerTest extends NbTestCase |
| implements PropertyChangeListener { |
| private ExplorerManager em; |
| private Keys keys; |
| private Node root; |
| private LinkedList<PropertyChangeEvent> events; |
| |
| static { |
| ExplorerManager.SCHEDULE_REMOVE_ASYNCH = false; |
| } |
| |
| public ExplorerManagerTest(String testName) { |
| super(testName); |
| } |
| |
| /** This code is supposed to run in AWT test. |
| */ |
| @Override |
| protected boolean runInEQ() { |
| return true; |
| } |
| |
| @Override |
| protected Level logLevel() { |
| return Level.FINE; |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| em = new ExplorerManager(); |
| keys = new Keys(); |
| root = new AbstractNode(keys); |
| Node[] justAsk = root.getChildren().getNodes(true); |
| em.setRootContext(root); |
| events = new LinkedList<PropertyChangeEvent>(); |
| } |
| |
| public void propertyChange(PropertyChangeEvent ev) { |
| assertFalse("No read lock held", Children.MUTEX.isReadAccess()); |
| assertFalse("No write lock held", Children.MUTEX.isWriteAccess()); |
| |
| events.add(ev); |
| } |
| |
| public void testScalingSelectionChange() throws Exception { |
| |
| int [] sizes = new int[] { 10, 100, 1000, 10000 }; |
| for (int i = 0; i<sizes.length; i++) { |
| int count = computeEqualsCount(sizes[i]); |
| assertTrue("n*log(n) complexity of selection change", |
| count < sizes[i] * Math.log(sizes[i])); |
| } |
| } |
| |
| private int computeEqualsCount(int size) throws Exception { |
| Integer [] arr = new Integer[size]; |
| for (int i = 0; i<arr.length; i++) { |
| arr[i] = i; |
| } |
| ExplorerManager myem = new ExplorerManager(); |
| IntKeys mykeys = new IntKeys(); |
| mykeys.keys(arr); |
| Node myroot = new AbstractNode(mykeys); |
| myem.setRootContext(myroot); |
| |
| Node[] ch = myroot.getChildren().getNodes(true); |
| |
| Node[] neuCh = new Node[ch.length-1]; |
| System.arraycopy(ch, 0, neuCh, 0, neuCh.length); |
| myem.setSelectedNodes(neuCh); |
| IntKeys.eqCounter = 0; |
| myem.setSelectedNodes(ch); |
| myem.setSelectedNodes(neuCh); |
| |
| return IntKeys.eqCounter; |
| } |
| |
| public void testNormalSelectionChange() throws Exception { |
| final Node a = keys.key("a key"); |
| |
| em.addPropertyChangeListener(this); |
| |
| em.setSelectedNodes(new Node[] { a }); |
| Node[] arr = em.getSelectedNodes(); |
| assertEquals("One selected", 1, arr.length); |
| assertEquals("A is there", a, arr[0]); |
| |
| |
| assertEquals("One event", 1, events.size()); |
| PropertyChangeEvent ev = (PropertyChangeEvent)events.removeFirst(); |
| assertEquals("Name is good", ExplorerManager.PROP_SELECTED_NODES, ev.getPropertyName()); |
| |
| events.clear(); |
| em.setSelectedNodes(new Node[] { a }); |
| assertEquals("No change: " + events, 0, events.size()); |
| } |
| |
| public void testCannotSetNodesNotUnderTheRoot() throws Exception { |
| final Node a = new AbstractNode(Children.LEAF); |
| em.setSelectedNodes(new Node[]{a}); |
| assertEquals(0, em.getSelectedNodes().length); |
| } |
| |
| |
| public void testSetNodesSurviveChangeOfNodes() throws Exception { |
| final Node a = keys.key("toRemove"); |
| |
| class ChangeTheSelectionInMiddleOfMethod implements VetoableChangeListener { |
| public int cnt; |
| |
| public void vetoableChange(PropertyChangeEvent evt) { |
| cnt++; |
| keys.keys(new String[0]); |
| } |
| } |
| |
| ChangeTheSelectionInMiddleOfMethod list = new ChangeTheSelectionInMiddleOfMethod(); |
| em.addVetoableChangeListener(list); |
| |
| em.setSelectedNodes(new Node[] { a }); |
| |
| assertEquals("Vetoable listener called", 1, list.cnt); |
| assertEquals("Node is dead", null, a.getParentNode()); |
| |
| // handling of removed nodes is done asynchronously |
| em.waitFinished(); |
| |
| Node[] arr = em.getSelectedNodes(); |
| assertEquals("No nodes can be selected", 0, arr.length); |
| } |
| |
| public void testCannotVetoSetToEmptySelection() throws Exception { |
| final Node a = keys.key("toRemove"); |
| |
| em.setSelectedNodes(new Node[] { a }); |
| |
| class NeverCalledVeto implements VetoableChangeListener { |
| public int cnt; |
| |
| public void vetoableChange(PropertyChangeEvent evt) { |
| cnt++; |
| keys.keys(new String[0]); |
| } |
| } |
| |
| NeverCalledVeto list = new NeverCalledVeto(); |
| em.addVetoableChangeListener(list); |
| |
| em.setSelectedNodes(new Node[0]); |
| |
| assertEquals("Veto not called", 0, list.cnt); |
| Node[] arr = em.getSelectedNodes(); |
| assertEquals("No nodes can be selected", 0, arr.length); |
| } |
| |
| @RandomlyFails // NB-Core-Build #1110 |
| public void testGarbageCollectOfExploreredContextIssue124712() throws Exception { |
| class K extends Children.Keys<String> { |
| public void keys(String... keys) { |
| setKeys(keys); |
| } |
| |
| @Override |
| protected Node[] createNodes(String key) { |
| AbstractNode an = new AbstractNode(new K()); |
| an.setName(key); |
| return new Node[] { an }; |
| } |
| } |
| |
| K myKeys = new K(); |
| myKeys.keys("a", "b", "c"); |
| AbstractNode myRoot = new AbstractNode(myKeys); |
| myRoot.setName("root"); |
| em.setRootContext(myRoot); |
| |
| Node b = myRoot.getChildren().getNodes()[1]; |
| assertEquals("b", b.getDisplayName()); |
| ((K)b.getChildren()).keys("1", "2", "3"); |
| |
| em.setExploredContext(b); |
| em.setSelectedNodes(new Node[] { b.getChildren().getNodes()[2] }); |
| |
| Reference<?> refB = new WeakReference<Object>(b); |
| Reference<?> ref1 = new WeakReference<Object>(b.getChildren().getNodes()[0]); |
| Reference<?> ref2 = new WeakReference<Object>(b.getChildren().getNodes()[1]); |
| Reference<?> ref3 = new WeakReference<Object>(b.getChildren().getNodes()[2]); |
| |
| myKeys.keys(); |
| b = null; |
| |
| ref = em; |
| em.waitFinished(); |
| |
| assertEquals("Explored context is the root context", myRoot, em.getExploredContext()); |
| assertEquals("No selected nodes", 0, em.getSelectedNodes().length); |
| |
| assertGC("1", ref1); |
| assertGC("2", ref2); |
| assertGC("3", ref3); |
| assertGC("b", refB); |
| } |
| |
| public void testGarbageCollectOfDeepExploreredContextIssue124712() throws Exception { |
| class K extends Children.Keys<String> { |
| public void keys(String... keys) { |
| setKeys(keys); |
| } |
| |
| @Override |
| protected Node[] createNodes(String key) { |
| AbstractNode an = new AbstractNode(new K()); |
| an.setName(key); |
| return new Node[] { an }; |
| } |
| } |
| |
| K myKeys = new K(); |
| myKeys.keys("a", "b", "c"); |
| AbstractNode myRoot = new AbstractNode(myKeys); |
| myRoot.setName("root"); |
| em.setRootContext(myRoot); |
| |
| Node mezi = myRoot.getChildren().getNodes()[1]; |
| ((K)mezi.getChildren()).keys("a", "b", "c"); |
| |
| Node b = mezi.getChildren().getNodes()[1]; |
| assertEquals("b", b.getDisplayName()); |
| ((K)b.getChildren()).keys("1", "2", "3"); |
| |
| em.setExploredContext(b); |
| |
| Reference<?> refB = new WeakReference<Object>(b); |
| Reference<?> ref1 = new WeakReference<Object>(b.getChildren().getNodes()[0]); |
| Reference<?> ref2 = new WeakReference<Object>(b.getChildren().getNodes()[1]); |
| Reference<?> ref3 = new WeakReference<Object>(b.getChildren().getNodes()[2]); |
| |
| myKeys.keys(); |
| b = null; |
| |
| ref = em; |
| em.waitFinished(); |
| |
| assertEquals("Explored context is the root context", myRoot, em.getExploredContext()); |
| assertEquals("No selected nodes", 0, em.getSelectedNodes().length); |
| |
| assertGC("1", ref1); |
| assertGC("2", ref2); |
| assertGC("3", ref3); |
| assertGC("b", refB); |
| } |
| public void testGarbageCollectWithStrangeSelectionIssue124712() throws Exception { |
| class K extends Children.Keys<String> { |
| public void keys(String... keys) { |
| setKeys(keys); |
| } |
| |
| @Override |
| protected Node[] createNodes(String key) { |
| AbstractNode an = new AbstractNode(new K()); |
| an.setName(key); |
| return new Node[] { an }; |
| } |
| } |
| |
| K myKeys = new K(); |
| myKeys.keys("a", "b", "c"); |
| AbstractNode myRoot = new AbstractNode(myKeys); |
| myRoot.setName("root"); |
| em.setRootContext(myRoot); |
| |
| Node mezi = myRoot.getChildren().getNodes()[1]; |
| ((K)mezi.getChildren()).keys("a", "b", "c"); |
| |
| Node b = mezi.getChildren().getNodes()[1]; |
| assertEquals("b", b.getDisplayName()); |
| ((K)b.getChildren()).keys("1", "2", "3"); |
| |
| em.setExploredContext(b); |
| em.setSelectedNodes(new Node[] { mezi }); |
| em.setSelectedNodes(new Node[0]); |
| |
| em.waitFinished(); |
| |
| Reference<?> refB = new WeakReference<Object>(b); |
| Reference<?> ref1 = new WeakReference<Object>(b.getChildren().getNodes()[0]); |
| Reference<?> ref2 = new WeakReference<Object>(b.getChildren().getNodes()[1]); |
| Reference<?> ref3 = new WeakReference<Object>(b.getChildren().getNodes()[2]); |
| |
| myKeys.keys(); |
| b = null; |
| |
| ref = em; |
| em.waitFinished(); |
| |
| assertEquals("Explored context is the root context", myRoot, em.getExploredContext()); |
| assertEquals("No selected nodes", 0, em.getSelectedNodes().length); |
| |
| assertGC("1", ref1); |
| assertGC("2", ref2); |
| assertGC("3", ref3); |
| assertGC("b", refB); |
| } |
| |
| public void testSetRootContext() throws InterruptedException { |
| final int count = 1000; |
| final AtomicBoolean failed = new AtomicBoolean(false); |
| |
| class SetRootContext extends Thread { |
| |
| final Node root; |
| |
| public SetRootContext(Node root) { |
| this.root = root; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| for (int i = 0; i < count; i++) { |
| em.setRootContext(root); |
| } |
| } catch (IllegalArgumentException e) { |
| failed.set(true); |
| } |
| } |
| } |
| |
| Thread t1 = new SetRootContext(new AbstractNode(Children.LEAF)); |
| Thread t2 = new SetRootContext(new AbstractNode(Children.LEAF)); |
| Thread t3 = new SetRootContext(new AbstractNode(Children.LEAF)); |
| Thread t4 = new SetRootContext(new AbstractNode(Children.LEAF)); |
| |
| t1.start(); |
| t2.start(); |
| t3.start(); |
| t4.start(); |
| |
| t1.join(); |
| t2.join(); |
| t3.join(); |
| t4.join(); |
| |
| if (failed.get()) { |
| fail("IAE during setRootContext()"); |
| } |
| } |
| |
| private static Object ref; |
| |
| private static final class Keys extends Children.Keys<String> { |
| public Node key(String k) { |
| keys(new String[] { k }); |
| return getNodes()[0]; |
| } |
| public void keys(String[] keys) { |
| super.setKeys(keys); |
| } |
| protected Node[] createNodes(String o) { |
| AbstractNode an = new AbstractNode(Children.LEAF); |
| an.setName(o); |
| return new Node[] { an }; |
| } |
| } |
| |
| private static final class IntKeys extends Children.Keys<Integer> { |
| static int eqCounter; |
| |
| public void keys(Integer[] keys) { |
| super.setKeys(keys); |
| } |
| protected Node[] createNodes(Integer o) { |
| AbstractNode an = new CountingNode(); |
| an.setName(Integer.toString(o)); |
| return new Node[] { an }; |
| } |
| private static class CountingNode extends AbstractNode { |
| CountingNode() { |
| super (Children.LEAF); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| eqCounter++; |
| return super.equals(obj); |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode(); |
| } |
| } |
| } |
| } |