KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > explorer > view > VisualizerNode


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.openide.explorer.view;
20
21 import java.util.logging.Level JavaDoc;
22 import java.util.logging.Logger JavaDoc;
23 import org.openide.nodes.*;
24 import org.openide.util.Mutex;
25 import org.openide.util.Utilities;
26
27 import java.awt.Image JavaDoc;
28
29 import java.beans.BeanInfo JavaDoc;
30
31 import java.lang.ref.Reference JavaDoc;
32 import java.lang.ref.WeakReference JavaDoc;
33
34 import java.util.*;
35
36 import javax.swing.Icon JavaDoc;
37 import javax.swing.ImageIcon JavaDoc;
38 import javax.swing.SwingUtilities JavaDoc;
39 import javax.swing.event.EventListenerList JavaDoc;
40 import javax.swing.tree.TreeNode JavaDoc;
41
42
43 /** Visual representation of one node. Holds necessary information about nodes
44 * like icon, name, description and also list of its children.
45 * <P>
46 * There is at most one VisualizerNode for one node. All of them are held in a cache.
47 * <P>
48 * VisualizerNode provides a thread-safe layer between Nodes, which may fire
49 * property changes on any thread, and the AWT dispatch thread
50 * thread.
51 *
52 * @author Jaroslav Tulach
53 */

54 final class VisualizerNode extends EventListenerList JavaDoc implements NodeListener, TreeNode JavaDoc, Runnable JavaDoc {
55     /** one template to use for searching for visualizers */
56     private static final VisualizerNode TEMPLATE = new VisualizerNode(0);
57     /** a shared logger for the visualizer functionality */
58     static final Logger JavaDoc LOG = Logger.getLogger(VisualizerNode.class.getName());
59
60     /** constant holding empty reference to children */
61     private static final Reference JavaDoc<VisualizerChildren> NO_REF = new WeakReference JavaDoc<VisualizerChildren>(null);
62
63     /** cache of visualizers */
64     private static WeakHashMap<VisualizerNode, Reference JavaDoc<VisualizerNode>> cache =
65             new WeakHashMap<VisualizerNode, Reference JavaDoc<VisualizerNode>>();
66
67     /** empty visualizer */
68     public static final VisualizerNode EMPTY = getVisualizer(null, Node.EMPTY);
69
70     /** queue processor to transfer requests to event queue */
71     private static final QP QUEUE = new QP();
72     private static final String JavaDoc UNKNOWN = new String JavaDoc();
73     static final long serialVersionUID = 3726728244698316872L;
74     private static final String JavaDoc NO_HTML_DISPLAYNAME = "noHtmlDisplayName"; //NOI18N
75

76     /** loaded default icon */
77     private static Icon JavaDoc defaultIcon;
78
79     /** default icon to use when none is present */
80     private static final String JavaDoc DEFAULT_ICON = "org/openide/nodes/defaultNode.gif"; // NOI18N
81

82     /** Cached icon - pre-html, there was a separate cache in NodeRenderer, but
83      * if we're keeping a weak cache of VisualizerNodes, there's no reason not
84      * to keep it here */

85     private Icon JavaDoc icon = null;
86
87     /** node. Do not modify!!! */
88     Node JavaDoc node;
89
90     /** system hashcode of the node */
91     private int hashCode;
92
93     /** visualizer children attached thru weak references Reference (VisualizerChildren) */
94     private Reference JavaDoc<VisualizerChildren> children = NO_REF;
95
96     /** the VisualizerChildren that contains this VisualizerNode or null */
97     private VisualizerChildren parent;
98
99     /** cached name */
100     private String JavaDoc name;
101
102     /** cached display name */
103     private String JavaDoc displayName;
104
105     /** cached short description */
106     private String JavaDoc shortDescription;
107     private transient boolean inRead;
108     private String JavaDoc htmlDisplayName = null;
109     private int cachedIconType = -1;
110
111     /** Constructor that creates template for the node.
112     */

113     private VisualizerNode(int hashCode) {
114         this.hashCode = hashCode;
115         this.node = null;
116     }
117
118     /** Creates new VisualizerNode
119     * @param n node to refer to
120     */

121     private VisualizerNode(Node JavaDoc n) {
122         node = n;
123         hashCode = System.identityHashCode(node);
124
125         // attach as a listener
126
node.addNodeListener(NodeOp.weakNodeListener(this, node));
127
128         // uiListener = WeakListener.propertyChange (this, null);
129
// UIManager.addPropertyChangeListener (uiListener);
130
name = UNKNOWN;
131         displayName = UNKNOWN;
132         shortDescription = UNKNOWN;
133     }
134
135     // bugfix #29435, getVisualizer is synchronized in place of be called only from EventQueue
136

137     /** Finds VisualizerNode for given node.
138     * @param ch the children this visualizer should belong to
139     * @param n the node
140     * @return the visualizer
141     */

142     public static VisualizerNode getVisualizer(VisualizerChildren ch, Node JavaDoc n) {
143         return getVisualizer(ch, n, true);
144     }
145
146     /** Finds VisualizerNode for given node.
147     * @param ch the children this visualizer should belong to
148     * @param n the node
149     * @return the visualizer or null
150     */

151     public static synchronized VisualizerNode getVisualizer(VisualizerChildren ch, Node JavaDoc n, boolean create) {
152         TEMPLATE.hashCode = System.identityHashCode(n);
153         TEMPLATE.node = n;
154
155         Reference JavaDoc<VisualizerNode> r = cache.get(TEMPLATE);
156
157         TEMPLATE.hashCode = 0;
158         TEMPLATE.node = null;
159
160         VisualizerNode v = (r == null) ? null : r.get();
161
162         if (v == null) {
163             if (!create) {
164                 return null;
165             }
166
167             v = new VisualizerNode(n);
168             cache.put(v, new WeakReference JavaDoc<VisualizerNode>(v));
169         }
170
171         if (ch != null) {
172             v.parent = ch;
173         }
174
175         return v;
176     }
177
178     /** Returns cached short description.
179      * @return short description of represented node
180      */

181     public String JavaDoc getShortDescription() {
182         String JavaDoc desc = shortDescription;
183
184         if (desc == UNKNOWN) {
185             shortDescription = desc = node.getShortDescription();
186         }
187
188         return desc;
189     }
190
191     /** Returns cached display name.
192      * @return display name of represented node
193      */

194     public String JavaDoc getDisplayName() {
195         if (displayName == UNKNOWN) {
196             displayName = (node == null) ? null : node.getDisplayName();
197         }
198
199         return displayName;
200     }
201
202     /** Returns cached name.
203      * @return name of represented node
204      */

205     public String JavaDoc getName() {
206         if (name == UNKNOWN) {
207             name = (node == null) ? null : node.getName();
208         }
209
210         return name;
211     }
212
213     /** Getter for list of children of this visualizer.
214     * @return list of VisualizerNode objects
215     */

216     public List JavaDoc<VisualizerNode> getChildren() {
217         VisualizerChildren ch = children.get();
218
219         if ((ch == null) && !node.isLeaf()) {
220             // initialize the nodes children before we enter
221
// the readAccess section
222
Node JavaDoc[] tmpInit = node.getChildren().getNodes();
223
224             // go into lock to ensure that no childrenAdded, childrenRemoved,
225
// childrenReordered notifications occures and that is why we do
226
// not loose any changes
227
ch = Children.MUTEX.readAccess(
228                     new Mutex.Action<VisualizerChildren>() {
229                         public VisualizerChildren run() {
230                             Node JavaDoc[] nodes = node.getChildren().getNodes();
231                             VisualizerChildren vc = new VisualizerChildren(VisualizerNode.this, nodes);
232                             notifyVisualizerChildrenChange(nodes.length, vc);
233
234                             return vc;
235                         }
236                     }
237                 );
238         }
239
240         if (LOG.isLoggable(Level.FINER)) {
241             // this assert is too expensive during the performance tests:
242
assert (ch == null) || !ch.list.contains(null) : ch.list + " from " + node;
243         }
244
245         return (ch == null) ? Collections.<VisualizerNode>emptyList() : ch.list;
246     }
247
248     //
249
// TreeNode interface (callable only from AWT-Event-Queue)
250
//
251
public int getIndex(final javax.swing.tree.TreeNode JavaDoc p1) {
252         return getChildren().indexOf(p1);
253     }
254
255     public boolean getAllowsChildren() {
256         return !isLeaf();
257     }
258
259     public javax.swing.tree.TreeNode JavaDoc getChildAt(int p1) {
260         List JavaDoc ch = getChildren();
261         VisualizerNode vn = (VisualizerNode) ch.get(p1);
262         assert vn != null : "Null child in " + ch + " from " + node;
263
264         return vn;
265     }
266
267     public int getChildCount() {
268         return getChildren().size();
269     }
270
271     public java.util.Enumeration JavaDoc<VisualizerNode> children() {
272         List JavaDoc<VisualizerNode> l = getChildren();
273         assert !l.contains(null) : "Null child in " + l + " from " + node;
274
275         return java.util.Collections.enumeration(l);
276     }
277
278     public boolean isLeaf() {
279         return node.isLeaf();
280     }
281
282     public javax.swing.tree.TreeNode JavaDoc getParent() {
283         Node JavaDoc parent = node.getParentNode();
284
285         return (parent == null) ? null : getVisualizer(null, parent);
286     }
287
288     // **********************************************
289
// Can be called under Children.MUTEX.writeAccess
290
// **********************************************
291

292     /** Fired when a set of new children is added.
293     * @param ev event describing the action
294     */

295     public void childrenAdded(NodeMemberEvent ev) {
296         VisualizerChildren ch = children.get();
297
298         LOG.log(Level.FINER, "childrenAdded {0}", ev); // NOI18N
299
if (ch == null) {
300             LOG.log(Level.FINER, "childrenAdded - exit"); // NOI18N
301
return;
302         }
303
304         QUEUE.runSafe(new VisualizerEvent.Added(ch, ev.getDelta(), ev.getDeltaIndices()));
305         LOG.log(Level.FINER, "childrenAdded - end"); // NOI18N
306
}
307
308     /** Fired when a set of children is removed.
309     * @param ev event describing the action
310     */

311     public void childrenRemoved(NodeMemberEvent ev) {
312         VisualizerChildren ch = children.get();
313
314         LOG.log(Level.FINER, "childrenRemoved {0}", ev); // NOI18N
315
if (ch == null) {
316             LOG.log(Level.FINER, "childrenRemoved - exit"); // NOI18N
317
return;
318         }
319
320         QUEUE.runSafe(new VisualizerEvent.Removed(ch, ev.getDelta()));
321         LOG.log(Level.FINER, "childrenRemoved - end"); // NOI18N
322
}
323
324     /** Fired when the order of children is changed.
325     * @param ev event describing the change
326     */

327     public void childrenReordered(NodeReorderEvent ev) {
328         doChildrenReordered(ev.getPermutation());
329     }
330
331     // helper method (called from TreeTableView.sort)
332
void doChildrenReordered(int[] perm) {
333         VisualizerChildren ch = children.get();
334
335         LOG.log(Level.FINER, "childrenReordered {0}", perm); // NOI18N
336
if (ch == null) {
337             LOG.log(Level.FINER, "childrenReordered - exit"); // NOI18N
338
return;
339         }
340
341         QUEUE.runSafe(new VisualizerEvent.Reordered(ch, perm));
342         LOG.log(Level.FINER, "childrenReordered - end"); // NOI18N
343
}
344
345     void reorderChildren(Comparator<VisualizerNode> c) {
346         assert SwingUtilities.isEventDispatchThread();
347
348         VisualizerChildren ch = children.get();
349
350         if (ch == null) {
351             return;
352         }
353
354         new VisualizerEvent.Reordered(ch, c).run();
355     }
356
357     void naturalOrder() {
358         //force new creation of the children list in the natural order
359
children.clear();
360         getChildren();
361
362         //sort the children list with a dummy comparator to throw events needed
363
reorderChildren(
364             new Comparator<VisualizerNode>() {
365                 public int compare(VisualizerNode o1, VisualizerNode o2) {
366                     return 0;
367                 }
368             }
369         );
370     }
371
372     /** Fired when the node is deleted.
373     * @param ev event describing the node
374     */

375     public void nodeDestroyed(NodeEvent ev) {
376         // ignore for now
377
}
378
379     /** Change in the node properties (icon, etc.)
380     */

381     public void propertyChange(final java.beans.PropertyChangeEvent JavaDoc evt) {
382         String JavaDoc name = evt.getPropertyName();
383         boolean isIconChange = Node.PROP_ICON.equals(name) || Node.PROP_OPENED_ICON.equals(name);
384
385         if (Node.PROP_NAME.equals(name) || Node.PROP_DISPLAY_NAME.equals(name) || isIconChange) {
386             if (isIconChange) {
387                 //Ditch the cached icon type so the next call to getIcon() will
388
//recreate the ImageIcon
389
cachedIconType = -1;
390             }
391
392             if (Node.PROP_DISPLAY_NAME.equals(name)) {
393                 htmlDisplayName = null;
394             }
395
396             SwingUtilities.invokeLater(this);
397
398             return;
399         }
400
401         // bugfix #37748, VisualizerNode ignores change of short desc if it is not read yet (set to UNKNOWN)
402
if (Node.PROP_SHORT_DESCRIPTION.equals(name) && (shortDescription != UNKNOWN)) {
403             SwingUtilities.invokeLater(this);
404
405             return;
406         }
407
408         if (Node.PROP_LEAF.equals(name)) {
409             SwingUtilities.invokeLater(
410                 new Runnable JavaDoc() {
411                     public void run() {
412                         children = NO_REF;
413
414                         // notify models
415
VisualizerNode parent = VisualizerNode.this;
416
417                         while (parent != null) {
418                             Object JavaDoc[] listeners = parent.getListenerList();
419
420                             for (int i = listeners.length - 1; i >= 0; i -= 2) {
421                                 ((NodeModel) listeners[i]).structuralChange(VisualizerNode.this);
422                             }
423
424                             parent = (VisualizerNode) parent.getParent();
425                         }
426                     }
427                 }
428             );
429         }
430     }
431
432     /** Update the state of this class by retrieving new name, etc.
433     * And fire change to all listeners. Only by AWT-Event-Queue
434     */

435     public void run() {
436         if (!inRead) {
437             try {
438                 // call the foreign code under the read lock
439
// so all potential structure modifications
440
// are queued until after we finish.
441
// see issue #48993
442
inRead = true;
443                 Children.MUTEX.readAccess(this);
444             } finally {
445                 inRead = false;
446             }
447
448             return;
449         }
450
451         name = node.getName();
452         displayName = node.getDisplayName();
453         shortDescription = UNKNOWN;
454
455         //
456
// notify models
457
//
458
VisualizerNode parent = this;
459
460         while (parent != null) {
461             Object JavaDoc[] listeners = parent.getListenerList();
462
463             for (int i = listeners.length - 1; i >= 0; i -= 2) {
464                 ((NodeModel) listeners[i]).update(this);
465             }
466
467             parent = (VisualizerNode) parent.getParent();
468         }
469     }
470
471     //
472
// Access to VisualizerChildren
473
//
474

475     /** Notifies change in the amount of children. This is used to distinguish between
476     * weak and hard reference. Called from VisualizerChildren
477     * @param size amount of children
478     * @param ch the children
479     */

480     void notifyVisualizerChildrenChange(int size, VisualizerChildren ch) {
481         if (size == 0) {
482             // hold the children hard
483
children = new StrongReference<VisualizerChildren>(ch);
484         } else {
485             children = new WeakReference JavaDoc<VisualizerChildren>(ch);
486         }
487     }
488
489     // ********************************
490
// This can be called from anywhere
491
// ********************************
492

493     /** Adds visualizer listener.
494     */

495     public synchronized void addNodeModel(NodeModel l) {
496         add(NodeModel.class, l);
497     }
498
499     /** Removes visualizer listener.
500     */

501     public synchronized void removeNodeModel(NodeModel l) {
502         remove(NodeModel.class, l);
503     }
504
505     /** Hash code
506     */

507     public int hashCode() {
508         return hashCode;
509     }
510
511     /** Equals two objects are equal if they have the same hash code
512     */

513     public boolean equals(Object JavaDoc o) {
514         if (!(o instanceof VisualizerNode)) {
515             return false;
516         }
517
518         VisualizerNode v = (VisualizerNode) o;
519
520         return v.node == node;
521     }
522
523     /** String name is taken from the node.
524     */

525     public String JavaDoc toString() {
526         return getDisplayName();
527     }
528
529     public String JavaDoc getHtmlDisplayName() {
530         if (htmlDisplayName == null) {
531             htmlDisplayName = node.getHtmlDisplayName();
532
533             if (htmlDisplayName == null) {
534                 htmlDisplayName = NO_HTML_DISPLAYNAME;
535             }
536         }
537
538         return (htmlDisplayName == NO_HTML_DISPLAYNAME) ? null : htmlDisplayName;
539     }
540
541     Icon JavaDoc getIcon(boolean opened, boolean large) {
542         int newCacheType = getCacheType(opened, large);
543
544         if (cachedIconType != newCacheType) {
545             int iconType = large ? BeanInfo.ICON_COLOR_32x32 : BeanInfo.ICON_COLOR_16x16;
546
547             Image JavaDoc image = opened ? node.getOpenedIcon(iconType) : node.getIcon(iconType);
548
549             // bugfix #28515, check if getIcon contract isn't broken
550
if (image == null) {
551                 String JavaDoc method = opened ? "getOpenedIcon" : "getIcon"; // NOI18N
552
LOG.warning(
553                     "Node \"" + node.getName() + "\" [" + node.getClass().getName() + "] cannot return null from " +
554                     method + "(). See Node." + method + " contract."
555                 ); // NOI18N
556

557                 icon = getDefaultIcon();
558             } else {
559                 icon = new ImageIcon JavaDoc(image);
560             }
561         }
562
563         cachedIconType = newCacheType;
564
565         return icon;
566     }
567
568     /** Some simple bitmasking to determine the type of the cached icon.
569      * Generally, it's worth caching one, but not a bunch - generally one will
570      * be used repeatedly. */

571     private static final int getCacheType(boolean opened, boolean large) {
572         return (opened ? 2 : 0) | (large ? 1 : 0);
573     }
574
575     /** Loads default icon if not loaded. */
576     private static Icon JavaDoc getDefaultIcon() {
577         if (defaultIcon == null) {
578             defaultIcon = new ImageIcon JavaDoc(Utilities.loadImage(DEFAULT_ICON));
579         }
580
581         return defaultIcon;
582     }
583
584     static void runQueue() {
585         QUEUE.run();
586     }
587
588     /** Strong reference.
589     */

590     private static final class StrongReference<T> extends WeakReference JavaDoc<T> {
591         private T o;
592
593         public StrongReference(T o) {
594             super(null);
595             this.o = o;
596         }
597
598         public T get() {
599             return o;
600         }
601     }
602
603     /** Class that processes runnables in event queue. It guarantees that
604     * the order of processed objects will be exactly the same as they
605     * arrived.
606     */

607     private static final class QP extends Object JavaDoc implements Runnable JavaDoc {
608         /** queue of all requests (Runnable) that should be processed
609          * AWT-Event queue.
610          */

611         private LinkedList<Runnable JavaDoc> queue = null;
612
613         QP() {
614         }
615
616         /** Runs the runnable in event thread.
617          * @param run what should run
618          */

619         public void runSafe(Runnable JavaDoc run) {
620             boolean isNew = false;
621
622             synchronized (this) {
623                 // access to queue variable is synchronized
624
if (queue == null) {
625                     queue = new LinkedList<Runnable JavaDoc>();
626                     isNew = true;
627                 }
628
629                 queue.add(run);
630             }
631
632             if (isNew) {
633                 // either starts the processing of the queue immediatelly
634
// (if we are in AWT-Event thread) or uses
635
// SwingUtilities.invokeLater to do so
636
Mutex.EVENT.writeAccess(this);
637             }
638         }
639
640         /** Processes the queue.
641          */

642         public void run() {
643             Enumeration<Runnable JavaDoc> en;
644
645             synchronized (this) {
646                 // access to queue variable is synchronized
647
if (queue == null) {
648                     LOG.log(Level.FINER, "Queue empty"); // NOI18N
649
return;
650                 }
651
652                 en = Collections.enumeration(queue);
653                 queue = null;
654                 LOG.log(Level.FINER, "Queue emptied"); // NOI18N
655
}
656
657             while (en.hasMoreElements()) {
658                 Runnable JavaDoc r = en.nextElement();
659                 LOG.log(Level.FINER, "Running {0}", r); // NOI18N
660
Children.MUTEX.readAccess(r); // run the update under Children.MUTEX
661
LOG.log(Level.FINER, "Finished {0}", r); // NOI18N
662
}
663             LOG.log(Level.FINER, "Queue processing over"); // NOI18N
664
}
665     }
666 }
667
Popular Tags