KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > util > actions > NodeAction


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
20 package org.openide.util.actions;
21
22
23 import java.awt.Component JavaDoc;
24 import java.awt.event.ActionEvent JavaDoc;
25 import java.beans.PropertyChangeListener JavaDoc;
26 import java.beans.PropertyChangeSupport JavaDoc;
27 import java.lang.ref.Reference JavaDoc;
28 import java.lang.ref.WeakReference JavaDoc;
29 import java.lang.reflect.Method JavaDoc;
30 import java.util.ArrayList JavaDoc;
31 import java.util.Collection JavaDoc;
32 import java.util.Iterator JavaDoc;
33 import java.util.List JavaDoc;
34 import java.util.Set JavaDoc;
35 import java.util.logging.Level JavaDoc;
36 import java.util.logging.Logger JavaDoc;
37 import javax.swing.Action JavaDoc;
38 import javax.swing.JMenuItem JavaDoc;
39 import org.openide.awt.Actions;
40 import org.openide.nodes.Node;
41 import org.openide.util.ContextAwareAction;
42 import org.openide.util.Exceptions;
43 import org.openide.util.Lookup;
44 import org.openide.util.LookupEvent;
45 import org.openide.util.LookupListener;
46 import org.openide.util.Mutex;
47 import org.openide.util.Utilities;
48 import org.openide.util.WeakListeners;
49 import org.openide.util.WeakSet;
50
51 /**
52  * A type of action that listens on change in activated nodes selection and
53  * allows its subclasses to simply change their enabled state and handle
54  * action invocation requests.
55  * <p>
56  * Whenever a list of activated nodes changes (a new <a HREF="@org-openide-windows@/org/openide/windows/TopComponent.html">
57  * TopComponent</a> is selected or
58  * its internal selection changes like in
59  * <a HREF="@org-openide-explorer@/org/openide/explorer/ExplorerUtils.html">explorer</a>)
60  * the overriden method {@link #enable}
61  * is called and state of the action is updated
62  * according to the result. When the action is performed, the subclasses are
63  * notified by a call to their {@link #performAction(Node[])} where they
64  * can perform their operation on the currently selected array of nodes.
65  *
66  * <p><strong>Note:</strong> if your action involves getting cookies
67  * from nodes, which in many cases is the correct design, please use
68  * <a HREF="CookieAction.html">CookieAction</a> instead, as that permits sensitivity to cookies
69  * and also listens to changes in supplied cookies.
70  */

