KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > nodes > FilterNode


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.nodes;
20
21 import java.lang.ref.Reference JavaDoc;
22 import java.lang.reflect.Method JavaDoc;
23 import org.openide.util.HelpCtx;
24 import org.openide.util.Lookup;
25 import org.openide.util.LookupEvent;
26 import org.openide.util.LookupListener;
27 import org.openide.util.WeakSet;
28 import org.openide.util.actions.SystemAction;
29 import org.openide.util.datatransfer.NewType;
30 import org.openide.util.datatransfer.PasteType;
31
32 import java.awt.Image JavaDoc;
33 import java.awt.datatransfer.Transferable JavaDoc;
34
35 import java.beans.PropertyChangeEvent JavaDoc;
36 import java.beans.PropertyChangeListener JavaDoc;
37
38 import java.io.IOException JavaDoc;
39
40 import java.lang.ref.WeakReference JavaDoc;
41
42 import java.util.*;
43 import java.util.logging.Level JavaDoc;
44 import java.util.logging.Logger JavaDoc;
45 import org.openide.nodes.Node.PropertySet;
46 import org.openide.util.Exceptions;
47 import org.openide.util.Lookup.Item;
48
49
50 /** A proxy for another node.
51 * Unless otherwise mentioned, all methods of the original node are delegated to.
52 * If desired, you can disable delegation of certain methods which are concrete in <code>Node</code>
53 * by calling {@link #disableDelegation}.
54 *
55 * <p><strong>Note:</strong> it is fine to subclass this class and use
56 * it to filter things. But please do not ever try to cast a node to
57 * <code>FilterNode</code>: it probably means you are doing something
58 * wrong. Instead, ask whatever <code>Node</code> you have for a proper
59 * kind of cookie (e.g. <code>DataObject</code>).
60 *
61 * @author Jaroslav Tulach
62 */

