All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.nodes.Children Maven / Gradle / Ivy

The newest version!
/*
 * 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.nodes;

import java.lang.ref.WeakReference;
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.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.util.Enumerations;
import org.openide.util.Mutex;
import org.openide.util.Parameters;

/** 
* Factory for the child Nodes of a Node.  Every Node has a Children object.
* Children are initially un-initialized, and child Nodes are created on
* demand when, for example, the Node is expanded in an Explorer view.
* If you know your Node has no child nodes, pass Children.LEAF.
* Typically a Children object will create a Collection of objects from
* some data model, and create one or more Nodes for each object on demand.
* 
* If initializing the list of children of a Node is time-consuming (i.e. it
* does I/O, parses a file or some other expensive operation), implement
* ChildFactory and pass it to Children.create (theFactory, true) to have
* the child nodes be computed asynchronously on a background thread.
*
* 

In almost all cases you want to subclass ChildFactory and pass it to * Children.create(), or subclass {@link Children.Keys}. * Subclassing Children directly is not recommended. * * @author Jaroslav Tulach */ public abstract class Children extends Object { /** A package internal accessor object to provide priviledged * access to children. */ static final Mutex.Privileged PR = new Mutex.Privileged(); /** Lock for access to hierarchy of all node lists. * Anyone who needs to ensure that there will not * be shared accesses to hierarchy nodes can use this * mutex. *

* All operations on the hierarchy of nodes (add, remove, etc.) are * done in the {@link Mutex#writeAccess} method of this lock, so if someone * needs for a certain amount of time to forbid modification, * he can execute his code in {@link Mutex#readAccess}. */ public static final Mutex MUTEX = new Mutex(PR, new ProjectManagerDeadlockDetector()); /** The object representing an empty set of children. Should * be used to represent the children of leaf nodes. The same * object may be used by all such nodes. */ public static final Children LEAF = new Empty(); static final Logger LOG = Logger.getLogger(Children.class.getName()); /** access to entries/nodes */ private EntrySupport entrySupport; /** parent node for all nodes in this list (can be null) */ Node parent; /* private StringBuffer debug = new StringBuffer (); private void printStackTrace() { Exception e = new Exception (); java.io.StringWriter w1 = new java.io.StringWriter (); java.io.PrintWriter w = new java.io.PrintWriter (w1); e.printStackTrace(w); w.close (); debug.append (w1.toString ()); debug.append ('\n'); } */ /** Constructor. */ public Children() { this(false); } Children(boolean lazy) { lazySupport = lazy; } /** * Initializes entry support. */ EntrySupport entrySupport() { synchronized (Children.class) { if (getEntrySupport() == null) { LOG.finer("Initializing entrySupport"); EntrySupport es = lazySupport ? new EntrySupportLazy(this) : new EntrySupportDefault(this); setEntrySupport(es); postInitializeEntrySupport(es); } return getEntrySupport(); } } boolean lazySupport; boolean isLazy() { return lazySupport; } void checkSupport() { } /** * Does further initialization of entry support. Is called just once, under internal * lock so subclasses should behave sane. Can be overriden just in this * package, until found that this is needed somewhere else. */ void postInitializeEntrySupport(EntrySupport es) { } /** Setter of parent node for this list of children. Each children in the list * will have this node set as parent. The parent node will return nodes in * this list as its children. *

* This method is called from the Node constructor * * @param n node to attach to * @exception IllegalStateException when this object is already used with * different node */ final void attachTo(final Node n) throws IllegalStateException { // special treatment for LEAF object. if (this == LEAF) { // do not attaches the node because the LEAF cannot have children // and that is why it need not set parent node for them return; } synchronized (this) { if (parent != null) { // already used throw new IllegalStateException( "An instance of Children may not be used for more than one parent node." ); // NOI18N } // attach itself as a node list for given node parent = n; } // do not get Children.MUTEX if not necessary Node[] nodes = testNodes(); if (nodes == null) { return; } // this is the only place where parent is changed, // but only under readAccess => double check if // it happened correctly try { PR.enterReadAccess(); nodes = testNodes(); if (nodes == null) { return; } // fire the change for (int i = 0; i < nodes.length; i++) { Node node = nodes[i]; node.assignTo(Children.this, i); node.fireParentNodeChange(null, parent); } } finally { PR.exitReadAccess(); } } /** Called when the node changes it's children to different nodes. * * @param n node to detach from * @exception IllegalStateException if children were already detached */ final void detachFrom() { // special treatment for LEAF object. if (this == LEAF) { // no need to do anything return; } Node oldParent = null; synchronized (this) { if (parent == null) { // already detached throw new IllegalStateException("Trying to detach children which do not have parent"); // NOI18N } // remember old parent oldParent = parent; // attach itself as a node list for given node parent = null; } // this is the only place where parent is changed, // but only under readAccess => double check if // it happened correctly try { PR.enterReadAccess(); Node[] nodes = testNodes(); if (nodes == null) { return; } // fire the change for (int i = 0; i < nodes.length; i++) { Node node = nodes[i]; node.deassignFrom(Children.this); node.fireParentNodeChange(oldParent, null); } } finally { PR.exitReadAccess(); } } /** * Create a Children object using the passed ChildFactory * object. The ChildFactory will be asked to create a list * of model objects that are the children; then for each object in the list, * {@link ChildFactory#createNodesForKey} will be called to instantiate * one or more Nodes for that object. * @param factory a factory which will provide child objects * @param asynchronous If true, the factory will always be called to * create the list of keys on * a background thread, displaying a "Please Wait" child node until * some or all child nodes have been computed. If so, * when it is expanded, the node that owns * the returned Children object will display a "Please Wait" * node while the children are computed in the background. Pass true * for any case where computing child nodes is expensive and should * not be done in the event thread. * @return a children object which * will invoke the factory instance as needed to supply model * objects and child nodes for it * @throws IllegalStateException if the passed factory has already * been used in a previous call to this method * @since org.openide.nodes 7.1 */ public static Children create (ChildFactory factory, boolean asynchronous) { if (factory == null) throw new NullPointerException ("Null factory"); if (asynchronous) { AsynchChildren ch = new AsynchChildren (factory); factory.setObserver(ch); return ch; } else { SynchChildren ch = new SynchChildren (factory); factory.setObserver(ch); return ch; } } /** * Create a lazy children implementation. * @param factory The {@link Callable} whose call() method * is called just when node's children are really needed. * @return Provides lazy children implementation that can be passed * to {@link Node} constructor and thus allows the client code to decide * what children the node should have when {@link Callable#call()} is called. * @since 7.18 */ public static Children createLazy(Callable factory) { return new LazyChildren(factory); } /** Get the parent node of these children. * @return the node attached to this children object, or null if there is none yet */ protected final Node getNode() { return parent; } /** Allows access to the clone method for Node. * @return cloned hierarchy * @exception CloneNotSupportedException if not supported */ final Object cloneHierarchy() throws CloneNotSupportedException { return clone(); } /** Handles cloning in the right way, that can be later extended by * subclasses. Of course each subclass that wishes to support cloning * must implement the Cloneable interface, otherwise this method throws * CloneNotSupportedException. * * @return cloned version of this object, with the same class, uninitialized and without * a parent node * *exception CloneNotSupportedException if Cloneable interface is not implemented */ @Override protected Object clone() throws CloneNotSupportedException { synchronized (Children.class) { Children ch = (Children) super.clone(); ch.parent = null; ch.setEntrySupport(null); return ch; } } /** * Add nodes to this container but do not call this method. * If you think you need to do this probably you really wanted to use * {@link Children.Keys#setKeys} instead. * The parent node of these nodes * is changed to the parent node of this list. Each node can be added * only once. If there is some reason a node cannot be added, for example * if the node expects only a special type of subnodes, the method should * do nothing and return false to signal that the addition has not been successful. *

* This method should be implemented by subclasses to filter some nodes, etc. * * @param nodes set of nodes to add to the list * @return true if successfully added */ public abstract boolean add(final Node[] nodes); /** Remove nodes from the list. Only nodes that are present are * removed. * * @param nodes nodes to be removed * @return true if the nodes could be removed */ public abstract boolean remove(final Node[] nodes); /** Get the nodes as an enumeration. * @return enumeration of nodes */ public final Enumeration nodes() { return Enumerations.array(getNodes()); } /** Find a child node by name. * This may be overridden in subclasses to provide a more advanced way of finding the * child, but the default implementation simply scans through the list of nodes * to find the first one with the requested name. *

Normally the list of nodes should have been computed by the time this returns, * but see {@link #getNodes()} for an important caveat as to why this may not * be doing what you want and what to do instead. * @param name (code) name of child node to find or null if any arbitrary child may * be returned * @return the node or null if it could not be found */ public Node findChild(String name) { Node[] list = getNodes(); if (list.length == 0) { return null; } if (name == null) { // return any node return list[0]; } for (int i = 0; i < list.length; i++) { if (name.equals(list[i].getName())) { // ok, we have found it return list[i]; } } return null; } /** Method that can be used to test whether the children content has * ever been used or it is still not initalized. * @return true if children has been used before * @see #addNotify */ protected final boolean isInitialized() { return entrySupport().isInitialized(); } /** Getter for a child at a given position. If child with such index * does not exists it returns null. * * @param index the index of a node we want to know (non negative) * @return the node at given index or null * @since org.openide.nodes 7.5 */ public final Node getNodeAt(int index) { checkSupport(); return entrySupport().getNodeAt(index); } /** Get a (sorted) array of nodes in this list. * If the children object is not yet initialized, * it will be (using {@link #addNotify}) before * the nodes are returned. *

Warning: not all children * implementations do a complete calculation at * this point, see {@link #getNodes(boolean)} * @return array of nodes */ // private static String off = ""; // NOI18N public final Node[] getNodes() { checkSupport(); return entrySupport().getNodes(false); } /** Get a (sorted) array of nodes in this list. * * This method is usefull if you need a fully initialized array of nodes * for things like MenuView, node navigation from scripts/tests and so on. * But in general if you are trying to get useful data by calling * this method, you are probably doing something wrong. * Usually you should be asking some underlying model * for information, not the nodes for children. For example, * DataFolder.getChildren() * is a much more appropriate way to get what you want for the case of folder children. * * If you're extending children, you should make sure this method * will return a complete list of nodes. The default implementation will do * this correctly so long as your subclass implement findChild(null) * to initialize all subnodes. * *

Note:You should not call this method from inside * {@link org.openide.nodes.Children#MUTEX Children.MUTEX}.readAccess(). * If you do so, the Node will be unable to update its state * before you leave the readAccess(). * * @since 2.17 * * @param optimalResult whether to try to get a fully initialized array * or to simply delegate to {@link #getNodes()} * @return array of nodes */ public Node[] getNodes(boolean optimalResult) { checkSupport(); return entrySupport().getNodes(optimalResult); } /** Get the number of nodes in the list. * @return the count */ public final int getNodesCount() { checkSupport(); return entrySupport().getNodesCount(false); } /** Get the number of nodes in the list * @param optimalResult whether to try to perform full initialization * or to simply delegate to {@link #getNodesCount()} * @return the count * @since org.openide.nodes 7.6 */ public int getNodesCount(boolean optimalResult) { checkSupport(); return entrySupport().getNodesCount(optimalResult); } /** Creates an immutable snapshot representing the current view of the nodes * in this children object. This is No attempt is made to extract incorrect or invalid * nodes from the list, as a result, the value may not be exactly the same * as returned by {@link #getNodes()}. * * @return immutable and unmodifiable list of nodes in this children object * @since 7.7 */ public final List snapshot() { return entrySupport().snapshot(); } static final int[] getSnapshotIdxs(List snapshot) { int[] idxs = new int[snapshot.size()]; for (int i = 0; i < idxs.length; i++) { idxs[i] = i; } return idxs; } // // StateNotifications // /** Called when children are first asked for nodes. * Typical implementations at this time calculate * their node list (or keys for {@link Children.Keys} etc.).
* Notice: call to getNodes() inside of this method will return * an empty array of nodes. * @see #isInitialized */ protected void addNotify() { } /** Called when all the children Nodes are freed from memory. * Typical implementations at this time clear all the keys * (in case of {@link Children.Keys}) etc. * * Note that this is usually not the best place for unregistering * listeners, etc., as listeners usually keep the child nodes * in memory, preventing them from being collected, thus preventing * this method to be called in the first place. */ protected void removeNotify() { } /** Method that can be overriden in subclasses to * do additional work and then call addNotify. */ void callAddNotify() { //System.err.println("Thread: " + Thread.currentThread().getName() + ", N: " + getNode()); //System.err.println("Children: " + this); addNotify(); //System.err.println("Finished: " + this); } final void callRemoveNotify() { removeNotify(); } /** Called when the nodes have been removed from the children. * This method should allow subclasses to clean the nodes somehow. *

* Current implementation notifies all listeners on the nodes * that nodes have been deleted. * * @param arr array of deleted nodes */ void destroyNodes(Node[] arr) { } /** @return either nodes associated with this children or null if * they are not created */ private Node[] testNodes() { return getEntrySupport() == null ? null : entrySupport().testNodes(); } /** * @return the entrySupport */ final EntrySupport getEntrySupport() { return entrySupport; } /** * @param entrySupport the entrySupport to set */ final void setEntrySupport(EntrySupport entrySupport) { assert Thread.holdsLock(Children.class); this.entrySupport = entrySupport; } /** Interface that provides a set of nodes. */ static interface Entry { /** Set of nodes associated with this entry. */ public Collection nodes(Object source); } /** Empty list of children. Does not allow anybody to insert a node. * Treated especially in the attachTo method. */ private static final class Empty extends Children { Empty() { } /** @return false, does no action */ public boolean add(Node[] nodes) { return false; } /** @return false, does no action */ public boolean remove(Node[] nodes) { return false; } } /** Implements the storage of node children by an array. * Each new child is added at the end of the array. The nodes are * returned in the order they were inserted. * *

* Directly subclassing this class is discouraged. * {@link Children.Keys} is preferable. * */ public static class Array extends Children implements Cloneable { /** the entry used for all nodes in the following collection * this object is used for synchronization of operations that * need to be synchronized on this instance of Children, but * we cannot synchronize on this instance because it is public * and somebody else could synchronize too. */ Entry nodesEntry; /** vector of added children */ protected Collection nodes; /** Constructs a new list and allows a subclass to * provide its own implementation of Collection to store * data in. The collection should be empty and should not * be directly accessed in any way after creation. * * @param c collection to store data in */ protected Array(Collection c) { this(); nodes = c; } /** Constructs a new array children without any assigned collection. * The collection will be created by a call to method initCollection the * first time, children will be used. */ public Array() { this(false); } Array(boolean lazy) { super(lazy); if (!lazy) { nodesEntry = createNodesEntry(); } } @Override void postInitializeEntrySupport(EntrySupport es) { if (!lazySupport) { if (getNodesEntry() == null) { nodesEntry = createNodesEntry(); } es.setEntries(Collections.singleton(getNodesEntry()), true); } else if (getNodesEntry() != null) { nodesEntry = null; } } /** Clones all nodes that are contained in the children list. * * @return the cloned array for this children */ @Override public Object clone() { try { final Children.Array ar = (Array) super.clone(); try { PR.enterReadAccess(); if (nodes != null) { // nodes already initilized // used to create the right type of collection // clears the content of the collection // JST: hack, but I have no better idea how to write this // pls. notice that in initCollection you can test // whether nodes == null => real initialization // nodes != null => only create new empty collection ar.nodes = ar.initCollection(); ar.nodes.clear(); // insert copies of the nodes for (Node n : nodes) { ar.nodes.add(n.cloneNode()); } } } finally { PR.exitReadAccess(); } return ar; } catch (CloneNotSupportedException e) { // this cannot happen throw new InternalError(); } } /** Allow subclasses to create a collection, the first time the * children are used. It is called only if the collection has not * been passed in the constructor. *

* The current implementation returns ArrayList. * * @return empty or initialized collection to use */ protected Collection initCollection() { return new ArrayList(); } /** This method can be called by subclasses that * directly modify the nodes collection to update the * state of the nodes appropriatelly. * This method should be called under * MUTEX.writeAccess. */ final void refreshImpl() { if (isInitialized()) { Array.this.entrySupport().refreshEntry(getNodesEntry()); entrySupport().getNodes(false); } else if (nodes != null) { for (Node n : nodes) { n.assignTo(this, -1); } } } /** This method can be called by subclasses that * directly modify the nodes collection to update the * state of the nodes appropriatelly. */ protected final void refresh() { checkSupport(); if (lazySupport) { return; } MUTEX.postWriteRequest(new Runnable() { public void run() { refreshImpl(); } } ); } /** Getter for the entry. */ final Entry getNodesEntry() { return nodesEntry; } /** This method allows subclasses (only in this package) to * provide own version of entry. Usefull for SortedArray. */ Entry createNodesEntry() { return new AE(); } /** Getter for nodes. */ private static final Object COLLECTION_LOCK = new Object(); final Collection getCollection() { synchronized (COLLECTION_LOCK) { if (nodes == null) { nodes = initCollection(); } } return nodes; } /* * @param arr nodes to add * @return true if changed false if not */ @Override public boolean add(final Node[] arr) { synchronized (COLLECTION_LOCK) { if (!getCollection().addAll(Arrays.asList(arr))) { // no change to the collection return false; } } refresh(); return true; } /* * @param arr nodes to remove * @return true if changed false if not */ @Override public boolean remove(final Node[] arr) { synchronized (COLLECTION_LOCK) { final Collection collection = getCollection(); // fast check boolean same = false; if (collection.size() == arr.length) { same = true; int i = 0; for (Node n : collection) { if (n != arr[i++]) { same = false; break; } } } if (same) { collection.clear(); } else { if (!collection.removeAll(Arrays.asList(arr))) { // the collection was not changed return false; } } } refresh(); return true; } /** One entry that holds all the nodes in the collection * member called nodes. */ private final class AE extends Object implements Entry { AE() { } /** List of elements. */ @Override public Collection nodes(Object source) { Collection c = getCollection(); if (c.isEmpty()) { return Collections.emptyList(); } else { synchronized (COLLECTION_LOCK) { return new ArrayList(c); } } } @Override public String toString() { return "Children.Array.AE" + getCollection(); // NOI18N } } } /** Implements the storage of node children by a map. * This class also permits * association of a key with any node and to remove nodes by key. * Subclasses should reasonably * implement {@link #add} and {@link #remove}. * @param the key type */ public static class Map extends Children { /** A map to use to store children in. * Keys are Objects, values are {@link Node}s. * Do not modify elements in the map! Use it only for read access. */ protected java.util.Map nodes; /** Constructs a new list with a supplied map object. * Should be used by subclasses desiring an alternate storage method. * The map must not be explicitly modified after creation. * * @param m the map to use for this list */ protected Map(java.util.Map m) { nodes = m; } /** Constructs a new list using {@link HashMap}. */ public Map() { } /** Getter for the map. * Ensures that the map has been initialized. */ final java.util.Map getMap() { // package private only to simplify access from inner classes if (nodes == null) { nodes = initMap(); } return nodes; } /** Called on first use. */ @Override final void callAddNotify() { entrySupport().setEntries(createEntries(getMap()), true); super.callAddNotify(); } /** Method that allows subclasses (SortedMap) to redefine * order of entries. * @param map the map (Object, Node) * @return collection of (Entry) */ Collection createEntries(java.util.Map map) { List l = new LinkedList(); for (java.util.Map.Entry e : map.entrySet()) { l.add(new ME(e.getKey(), e.getValue())); } return l; } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. * This method should be called under * MUTEX.writeAccess. */ final void refreshImpl() { entrySupport().setEntries(createEntries(getMap())); } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. */ protected final void refresh() { try { PR.enterWriteAccess(); refreshImpl(); } finally { PR.exitWriteAccess(); } } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. * This method should be called under * MUTEX.writeAccess. * * @param key the key that should be refreshed */ final void refreshKeyImpl(T key) { entrySupport().refreshEntry(new ME(key, null)); } /** Allows subclasses that directly modifies the * map with nodes to synchronize the state of the children. * * @param key the key that should be refreshed */ protected final void refreshKey(final T key) { try { PR.enterWriteAccess(); refreshKeyImpl(key); } finally { PR.exitWriteAccess(); } } /** Add a collection of new key/value pairs into the map. * The supplied map may contain any keys, but the values must be {@link Node}s. * * @param map the map with pairs to add */ protected final void putAll(final java.util.Map map) { try { PR.enterWriteAccess(); getMap().putAll(map); refreshImpl(); // PENDING sometime we should also call refreshKey... } finally { PR.exitWriteAccess(); } } /** Add one key and one node to the list. * @param key the key * @param node the node */ protected final void put(final T key, final Node node) { try { PR.enterWriteAccess(); if (getMap().put(key, node) != null) { refreshKeyImpl(key); } else { refreshImpl(); } } finally { PR.exitWriteAccess(); } } /** Remove some children from the list by key. * @param keys collection of keys to remove */ protected final void removeAll(final Collection keys) { try { PR.enterWriteAccess(); getMap().keySet().removeAll(keys); refreshImpl(); } finally { PR.exitWriteAccess(); } } /** Remove a given child node from the list by its key. * @param key key to remove */ protected void remove(final T key) { try { PR.enterWriteAccess(); if (nodes != null && nodes.remove(key) != null) { refreshImpl(); } } finally { PR.exitWriteAccess(); } } /** Initialize some nodes. Allows a subclass to * provide a default map to initialize the map with. * Called only if the map has not been provided in the constructor. * *

* The default implementation returns new HashMap (7). * * @return a map from keys to nodes */ protected java.util.Map initMap() { return new HashMap(7); } /** Does nothing. Should be reimplemented in a subclass wishing * to support external addition of nodes. * * @param arr nodes to add * @return false in the default implementation */ public boolean add(Node[] arr) { return false; } /** Does nothing. Should be reimplemented in a subclass wishing * to support external removal of nodes. * @param arr nodes to remove * @return false in the default implementation */ public boolean remove(Node[] arr) { return false; } /** Entry mapping one key to the node. */ static final class ME extends Object implements Entry { /** key */ public Object key; /** node set */ public Node node; /** Constructor. */ public ME(Object key, Node node) { this.key = key; this.node = node; } /** Nodes */ public Collection nodes(Object source) { return Collections.singleton(node); } /** Hash code. */ @Override public int hashCode() { return key.hashCode(); } /** Equals. */ @Override public boolean equals(Object o) { if (o instanceof ME) { ME me = (ME) o; return key.equals(me.key); } return false; } @Override public String toString() { return "Key (" + key + ")"; // NOI18N } } } /** Maintains a list of children sorted by the provided comparator in an array. * The comparator can change during the lifetime of the children, in which case * the children are resorted. */ public static class SortedArray extends Children.Array { /** comparator to use */ private Comparator comp; /** Create an empty list of children. */ public SortedArray() { } /** Create an empty list with a specified storage method. * * @param c collection to store data in * @see Children.Array#Array(Collection) */ protected SortedArray(Collection c) { super(c); } /** Set the comparator. The children will be resorted. * The comparator is used to compare Nodes, if no * comparator is used then nodes will be compared by * the use of natural ordering. * * @param c the new comparator */ public void setComparator(final Comparator c) { try { PR.enterWriteAccess(); comp = c; refresh(); } finally { PR.exitWriteAccess(); } } /** Get the current comparator. * @return the comparator */ public Comparator getComparator() { return comp; } /** This method allows subclasses (only in this package) to * provide own version of entry. Useful for SortedArray. */ @Override Entry createNodesEntry() { return new SAE(); } /** One entry that holds all the nodes in the collection * member called nodes. */ private final class SAE extends Object implements Entry { /** Constructor that provides the original comparator. */ public SAE() { } /** List of elements. */ public Collection nodes(Object source) { List al = new ArrayList(getCollection()); al.sort(comp); return al; } } } // end of SortedArray /** Maintains a list of children sorted by the provided comparator in a map. * Similar to {@link Children.SortedArray}. */ public static class SortedMap extends Children.Map { /** comparator to use */ private Comparator comp; /** Create an empty list. */ public SortedMap() { } /** Create an empty list with a specific storage method. * * @param map the map to use with this object * @see Children.Map#Map(java.util.Map) */ protected SortedMap(java.util.Map map) { super(map); } /** Set the comparator. The children will be resorted. * The comparator is used to compare Nodes, if no * comparator is used then values will be compared by * the use of natural ordering. * * @param c the new comparator that should compare nodes */ public void setComparator(final Comparator c) { try { PR.enterWriteAccess(); comp = c; refresh(); } finally { PR.exitWriteAccess(); } } /** Get the current comparator. * @return the comparator */ public Comparator getComparator() { return comp; } /** Method that allows subclasses (SortedMap) to redefine * order of entries. * @param map the map (Object, Node) * @return collection of (Entry) */ @Override Collection createEntries(java.util.Map map) { // SME objects use natural ordering Set l = new TreeSet(new SMComparator()); for (java.util.Map.Entry e : map.entrySet()) { l.add(new ME(e.getKey(), e.getValue())); } return l; } /** Sorted map entry can be used for comparing. */ final class SMComparator implements Comparator { public int compare(ME me1, ME me2) { Comparator c = comp; if (c == null) { // compare keys @SuppressWarnings("unchecked") // we just assume that it is comparable, not statically checked int r = ((Comparable) me1.key).compareTo(me2.key); return r; } else { return c.compare(me1.node, me2.node); } } } } // end of SortedMap /** Implements an array of child nodes associated nonuniquely with keys and sorted by these keys. * There is a {@link #createNodes(Object) method} that should for each * key create an array of nodes that represents the key. * *

This class is preferable to {@link Children.Array} because *

    *
  1. It more clearly separates model from view and encourages use of a discrete model. *
  2. It correctly handles adding, removing, and reordering children while preserving * existing node selections in a tree (or other) view where possible. *
* *

Typical usage: *

    *
  1. Subclass. *
  2. Decide what type your key should be. *
  3. Implement {@link #createNodes} to create some nodes * (usually exactly one) per key. *
  4. Override {@link Children#addNotify} to compute a set of keys * and set it using {@link #setKeys(Collection)}. * The collection may be ordered. *
  5. Override {@link Children#removeNotify} to just call * setKeys on {@link Collections#EMPTY_SET}. *
  6. When your model changes, call setKeys * with the new set of keys. Children.Keys will * be smart and calculate exactly what it needs to do effficiently. *
  7. (Optional) if your notion of what the node for a * given key changes (but the key stays the same), you can * call {@link #refreshKey}. Usually this is not necessary. *
* Note that for simple cases, it may be preferable to subclass * {@link ChildFactory} and pass the result to * {@link Children#create(org.openide.nodes.ChildFactory, boolean) }; doing so makes it easy to switch to using child * nodes computed on a background thread if necessary for performance * reasons. *

Related documentation

* * * @param the type of the key */ public abstract static class Keys extends Children.Array { /** the last runnable (created in method setKeys) for each children object. */ private static java.util.Map,Runnable> lastRuns = new HashMap,Runnable>(11); /** add array children before or after keys ones */ boolean before; public Keys() { this(false); } /** Constructor for optional "lazy behavoir" (tries to avoid * computation of nodes if possible). *

There are certain requirements for usage of lazy mode: * It is forbidden to create more than 1 node in {@link #createNodes} * for key. In optimal case there should be 1:1 pairing between key and Node, * but it is also possible to have 1:0 pairing - create no node (return null). * In such case after detection that there is no Node for key, * the key is automatically removed and change (removal of * "dummy" Node) is fired. * @param lazy whether to introduce lazy behavior * @since org.openide.nodes 7.6 */ protected Keys(boolean lazy) { super(lazy); } /** Special handling for clonning. */ @Override public Object clone() { Keys k = (Keys) super.clone(); return k; } @Override void checkSupport() { if (lazySupport && nodes != null && nodes.size() > 0) { fallbackToDefaultSupport(); } } void fallbackToDefaultSupport() { LOG.warning("Fallbacking entry support from lazy to default - Children.Array method was used"); // NOI18N switchSupport(false); } void switchSupport(boolean toLazy) { if (toLazy == lazySupport) { return; } try { Children.PR.enterWriteAccess(); List entries = entrySupport().getEntries(); boolean init = entrySupport().isInitialized(); if (init && parent != null) { List snapshot = getEntrySupport().snapshot(); if (snapshot.size() > 0) { int[] idxs = getSnapshotIdxs(snapshot); parent.fireSubNodesChangeIdx(false, idxs, null, Collections.emptyList(), snapshot); } } synchronized (Children.class) { setEntrySupport(null); } lazySupport = toLazy; if (toLazy) { nodesEntry = null; } else { nodesEntry = createNodesEntry(); entries = new ArrayList(entries); entries.add(before ? 0 : entries.size(), nodesEntry); } if (init) { entrySupport().notifySetEntries(); } entrySupport().setEntries(entries); } finally { Children.PR.exitWriteAccess(); } } /** * @deprecated Do not use! Just call {@link #setKeys(Collection)} with a larger set. */ @Deprecated @Override public boolean add(Node[] arr) { if (lazySupport) { fallbackToDefaultSupport(); } return super.add(arr); } /** * @deprecated Do not use! Just call {@link #setKeys(Collection)} with a smaller set. */ @Deprecated @Override public boolean remove(final Node[] arr) { if (lazySupport) { return false; } try { PR.enterWriteAccess(); if (nodes != null) { // removing from array, just if the array nodes are // really created // expecting arr.length == 1, which is the usual case for (int i = 0; i < arr.length; i++) { if (!nodes.contains(arr[i])) { arr[i] = null; } } super.remove(arr); } } finally { PR.exitWriteAccess(); } return true; } /** Refresh the child nodes for a given key. * * @param key the key to refresh */ protected final void refreshKey(final T key) { MUTEX.postWriteRequest( new Runnable() { public void run() { Keys.this.entrySupport().refreshEntry(createEntryForKey(key)); } } ); } /** To be overriden in FilterNode.Children */ Entry createEntryForKey(T key) { return new KE(key); } /** Set new keys for this children object. Setting of keys * does not necessarily lead to the creation of nodes. It happens only * when the list has already been initialized. * * @param keysSet the keys for the nodes (collection of any objects) */ protected final void setKeys(Collection keysSet) { boolean asserts = false; assert asserts = true; int sz = keysSet.size(); if (asserts && sz < 10) { List keys = new ArrayList(keysSet); for (int i = 0; i < sz - 1; i++) { T a = keys.get(i); for (int j = i + 1; j < sz; j++) { T b = keys.get(j); assert !(a.equals(b) && a.hashCode() != b.hashCode()) : "bad equals/hashCode in " + a + " vs. " + b + " class: " + b.getClass().getName(); } } } final List l = new ArrayList(keysSet.size() + 1); KE updator = new KE(); if (lazySupport) { updator.updateList(keysSet, l); } else { if (before) { l.add(getNodesEntry()); } updator.updateList(keysSet, l); if (!before) { l.add(getNodesEntry()); } } applyKeys(l); } /** Set keys for this list. * * @param keys the keys for the nodes * @see #setKeys(Collection) */ protected final void setKeys(final T[] keys) { boolean asserts = false; assert asserts = true; int sz = keys.length; if (asserts && sz < 10) { for (int i = 0; i < sz - 1; i++) { T a = keys[i]; for (int j = i + 1; j < sz; j++) { T b = keys[j]; assert !(a.equals(b) && a.hashCode() != b.hashCode()) : "bad equals/hashCode in " + a + " vs. " + b; } } } final List l = new ArrayList(keys.length + 1); KE updator = new KE(); if (lazySupport) { updator.updateList(keys, l); } else { if (before) { l.add(getNodesEntry()); } updator.updateList(keys, l); if (!before) { l.add(getNodesEntry()); } } applyKeys(l); } /** Applies the keys. */ private void applyKeys(final List l) { Runnable invoke = new Runnable() { public void run() { if (keysCheck(Keys.this, this)) { // no next request after me entrySupport().setEntries(l); // clear this runnable keysExit(Keys.this, this); } } }; keysEnter(this, invoke); MUTEX.postWriteRequest(invoke); } /** Set whether new nodes should be added to the beginning or end of sublists for a given key. * Generally should not be used. * @param b true if the children should be added before */ protected final void setBefore(final boolean b) { try { PR.enterWriteAccess(); if (before != b && !lazySupport) { List l = entrySupport().getEntries(); l.remove(getNodesEntry()); before = b; if (b) { l.add(0, getNodesEntry()); } else { l.add(getNodesEntry()); } entrySupport().setEntries(l); } } finally { PR.exitWriteAccess(); } } /** Create nodes for a given key. * @param key the key * @return child nodes for this key or null if there should be no * nodes for this key */ protected abstract Node[] createNodes(T key); /** Called when the nodes have been removed from the children. * This method should allow subclasses to clean the nodes somehow. *

* Current implementation notifies all listeners on the nodes * that nodes have been deleted. * * @param arr array of deleted nodes */ @Override protected void destroyNodes(Node[] arr) { for (int i = 0; i < arr.length; i++) { arr[i].fireNodeDestroyed(); } } /** Enter of setKeys. * @param ch the children * @param run runnable */ private static synchronized void keysEnter(Keys ch, Runnable run) { lastRuns.put(ch, run); } /** Clears the entry for the children * @param ch children */ private static synchronized void keysExit(Keys ch, Runnable r) { Runnable reg = lastRuns.remove(ch); if ((reg != null) && !reg.equals(r)) { lastRuns.put(ch, reg); } } /** Check whether the runnable is "the current" for given children. * @param ch children * @param run runnable * @return true if the runnable shoul run */ private static synchronized boolean keysCheck(Keys ch, Runnable run) { return run == lastRuns.get(ch); } /** Entry for a key */ class KE extends Dupl { /** Has default constructor that allows to create a factory * of KE objects for use with updateList */ public KE() { } /** Creates directly an instance of the KE object. */ public KE(T key) { this.key = key; } /** Nodes are taken from the create nodes. */ public Collection nodes(Object source) { Node[] arr = createNodes(getKey()); if (arr == null) { return Collections.emptyList(); } else { return new LinkedList(Arrays.asList(arr)); } } @Override public String toString() { String s = getKey().toString(); if (s.length() > 80) { s = s.substring(s.length() - 80); } return "Children.Keys.KE[" + s + "," + getCnt() + "]"; // NOI18N } } } // end of Keys /** Supporting class that provides support for duplicated * objects that still should not be equal. *

* It counts the number of times an object has been * added to the collection and if the same object is added * more than once it is indexed by a number. */ // package-private for tests only! abstract static class Dupl implements Cloneable, Entry { /** the key either real value or Dupl (Dupl (Dupl (... value ...)))*/ protected Object key; Dupl() { } /** Updates the second collection with values from the first one. * If there is a multiple occurrence of an object in the first collection * a Dupl for the object is created to encapsulate it. */ public final void updateList(Collection src, Collection> target) { java.util.Map map = new HashMap(src.size() * 2); for (T o : src) { updateListAndMap(o, target, map); } } /** Updates the second collection with values from the first array. * If there is a multiple occurrence of an object in the first array * a Dupl for the object is created to encapsulate it. */ public final void updateList(T[] arr, Collection> target) { java.util.Map map = new HashMap(arr.length * 2); for (T o : arr) { updateListAndMap(o, target, map); } } /** Updates the linked list and the map with right * values. The map is used to count the number of times * a element occurs in the list. * * @param obj object to add * @param list the list to add obj to * @param map to track number of occurrences in the array */ public final void updateListAndMap(T obj, Collection> list, java.util.Map map) { Parameters.notNull("obj", obj); // optimized for first occurrence // of each object because often occurrences should be rare Object prev = map.put(obj, this); if (prev == null) { // first occurrence of object obj list.add(createInstance(obj, 0)); return; } else { if (prev == this) { // second occurrence of the object map.put(obj, 1); list.add(createInstance(obj, 1)); return; } else { int cnt = ((Integer) prev) + 1; map.put(obj, cnt); list.add(createInstance(obj, cnt)); return; } } } /** Gets the key represented by this object. * @return the key */ @SuppressWarnings("unchecked") // data structure really weird public T getKey() { if (key instanceof Dupl) { return (T) ((Dupl) key).getKey(); } else { return (T) key; } } /** Counts the index of this key. * @return integer */ public int getCnt() { int cnt = 0; Dupl d = this; while (d.key instanceof Dupl) { d = (Dupl) d.key; cnt++; } return cnt; } /** Create instance of Dupl (uses cloning of the class) */ @SuppressWarnings("unchecked") // data structure really weird private final Dupl createInstance(Object obj, int cnt) { try { // creates the chain of Dupl (Dupl (Dupl (obj))) where // for cnt = 0 the it would look like Dupl (obj) // for cnt = 1 like Dupl (Dupl (obj)) Dupl d = (Dupl) this.clone(); Dupl first = d; while (cnt-- > 0) { Dupl n = (Dupl) this.clone(); d.key = n; d = n; } d.key = obj; return first; } catch (CloneNotSupportedException ex) { throw new InternalError(); } } @Override public int hashCode() { return getKey().hashCode(); } @Override public boolean equals(Object o) { if (o instanceof Dupl) { Dupl d = (Dupl) o; return getKey().equals(d.getKey()) && (getCnt() == d.getCnt()); } return false; } } /** * Lazy children implementation */ static class LazyChildren extends Children { private Callable factory; private Children original; private final Object originalLock= new Object(); LazyChildren(Callable factory) { this.factory = factory; } Children getOriginal() { synchronized (originalLock) { if (original == null) { try { original = factory.call(); } catch (Exception ex) { throw new RuntimeException(ex); } } return original; } } @Override public boolean add(Node[] nodes) { return getOriginal().add(nodes); } @Override public boolean remove(Node[] nodes) { return getOriginal().remove(nodes); } @Override protected void addNotify() { getOriginal().addNotify(); } @Override protected void removeNotify() { getOriginal().removeNotify(); } @Override EntrySupport entrySupport() { return getOriginal().entrySupport(); } @Override public Node findChild(String name) { return getOriginal().findChild(name); } } /* static void printNodes (Node[] n) { for (int i = 0; i < n.length; i++) { System.out.println (" " + i + ". " + n[i].getName () + " number: " + System.identityHashCode (n[i])); } } */ /* JST: Useful test routine ;-) * static { final TopComponent.Registry r = TopComponent.getRegistry (); r.addPropertyChangeListener (new PropertyChangeListener () { Node last = new AbstractNode (LEAF); public void propertyChange (PropertyChangeEvent ev) { Node[] arr = r.getCurrentNodes (); if (arr != null && arr.length == 1) { last = arr[0]; } System.out.println ( "Activated node: " + last + " \nparent: " + last.getParentNode () ); } }); } */ private static final class ProjectManagerDeadlockDetector implements Executor { private final Mutex FALLBACK = new Mutex(); private final AtomicReference> pmMutexRef = new AtomicReference>(); public void execute(Runnable command) { boolean ea = false; assert ea = true; if (ea) { Mutex mutex = getPMMutex(); if (mutex != null && (mutex.isReadAccess() || mutex.isWriteAccess())) { throw new IllegalStateException("Should not acquire Children.MUTEX while holding ProjectManager.mutex()"); } } command.run(); } private Mutex getPMMutex() { for (;;) { Mutex mutex = null; WeakReference weakRef = pmMutexRef.get(); if (weakRef != null) { mutex = weakRef.get(); } if (mutex != null) { return mutex; } mutex = callPMMutexMethod(); if (mutex != null) { WeakReference newWeakRef = new WeakReference(mutex); if (pmMutexRef.compareAndSet(weakRef, newWeakRef)) { return mutex; } } else { return null; } } } private Mutex callPMMutexMethod() { Class clazz = null; Method method = null; try { clazz = Thread.currentThread().getContextClassLoader().loadClass("org.netbeans.api.project.ProjectManager"); // NOI18N method = clazz.getMethod("mutex"); // NOI18N return (Mutex) method.invoke(null); } catch (ReflectiveOperationException | ExceptionInInitializerError | IllegalArgumentException e) { return FALLBACK; } catch (ClassCastException e) { // observed to occur in MemoryValidationTest Class type = method.getReturnType(); LOG.log(Level.WARNING, "Reopen #175325 and save complete log: type=" + type.getName() + " type.cl=" + type.getClassLoader() + " Mutex.cl=" + Mutex.class.getClassLoader() + " clazz.cl=" + clazz.getClassLoader(), e); return null; } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy