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: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.project.ui.actions;
043:
044: import java.text.MessageFormat;
045: import java.util.Arrays;
046: import java.util.HashMap;
047: import java.util.HashSet;
048: import java.util.List;
049: import java.util.Map;
050: import java.util.Set;
051: import javax.swing.Action;
052: import org.netbeans.api.project.FileOwnerQuery;
053: import org.netbeans.api.project.Project;
054: import org.netbeans.spi.project.ActionProvider;
055: import org.netbeans.api.project.ProjectUtils;
056: import org.openide.filesystems.FileObject;
057: import org.openide.loaders.DataObject;
058: import org.openide.util.Lookup;
059: import org.openide.util.WeakSet;
060:
061: /** Nice utility methods to be used in ProjectBased Actions
062: *
063: * @author Pet Hrebejk
064: */
065: class ActionsUtil {
066:
067: /*
068: public static LookupResultsCache lookupResultsCache;
069: */
070:
071: public static final ShortcutsManager SHORCUTS_MANAGER = new ShortcutsManager();
072:
073: public static HashMap<String, MessageFormat> pattern2format = new HashMap<String, MessageFormat>();
074:
075: /** Finds all projects in given lookup. If the command is not null it will check
076: * whther given command is enabled on all projects. If and only if all projects
077: * have the command supported it will return array including the project. If there
078: * is one project with the command disabled it will return empty array.
079: */
080: public static Project[] getProjectsFromLookup(Lookup lookup,
081: String command) {
082: /*
083: if ( lookupResultsCache == null ) {
084: lookupResultsCache = new LookupResultsCache( new Class[] { Project.class, DataObject.class } );
085: }
086:
087: Project[] projectsArray = lookupResultsCache.getProjects( lookup );
088: */
089: // #74161: do not cache
090: // First find out whether there is a project directly in the Lookup
091: Set<Project> result = new HashSet<Project>();
092: for (Project p : lookup.lookupAll(Project.class)) {
093: result.add(p);
094: }
095: // Now try to guess the project from dataobjects
096: for (DataObject dObj : lookup.lookupAll(DataObject.class)) {
097: FileObject fObj = dObj.getPrimaryFile();
098: Project p = FileOwnerQuery.getOwner(fObj);
099: if (p != null) {
100: result.add(p);
101: }
102: }
103: Project[] projectsArray = result.toArray(new Project[result
104: .size()]);
105:
106: if (command != null) {
107: // All projects have to have the command enabled
108: for (Project p : projectsArray) {
109: if (!commandSupported(p, command, lookup)) {
110: return new Project[0];
111: }
112: }
113: }
114:
115: return projectsArray;
116: }
117:
118: /** In given lookup will find all FileObjects owned by given project
119: * with given command supported.
120: */
121: public static FileObject[] getFilesFromLookup(Lookup lookup,
122: Project project) {
123: HashSet<FileObject> result = new HashSet<FileObject>();
124: for (DataObject dObj : lookup.lookupAll(DataObject.class)) {
125: FileObject fObj = dObj.getPrimaryFile();
126: Project p = FileOwnerQuery.getOwner(fObj);
127: if (p != null && p.equals(project)) {
128: result.add(fObj);
129: }
130:
131: }
132:
133: FileObject[] fos = new FileObject[result.size()];
134: result.toArray(fos);
135: return fos;
136: }
137:
138: /**
139: * Tests whether given command is available on the project and whether
140: * the action as to be enabled in current Context
141: * @param project Project to test
142: * @param command Command for test
143: * @param context Lookup representing current context or null if context
144: * does not matter.
145: */
146: public static boolean commandSupported(Project project,
147: String command, Lookup context) {
148: //We have to look whether the command is supported by the project
149: ActionProvider ap = project.getLookup().lookup(
150: ActionProvider.class);
151: if (ap != null) {
152: List commands = Arrays.asList(ap.getSupportedActions());
153: if (commands.contains(command)) {
154: if (context == null
155: || ap.isActionEnabled(command, context)) {
156: //System.err.println("cS: true project=" + project + " command=" + command + " context=" + context);
157: return true;
158: }
159: }
160: }
161: //System.err.println("cS: false project=" + project + " command=" + command + " context=" + context);
162: return false;
163: }
164:
165: public static String formatProjectSensitiveName(String namePattern,
166: Project projects[]) {
167:
168: // Set the action's name
169: if (projects == null || projects.length == 0) {
170: // No project selected
171: return ActionsUtil.formatName(namePattern, 0, null);
172: } else {
173: // Some project selected
174: // XXX what about passing an object that computes the name lazily
175: return ActionsUtil.formatName(namePattern, projects.length,
176: new Wrapper(projects[0]));
177: }
178: }
179:
180: private static class Wrapper {
181: Wrapper(Project prj) {
182: project = prj;
183: }
184:
185: private Project project;
186:
187: @Override
188: public String toString() {
189: return ProjectUtils.getInformation(project)
190: .getDisplayName();
191: }
192:
193: }
194:
195: /** Good for formating names of actions with some two parameter pattern
196: * {0} nuber of objects (e.g. Projects or files ) and {1} name of one
197: * or first object (e.g. Project or file) or null if the number is == 0
198: * {2} whats the type of the name 0 == normal, 1 == menu, 2 == popup
199: */
200: public static String formatName(String namePattern,
201: int numberOfObjects, Object firstObjectName) {
202:
203: MessageFormat mf = null;
204:
205: synchronized (pattern2format) {
206: mf = pattern2format.get(namePattern);
207: if (mf == null) {
208: mf = new MessageFormat(namePattern);
209: pattern2format.put(namePattern, mf);
210: }
211: }
212:
213: StringBuffer result = new StringBuffer();
214:
215: mf.format(new Object[] {
216: numberOfObjects,
217: firstObjectName == null ? "" : firstObjectName
218: .toString(), }, result, null);
219:
220: return result.toString();
221: }
222:
223: // Innerclasses ------------------------------------------------------------
224:
225: /** Manages shortcuts based on the action's command. Usefull for File and
226: * projects actions.
227: */
228:
229: public static class ShortcutsManager {
230:
231: // command -> shortcut
232: Map<String, Object> shorcuts = new HashMap<String, Object>();
233:
234: // command -> WeakSet of actions
235: HashMap<String, Set<Action>> actions = new HashMap<String, Set<Action>>();
236:
237: public void registerAction(String command, Action action) {
238:
239: synchronized (this ) {
240: Set<Action> commandActions = actions.get(command);
241:
242: if (commandActions == null) {
243: commandActions = new WeakSet<Action>();
244: actions.put(command, commandActions);
245: }
246:
247: commandActions.add(action);
248:
249: }
250:
251: Object shorcut = getShortcut(command);
252:
253: if (shorcut != null) {
254: action.putValue(Action.ACCELERATOR_KEY, shorcut);
255: }
256:
257: }
258:
259: public void registerShortcut(String command, Object shortcut) {
260:
261: Set<Action> actionsToChange = null;
262:
263: synchronized (this ) {
264:
265: Object exShorcut = getShortcut(command);
266:
267: if ((exShorcut != null && exShorcut.equals(shortcut)) || // Shorcuts are equal
268: (exShorcut == null && shortcut == null)) { // or both are null
269: return; // No action needed
270: }
271:
272: shorcuts.put(command, shortcut);
273:
274: Set<Action> commandActions = actions.get(command);
275: if (commandActions != null && !commandActions.isEmpty()) {
276: actionsToChange = new HashSet<Action>();
277: actionsToChange.addAll(commandActions);
278: }
279:
280: }
281:
282: if (actionsToChange != null) {
283: // Need to change actions in existing actions
284: for (Action a : actionsToChange) {
285: if (a != null) {
286: a.putValue(Action.ACCELERATOR_KEY, shortcut);
287: }
288: }
289: }
290:
291: }
292:
293: public synchronized Object getShortcut(String command) {
294: return shorcuts.get(command);
295: }
296:
297: }
298:
299: /** Caches the projects and files included in the last quried lookup.
300: *
301: * Using weak references to fix issue #67846. Please note that holding the
302: * lookup results weak may cause that the cache will miss much more often
303: * than strictly necessary, but it is the best solution found so far.
304: * Holding the results weak should not break the correctness, it may only
305: * cause the cache will not work very well (or not at all).
306: *
307: * Please see also the tests.
308: */
309: /* XXX #74161: does not actually work
310: private static class LookupResultsCache implements LookupListener {
311:
312: private Class<?> watch[];
313:
314: private Reference<Lookup> lruLookup;
315: private List<Reference<Lookup.Result>> lruResults;
316: private Project[] projects;
317:
318: LookupResultsCache( Class[] watch ) {
319: this.watch = watch;
320: }
321:
322: public synchronized Project[] getProjects( Lookup lookup ) {
323: Lookup lruLookupLocal = lruLookup != null ? lruLookup.get() : null;
324:
325: if ( lookup != lruLookupLocal ) { // Lookup changed
326: if ( lruResults != null ) {
327: for (Reference<Lookup.Result> r : lruResults) {
328: Lookup.Result result = r.get();
329: if (result != null) {
330: result.removeLookupListener( this ); // Deregister
331: }
332: }
333: lruResults = null;
334: }
335: makeDirty();
336: lruLookupLocal = null;
337: }
338:
339: if ( lruLookupLocal == null ) { // Needs to attach to lookup
340: lruLookup = new CleanableWeakReference<Lookup>(lruLookupLocal = lookup);
341: lruResults = new ArrayList<Reference<Lookup.Result>>();
342: for (Class<?> c : watch) {
343: Lookup.Result result = lookup.lookupResult(c);
344:
345: result.allItems();
346: result.addLookupListener( this );
347:
348: lruResults.add(new CleanableWeakReference<Lookup.Result>(result));
349: }
350: }
351:
352: if ( isDirty() ) { // Needs to recompute the result
353:
354: Set<Project> result = new HashSet<Project>();
355:
356: // First find out whether there is a project directly in the Lookup
357: for (Project p : lruLookupLocal.lookupAll(Project.class)) {
358: result.add(p);
359: }
360:
361: // Now try to guess the project from dataobjects
362: for (DataObject dObj : lruLookupLocal.lookupAll(DataObject.class)) {
363: FileObject fObj = dObj.getPrimaryFile();
364: Project p = FileOwnerQuery.getOwner(fObj);
365: if ( p != null ) {
366: result.add( p );
367: }
368:
369: }
370:
371: projects = new Project[ result.size() ];
372: result.toArray( projects );
373:
374: }
375:
376: return projects;
377: }
378:
379:
380: private boolean isDirty() {
381: return projects == null;
382: }
383:
384: private synchronized void makeDirty() {
385: projects = null;
386: }
387:
388: // Lookup listener implementation --------------------------------------
389:
390: public void resultChanged( LookupEvent e ) {
391: makeDirty();
392: }
393:
394: private class CleanableWeakReference<T> extends WeakReference<T> implements Runnable {
395:
396: public CleanableWeakReference(T o) {
397: super(o, Utilities.activeReferenceQueue());
398: }
399:
400: public void run() {
401: synchronized (LookupResultsCache.this) {
402: lruLookup = null;
403: lruResults = null;
404: projects = null;
405: }
406: }
407: }
408:
409: }
410: */
411:
412: }
|