71 public abstract class NodeAction extends CallableSystemAction implements ContextAwareAction {
72     private static final long serialVersionUID = -5672895970450115226L;
73
74     /** whether or not anyone is listening to PROP_ENABLED */
75     private static final String JavaDoc PROP_HAS_LISTENERS = "hasListeners"; // NOI18N
76

77     /** last-used nodes, as a Reference<Node[]> */
78     private static final String JavaDoc PROP_LAST_NODES = "lastNodes"; // NOI18N
79

80     /** last-computed enablement (Boolean) */
81     private static final String JavaDoc PROP_LAST_ENABLED = "lastEnabled"; // NOI18N
82

83     /** the selection listener, if any */
84     private static NodesL l;
85
86     /** set of actions with listeners */
87     private static final Set JavaDoc<NodeAction> listeningActions = new WeakSet<NodeAction>(100);
88
89     /* Initialize the listener.
90     */

91     protected void initialize() {
92         super.initialize();
93         putProperty(PROP_HAS_LISTENERS, Boolean.FALSE);
94
95         // Not yet determined:
96
putProperty(PROP_ENABLED, null);
97     }
98
99     /** Initializes selection listener.
100      * If you override this method, you must always call the super method first.
101      */

102     protected void addNotify() {
103         super.addNotify();
104
105         // initializes the listener
106
putProperty(PROP_HAS_LISTENERS, Boolean.TRUE);
107
108         synchronized (listeningActions) {
109             if (l == null) {
110                 l = new NodesL();
111             }
112
113             if (listeningActions.isEmpty()) {
114                 l.setActive(true);
115             }
116
117             listeningActions.add(this);
118         }
119     }
120
121     /** Shuts down the selection listener.
122      * If you override this method, you must always call the super method last.
123      */

124     protected void removeNotify() {
125         synchronized (listeningActions) {
126             listeningActions.remove(this);
127
128             if (listeningActions.isEmpty()) {
129                 l.setActive(false);
130             }
131         }
132
133         putProperty(PROP_HAS_LISTENERS, Boolean.FALSE);
134
135         // Previous results should no longer be cached:
136
putProperty(PROP_ENABLED, null);
137         super.removeNotify();
138     }
139
140     /** Test for enablement based on {@link #enable}.
141     * You probably ought not ever override this.
142     * @return <code>true</code> to enable
143     */

144     public boolean isEnabled() {
145         Node[] ns = null;
146         Boolean JavaDoc b = null;
147
148         synchronized (getLock()) {
149             b = (Boolean JavaDoc) getProperty(PROP_ENABLED);
150             
151             if (NodeAction.l == null) {
152                 NodeAction.l = new NodesL ();
153                 l.setActive (true);
154             }
155
156             NodesL listener = NodeAction.l;
157             
158             if (b == null) {
159                 ns = listener.getActivatedNodes(surviveFocusChange());
160
161                 Reference JavaDoc r = (Reference JavaDoc) getProperty(PROP_LAST_NODES);
162
163                 if ((r != null) && java.util.Arrays.equals((Node[]) r.get(), ns)) {
164                     // Still using the same Node[] we did last time. Remember the result.
165
b = (Boolean JavaDoc) getProperty(PROP_LAST_ENABLED);
166
167                     if (((Boolean JavaDoc) getProperty(PROP_HAS_LISTENERS)).booleanValue()) {
168                         putProperty(PROP_ENABLED, b);
169                     }
170                 } else {
171                     // Really need to compute it.
172
// #17433: do this outside the lock!
173
}
174
175                 // if inactive, we cannot safely cache results because node selection might change
176
}
177         }
178
179         if (b == null) {
180             b = (((ns != null) && enable(ns)) ? Boolean.TRUE : Boolean.FALSE);
181
182             synchronized (getLock()) {
183                 putProperty(PROP_LAST_NODES, new WeakReference JavaDoc<Node[]>(ns));
184                 putProperty(PROP_LAST_ENABLED, b);
185
186                 if (((Boolean JavaDoc) getProperty(PROP_HAS_LISTENERS)).booleanValue()) {
187                     putProperty(PROP_ENABLED, b);
188                 }
189             }
190         }
191
192         return b.booleanValue();
193     }
194
195     /* Change enablement state.
196      * Clears our previous cache.
197      * Some NodeAction subclasses (CookieAction, MoveUpAction, ...) may call this
198      * when some aspect of the node selection other than the selection itself
199      * changes, so we should clear the cache to ensure that the enablement status
200      * is respected.
201      */

202     public void setEnabled(boolean e) {
203         putProperty(PROP_LAST_ENABLED, null);
204         putProperty(PROP_LAST_NODES, null);
205
206         if (((Boolean JavaDoc) getProperty(PROP_HAS_LISTENERS)).booleanValue()) {
207             // Just set it; the next time selection chamges, we will recompute.
208
super.setEnabled(e);
209         } else {
210             // Problematic. If we just set PROP_ENABLED then the next time isEnabled()
211
// is called, even if the node selection is now different, we will be
212
// in trouble; it will not bother to call enable() again.
213
putProperty(PROP_ENABLED, null, true);
214         }
215     }
216
217     /** Perform the action with a specific action event.
218      * Normally this simply calls {@link #performAction()}, that is using
219      * the global node selection.
220      * However you may call this directly, with an action event whose
221      * source is either a node or an array of nodes, to invoke the action
222      * directly on that nodes or nodes. If you do this, the action must
223      * be such that it would be enabled on that node selection, otherwise
224      * the action is not required to behave correctly (that is, it can
225      * be written to assume that it is never called with a node selection
226      * it is not enabled on).
227      * @param ev action event
228      * @deprecated Using a special action event in this way is deprecated.
229      * Better is to use {@link #createContextAwareInstance} and pass
230      * a lookup containing all desired {@link Node} instances.
231      */

232     @Deprecated JavaDoc
233     public void actionPerformed(final ActionEvent JavaDoc ev) {
234         final Object JavaDoc s = (ev == null) ? null : ev.getSource();
235
236         if (s instanceof Node) {
237             org.netbeans.modules.openide.util.ActionsBridge.doPerformAction(
238                 this,
239                 new org.netbeans.modules.openide.util.ActionsBridge.ActionRunnable(ev, this, amIasynchronous()) {
240                     public void run() {
241                         performAction(new Node[] { (Node) s });
242                     }
243                 }
244             );
245         } else if (s instanceof Node[]) {
246             org.netbeans.modules.openide.util.ActionsBridge.doPerformAction(
247                 this,
248                 new org.netbeans.modules.openide.util.ActionsBridge.ActionRunnable(ev, this, amIasynchronous()) {
249                     public void run() {
250                         performAction((Node[]) s);
251                     }
252                 }
253             );
254         } else {
255             super.actionPerformed(ev);
256         }
257     }
258
259     /** Performs the action.
260      * In the default implementation, calls {@link #performAction(Node[])}.
261      * @deprecated Do not call this programmatically.
262      * Use {@link #createContextAwareInstance} to pass in a node selection.
263      * Do not override this method.
264      */

265     @Deprecated JavaDoc
266     public void performAction() {
267         performAction(getActivatedNodes());
268     }
269
270     /** Get the currently activated nodes.
271     * @return the nodes (may be empty but not <code>null</code>)
272     */

273     public final Node[] getActivatedNodes() {
274         NodesL listener = NodeAction.l;
275
276         return (listener == null) ? new Node[0] : listener.getActivatedNodes(true);
277     }
278
279     /** Specify the behavior of the action when a window with no
280     * activated nodes is selected.
281     * If the action should then be disabled,
282     * return <code>false</code> here; if the action should stay in the previous state,
283     * return <code>true</code>.
284     * <p>Note that {@link #getActivatedNodes} and {@link #performAction} are still
285     * passed the set of selected nodes from the old window, if you keep this feature on.
286     * This is useful, e.g., for an action like Compilation which should remain active
287     * even if the user switches to a window like the Output Window that has no associated nodes;
288     * then running the action will still use the last selection from e.g. an Explorer window
289     * or the Editor, if there was one to begin with.
290     *
291     * @return <code>true</code> in the default implementation
292     */

293     protected boolean surviveFocusChange() {
294         return true;
295     }
296
297     /**
298     * Perform the action based on the currently activated nodes.
299     * Note that if the source of the event triggering this action was itself
300     * a node, that node will be the sole argument to this method, rather
301     * than the activated nodes.
302     *
303     * @param activatedNodes current activated nodes, may be empty but not <code>null</code>
304     */

305     protected abstract void performAction(Node[] activatedNodes);
306
307     /**
308     * Test whether the action should be enabled based
309     * on the currently activated nodes.
310     *
311     * @param activatedNodes current activated nodes, may be empty but not <code>null</code>
312     * @return <code>true</code> to be enabled, <code>false</code> to be disabled
313     */

314     protected abstract boolean enable(Node[] activatedNodes);
315
316     /** Implements <code>ContextAwareAction</code> interface method.
317      *
318      * Returns a delegate action that is associated with a specific lookup and
319      * extracts the nodes it operates on from it. Otherwise it delegates to the
320      * regular NodeAction (especially to {@link #enable} and {@link #performAction} methods).
321      * Note: Never call directly methods <code>setEnabled</code> or <code>putValue</code>
322      * of this delegate action, it is useless, they are empty. The enablement
323      * state of the action is driven by the content of the <code>actionContext</code>.
324      *
325      * @param actionContext a lookup contains action context, cannot be <code>null</code>
326      * @return a delegate action
327      */

328     public Action JavaDoc createContextAwareInstance(Lookup actionContext) {
329         return new DelegateAction(this, actionContext);
330     }
331
332     /** Fire PROP_ENABLE if the value is currently known (and clear that value).
333      */

334     void maybeFireEnabledChange() {
335         boolean fire = false;
336
337         synchronized (getLock()) {
338             if (getProperty(PROP_ENABLED) != null) {
339                 putProperty(PROP_ENABLED, null);
340                 fire = true;
341             }
342         }
343
344         if (fire) {
345             try {
346                 firePropertyChange(PROP_ENABLED, null, null);
347             } catch (NullPointerException JavaDoc e) {
348                 // Probably because of a JDK bug that AbstractButton$ButtonActionPropertyChangeListener.propertyChange does not grok null values for "enabled" prop:
349
Exceptions.attachMessage(e,
350                                          "You cannot add " +
351                                          getClass().getName() +
352                                          " directly to a JMenu etc.; use org.openide.awt.Actions.connect instead"); // NOI18N
353
Logger.getLogger(NodeAction.class.getName()).log(Level.WARNING, null, e);
354             }
355         }
356     }
357     
358     /** Package private accessor.
359      */

360     final boolean amIasynchronous() {
361         return asynchronous();
362     }
363     
364     /** Node listener to check whether the action is enabled or not
365     */

366     private static final class NodesL implements LookupListener {
367         /** result with Nodes we listen to */
368         private volatile Lookup.Result<Node> result;
369
370         /** whether to change enablement of nodes marked to survive focus change */
371         private boolean chgSFC = false;
372
373         /** and those marked to not survive */
374         private boolean chgNSFC = false;
375
376         /** pointer to previously activated nodes (via Reference to Node) */
377         private Reference JavaDoc<Node>[] activatedNodes;
378
379         /** Constructor that checks the current state
380         */

381         public NodesL() {
382         }
383
384         /** Computes the list of activated nodes.
385          */

386         public Node[] getActivatedNodes(boolean survive) {
387 OUTER:
388             if (survive && (activatedNodes != null)) {
389                 Node[] arr = new Node[activatedNodes.length];
390
391                 for (int i = 0; i < arr.length; i++) {
392                     if ((arr[i] = activatedNodes[i].get()) == null) {
393                         break OUTER;
394                     }
395                 }
396
397                 return arr;
398             }
399
400             Lookup.Result<Node> r = result;
401
402             return (r == null) ? new Node[0] : r.allInstances().toArray(new Node[0]);
403         }
404
405         /** Activates/passivates the listener.
406         */

407         synchronized void setActive(boolean active) {
408             Lookup context = Utilities.actionsGlobalContext();
409
410             if (active) {
411                 if (result == null) {
412                     result = context.lookupResult(Node.class);
413                     result.addLookupListener(this);
414                 }
415             } else {
416                 // result.removeLookupListener (this);
417
// result = null;
418
// Any saved PROP_ENABLED will be bogus now:
419
forget(true);
420                 forget(false);
421             }
422         }
423
424         /** Property change listener.
425         */

426         public void resultChanged(LookupEvent ev) {
427             Lookup.Result<Node> r = result;
428
429             if (r == null) {
430                 return;
431             }
432
433             chgSFC = true;
434             chgNSFC = true;
435
436             Collection JavaDoc<? extends Lookup.Item<Node>> items = result.allItems();
437             boolean updateActivatedNodes = true;
438
439             if (items.size() == 1) {
440                 Lookup.Item<Node> item = items.iterator().next();
441
442                 if ("none".equals(item.getId()) && (item.getInstance() == null)) {
443                     // this is change of selected node to null,
444
// do not update activatedNodes
445
updateActivatedNodes = false;
446                 }
447             }
448
449             if (updateActivatedNodes) {
450                 Iterator JavaDoc<? extends Node> it = result.allInstances().iterator();
451                 ArrayList JavaDoc<Reference JavaDoc<Node>> list = new ArrayList JavaDoc<Reference JavaDoc<Node>>();
452
453                 while (it.hasNext()) {
454                     list.add(new WeakReference JavaDoc<Node>(it.next()));
455                 }
456
457                 activatedNodes = list.toArray(new Reference JavaDoc[list.size()]);
458             }
459
460             update();
461         }
462
463         /** Updates the state of the action.
464         */

465         public void update() {
466             if (chgSFC) {
467                 forget(true);
468                 chgSFC = false;
469             }
470
471             if (chgNSFC) {
472                 forget(false);
473                 chgNSFC = false;
474             }
475         }
476
477         /** Checks the state of the action.
478          * Or rather, it just forgets it ever knew.
479          * @param sfc if true, only survive-focus-change actions affected, else only not-s-f-c
480         */

481         private void forget(boolean sfc) {
482             List JavaDoc<NodeAction> as;
483
484             synchronized (listeningActions) {
485                 as = new ArrayList JavaDoc<NodeAction>(listeningActions.size());
486
487                 for (Iterator JavaDoc<NodeAction> it = listeningActions.iterator(); it.hasNext();) {
488                     as.add(it.next());
489                 }
490             }
491
492             Iterator JavaDoc<NodeAction> it = as.iterator();
493
494             while (it.hasNext()) {
495                 final NodeAction a = it.next();
496
497                 if (a.surviveFocusChange() == sfc) {
498                     Mutex.EVENT.readAccess(new Runnable JavaDoc() {
499                         public void run() {
500                             a.maybeFireEnabledChange();
501                         }
502                     });
503                 }
504             }
505         }
506     }
507      // end of NodesL
508

509     /** A delegate action that is usually associated with a specific lookup and
510      * extract the nodes it operates on from it. Otherwise it delegates to the
511      * regular NodeAction.
512      */

513     static class DelegateAction implements Action JavaDoc, LookupListener, Presenter.Menu, Presenter.Popup, Presenter.Toolbar {
514         private static final Node[] EMPTY_NODE_ARRAY = new Node[0];
515
516         /** action to delegate too */
517         private NodeAction delegate;
518
519         /** lookup we are associated with (or null) */
520         private org.openide.util.Lookup.Result<Node> result;
521
522         /** previous state of enabled */
523         private boolean enabled = true;
524
525         /** support for listeners */
526         private PropertyChangeSupport JavaDoc support = new PropertyChangeSupport JavaDoc(this);
527
528         public DelegateAction(NodeAction a, Lookup actionContext) {
529             this.delegate = a;
530
531             this.result = actionContext.lookupResult(Node.class);
532             this.result.addLookupListener(WeakListeners.create(LookupListener.class,
533                                                                this, this.result));
534             resultChanged(null);
535         }
536
537         /** Overrides superclass method, adds delegate description. */
538         public String JavaDoc toString() {
539             return super.toString() + "[delegate=" + delegate + "]"; // NOI18N
540
}
541
542         /** Nodes are taken from the lookup if any.
543          */

544         public final synchronized Node[] nodes() {
545             if (result != null) {
546                 return result.allInstances().toArray(EMPTY_NODE_ARRAY);
547             } else {
548                 return EMPTY_NODE_ARRAY;
549             }
550         }
551
552         /** Invoked when an action occurs.
553          */

554         public void actionPerformed(ActionEvent JavaDoc e) {
555             org.netbeans.modules.openide.util.ActionsBridge.doPerformAction (
556                 delegate,
557                 new org.netbeans.modules.openide.util.ActionsBridge.ActionRunnable(e, delegate, delegate.amIasynchronous()) {
558                     public void run() {
559                         delegate.performAction(nodes());
560                     }
561                 }
562             );
563         }
564
565         public void addPropertyChangeListener(PropertyChangeListener JavaDoc listener) {
566             support.addPropertyChangeListener(listener);
567         }
568
569         public void removePropertyChangeListener(PropertyChangeListener JavaDoc listener) {
570             support.removePropertyChangeListener(listener);
571         }
572
573         public void putValue(String JavaDoc key, Object JavaDoc o) {
574         }
575
576         public Object JavaDoc getValue(String JavaDoc key) {
577             return delegate.getValue(key);
578         }
579
580         public boolean isEnabled() {
581             return enabled;
582         }
583
584         public void setEnabled(boolean b) {
585         }
586
587         public void resultChanged(LookupEvent ev) {
588             boolean old = enabled;
589             enabled = delegate.enable(nodes());
590             support.firePropertyChange(PROP_ENABLED, old, enabled);
591         }
592
593         public JMenuItem JavaDoc getMenuPresenter() {
594             if (isMethodOverridden(delegate, "getMenuPresenter")) { // NOI18N
595

596                 return delegate.getMenuPresenter();
597             } else {
598                 return new Actions.MenuItem(this, true);
599             }
600         }
601
602         public JMenuItem JavaDoc getPopupPresenter() {
603             if (isMethodOverridden(delegate, "getPopupPresenter")) { // NOI18N
604

605                 return delegate.getPopupPresenter();
606             } else {
607                 return new Actions.MenuItem(this, false);
608             }
609         }
610
611         public Component JavaDoc getToolbarPresenter() {
612             if (isMethodOverridden(delegate, "getToolbarPresenter")) { // NOI18N
613

614                 return delegate.getToolbarPresenter();
615             } else {
616                 return new Actions.ToolbarButton(this);
617             }
618         }
619
620         private boolean isMethodOverridden(NodeAction d, String JavaDoc name) {
621             try {
622                 Method JavaDoc m = d.getClass().getMethod(name, new Class JavaDoc[0]);
623
624                 return m.getDeclaringClass() != CallableSystemAction.class;
625             } catch (java.lang.NoSuchMethodException JavaDoc ex) {
626                 ex.printStackTrace();
627                 throw new IllegalStateException JavaDoc("Error searching for method " + name + " in " + d); // NOI18N
628
}
629         }
630     }
631      // end of DelegateAction
632
}
633
Popular Tags