63 public class FilterNode extends Node {
64     /** Whether to delegate <code>setName</code>. */
65     protected static final int DELEGATE_SET_NAME = 1 << 0;
66
67     /** Whether to delegate <code>getName</code>. */
68     protected static final int DELEGATE_GET_NAME = 1 << 1;
69
70     /** Whether to delegate <code>setDisplayName</code>. */
71     protected static final int DELEGATE_SET_DISPLAY_NAME = 1 << 2;
72
73     /** Whether to delegate <code>getDisplayName</code>. */
74     protected static final int DELEGATE_GET_DISPLAY_NAME = 1 << 3;
75
76     /** Whether to delegate <code>setShortDescription</code>. */
77     protected static final int DELEGATE_SET_SHORT_DESCRIPTION = 1 << 4;
78
79     /** Whether to delegate <code>getShortDescription</code>. */
80     protected static final int DELEGATE_GET_SHORT_DESCRIPTION = 1 << 5;
81
82     /** Whether to delegate <code>destroy</code>. */
83     protected static final int DELEGATE_DESTROY = 1 << 6;
84
85     /** Whether to delegate <code>getActions</code>. */
86     protected static final int DELEGATE_GET_ACTIONS = 1 << 7;
87
88     /** Whether to delegate <code>getContextActions</code>. */
89     protected static final int DELEGATE_GET_CONTEXT_ACTIONS = 1 << 8;
90
91     /** Whether to delegate <code>setValue</code>.
92      * @since 4.25
93      */

94     protected static final int DELEGATE_SET_VALUE = 1 << 9;
95
96     /** Whether to delegate <code>getValue</code>.
97      * @since 4.25
98      */

99     protected static final int DELEGATE_GET_VALUE = 1 << 10;
100
101     /** Mask indicating delegation of all possible methods. */
102     private static final int DELEGATE_ALL = DELEGATE_SET_NAME | DELEGATE_GET_NAME | DELEGATE_SET_DISPLAY_NAME |
103         DELEGATE_GET_DISPLAY_NAME | DELEGATE_SET_SHORT_DESCRIPTION | DELEGATE_GET_SHORT_DESCRIPTION | DELEGATE_DESTROY |
104         DELEGATE_GET_ACTIONS | DELEGATE_GET_CONTEXT_ACTIONS | DELEGATE_SET_VALUE | DELEGATE_GET_VALUE;
105     private static final Map<Class JavaDoc<?>,Boolean JavaDoc> overridesGetDisplayNameCache = new WeakHashMap<Class JavaDoc<?>,Boolean JavaDoc>(27);
106     private static final Map<Class JavaDoc<?>,Boolean JavaDoc> replaceProvidedLookupCache = new WeakHashMap<Class JavaDoc<?>,Boolean JavaDoc>(27);
107
108     /** Depth of stack trace.
109      */

110     private static volatile int hashCodeDepth;
111
112     /** node to delegate to */
113     private Node original;
114
115     /** listener to property changes,
116     * accessible thru getPropertyChangeListener
117     */

118     private PropertyChangeListener JavaDoc propL;
119
120     /** listener to node changes
121     * Accessible thru get node listener
122     */

123     private NodeListener nodeL;
124
125     // Note: int (not long) to avoid need to ever synchronize when accessing it
126
// (Java VM spec does not guarantee that long's will be stored atomically)
127

128     /** @see #delegating */
129     private int delegateMask;
130
131     /** Is PropertyChangeListener attached to original node */
132     private boolean pchlAttached = false;
133
134     /** children provided or created the default ones? */
135     private boolean childrenProvided;
136
137     /** Create proxy.
138     * @param original the node to delegate to
139     */

140     public FilterNode(Node original) {
141         this(original, null);
142     }
143
144     /** Create proxy with a different set of children.
145     *
146     * @param original the node to delegate to
147     * @param children a set of children for this node
148     */

149     public FilterNode(Node original, org.openide.nodes.Children children) {
150         this(original, children, new FilterLookup());
151     }
152
153     /** Constructs new filter node with a provided children and lookup.
154      * The lookup is used to implement {@link FilterNode#getCookie} calls that just call
155      * <code>lookup.lookup(clazz)</code>. If this constructor is used,
156      * the code shall not override {@link FilterNode#getCookie} method, but do all
157      * its state manipulation in the lookup. Look at {@link Node#Node}
158      * constructor for best practices usage of this constructor.
159      *
160      * @param original the node we delegate to
161      * @param children the children to use for the filter node or <code>null</code> if
162      * default children should be provided
163      * @param lookup lookup to use. Do not pass <CODE>orginal.getLookup()</CODE> into this parameter.
164      * In such case use the {@link #FilterNode(Node, Children)} constructor.
165      *
166      * @since 4.4
167      */

168     public FilterNode(Node original, org.openide.nodes.Children children, Lookup lookup) {
169         super(
170             (children == null) ? (original.isLeaf() ? org.openide.nodes.Children.LEAF : new Children(original)) : children,
171             lookup
172         );
173
174         this.childrenProvided = children != null;
175         this.original = original;
176         init();
177
178         Lookup lkp = internalLookup(false);
179
180         if (lkp instanceof FilterLookup) {
181             ((FilterLookup) lkp).ownNode(this);
182         } else {
183             if (lkp == null) {
184                 // rely on default NodeLookup around getCookie.
185
getNodeListener();
186             }
187         }
188     }
189
190     /** Overrides package private method of a node that allows us to say
191      * that the lookup provided in the constructor should be replaced by
192      * something else
193      *
194      * @param lookup
195      * @return lookup or null
196      */

197     final Lookup replaceProvidedLookup(Lookup lookup) {
198         synchronized (replaceProvidedLookupCache) {
199             Boolean JavaDoc b = replaceProvidedLookupCache.get(getClass());
200
201             if (b == null) {
202                 b = !overridesAMethod("getCookie", Class JavaDoc.class); // NOI18N
203
replaceProvidedLookupCache.put(getClass(), b);
204             }
205
206             return b ? lookup : null;
207         }
208     }
209
210     /** Checks whether subclass overrides a method
211      */

212     private boolean overridesAMethod(String JavaDoc name, Class JavaDoc... arguments) {
213         if (getClass() == FilterNode.class) {
214             return false;
215         }
216
217         // we are subclass of FilterNode
218
try {
219             Method JavaDoc m = getClass().getMethod(name, arguments);
220
221             if (m.getDeclaringClass() != FilterNode.class) {
222                 // ok somebody overriden getCookie method
223
return true;
224             }
225         } catch (NoSuchMethodException JavaDoc ex) {
226             Exceptions.printStackTrace(ex);
227         }
228
229         return false;
230     }
231
232     /** Initializes the node.
233     */

234     private void init() {
235         delegateMask = DELEGATE_ALL;
236     }
237
238     void notifyPropertyChangeListenerAdded(PropertyChangeListener JavaDoc l) {
239         if (!pchlAttached) {
240             original.addPropertyChangeListener(getPropertyChangeListener());
241             pchlAttached = true;
242         }
243     }
244
245     void notifyPropertyChangeListenerRemoved(PropertyChangeListener JavaDoc l) {
246         if (getPropertyChangeListenersCount() == 0) {
247             original.removePropertyChangeListener(getPropertyChangeListener());
248             pchlAttached = false;
249         }
250     }
251
252     /** Removes all listeners (property and node) on
253     * the original node. Called from {@link NodeListener#nodeDestroyed},
254     * but can be called by any subclass to stop reflecting changes
255     * in the original node.
256     */

257     @Override JavaDoc
258     protected void finalize() {
259         original.removePropertyChangeListener(getPropertyChangeListener());
260         original.removeNodeListener(getNodeListener());
261     }
262
263     /** Enable delegation of a set of methods.
264     * These will be delegated to the original node.
265     * Since all available methods are delegated by default, normally you will not need to call this.
266     * @param mask bitwise disjunction of <code>DELEGATE_XXX</code> constants
267     * @throws IllegalArgumentException if the mask is invalid
268     */

269     protected final void enableDelegation(int mask) {
270         if ((mask & ~DELEGATE_ALL) != 0) {
271             throw new IllegalArgumentException JavaDoc("Bad delegation mask: " + mask); // NOI18N
272
}
273
274         delegateMask |= mask;
275     }
276
277     /** Disable delegation of a set of methods.
278     * The methods will retain their behavior from {@link Node}.
279     * <p>For example, if you wish to subclass <code>FilterNode</code>, giving your
280     * node a distinctive display name and tooltip, and performing some special
281     * action upon deletion, you may do so without risk of affecting the original
282     * node as follows:
283     * <br><code><pre>
284     * public MyNode extends FilterNode {
285     * public MyNode (Node orig) {
286     * super (orig, new MyChildren (orig));
287     * disableDelegation (DELEGATE_GET_DISPLAY_NAME | DELEGATE_SET_DISPLAY_NAME |
288     * DELEGATE_GET_SHORT_DESCRIPTION | DELEGATE_SET_SHORT_DESCRIPTION |
289     * DELEGATE_DESTROY);
290     * // these will affect only the filter node:
291     * setDisplayName ("Linking -> " + orig.getDisplayName ());
292     * setShortDescription ("Something different.");
293     * }
294     * public boolean canRename () { return false; }
295     * public void destroy () throws IOException {
296     * doMyCleanup ();
297     * super.destroy (); // calls Node.destroy(), not orig.destroy()
298     * }
299     * }
300     * </pre></code>
301     * <br>You may still manually delegate where desired using {@link #getOriginal}.
302     * Other methods abstract in <code>Node</code> may simply be overridden without
303     * any special handling.
304     * @param mask bitwise disjunction of <code>DELEGATE_XXX</code> constants
305     * @throws IllegalArgumentException if the mask is invalid
306     */

307     protected final void disableDelegation(int mask) {
308         if ((mask & ~DELEGATE_ALL) != 0) {
309             throw new IllegalArgumentException JavaDoc("Bad delegation mask: " + mask); // NOI18N
310
}
311
312         delegateMask &= ~mask;
313     }
314
315     /** Test whether we are currently delegating to some method. */
316     private final boolean delegating(int what) {
317         return (delegateMask & what) != 0;
318     }
319
320     /** Create new filter node for the original.
321     * Subclasses do not have to override this, but if they do not,
322     * the default implementation will filter the subclass filter, which is not
323     * very efficient.
324     * @return copy of this node
325     */

326     public Node cloneNode() {
327         if (isDefault()) {
328             // this is realy filter node without changed behaviour
329
// with the normal children => use normal constructor for the
330
// original node
331
return new FilterNode(original);
332         } else {
333             // create filter node for this node to reflect changed
334
// behaviour
335
return new FilterNode(this);
336         }
337     }
338
339     /** Tries to prevent issue 46993 by checking whether a node
340      * to be set as original is actually pointing to this node.
341      * @exception IllegalArgumentException if the check fails
342      * @return always true
343      */

344     private boolean checkIfIamAccessibleFromOriginal(Node original) {
345         if (this == original) {
346             throw new IllegalArgumentException JavaDoc("Node cannot be its own original (even thru indirect chain)"); // NOI18N
347
}
348
349         if (original instanceof FilterNode) {
350             FilterNode f = (FilterNode) original;
351             checkIfIamAccessibleFromOriginal(f.original);
352         }
353
354         return true;
355     }
356
357     /** Changes the original node for this node.
358      *@param original The new original node.
359      *@param changeChildren If set to <CODE>true</CODE> changes children
360      * of this node according to the new original node. If you pass
361      * children which are not instance of class
362      * {@link FilterNode.Children} into the constructor set this
363      * parameter to <CODE>false</CODE>. Be aware
364      * that this method aquires
365      * write lock on the nodes hierarchy ({@link Children#MUTEX}). Take care not to call this method
366      * under read lock.
367      *
368      *@throws java.lang.IllegalStateException if children which are not
369      * instance of <CODE>FilterNode.Children</CODE> were passed
370      * into the constructor and the method was called with the parameter
371      * <CODE>changeChildren</CODE> set to <CODE>true</CODE>.
372      *@since 1.39
373      */

374     protected final void changeOriginal(Node original, boolean changeChildren) {
375         if (
376             changeChildren && !(getChildren() instanceof FilterNode.Children) &&
377                 !(getChildren() == Children.LEAF /* && original.isLeaf () */)
378         ) {
379             throw new IllegalStateException JavaDoc("Can't change implicitly defined Children on FilterNode"); // NOI18N
380
}
381
382         assert checkIfIamAccessibleFromOriginal(original) : ""; // NOI18N
383

384         try {
385             Children.PR.enterWriteAccess();
386
387             // First remove the listeners from current original node
388
this.original.removeNodeListener(getNodeListener());
389
390             if (pchlAttached) {
391                 this.original.removePropertyChangeListener(getPropertyChangeListener());
392             }
393
394             // Set the new original node
395
this.original = original;
396
397             // attach listeners to new original node
398
this.original.addNodeListener(getNodeListener());
399
400             if (pchlAttached) {
401                 this.original.addPropertyChangeListener(getPropertyChangeListener());
402             }
403
404             // Reset children's original node.
405
if (changeChildren /* && !original.isLeaf () */) {
406                 if (original.isLeaf() && (getChildren() != Children.LEAF)) {
407                     setChildren(Children.LEAF);
408                 } else if (!original.isLeaf() && (getChildren() == Children.LEAF)) {
409                     setChildren(new Children(original));
410                 } else if (!original.isLeaf() && (getChildren() != Children.LEAF)) {
411                     ((FilterNode.Children) getChildren()).changeOriginal(original);
412                 }
413             }
414         } finally {
415             Children.PR.exitWriteAccess();
416         }
417
418         // Fire all sorts of events (everything gets changed after we
419
// reset the original node.)
420
Lookup lkp = internalLookup(false);
421
422         if (lkp instanceof FilterLookup) {
423             ((FilterLookup) lkp).checkNode();
424         }
425
426         fireCookieChange();
427         fireNameChange(null, null);
428         fireDisplayNameChange(null, null);
429         fireShortDescriptionChange(null, null);
430         fireIconChange();
431         fireOpenedIconChange();
432         firePropertySetsChange(null, null);
433     }
434
435     // ------------- START OF DELEGATED METHODS ------------
436
@Override JavaDoc
437     public void setValue(String JavaDoc attributeName, Object JavaDoc value) {
438         if (delegating(DELEGATE_SET_VALUE)) {
439             original.setValue(attributeName, value);
440         } else {
441             super.setValue(attributeName, value);
442         }
443     }
444
445     @Override JavaDoc
446     public Object JavaDoc getValue(String JavaDoc attributeName) {
447         if (delegating(DELEGATE_GET_VALUE)) {
448             return original.getValue(attributeName);
449         } else {
450             return super.getValue(attributeName);
451         }
452     }
453
454     /* Setter for system name. Fires info about property change.
455     * @param s the string
456     */

457     @Override JavaDoc
458     public void setName(String JavaDoc s) {
459         if (delegating(DELEGATE_SET_NAME)) {
460             original.setName(s);
461         } else {
462             super.setName(s);
463         }
464     }
465
466     /* @return the name of the original node
467     */

468     @Override JavaDoc
469     public String JavaDoc getName() {
470         if (delegating(DELEGATE_GET_NAME)) {
471             return original.getName();
472         } else {
473             return super.getName();
474         }
475     }
476
477     /* Setter for display name. Fires info about property change.
478     * @param s the string
479     */

480     @Override JavaDoc
481     public void setDisplayName(String JavaDoc s) {
482         if (delegating(DELEGATE_SET_DISPLAY_NAME)) {
483             original.setDisplayName(s);
484         } else {
485             super.setDisplayName(s);
486         }
487     }
488
489     /* @return the display name of the original node
490     */

491     @Override JavaDoc
492     public String JavaDoc getDisplayName() {
493         if (delegating(DELEGATE_GET_DISPLAY_NAME)) {
494             return original.getDisplayName();
495         } else {
496             return super.getDisplayName();
497         }
498     }
499
500     /* Setter for short description. Fires info about property change.
501     * @param s the string
502     */

503     @Override JavaDoc
504     public void setShortDescription(String JavaDoc s) {
505         if (delegating(DELEGATE_SET_SHORT_DESCRIPTION)) {
506             original.setShortDescription(s);
507         } else {
508             super.setShortDescription(s);
509         }
510     }
511
512     /* @return the description of the original node
513     */

514     @Override JavaDoc
515     public String JavaDoc getShortDescription() {
516         if (delegating(DELEGATE_GET_SHORT_DESCRIPTION)) {
517             return original.getShortDescription();
518         } else {
519             return super.getShortDescription();
520         }
521     }
522
523     /* Finds an icon for this node. Delegates to the original.
524     *
525     * @see java.bean.BeanInfo
526     * @param type constants from <CODE>java.bean.BeanInfo</CODE>
527     * @return icon to use to represent the bean
528     */

529     public Image JavaDoc getIcon(int type) {
530         return original.getIcon(type);
531     }
532
533     /* Finds an icon for this node. This icon should represent the node
534     * when it is opened (if it can have children). Delegates to original.
535     *
536     * @see java.bean.BeanInfo
537     * @param type constants from <CODE>java.bean.BeanInfo</CODE>
538     * @return icon to use to represent the bean when opened
539     */

540     public Image JavaDoc getOpenedIcon(int type) {
541         return original.getOpenedIcon(type);
542     }
543
544     public HelpCtx getHelpCtx() {
545         return original.getHelpCtx();
546     }
547
548     /* Can the original node be renamed?
549     *
550     * @return true if the node can be renamed
551     */

552     public boolean canRename() {
553         return original.canRename();
554     }
555
556     /* Can the original node be deleted?
557     * @return <CODE>true</CODE> if can, <CODE>false</CODE> otherwise
558     */

559     public boolean canDestroy() {
560         return original.canDestroy();
561     }
562
563     /* Degelates the delete operation to original.
564     */

565     @Override JavaDoc
566     public void destroy() throws java.io.IOException JavaDoc {
567         if (delegating(DELEGATE_DESTROY)) {
568             original.destroy();
569         } else {
570             super.destroy();
571         }
572     }
573
574     /** Used to access the destroy method when original nodes
575     * has been deleted
576     */

577     private final void originalDestroyed() {
578         try {
579             super.destroy();
580         } catch (IOException JavaDoc ex) {
581             Logger.getLogger(FilterNode.class.getName()).log(Level.WARNING, null, ex);
582         }
583     }
584
585     /* Getter for the list of property sets. Delegates to original.
586     *
587     * @return the array of property sets.
588     */

589     public PropertySet[] getPropertySets() {
590         return original.getPropertySets();
591     }
592
593     /* Called when an object is to be copied to clipboard.
594     * @return the transferable object dedicated to represent the
595     * content of clipboard
596     * @exception IOException is thrown when the
597     * operation cannot be performed
598     */

599     public Transferable JavaDoc clipboardCopy() throws IOException JavaDoc {
600         return original.clipboardCopy();
601     }
602
603     /* Called when an object is to be cut to clipboard.
604     * @return the transferable object dedicated to represent the
605     * content of clipboard
606     * @exception IOException is thrown when the
607     * operation cannot be performed
608     */

609     public Transferable JavaDoc clipboardCut() throws IOException JavaDoc {
610         return original.clipboardCut();
611     }
612
613     /* Returns true if this object allows copying.
614     * @returns true if this object allows copying.
615     */

616     public boolean canCopy() {
617         return original.canCopy();
618     }
619
620     /* Returns true if this object allows cutting.
621     * @returns true if this object allows cutting.
622     */

623     public boolean canCut() {
624         return original.canCut();
625     }
626
627     public Transferable JavaDoc drag() throws IOException JavaDoc {
628         return original.drag();
629     }
630
631     /* Default implementation that tries to delegate the implementation
632     * to the createPasteTypes method. Simply calls the method and
633     * tries to take the first provided argument. Ignores the action
634     * argument and index.
635     *
636     * @param t the transferable
637     * @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK
638     * @param index index between children the drop occured at or -1 if not specified
639     * @return null if the transferable cannot be accepted or the paste type
640     * to execute when the drop occures
641     */

642     public PasteType getDropType(Transferable JavaDoc t, int action, int index) {
643         return original.getDropType(t, action, index);
644     }
645
646     /* Which paste operations are allowed when transferable t is in clipboard?
647     * @param t the transferable in clipboard
648     * @return array of operations that are allowed
649     */

650     public PasteType[] getPasteTypes(Transferable JavaDoc t) {
651         return original.getPasteTypes(t);
652     }
653
654     /* Support for new types that can be created in this node.
655     * @return array of new type operations that are allowed
656     */

657     public NewType[] getNewTypes() {
658         return original.getNewTypes();
659     }
660
661     /* Delegates to original.
662     *
663     * @return array of system actions that should be in popup menu
664     */

665     @Override JavaDoc
666     @Deprecated JavaDoc
667     public SystemAction[] getActions() {
668         if (delegating(DELEGATE_GET_ACTIONS)) {
669             return original.getActions();
670         } else {
671             return super.getActions();
672         }
673     }
674
675     /* Delegates to original
676     */

677     @Override JavaDoc
678     @Deprecated JavaDoc
679     public SystemAction[] getContextActions() {
680         if (delegating(DELEGATE_GET_CONTEXT_ACTIONS)) {
681             return original.getContextActions();
682         } else {
683             return super.getContextActions();
684         }
685     }
686
687     /*
688     * @return default action of the original node or null
689     */

690     @Override JavaDoc
691     @Deprecated JavaDoc
692     public SystemAction getDefaultAction() {
693         return original.getDefaultAction();
694     }
695
696     @Override JavaDoc
697     public javax.swing.Action JavaDoc[] getActions(boolean context) {
698         if (context) {
699             if (!delegating(DELEGATE_GET_ACTIONS) || overridesAMethod("getContextActions")) { // NOI18N
700

701                 return super.getActions(context);
702             }
703         } else {
704             if (!delegating(DELEGATE_GET_CONTEXT_ACTIONS) || overridesAMethod("getActions")) { // NOI18N
705

706                 return super.getActions(context);
707             }
708         }
709
710         javax.swing.Action JavaDoc[] retValue;
711         retValue = original.getActions(context);
712
713         return retValue;
714     }
715
716     @Override JavaDoc
717     public javax.swing.Action JavaDoc getPreferredAction() {
718         javax.swing.Action JavaDoc retValue;
719
720         if (overridesAMethod("getDefaultAction")) { // NOI18N
721
retValue = super.getPreferredAction();
722         } else {
723             retValue = original.getPreferredAction();
724         }
725
726         return retValue;
727     }
728
729     /** Get a display name containing HTML markup. <strong><b>Note:</b> If you subclass
730      * FilterNode and override <code>getDisplayName()</code>, this method will
731      * always return null unless you override it as well (assuming that if you're
732      * changing the display name, you don't want an HTML display name constructed
733      * from the original node's display name to be what shows up in views of
734      * this node).</strong> If <code>getDisplayName()</code> is not overridden,
735      * this method will return whatever the original node returns from this
736      * method.
737      * <p>
738      * Note that if you do override <code>getDisplayName</code>, you should also override
739      * this method to return null.
740      *
741      *
742      *
743      * @see org.openide.nodes.Node#getHtmlDisplayName
744      * @return An HTML display name, if available, or null if no display name
745      * is available */

746     @Override JavaDoc
747     public String JavaDoc getHtmlDisplayName() {
748         if (overridesGetDisplayName()) {
749             return null;
750         } else {
751             return delegating(DELEGATE_GET_DISPLAY_NAME) ? original.getHtmlDisplayName() : super.getHtmlDisplayName();
752         }
753     }
754
755     private boolean overridesGetDisplayName() {
756         synchronized (overridesGetDisplayNameCache) {
757             Boolean JavaDoc b = overridesGetDisplayNameCache.get(getClass());
758
759             if (b == null) {
760                 b = overridesAMethod("getDisplayName"); // NOI18N
761
overridesGetDisplayNameCache.put(getClass(), b);
762             }
763
764             return b;
765         }
766     }
767
768     /*
769     * @return <CODE>true</CODE> if the original has a customizer.
770     */

771     public boolean hasCustomizer() {
772         return original.hasCustomizer();
773     }
774
775     /* Returns the customizer component.
776     * @return the component or <CODE>null</CODE> if there is no customizer
777     */

778     public java.awt.Component JavaDoc getCustomizer() {
779         return original.getCustomizer();
780     }
781
782     /** Delegates to original, if no special lookup provided in constructor,
783     * Otherwise it delegates to the lookup. Never override this method
784     * if the lookup is provided in constructor.
785     *
786     * @param type the class to look for
787     * @return instance of that class or null if this class of cookie
788     * is not supported
789     * @see Node#getCookie
790     */

791     @Override JavaDoc
792     public <T extends Node.Cookie> T getCookie(Class JavaDoc<T> type) {
793         Lookup l = internalLookup(true);
794
795         if (l != null) {
796             Object JavaDoc res = l.lookup(type);
797             return type.isInstance(res) && res instanceof Node.Cookie ? type.cast(res) : null;
798         }
799
800         return original.getCookie(type);
801     }
802
803     /** If this is FilterNode without any changes (subclassed, changed children)
804     * and the original provides handle, stores them and
805     * returns a new handle for the proxy.
806     * <p>Subclasses <strong>must</strong> override this if they wish for their nodes to be
807     * properly serializable.
808     *
809     * @return the handle, or <code>null</code> if this node is subclassed or
810     * uses changed children
811     */

812     public Node.Handle getHandle() {
813         if (!isDefault()) {
814             // subclasses has to implement the method by its own
815
return null;
816         }
817
818         Node.Handle original = this.original.getHandle();
819
820         if (original == null) {
821             // no original handle => no handle here
822
return null;
823         }
824
825         return new FilterHandle(original);
826     }
827
828     /** Test equality of original nodes.
829     * Note that for subclasses of <code>FilterNode</code>, or filter nodes with non-default children,
830     * the test reverts to object identity.
831     * <strong>Note:</strong> if you wish that the {@link Index} cookie works correctly on
832     * filtered nodes and their subnodes, and you are subclassing <code>FilterNode</code> or
833     * using non-default children, you will probably want to override this method to test
834     * equality of the specified node with this filter node's original node; otherwise Move Up
835     * and Move Down actions may be disabled.
836     * <p>Note though that it is often better to provide your own index cookie from a filter
837     * node. Only then it is possible to change the number of children relative to the original.
838     * And in many cases this is easier anyway, as for example with
839     * <code>DataFolder.Index</code> for data folders.
840     * @param o something to compare to, presumably a node or <code>FilterNode</code> of one
841     * @return true if this node's original node is the same as the parameter (or original node of parameter)
842     */

843     @Override JavaDoc
844     public boolean equals(Object JavaDoc o) {
845         // VERY DANGEROUS! Completely messes up visualizers and often original node is displayed rather than filter.
846
// Jst: I know that it is dangerous, but some code probably depends on it
847
if (!(o instanceof Node)) {
848             return false; // something else or null
849
}
850
851         if (this == o) {
852             return true; // shortcut
853
}
854
855         // get the "most original" ones....
856
Node left = getRepresentation(this);
857         Node right = getRepresentation((Node) o);
858
859         // cover nondefault FilterNodes (possibly) deep in the stack
860
if ((left instanceof FilterNode) || (right instanceof FilterNode)) {
861             return left == right;
862         }
863
864         return left.equals(right);
865     }
866
867     private static Node getRepresentation(Node n) {
868         while (n instanceof FilterNode) {
869             FilterNode fn = (FilterNode) n;
870
871             if (!fn.isDefault()) {
872                 return n;
873             }
874
875             n = fn.original;
876         }
877
878         return n; // either node or nondefault FilterNode
879
}
880
881     /** Hash by original nodes.
882     * Note that for subclasses of <code>FilterNode</code>, or filter nodes with non-default children,
883     * the hash reverts to the identity hash code.
884     * @return the delegated hash code
885     */

886     public int hashCode() {
887         try {
888             assert hashCodeLogging(true) : ""; // NOI18N
889

890             int result = isDefault() ? original.hashCode() : System.identityHashCode(this);
891             assert hashCodeLogging(false) : ""; // NOI18N
892

893             return result;
894         } catch (StackError err) {
895             err.add(this);
896             throw err;
897         }
898     }
899
900     /** Method for tracing the issue 46993. Counts the depth of execution
901      * and if larger than 1000 throws debugging exception.
902      */

903     private static boolean hashCodeLogging(boolean enter) {
904         if (hashCodeDepth > 1000) {
905             hashCodeDepth = 0;
906             throw new StackError();
907         }
908
909         if (enter) {
910             hashCodeDepth++;
911         } else {
912             hashCodeDepth--;
913         }
914
915         return true;
916     }
917
918     // public String toString () {
919
// return super.toString () + " original has children: " + original.getChildren ().getNodesCount (); // NOI18N
920
// }
921
// ----------- END OF DELEGATED METHODS ------------
922

923     /** Get the original node.
924     * <p><strong>Yes</strong> this is supposed to be protected! If you
925     * are not subclassing <code>FilterNode</code> yourself, you should
926     * not be calling it (nor casting to <code>FilterNode</code>). Use
927     * cookies instead.
928     * @return the node proxied to
929     */

930     protected Node getOriginal() {
931         return original;
932     }
933
934     /** Create a property change listener that allows listening on the
935     * original node properties (contained in property sets) and propagating
936     * them to the proxy.
937     * <P>
938     * This method is called during initialization and allows subclasses
939     * to modify the default behaviour.
940     *
941     * @return a {@link PropertyChangeAdapter} in the default implementation
942     */

943     protected PropertyChangeListener JavaDoc createPropertyChangeListener() {
944         return new PropertyChangeAdapter(this);
945     }
946
947     /** Creates a node listener that allows listening on the
948     * original node and propagating events to the proxy.
949     * <p>Intended for overriding by subclasses, as with {@link #createPropertyChangeListener}.
950     *
951     * @return a {@link FilterNode.NodeAdapter} in the default implementation
952     */

953     protected NodeListener createNodeListener() {
954         return new NodeAdapter(this);
955     }
956
957     /** Getter for property change listener.
958     */

959     synchronized PropertyChangeListener JavaDoc getPropertyChangeListener() {
960         if (propL == null) {
961             propL = createPropertyChangeListener();
962         }
963
964         return propL;
965     }
966
967     /** Getter for node listener.
968     */

969     synchronized NodeListener getNodeListener() {
970         if (nodeL == null) {
971             nodeL = createNodeListener();
972             getOriginal().addNodeListener(nodeL);
973         }
974
975         return nodeL;
976     }
977
978     /** Notified from Node that a listener has been added.
979      * Thus we force initialization of listeners.
980      */

981     final void listenerAdded() {
982         getNodeListener();
983     }
984
985     /** Check method whether the node has default behaviour or
986     * if it is either subclass of uses different children.
987     * @return true if it is default
988     */

989     private boolean isDefault() {
990         //System.err.print ("FilterNode.isDefault: ");
991
if (getClass() != FilterNode.class) {
992             //System.err.println("false\n\tsubclass of FilterNode");
993
return false;
994         }
995
996         return !childrenProvided;
997     }
998
999     /**
1000     * This method is used to change the Children from Children.LEAF to Children
1001     * typically used to when there is a setChildren() on the original node
1002     * setChildren will fire the appropriate events
1003     */

1004    final void updateChildren() {
1005        if (isDefault()) {
1006            org.openide.nodes.Children newChildren = null;
1007
1008            try {
1009                Children.PR.enterReadAccess();
1010
1011                if ((original.hierarchy == Children.LEAF) && (hierarchy != Children.LEAF)) {
1012                    newChildren = Children.LEAF;
1013                } else if ((original.hierarchy != Children.LEAF) && (hierarchy == Children.LEAF)) {
1014                    newChildren = new Children(original);
1015                }
1016            } finally {
1017                Children.PR.exitReadAccess();
1018            }
1019
1020            if (newChildren != null) {
1021                final org.openide.nodes.Children set = newChildren;
1022                Children.MUTEX.postWriteRequest(
1023                    new Runnable JavaDoc() {
1024                        public void run() {
1025                            setChildren(set);
1026                        }
1027                    }
1028                );
1029            }
1030        }
1031    }
1032
1033    /** An exception to be thrown from hashCode() to debug issue 46993.
1034     */

1035    private static class StackError extends StackOverflowError JavaDoc {
1036        private IdentityHashMap<FilterNode,FilterNode> nodes;
1037
1038        public void add(FilterNode n) {
1039            if (nodes == null) {
1040                nodes = new IdentityHashMap<FilterNode,FilterNode>();
1041            }
1042
1043            if (!nodes.containsKey(n)) {
1044                nodes.put(n, n);
1045            }
1046        }
1047
1048        @Override JavaDoc
1049        public String JavaDoc getMessage() {
1050            StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
1051            sb.append("StackOver in FilterNodes:\n"); // NOI18N
1052

1053            for (FilterNode f : nodes.keySet()) {
1054                sb.append(" class: "); // NOI18N
1055
sb.append(f.getClass().getName());
1056                sb.append(" id: "); // NOI18N
1057
sb.append(Integer.toString(System.identityHashCode(f), 16));
1058                sb.append("\n"); // NOI18N
1059
}
1060
1061            return sb.toString();
1062        }
1063    }
1064
1065    /** Adapter that listens on changes in an original node
1066    * and refires them in a proxy.
1067    * This adapter is created during
1068    * initialization in {@link FilterNode#createPropertyChangeListener}. The method
1069    * can be overridden and this class used as the super class for the
1070    * new implementation.
1071    * <P>
1072    * A reference to the proxy is stored by weak reference, so it does not
1073    * prevent the node from being finalized.
1074    */

1075    protected static class PropertyChangeAdapter extends Object JavaDoc implements PropertyChangeListener JavaDoc {
1076        private Reference JavaDoc<FilterNode> fn;
1077
1078        /** Create a new adapter.
1079        * @param fn the proxy
1080        */

1081        public PropertyChangeAdapter(FilterNode fn) {
1082            this.fn = new WeakReference JavaDoc<FilterNode>(fn);
1083        }
1084
1085        /* Find the node we are attached to. If it is not null call property
1086        * change method with two arguments.
1087        */

1088        public final void propertyChange(PropertyChangeEvent JavaDoc ev) {
1089            FilterNode fn = this.fn.get();
1090
1091            if (fn == null) {
1092                return;
1093            }
1094
1095            propertyChange(fn, ev);
1096        }
1097
1098        /** Actually propagate the event.
1099        * Intended for overriding.
1100        * @param fn the proxy
1101        * @param ev the event
1102        */

1103        protected void propertyChange(FilterNode fn, PropertyChangeEvent JavaDoc ev) {
1104            fn.firePropertyChange(ev.getPropertyName(), ev.getOldValue(), ev.getNewValue());
1105        }
1106    }
1107
1108    /** Adapter that listens on changes in an original node and refires them
1109    * in a proxy. Created in {@link FilterNode#createNodeListener}.
1110    * @see FilterNode.PropertyChangeAdapter
1111    */

1112    protected static class NodeAdapter extends Object JavaDoc implements NodeListener {
1113        private Reference JavaDoc<FilterNode> fn;
1114
1115        /** Create an adapter.
1116        * @param fn the proxy
1117        */

1118        public NodeAdapter(FilterNode fn) {
1119            this.fn = new WeakReference JavaDoc<FilterNode>(fn);
1120        }
1121
1122        /* Tests if the reference to the node provided in costructor is
1123        * still valid (it has not been finalized) and if so, calls propertyChange (Node, ev).
1124        */

1125        public final void propertyChange(PropertyChangeEvent JavaDoc ev) {
1126            FilterNode fn = this.fn.get();
1127
1128            if (fn == null) {
1129                return;
1130            }
1131
1132            propertyChange(fn, ev);
1133        }
1134
1135        /** Actually refire the change event in a subclass.
1136        * The default implementation ignores changes of the <code>parentNode</code> property but refires
1137        * everything else.
1138        *
1139        * @param fn the filter node
1140        * @param ev the event to fire
1141        */

1142        protected void propertyChange(FilterNode fn, PropertyChangeEvent JavaDoc ev) {
1143            String JavaDoc n = ev.getPropertyName();
1144
1145            if (n.equals(Node.PROP_PARENT_NODE)) {
1146                // does nothing
1147
return;
1148            }
1149
1150            if (n.equals(Node.PROP_DISPLAY_NAME)) {
1151                fn.fireOwnPropertyChange(PROP_DISPLAY_NAME, (String JavaDoc) ev.getOldValue(), (String JavaDoc) ev.getNewValue());
1152
1153                return;
1154            }
1155
1156            if (n.equals(Node.PROP_NAME)) {
1157                fn.fireOwnPropertyChange(PROP_NAME, (String JavaDoc) ev.getOldValue(), (String JavaDoc) ev.getNewValue());
1158
1159                return;
1160            }
1161
1162            if (n.equals(Node.PROP_SHORT_DESCRIPTION)) {
1163                fn.fireOwnPropertyChange(PROP_SHORT_DESCRIPTION, (String JavaDoc) ev.getOldValue(), (String JavaDoc) ev.getNewValue());
1164
1165                return;
1166            }
1167
1168            if (n.equals(Node.PROP_ICON)) {
1169                fn.fireIconChange();
1170
1171                return;
1172            }
1173
1174            if (n.equals(Node.PROP_OPENED_ICON)) {
1175                fn.fireOpenedIconChange();
1176
1177                return;
1178            }
1179
1180            if (n.equals(Node.PROP_PROPERTY_SETS)) {
1181                fn.firePropertySetsChange((PropertySet[]) ev.getOldValue(), (PropertySet[]) ev.getNewValue());
1182
1183                return;
1184            }
1185
1186            if (n.equals(Node.PROP_COOKIE)) {
1187                fn.fireCookieChange();
1188
1189                return;
1190            }
1191
1192            if (n.equals(Node.PROP_LEAF)) {
1193                fn.updateChildren();
1194
1195                /*
1196                fn.fireOwnPropertyChange(
1197                    Node.PROP_LEAF, ev.getOldValue(), ev.getNewValue()
1198                );
1199                 */

1200            }
1201        }
1202
1203        /** Does nothing.
1204        * @param ev event describing the action
1205        */

1206        public void childrenAdded(NodeMemberEvent ev) {
1207        }
1208
1209        /** Does nothing.
1210        * @param ev event describing the action
1211        */

1212        public void childrenRemoved(NodeMemberEvent ev) {
1213        }
1214
1215        /** Does nothing.
1216        * @param ev event describing the action
1217        */

1218        public void childrenReordered(NodeReorderEvent ev) {
1219        }
1220
1221        /* Does nothing.
1222        * @param ev event describing the node
1223        */

1224        public final void nodeDestroyed(NodeEvent ev) {
1225            FilterNode fn = this.fn.get();
1226
1227            if (fn == null) {
1228                return;
1229            }
1230
1231            fn.originalDestroyed();
1232        }
1233    }
1234
1235    /** Children for a filter node. Listens on changes in subnodes of
1236    * the original node and asks this filter node to creates representatives for
1237    * these subnodes.
1238    * <P>
1239    * This class is used as the default for subnodes of filter node, but
1240    * subclasses may modify it or provide a totally different implementation.
1241     * <p><code>FilterNode.Children</code> is not well suited to cases where you need to insert
1242     * additional nodes at the beginning or end of the list, or where you may need
1243     * to merge together multiple original children lists, or reorder them, etc.
1244     * That is because the keys are of type <code>Node</code>, one for each original
1245     * child, and the keys are reset during {@link #addNotify}, {@link #filterChildrenAdded},
1246     * {@link #filterChildrenRemoved}, and {@link #filterChildrenReordered}, so it is
1247     * not trivial to use different keys: you would need to override <code>addNotify</code>
1248     * (calling super first!) and the other three update methods. For such complex cases
1249     * you will do better by creating your own <code>Children.Keys</code> subclass, setting
1250     * keys that are useful to you, and keeping a <code>NodeListener</code> on the original
1251     * node to handle changes.
1252    */

1253    public static class Children extends org.openide.nodes.Children.Keys<Node> implements Cloneable JavaDoc {
1254        /** Original node. Should not be modified. */
1255        protected Node original;
1256
1257        /** node listener on original */
1258        private ChildrenAdapter nodeL;
1259
1260        /** Create children.
1261         * @param or original node to take children from */

1262        public Children(Node or) {
1263            original = or;
1264        }
1265
1266        /** Sets the original children for this children.
1267         * Be aware that this method aquires
1268         * write lock on the nodes hierarchy ({@link Children#MUTEX}).
1269         * Take care not to call this method under read lock.
1270         * @param original The new original node.
1271         * @since 1.39
1272         */

1273        protected final void changeOriginal(Node original) {
1274            try {
1275                PR.enterWriteAccess();
1276
1277                boolean wasAttached = nodeL != null;
1278
1279                // uregister from the original node
1280
if (wasAttached) {
1281                    this.original.removeNodeListener(nodeL);
1282                    nodeL = null;
1283                }
1284
1285                // reset the original node
1286
this.original = original;
1287
1288                if (wasAttached) {
1289                    addNotifyImpl();
1290                }
1291            } finally {
1292                PR.exitWriteAccess();
1293            }
1294        }
1295
1296        /** Closes the listener, if any, on the original node.
1297        */

1298        @Override JavaDoc
1299        protected void finalize() {
1300            if (nodeL != null) {
1301                original.removeNodeListener(nodeL);
1302            }
1303
1304            nodeL = null;
1305        }
1306
1307        /* Clones the children object.
1308        */

1309        @Override JavaDoc
1310        public Object JavaDoc clone() {
1311            return new Children(original);
1312        }
1313
1314        /** Initializes listening to changes in original node.
1315        */

1316        @Override JavaDoc
1317        protected void addNotify() {
1318            addNotifyImpl();
1319        }
1320
1321        private void addNotifyImpl() {
1322            // add itself to reflect to changes children of original node
1323
nodeL = new ChildrenAdapter(this);
1324            original.addNodeListener(nodeL);
1325
1326            updateKeys();
1327        }
1328
1329        /** Clears current keys, because all mirrored nodes disappeared.
1330        */

1331        @Override JavaDoc
1332        protected void removeNotify() {
1333            setKeys(Collections.<Node>emptySet());
1334
1335            if (nodeL != null) {
1336                original.removeNodeListener(nodeL);
1337                nodeL = null;
1338            }
1339        }
1340
1341        /** Allows subclasses to override
1342        * creation of node representants for nodes in the mirrored children
1343        * list. The default implementation simply uses {@link Node#cloneNode}.
1344        * <p>Note that this method is only suitable for a 1-to-1 mirroring.
1345        *
1346        * @param node node to create copy of
1347        * @return copy of the original node
1348        */

1349        protected Node copyNode(Node node) {
1350            return node.cloneNode();
1351        }
1352
1353        /* Implements find of child by finding the original child and then [PENDING]
1354        * @param name of node to find
1355        * @return the node or null
1356        */

1357        @Override JavaDoc
1358        public Node findChild(String JavaDoc name) {
1359            original.getChildren().findChild(name);
1360
1361            return super.findChild(name);
1362        }
1363
1364        /** Create nodes representing copies of the original node's children.
1365        * The default implementation returns exactly one representative for each original node,
1366        * as returned by {@link #copyNode}.
1367        * Subclasses may override this to avoid displaying a copy of an original child at all,
1368        * or even to display multiple nodes representing the original.
1369        * @param key the original child node
1370        * @return zero or more nodes representing the original child node
1371        */

1372        protected Node[] createNodes(Node key) {
1373            // is run under read access lock so nobody can change children
1374
return new Node[] { copyNode(key) };
1375        }
1376
1377        /* Delegates to children of the original node.
1378        *
1379        * @param arr nodes to add
1380        * @return true/false
1381        */

1382        @Override JavaDoc
1383        @Deprecated JavaDoc
1384        public boolean add(Node[] arr) {
1385            return original.getChildren().add(arr);
1386        }
1387
1388        /* Delegates to filter node.
1389        * @param arr nodes to remove
1390        * @return true/false
1391        */

1392        @Override JavaDoc
1393        @Deprecated JavaDoc
1394        public boolean remove(Node[] arr) {
1395            return original.getChildren().remove(arr);
1396        }
1397
1398        /** Called when the filter node adds a new child.
1399        * The default implementation makes a corresponding change.
1400        * @param ev info about the change
1401        */

1402        protected void filterChildrenAdded(NodeMemberEvent ev) {
1403            updateKeys();
1404        }
1405
1406        /** Called when the filter node removes a child.
1407        * The default implementation makes a corresponding change.
1408        * @param ev info about the change
1409        */

1410        protected void filterChildrenRemoved(NodeMemberEvent ev) {
1411            updateKeys();
1412        }
1413
1414        /** Called when the filter node reorders its children.
1415        * The default implementation makes a corresponding change.
1416        * @param ev info about the change
1417        */

1418        protected void filterChildrenReordered(NodeReorderEvent ev) {
1419            updateKeys();
1420        }
1421
1422        /** variable to notify that there is a cyclic update.
1423        * Used only in updateKeys method
1424        */

1425
1426        // private transient boolean cyclic;
1427

1428        /** Update keys from original nodes */
1429        private void updateKeys() {
1430            ChildrenAdapter runnable = nodeL;
1431
1432            if (runnable != null) {
1433                runnable.run();
1434            }
1435        }
1436
1437        /**
1438         * Implementation that ensures the original node is fully initialized
1439         * if optimal result is requested.
1440         *
1441         * @param optimalResult if <code>true</code>, the method will block
1442         * until the original node is fully initialized.
1443         * @since 3.9
1444         */

1445        @Override JavaDoc
1446        public Node[] getNodes(boolean optimalResult) {
1447            if (optimalResult) {
1448                setKeys(original.getChildren().getNodes(true));
1449            }
1450
1451            return getNodes();
1452        }
1453    }
1454
1455    /** Adapter that listens on changes in the original node and fires them
1456    * in this node.
1457    * Used as the default listener in {@link FilterNode.Children},
1458    * and is intended for refinement by its subclasses.
1459    */

1460    private static class ChildrenAdapter extends Object JavaDoc implements NodeListener, Runnable JavaDoc {
1461        /** children object to notify about addition of children.
1462        * Can be null. Set from Children's initNodes method.
1463        */

1464        private Reference JavaDoc<Children> children;
1465
1466        /** Create a new adapter.
1467        * @param ch the children list
1468        */

1469        public ChildrenAdapter(Children ch) {
1470            this.children = new WeakReference JavaDoc<Children>(ch);
1471        }
1472
1473        /** Called to update the content of children.
1474         */

1475        public void run() {
1476            Children ch = children.get();
1477
1478            if (ch != null) {
1479                Node[] arr = ch.original.getChildren().getNodes();
1480                ch.setKeys(arr);
1481            }
1482        }
1483
1484        /** Does nothing.
1485        * @param ev the event
1486        */

1487        public void propertyChange(PropertyChangeEvent JavaDoc ev) {
1488        }
1489
1490        /* Informs that a set of new children has been added.
1491        * @param ev event describing the action
1492        */

1493        public void childrenAdded(NodeMemberEvent ev) {
1494            Children children = this.children.get();
1495
1496            if (children == null) {
1497                return;
1498            }
1499
1500            children.filterChildrenAdded(ev);
1501        }
1502
1503        /* Informs that a set of children has been removed.
1504        * @param ev event describing the action
1505        */

1506        public void childrenRemoved(NodeMemberEvent ev) {
1507            Children children = this.children.get();
1508
1509            if (children == null) {
1510                return;
1511            }
1512
1513            children.filterChildrenRemoved(ev);
1514        }
1515
1516        /* Informs that a set of children has been reordered.
1517        * @param ev event describing the action
1518        */

1519        public void childrenReordered(NodeReorderEvent ev) {
1520            Children children = this.children.get();
1521
1522            if (children == null) {
1523                return;
1524            }
1525
1526            children.filterChildrenReordered(ev);
1527        }
1528
1529        /** Does nothing.
1530        * @param ev the event
1531        */

1532        public void nodeDestroyed(NodeEvent ev) {
1533        }
1534    }
1535
1536    /** Filter node handle.
1537    */

1538    private static final class FilterHandle implements Node.Handle {
1539        static final long serialVersionUID = 7928908039428333839L;
1540        private Node.Handle original;
1541
1542        public FilterHandle(Node.Handle original) {
1543            this.original = original;
1544        }
1545
1546        public Node getNode() throws IOException JavaDoc {
1547            return new FilterNode(original.getNode());
1548        }
1549
1550        @Override JavaDoc
1551        public String JavaDoc toString() {
1552            return "FilterHandle[" + original + "]"; // NOI18N
1553
}
1554    }
1555
1556    /** Special ProxyLookup
1557     */

1558    private static final class FilterLookup extends Lookup {
1559        /** node we belong to */
1560        private FilterNode node;
1561
1562        /** lookup we delegate too */
1563        private Lookup delegate;
1564
1565        /** set of all results associated to this lookup */
1566        private Set<ProxyResult> results;
1567
1568        FilterLookup() {
1569        }
1570
1571        /** Registers own node.
1572         */

1573        public void ownNode(FilterNode n) {
1574            this.node = n;
1575        }
1576
1577        /** A method that replaces instance of original node
1578         * with a new one
1579         */

1580        private <T> T replaceNodes(T orig, Class JavaDoc<T> clazz) {
1581            if (isNodeQuery(clazz) && (orig == node.getOriginal()) && clazz.isInstance(node)) {
1582                return clazz.cast(node);
1583            } else {
1584                return orig;
1585            }
1586        }
1587
1588        /** Changes the node we delegate to if necessary.
1589         * @param n the node to delegate to
1590         */

1591        public Lookup checkNode() {
1592            Lookup l = node.getOriginal().getLookup();
1593
1594            if (delegate == l) {
1595                return l;
1596            }
1597
1598            Iterator<ProxyResult> toCheck = null;
1599
1600            synchronized (this) {
1601                if (l != delegate) {
1602                    this.delegate = l;
1603
1604                    if (results != null) {
1605                        toCheck = new ArrayList<ProxyResult>(results).iterator();
1606                    }
1607                }
1608            }
1609
1610            if (toCheck != null) {
1611                // update
1612

1613                while (toCheck.hasNext()) {
1614                    ProxyResult p = toCheck.next();
1615
1616                    if (p.updateLookup(l)) {
1617                        p.resultChanged(null);
1618                    }
1619                }
1620            }
1621
1622            return delegate;
1623        }
1624
1625        public <T> Result<T> lookup(Template<T> template) {
1626            ProxyResult<T> p = new ProxyResult<T>(template);
1627
1628            synchronized (this) {
1629                if (results == null) {
1630                    results = new WeakSet<ProxyResult>();
1631                }
1632
1633                results.add(p);
1634            }
1635
1636            return p;
1637        }
1638
1639        public <T> T lookup(Class JavaDoc<T> clazz) {
1640            T result = checkNode().lookup(clazz);
1641
1642            if (result == null && clazz.isInstance(node)) {
1643                result = clazz.cast(node);
1644            }
1645
1646            return replaceNodes(result, clazz);
1647        }
1648
1649        /** Finds out whether a query for a class can be influenced
1650         * by a state of the "nodes" lookup and whether we should
1651         * initialize listening
1652         */

1653        private static boolean isNodeQuery(Class JavaDoc<?> c) {
1654            return Node.class.isAssignableFrom(c) || c.isAssignableFrom(Node.class);
1655        }
1656
1657        @Override JavaDoc
1658        public <T> Item<T> lookupItem(Template<T> template) {
1659            boolean nodeQ = isNodeQuery(template.getType());
1660            Item<T> i = checkNode().lookupItem(template);
1661
1662            if (
1663                nodeQ &&
1664                i == null &&
1665                template.getType().isInstance(node) &&
1666                (template.getInstance() == null || template.getInstance() == node)
1667            ) {
1668                i = checkNode().lookupItem(wackohacko(template.getId(), template.getInstance()));
1669            }
1670
1671            return nodeQ && i != null ? new FilterItem<T>(i, template.getType()) : i;
1672        }
1673        
1674        @SuppressWarnings JavaDoc("unchecked") // cannot type-check this but ought to be safe
1675
private static <T> Lookup.Template<T> wackohacko(String JavaDoc id, T instance) {
1676            return new Lookup.Template(Node.class, id, instance);
1677        }
1678
1679        /**
1680         * Result used in SimpleLookup. It holds a reference to the collection
1681         * passed in constructor. As the contents of this lookup result never
1682         * changes the addLookupListener and removeLookupListener are empty.
1683         */

1684        private final class ProxyResult<T> extends Result<T> implements LookupListener {
1685            /** Template used for this result. It is never null.*/
1686            private Template<T> template;
1687
1688            /** result to delegate to */
1689            private Lookup.Result<T> delegate;
1690
1691            /** listeners set */
1692            private javax.swing.event.EventListenerList JavaDoc listeners;
1693
1694            /** Just remembers the supplied argument in variable template.*/
1695            ProxyResult(Template<T> template) {
1696                this.template = template;
1697            }
1698
1699            /** Checks state of the result
1700             */

1701            private Result<T> checkResult() {
1702                updateLookup(checkNode());
1703
1704                return this.delegate;
1705            }
1706
1707            /** Updates the state of the lookup.
1708             * @return true if the lookup really changed
1709             */

1710            public boolean updateLookup(Lookup l) {
1711                Collection<? extends Item<T>> oldPairs = (delegate != null) ? delegate.allItems() : null;
1712
1713                synchronized (this) {
1714                    if (delegate != null) {
1715                        delegate.removeLookupListener(this);
1716                    }
1717
1718                    delegate = l.lookup(template);
1719
1720                    if (template.getType().isAssignableFrom(node.getClass()) && delegate.allItems().isEmpty()) {
1721                        delegate = l.lookup(wackohacko(template.getId(), template.getInstance()));
1722                    }
1723
1724                    delegate.addLookupListener(this);
1725                }
1726
1727                if (oldPairs == null) {
1728                    // nobody knows about a change
1729
return false;
1730                }
1731
1732                Collection<? extends Item<T>> newPairs = delegate.allItems();
1733
1734                return !oldPairs.equals(newPairs);
1735            }
1736
1737            public synchronized void addLookupListener(LookupListener l) {
1738                if (listeners == null) {
1739                    listeners = new javax.swing.event.EventListenerList JavaDoc();
1740                }
1741
1742                listeners.add(LookupListener.class, l);
1743            }
1744
1745            public synchronized void removeLookupListener(LookupListener l) {
1746                if (listeners != null) {
1747                    listeners.remove(LookupListener.class, l);
1748                }
1749            }
1750
1751            public Collection<? extends T> allInstances() {
1752                Collection<? extends T> c = checkResult().allInstances();
1753
1754                if (isNodeQuery(template.getType())) {
1755                    List<T> ll = new ArrayList<T>(c.size());
1756                    for (T o : c) {
1757                        ll.add(replaceNodes(o, template.getType()));
1758                    }
1759
1760                    return ll;
1761                } else {
1762                    return c;
1763                }
1764            }
1765
1766            @Override JavaDoc
1767            public Set<Class JavaDoc<? extends T>> allClasses() {
1768                return checkResult().allClasses();
1769            }
1770
1771            @Override JavaDoc
1772            public Collection<? extends Item<T>> allItems() {
1773                return checkResult().allItems();
1774            }
1775
1776            /** A change in lookup occured.
1777             * @param ev event describing the change
1778             *
1779             */

1780            public void resultChanged(LookupEvent anEvent) {
1781                javax.swing.event.EventListenerList JavaDoc l = this.listeners;
1782
1783                if (l == null) {
1784                    return;
1785                }
1786
1787                Object JavaDoc[] listeners = l.getListenerList();
1788
1789                if (listeners.length == 0) {
1790                    return;
1791                }
1792
1793                LookupEvent ev = new LookupEvent(this);
1794
1795                for (int i = listeners.length - 1; i >= 0; i -= 2) {
1796                    LookupListener ll = (LookupListener) listeners[i];
1797                    ll.resultChanged(ev);
1798                }
1799            }
1800        }
1801         // end of ProxyResult
1802

1803        /** Item that exchanges the original node for the FilterNode */
1804        private final class FilterItem<T> extends Lookup.Item<T> {
1805            private Item<T> delegate;
1806            private Class JavaDoc<T> clazz;
1807
1808            FilterItem(Item<T> d, Class JavaDoc<T> clazz) {
1809                this.delegate = d;
1810                this.clazz = clazz;
1811            }
1812
1813            public String JavaDoc getDisplayName() {
1814                return delegate.getDisplayName();
1815            }
1816
1817            public String JavaDoc getId() {
1818                return delegate.getId();
1819            }
1820
1821            public T getInstance() {
1822                return replaceNodes(delegate.getInstance(), clazz);
1823            }
1824
1825            public Class JavaDoc<? extends T> getType() {
1826                return delegate.getType();
1827            }
1828        }
1829    }
1830     // end of FilterLookup
1831
}
1832
Popular Tags