/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.explorer.view;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.*;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.swing.tree.TreeNode;
import org.openide.ErrorManager;
import org.openide.nodes.*;
import org.openide.util.Mutex;
import org.openide.util.WeakListener;
import org.openide.util.enums.QueueEnumeration;
/** Visual representation of one node. Holds necessary information about nodes
* like icon, name, description and also list of its children.
* <P>
* There is at most one VisualizerNode for one node. All of them are hold in a cache.
* <P>
* The VisualizerNode level provides secure layer between Nodes and Swing AWT dispatch
* thread.
*
* @author Jaroslav Tulach
*/
final class VisualizerNode extends EventListenerList
implements NodeListener, TreeNode, Runnable {
/** one template to use for searching for visualizers */
private static final VisualizerNode TEMPLATE = new VisualizerNode (0);
/** constant holding empty reference to children */
private static final Reference NO_REF = new WeakReference (null);
/** cache of visializers (VisualizerNode, Reference (VisualizerNode)) */
private static WeakHashMap cache = new WeakHashMap ();
/** empty visualizer */
public static final VisualizerNode EMPTY = getVisualizer (null, Node.EMPTY);
/** queue processor to transfer requests to event queue */
private static final QP QUEUE = new QP ();
private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.explorer.view.VisualizerNode"); // NOI18N
// bugfix #29435, getVisualizer is synchronized in place of be called only from EventQueue
/** Finds VisualizerNode for given node.
* @param ch the children this visualizer should belong to
* @param n the node
* @return the visualizer
*/
public static synchronized VisualizerNode getVisualizer (VisualizerChildren ch, Node n) {
TEMPLATE.hashCode = System.identityHashCode (n);
TEMPLATE.node = n;
Reference r = (Reference)cache.get (TEMPLATE);
TEMPLATE.hashCode = 0;
TEMPLATE.node = null;
VisualizerNode v = r == null ? null : (VisualizerNode)r.get ();
if (v == null) {
v = new VisualizerNode (n);
cache.put (v, new WeakReference (v));
}
if (ch != null) {
v.parent = ch;
}
return v;
}
/** node. Do not modify!!! */
Node node;
/** system hashcode of the node */
private int hashCode;
/** visualizer children attached thru weak references Reference (VisualizerChildren) */
private Reference children = NO_REF;
/** the VisualizerChildren that contains this VisualizerNode or null */
private VisualizerChildren parent;
/** cached name */
private String name;
/** cached display name */
private String displayName;
private static final String UNKNOWN = new String();
/** cached short description */
private String shortDescription;
static final long serialVersionUID =3726728244698316872L;
/** Constructor that creates template for the node.
*/
private VisualizerNode (int hashCode) {
this.hashCode = hashCode;
this.node = null;
}
/** Creates new VisualizerNode
* @param n node to refer to
*/
private VisualizerNode(Node n) {
node = n;
hashCode = System.identityHashCode (node);
// attach as a listener
node.addNodeListener (WeakListener.node (this, node));
// uiListener = WeakListener.propertyChange (this, null);
// UIManager.addPropertyChangeListener (uiListener);
name = UNKNOWN;
displayName = UNKNOWN;
shortDescription = UNKNOWN;
}
/** Returns cached short description.
* @return short description of represented node
*/
public String getShortDescription() {
String desc = shortDescription;
if( desc == UNKNOWN ) {
shortDescription = desc = node.getShortDescription ();
}
return desc;
}
/** Returns cached display name.
* @return display name of represented node
*/
public String getDisplayName () {
if (displayName == UNKNOWN) {
displayName = node == null ? null : node.getDisplayName ();
}
return displayName;
}
/** Returns cached name.
* @return name of represented node
*/
public String getName () {
if (name == UNKNOWN) {
name = node == null ? null : node.getName ();
}
return name;
}
/** Getter for list of children of this visualizer.
* @return list of VisualizerNode objects
*/
public List getChildren () {
VisualizerChildren ch = (VisualizerChildren)children.get ();
if (ch == null && !node.isLeaf ()) {
// initialize the nodes children before we enter
// the readAccess section
Node[] tmpInit = node.getChildren ().getNodes ();
// go into lock to ensure that no childrenAdded, childrenRemoved,
// childrenReordered notifications occures and that is why we do
// not loose any changes
ch = (VisualizerChildren)Children.MUTEX.readAccess (new Mutex.Action () {
public Object run () {
Node[] nodes = node.getChildren ().getNodes ();
VisualizerChildren vc = new VisualizerChildren (
VisualizerNode.this, nodes
);
notifyVisualizerChildrenChange (nodes.length, vc);
return vc;
}
});
}
return ch == null ? Collections.EMPTY_LIST : ch.list;
}
//
// TreeNode interface (callable only from AWT-Event-Queue)
//
public int getIndex(final javax.swing.tree.TreeNode p1) {
return getChildren ().indexOf (p1);
}
public boolean getAllowsChildren() {
return !isLeaf ();
}
public javax.swing.tree.TreeNode getChildAt(int p1) {
List ch = getChildren();
VisualizerNode vn = (VisualizerNode)ch.get (p1);
if (vn == null) {
System.out.println("Children are: "); // NOI18N
for (Iterator it = ch.iterator(); it.hasNext(); ) {
System.out.println(" " + it.next()); // NOI18N
}
throw new IllegalStateException("VisualizerNode.getChildAt() returning null!"); // NOI18N
}
return vn;
}
public int getChildCount() {
return getChildren ().size ();
}
public java.util.Enumeration children() {
if (err.isLoggable(ErrorManager.INFORMATIONAL)) {
List l = getChildren();
if (l.contains(null)) {
err.log("Children are: "); // NOI18N
for (Iterator it = l.iterator(); it.hasNext(); ) {
err.log(" " + it.next()); // NOI18N
}
throw new IllegalStateException("VisualizerNode.children() contains null!"); // NOI18N
}
return java.util.Collections.enumeration(l);
}
return java.util.Collections.enumeration (getChildren ());
}
public boolean isLeaf() {
return node.isLeaf ();
}
public javax.swing.tree.TreeNode getParent() {
Node parent = node.getParentNode ();
return parent == null ? null : getVisualizer (null, parent);
}
// **********************************************
// Can be called under Children.MUTEX.writeAccess
// **********************************************
/** Fired when a set of new children is added.
* @param ev event describing the action
*/
public void childrenAdded(NodeMemberEvent ev) {
VisualizerChildren ch = (VisualizerChildren)children.get ();
if (ch == null) return;
QUEUE.runSafe (new VisualizerEvent.Added (
ch, ev.getDelta (), ev.getDeltaIndices ()
));
}
/** Fired when a set of children is removed.
* @param ev event describing the action
*/
public void childrenRemoved(NodeMemberEvent ev) {
VisualizerChildren ch = (VisualizerChildren)children.get ();
if (ch == null) return;
QUEUE.runSafe (new VisualizerEvent.Removed (ch, ev.getDelta ()) );
}
/** Fired when the order of children is changed.
* @param ev event describing the change
*/
public void childrenReordered(NodeReorderEvent ev) {
doChildrenReordered (ev.getPermutation ());
}
// helper method (called from TreeTableView.sort)
void doChildrenReordered (int[] perm) {
VisualizerChildren ch = (VisualizerChildren)children.get ();
if (ch == null) return;
QUEUE.runSafe (new VisualizerEvent.Reordered (ch, perm));
}
/** Fired when the node is deleted.
* @param ev event describing the node
*/
public void nodeDestroyed(NodeEvent ev) {
// ignore for now
}
/** Change in the node properties (icon, etc.)
*/
public void propertyChange(final java.beans.PropertyChangeEvent evt) {
String name = evt.getPropertyName ();
if (
Node.PROP_NAME.equals (name) ||
Node.PROP_DISPLAY_NAME.equals (name) ||
Node.PROP_SHORT_DESCRIPTION.equals (name) ||
Node.PROP_ICON.equals (name) ||
Node.PROP_OPENED_ICON.equals (name)
) {
SwingUtilities.invokeLater (this);
return;
}
if ( Node.PROP_LEAF.equals( name ) ) {
SwingUtilities.invokeLater( new Runnable() {
public void run() {
children = NO_REF;
// notify models
VisualizerNode parent = VisualizerNode.this;
while (parent != null) {
Object[] listeners = parent.getListenerList ();
for (int i = listeners.length - 1; i >= 0; i -= 2) {
((NodeModel)listeners[i]).structuralChange (VisualizerNode.this);
}
parent = (VisualizerNode)parent.getParent ();
}
}
} );
}
/*
if (
"lookAndFeel".equals (name) // NOI18N
) {
SwingUtilities.invokeLater (this);
}
*/
}
/** Update the state of this class by retrieving new name, etc.
* And fire change to all listeners. Only by AWT-Event-Queue
*/
public void run () {
name = node.getName ();
displayName = node.getDisplayName ();
shortDescription = UNKNOWN;
//
// notify models
//
VisualizerNode parent = this;
while (parent != null) {
Object[] listeners = parent.getListenerList ();
for (int i = listeners.length - 1; i >= 0; i -= 2) {
((NodeModel)listeners[i]).update (this);
}
parent = (VisualizerNode)parent.getParent ();
}
}
//
// Access to VisualizerChildren
//
/** Notifies change in the amount of children. This is used to distinguish between
* weak and hard reference. Called from VisualizerChildren
* @param size amount of children
* @param ch the children
*/
void notifyVisualizerChildrenChange (int size, VisualizerChildren ch) {
if (size == 0) {
// hold the children hard
children = new StrongReference (ch);
} else {
children = new WeakReference (ch);
}
}
// ********************************
// This can be called from anywhere
// ********************************
/** Adds visualizer listener.
*/
public synchronized void addNodeModel (NodeModel l) {
add (NodeModel.class, l);
}
/** Removes visualizer listener.
*/
public synchronized void removeNodeModel (NodeModel l) {
remove (NodeModel.class, l);
}
/** Hash code
*/
public int hashCode () {
return hashCode;
}
/** Equals two objects are equal if they have the same hash code
*/
public boolean equals (Object o) {
if (!(o instanceof VisualizerNode)) return false;
VisualizerNode v = (VisualizerNode)o;
return v.node == node;
}
/** String name is taken from the node.
*/
public String toString () {
return getDisplayName ();
}
/** Strong reference.
*/
private static final class StrongReference extends WeakReference {
private Object o;
public StrongReference (Object o) {
super (null);
this.o = o;
}
public Object get () {
return o;
}
}
/** Class that processes runnables in event queue. It guarantees that
* the order of processed objects will be exactly the same as they
* arrived.
*/
private static final class QP extends Object implements Runnable {
QP() {}
/** queue of all requests (Runnable) that should be processed
* AWT-Event queue.
*/
private QueueEnumeration queue = null;
/** Runs the runnable in event thread.
* @param run what should run
*/
public void runSafe (Runnable run) {
boolean isNew = false;
synchronized (this) {
// access to queue variable is synchronized
if (queue == null) {
queue = new QueueEnumeration ();
isNew = true;
}
queue.put (run);
}
if (isNew) {
// either starts the processing of the queue immediatelly
// (if we are in AWT-Event thread) or uses
// SwingUtilities.invokeLater to do so
Mutex.EVENT.writeAccess (this);
}
}
/** Processes the queue.
*/
public void run () {
QueueEnumeration en;
synchronized (this) {
// access to queue variable is synchronized
en = queue;
queue = null;
}
while (en.hasMoreElements ()) {
Runnable r = (Runnable)en.nextElement ();
r.run ();
}
}
}
}