001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.modules.profiler.actions;
042:
043: import org.netbeans.api.project.FileOwnerQuery;
044: import org.netbeans.api.project.Project;
045: import org.netbeans.api.project.ProjectUtils;
046: import org.netbeans.modules.profiler.ui.NBSwingWorker;
047: import org.netbeans.modules.profiler.ui.panels.ProgressDisplayer;
048: import org.netbeans.modules.profiler.utils.IDEUtils;
049: import org.netbeans.modules.profiler.utils.OutputParameter;
050: import org.netbeans.spi.project.ActionProvider;
051: import org.openide.DialogDisplayer;
052: import org.openide.NotifyDescriptor;
053: import org.openide.filesystems.FileObject;
054: import org.openide.loaders.DataObject;
055: import org.openide.util.*;
056: import java.awt.event.ActionEvent;
057: import java.text.MessageFormat;
058: import java.util.Arrays;
059: import java.util.Collection;
060: import java.util.HashSet;
061: import java.util.Iterator;
062: import java.util.List;
063: import java.util.Set;
064: import javax.swing.AbstractAction;
065: import javax.swing.Action;
066: import javax.swing.Icon;
067:
068: /**
069: * Action sensitive to current project
070: *
071: * @author Ian Formanek
072: */
073: public class ProjectSensitiveAction extends AbstractAction implements
074: ContextAwareAction, LookupListener {
075: //~ Inner Interfaces ---------------------------------------------------------------------------------------------------------
076:
077: public interface ProfilerProjectActionPerformer {
078: //~ Methods --------------------------------------------------------------------------------------------------------------
079:
080: /**
081: * Called when the context of the action changes and the action should
082: * be enabled or disabled within the new context, according to the newly
083: * selected project.
084: *
085: * @param project the currently selected project, or null if no project is selected
086: * @return true to enable the action, false to disable it
087: */
088: public boolean enable(Project project, Lookup context,
089: boolean lightweightOnly);
090:
091: /**
092: * Called when the user invokes the action.
093: *
094: * @param project the project this action was invoked for (XXX can this be null or not?)
095: * @throws IllegalStateException when trying to perform the action in an illegal context
096: */
097: public void perform(Project project, Lookup context);
098: }
099:
100: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
101:
102: public static final class ActionsUtil {
103: //~ Methods --------------------------------------------------------------------------------------------------------------
104:
105: /**
106: * In given lookup will find all FileObjects owned by given project
107: * with given command supported.
108: */
109: public static FileObject[] getFilesFromLookup(
110: final Lookup lookup, final Project project) {
111: final HashSet result = new HashSet();
112: final Collection dataObjects = lookup.lookup(
113: new Lookup.Template(DataObject.class))
114: .allInstances();
115:
116: for (Iterator it = dataObjects.iterator(); it.hasNext();) {
117: final DataObject dObj = (DataObject) it.next();
118: final FileObject fObj = dObj.getPrimaryFile();
119: final Project p = FileOwnerQuery.getOwner(fObj);
120:
121: if ((p != null) && p.equals(project)) {
122: result.add(fObj);
123: }
124: }
125:
126: final FileObject[] fos = new FileObject[result.size()];
127: result.toArray(fos);
128:
129: return fos;
130: }
131:
132: /**
133: * Finds all projects in given lookup. If the command is not null it will check
134: * whether given command is enabled on all projects. If and only if all projects
135: * have the command supported it will return array including the project. If there
136: * is one project with the command disabled it will return empty array.
137: */
138: public static Project[] getProjectsFromLookup(
139: final Lookup lookup, final String command) {
140: final Set result = new HashSet();
141:
142: // First find out whether there is a project directly in the Lookup
143: final Collection projects = lookup.lookup(
144: new Lookup.Template(Project.class)).allInstances();
145:
146: for (Iterator it = projects.iterator(); it.hasNext();) {
147: final Project p = (Project) it.next();
148: result.add(p);
149: }
150:
151: // Now try to guess the project from dataobjects
152: final Collection dataObjects = lookup.lookup(
153: new Lookup.Template(DataObject.class))
154: .allInstances();
155:
156: for (Iterator it = dataObjects.iterator(); it.hasNext();) {
157: final DataObject dObj = (DataObject) it.next();
158: final FileObject fObj = dObj.getPrimaryFile();
159: final Project p = FileOwnerQuery.getOwner(fObj);
160:
161: if (p != null) {
162: result.add(p);
163: }
164: }
165:
166: final Project[] projectsArray = new Project[result.size()];
167: result.toArray(projectsArray);
168:
169: if (command != null) {
170: // All projects have to have the command enabled
171: for (int i = 0; i < projectsArray.length; i++) {
172: if (!commandSupported(projectsArray[i], command,
173: lookup)) {
174: return new Project[0];
175: }
176: }
177: }
178:
179: return projectsArray;
180: }
181:
182: /**
183: * Tests whether given command is available on the project and whether
184: * the action as to be enabled in current Context
185: *
186: * @param project Project to test
187: * @param command Command for test
188: * @param context Lookup representing current context or null if context
189: * does not matter.
190: */
191: public static boolean commandSupported(final Project project,
192: final String command, final Lookup context) {
193: //We have to look whether the command is supported by the project
194: final ActionProvider ap = (ActionProvider) project
195: .getLookup().lookup(ActionProvider.class);
196:
197: if (ap != null) {
198: final List commands = Arrays.asList(ap
199: .getSupportedActions());
200:
201: if (commands.contains(command)) {
202: if ((context == null)
203: || ap.isActionEnabled(command, context)) {
204: return true;
205: }
206: }
207: }
208:
209: return false;
210: }
211:
212: /**
213: * Good for formating names of actions with some two parameter pattern
214: * {0} nuber of objects (e.g. Projects or files ) and {1} name of one
215: * or first object (e.g. Project or file) or null if the number is == 0
216: */
217: public static String formatName(final String namePattern,
218: final int numberOfObjects, final String firstObjectName) {
219: return MessageFormat.format(namePattern, new Object[] {
220: new Integer(numberOfObjects),
221: (firstObjectName == null) ? "" : firstObjectName, //NOI18N
222: });
223: }
224:
225: public static String formatProjectSensitiveName(
226: final String namePattern, final Project[] projects) {
227: // Set the action's name
228: if ((projects == null) || (projects.length == 0)) {
229: // No project selected
230: return ActionsUtil.formatName(namePattern, 0, null);
231: } else {
232: // Some project selected
233: return ActionsUtil.formatName(namePattern,
234: projects.length, ProjectUtils.getInformation(
235: projects[0]).getDisplayName());
236: }
237: }
238: }
239:
240: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
241:
242: private static boolean refreshing = false;
243:
244: //~ Instance fields ----------------------------------------------------------------------------------------------------------
245:
246: protected volatile boolean actionEnabled = false;
247: private final Lookup lookup;
248: private final Object initLock = new Object();
249: private final Object refreshRequestLock = new Object();
250: private final ProfilerProjectActionPerformer performer;
251: private final String namePattern;
252: private final LookupListener[] resultListeners;
253: private final Class[] watch;
254:
255: // @GuarderBy initLock
256: private volatile boolean isCalculated = false;
257:
258: // @GuardedBy refreshRequestLock
259: private int refreshRequested = 0;
260:
261: //~ Constructors -------------------------------------------------------------------------------------------------------------
262:
263: /**
264: * Constructor for global actions. E.g. actions in main menu which
265: * listen to the global context.
266: */
267: ProjectSensitiveAction(
268: final ProfilerProjectActionPerformer performer,
269: final String name, final String namePattern,
270: final Icon icon, Lookup lookup) {
271: super (name);
272:
273: if (icon != null) {
274: putValue(SMALL_ICON, icon);
275: }
276:
277: if (lookup == null) {
278: lookup = Utilities.actionsGlobalContext();
279: }
280:
281: this .lookup = lookup;
282: this .watch = new Class[] { Project.class, DataObject.class };
283: this .resultListeners = new LookupListener[watch.length];
284:
285: // Needs to listen on changes in results
286: for (int i = 0; i < watch.length; i++) {
287: final Lookup.Result result = lookup
288: .lookup(new Lookup.Template(watch[i]));
289: resultListeners[i] = (LookupListener) WeakListeners.create(
290: LookupListener.class, this , result);
291: result.addLookupListener(resultListeners[i]);
292: }
293:
294: this .performer = performer;
295: this .namePattern = namePattern;
296:
297: init();
298: }
299:
300: //~ Methods ------------------------------------------------------------------------------------------------------------------
301:
302: /**
303: * Needs to override isEnabled in order to force refresh
304: */
305: public final boolean isEnabled() {
306: init(); // the action must be initialized
307:
308: return super .isEnabled();
309: }
310:
311: public final void actionPerformed(final ActionEvent e) {
312: actionPerformed(lookup);
313: }
314:
315: public static ProjectSensitiveAction projectSensitiveAction(
316: final ProfilerProjectActionPerformer performer,
317: final String name, final String namePattern, final Icon icon) {
318: return new ProjectSensitiveAction(performer, name, namePattern,
319: icon, null);
320: }
321:
322: // Implementation of ContextAwareAction ------------------------------------
323: public Action createContextAwareInstance(final Lookup actionContext) {
324: return new ProjectSensitiveAction(getPerformer(), getName(),
325: getNamePattern(), (Icon) getValue(SMALL_ICON),
326: actionContext);
327: }
328:
329: // Implementation of LookupListener ----------------------------------------
330: public final void resultChanged(final LookupEvent e) {
331: isCalculated = false;
332: }
333:
334: protected final void setDisplayName(final String name) {
335: putValue(NAME, name);
336: }
337:
338: protected final String getName() {
339: return (String) getValue(NAME);
340: }
341:
342: protected final String getNamePattern() {
343: return namePattern;
344: }
345:
346: protected final ProfilerProjectActionPerformer getPerformer() {
347: return performer;
348: }
349:
350: protected void actionPerformed(final Lookup context) {
351: final Project[] projects = ActionsUtil.getProjectsFromLookup(
352: context, null);
353:
354: if (projects.length == 1) {
355: if (performer != null) {
356: new NBSwingWorker(false) {
357: private boolean isEnabled = false;
358: private final OutputParameter<Boolean> isCancelled = new OutputParameter<Boolean>(
359: Boolean.FALSE);
360: private final OutputParameter<ProgressDisplayer> progress = new OutputParameter<ProgressDisplayer>(
361: null);
362:
363: protected void doInBackground() {
364: isEnabled = performer.enable(projects[0],
365: context, false);
366: }
367:
368: protected void done() {
369: if (progress.isSet()) {
370: progress.getValue().close();
371: }
372:
373: if (isEnabled && !isCancelled.getValue()) {
374: performer.perform(projects[0], context);
375: } else if (!isCancelled.getValue()) {
376: NotifyDescriptor failure = new NotifyDescriptor.Message(
377: java.util.ResourceBundle
378: .getBundle(
379: "org/netbeans/modules/profiler/actions/Bundle")
380: .getString(
381: "AntActions_LazyEnablementFailure"));
382: DialogDisplayer.getDefault().notifyLater(
383: failure);
384: }
385: }
386:
387: protected void nonResponding() {
388: progress
389: .setValue(ProgressDisplayer
390: .showProgress(
391: java.util.ResourceBundle
392: .getBundle(
393: "org/netbeans/modules/profiler/actions/Bundle")
394: .getString(
395: "AntActions_LazyEnablementProgressMessage"),
396: new ProgressDisplayer.ProgressController() {
397: public boolean cancel() {
398: if (progress
399: .isSet()) {
400: progress
401: .getValue()
402: .close();
403: }
404:
405: progress
406: .setValue(null);
407: isCancelled
408: .setValue(true);
409:
410: return true;
411: }
412: }));
413: }
414: }.execute();
415: }
416: }
417: }
418:
419: protected void doRefresh(final Lookup context) {
420: final Project[] projects = ActionsUtil.getProjectsFromLookup(
421: context, null);
422:
423: setDisplayName(ActionsUtil.formatProjectSensitiveName(
424: getNamePattern(), projects));
425:
426: if ((projects != null) && (projects.length > 0)) {
427: setEnabled(getPerformer()
428: .enable(projects[0], context, true));
429: } else {
430: setEnabled(false);
431: }
432: }
433:
434: /**
435: * Initializes the action
436: */
437: private void init() {
438: if (isCalculated) {
439: return;
440: }
441:
442: synchronized (initLock) {
443: if (isCalculated) {
444: return;
445: }
446:
447: refresh(ProjectSensitiveAction.this .lookup);
448: isCalculated = true;
449: }
450: }
451:
452: private void refresh(final Lookup context) {
453: synchronized (refreshRequestLock) {
454: if (refreshRequested++ > 0) {
455: return;
456: }
457:
458: IDEUtils.runInEventDispatchThread(new Runnable() {
459: public void run() {
460: int oldReqCount = -1;
461: int currentReqCount = -1;
462:
463: synchronized (refreshRequestLock) {
464: oldReqCount = refreshRequested;
465: }
466:
467: doRefresh(lookup);
468:
469: synchronized (refreshRequestLock) {
470: currentReqCount = refreshRequested;
471: refreshRequested = 0;
472: }
473:
474: if (oldReqCount != currentReqCount) {
475: refresh(context);
476: }
477: }
478: });
479: }
480: }
481: }
|