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: * Portions Copyrighted 2007 Sun Microsystems, Inc.
027: */
028: package org.netbeans.modules.loadgenerator.project.ui;
029:
030: import java.awt.Image;
031: import java.awt.event.ActionEvent;
032: import java.beans.PropertyChangeEvent;
033: import java.beans.PropertyChangeListener;
034: import java.util.ArrayList;
035: import java.util.Arrays;
036: import java.util.Collection;
037: import java.util.HashMap;
038: import java.util.HashSet;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042: import java.util.Set;
043: import javax.swing.AbstractAction;
044: import javax.swing.SwingUtilities;
045: import javax.swing.event.ChangeEvent;
046: import javax.swing.event.ChangeListener;
047: import org.netbeans.api.project.Project;
048: import org.netbeans.api.project.ProjectUtils;
049: import org.netbeans.api.project.SourceGroup;
050: import org.netbeans.api.project.Sources;
051: import org.netbeans.api.queries.VisibilityQuery;
052: import org.netbeans.modules.loadgenerator.api.EngineManager;
053: import org.netbeans.spi.project.ui.support.CommonProjectActions;
054: import org.netbeans.spi.project.ui.support.NodeFactory;
055: import org.netbeans.spi.project.ui.support.NodeList;
056: import org.openide.filesystems.FileChangeAdapter;
057: import org.openide.filesystems.FileChangeListener;
058: import org.openide.filesystems.FileEvent;
059: import org.openide.filesystems.FileObject;
060: import org.openide.filesystems.FileRenameEvent;
061: import org.openide.filesystems.FileStateInvalidException;
062: import org.openide.filesystems.FileStatusEvent;
063: import org.openide.filesystems.FileStatusListener;
064: import org.openide.filesystems.FileSystem;
065: import org.openide.filesystems.FileUtil;
066: import org.openide.loaders.DataFolder;
067: import org.openide.loaders.DataObject;
068: import org.openide.loaders.DataObjectNotFoundException;
069: import org.openide.nodes.Children;
070: import org.openide.nodes.Node;
071: import org.openide.util.ChangeSupport;
072: import org.openide.util.Exceptions;
073: import org.openide.util.Lookup;
074: import org.openide.util.NbBundle;
075: import org.openide.util.RequestProcessor;
076: import org.openide.util.Utilities;
077: import org.openide.util.WeakListeners;
078: import org.openide.util.lookup.Lookups;
079:
080: /**
081: *
082: * @author Jaroslav Bachorik
083: */
084: public class ScriptNodeFactory implements NodeFactory {
085:
086: public NodeList createNodes(Project p) {
087: return new ScriptNodeList(p);
088: }
089:
090: private static class ScriptNodeList implements NodeList<String>,
091: PropertyChangeListener {
092:
093: private static final String SCRIPT_FILES = "scriptFiles"; //NOI18N
094: private final Project project;
095: private final ChangeSupport changeSupport = new ChangeSupport(
096: this );
097:
098: ScriptNodeList(Project proj) {
099: project = proj;
100: }
101:
102: public List<String> keys() {
103: List<String> result = new ArrayList<String>();
104: result.add(SCRIPT_FILES);
105: return result;
106: }
107:
108: public void addChangeListener(ChangeListener l) {
109: changeSupport.addChangeListener(l);
110: }
111:
112: public void removeChangeListener(ChangeListener l) {
113: changeSupport.removeChangeListener(l);
114: }
115:
116: public Node node(String key) {
117: if (key == SCRIPT_FILES) {
118: return new ScriptNode(project);
119: }
120: assert false : "No node for key: " + key;
121: return null;
122: }
123:
124: public void addNotify() {
125: }
126:
127: public void removeNotify() {
128: }
129:
130: public void propertyChange(PropertyChangeEvent evt) {
131: // The caller holds ProjectManager.mutex() read lock
132: SwingUtilities.invokeLater(new Runnable() {
133:
134: public void run() {
135: changeSupport.fireChange();
136: }
137: });
138: }
139: }
140:
141: private static Lookup createLookup(Project project) {
142: DataFolder rootFolder = DataFolder.findFolder(project
143: .getProjectDirectory());
144: // XXX Remove root folder after FindAction rewrite
145: return Lookups.fixed(new Object[] { project, rootFolder });
146: }
147:
148: private static final class ScriptNode extends
149: org.openide.nodes.AbstractNode implements Runnable,
150: FileStatusListener, ChangeListener, PropertyChangeListener {
151:
152: private static final Image LOADGEN_FILES_BADGE = Utilities
153: .loadImage(
154: "org/netbeans/modules/loadgenerator/project/ui/resources/loadgen_badge.png",
155: true); // NOI18N
156: private Node projectNode;
157:
158: // icon badging >>>
159: private Set files;
160: private Map fileSystemListeners;
161: private RequestProcessor.Task task;
162: private final Object privateLock = new Object();
163: private boolean iconChange;
164: private boolean nameChange;
165: private ChangeListener sourcesListener;
166: private Map groupsListeners;
167: private final Project project;
168: // icon badging <<<
169: private String iconbase = "org/openide/loaders/defaultFolder";
170:
171: public ScriptNode(Project prj) {
172: super (ScriptChildren.forProject(prj), createLookup(prj));
173: this .project = prj;
174: setName("loadgenScripts"); // NOI18N
175: setIconBase(iconbase);
176:
177: FileObject projectDir = prj.getProjectDirectory();
178: try {
179: DataObject projectDo = DataObject.find(projectDir);
180: if (projectDo != null) {
181: projectNode = projectDo.getNodeDelegate();
182: }
183: } catch (DataObjectNotFoundException e) {
184: }
185: }
186:
187: public Image getIcon(int type) {
188: Image img = computeIcon(false, type);
189: return (img != null) ? img : super .getIcon(type);
190: }
191:
192: public Image getOpenedIcon(int type) {
193: Image img = computeIcon(true, type);
194: return (img != null) ? img : super .getIcon(type);
195: }
196:
197: private Node getDataFolderNodeDelegate() {
198: return getLookup().lookup(DataFolder.class)
199: .getNodeDelegate();
200: }
201:
202: private Image computeIcon(boolean opened, int type) {
203: Image image;
204:
205: image = opened ? getDataFolderNodeDelegate().getOpenedIcon(
206: type) : getDataFolderNodeDelegate().getIcon(type);
207: image = Utilities.mergeImages(image, LOADGEN_FILES_BADGE,
208: 8, 8);
209:
210: return image;
211: }
212:
213: public String getDisplayName() {
214: return NbBundle.getMessage(ScriptNodeFactory.class,
215: "LBL_Node_Script"); //NOI18N
216: }
217:
218: public javax.swing.Action[] getActions(boolean context) {
219: return new javax.swing.Action[] {
220: CommonProjectActions.newFileAction(),
221: new ScriptsRefreshAction(this ) };
222: }
223:
224: public void run() {
225: boolean fireIcon;
226: boolean fireName;
227: synchronized (privateLock) {
228: fireIcon = iconChange;
229: fireName = nameChange;
230: iconChange = false;
231: nameChange = false;
232: }
233: if (fireIcon) {
234: fireIconChange();
235: fireOpenedIconChange();
236: }
237: if (fireName) {
238: fireDisplayNameChange(null, null);
239: }
240: }
241:
242: public void annotationChanged(FileStatusEvent event) {
243: if (task == null) {
244: task = RequestProcessor.getDefault().create(this );
245: }
246:
247: synchronized (privateLock) {
248: if ((!iconChange && event.isIconChange())
249: || (!nameChange && event.isNameChange())) {
250: Iterator it = files.iterator();
251: while (it.hasNext()) {
252: FileObject fo = (FileObject) it.next();
253: if (event.hasChanged(fo)) {
254: iconChange |= event.isIconChange();
255: nameChange |= event.isNameChange();
256: }
257: }
258: }
259: }
260:
261: task.schedule(50); // batch by 50 ms
262: }
263:
264: public void stateChanged(ChangeEvent e) {
265: setProjectFiles(project);
266: }
267:
268: public void propertyChange(PropertyChangeEvent evt) {
269: setProjectFiles(project);
270: }
271:
272: protected void setProjectFiles(Project project) {
273: Sources sources = ProjectUtils.getSources(project); // returns singleton
274: if (sourcesListener == null) {
275: sourcesListener = WeakListeners.change(this , sources);
276: sources.addChangeListener(sourcesListener);
277: }
278: setGroups(Arrays.asList(sources
279: .getSourceGroups(Sources.TYPE_GENERIC)));
280: }
281:
282: private void setGroups(Collection groups) {
283: if (groupsListeners != null) {
284: Iterator it = groupsListeners.keySet().iterator();
285: while (it.hasNext()) {
286: SourceGroup group = (SourceGroup) it.next();
287: PropertyChangeListener pcl = (PropertyChangeListener) groupsListeners
288: .get(group);
289: group.removePropertyChangeListener(pcl);
290: }
291: }
292: groupsListeners = new HashMap();
293: Set roots = new HashSet();
294: Iterator it = groups.iterator();
295: while (it.hasNext()) {
296: SourceGroup group = (SourceGroup) it.next();
297: PropertyChangeListener pcl = WeakListeners
298: .propertyChange(this , group);
299: groupsListeners.put(group, pcl);
300: group.addPropertyChangeListener(pcl);
301: FileObject fo = group.getRootFolder();
302: roots.add(fo);
303: }
304: setFiles(roots);
305: }
306:
307: protected void setFiles(Set files) {
308: if (fileSystemListeners != null) {
309: Iterator it = fileSystemListeners.keySet().iterator();
310: while (it.hasNext()) {
311: FileSystem fs = (FileSystem) it.next();
312: FileStatusListener fsl = (FileStatusListener) fileSystemListeners
313: .get(fs);
314: fs.removeFileStatusListener(fsl);
315: }
316: }
317:
318: fileSystemListeners = new HashMap();
319: this .files = files;
320: if (files == null) {
321: return;
322: }
323: Iterator it = files.iterator();
324: Set hookedFileSystems = new HashSet();
325: while (it.hasNext()) {
326: FileObject fo = (FileObject) it.next();
327: try {
328: FileSystem fs = fo.getFileSystem();
329: if (hookedFileSystems.contains(fs)) {
330: continue;
331: }
332: hookedFileSystems.add(fs);
333: FileStatusListener fsl = FileUtil
334: .weakFileStatusListener(this , fs);
335: fs.addFileStatusListener(fsl);
336: fileSystemListeners.put(fs, fsl);
337: } catch (FileStateInvalidException e) {
338: Exceptions.printStackTrace(Exceptions
339: .attachMessage(e, "Can not get " + fo
340: + " filesystem, ignoring...")); // NO18N
341: }
342: }
343: }
344: }
345:
346: private static final class ScriptChildren extends
347: Children.Keys<FileObject> {
348:
349: private final EngineManager lgEngineManager = Lookup
350: .getDefault().lookup(EngineManager.class);
351:
352: private final Set<FileObject> keys;
353: private final java.util.Comparator<FileObject> comparator = new NodeComparator();
354:
355: private final FileChangeListener anyFileListener = new FileChangeAdapter() {
356:
357: public void fileDataCreated(FileEvent fe) {
358: addKey(fe.getFile());
359: }
360:
361: public void fileFolderCreated(FileEvent fe) {
362: addKey(fe.getFile());
363: }
364:
365: public void fileRenamed(FileRenameEvent fe) {
366: addKey(fe.getFile());
367: }
368:
369: public void fileDeleted(FileEvent fe) {
370: removeKey(fe.getFile());
371: }
372: };
373:
374: private final Project project;
375:
376: private ScriptChildren(Project project) {
377: this .project = project;
378: keys = new HashSet<FileObject>();
379: this .project.getProjectDirectory().addFileChangeListener(
380: anyFileListener);
381: }
382:
383: public static Children forProject(Project project) {
384: return new ScriptChildren(project);
385: }
386:
387: @Override
388: protected void addNotify() {
389: createKeys();
390: doSetKeys();
391: }
392:
393: @Override
394: protected void removeNotify() {
395: removeListeners();
396: keys.clear();
397: }
398:
399: public Node[] createNodes(FileObject key) {
400: Node n = null;
401:
402: if (keys.contains(key)) {
403: try {
404: DataObject dataObject = DataObject.find(key);
405: n = dataObject.getNodeDelegate().cloneNode();
406: } catch (DataObjectNotFoundException dnfe) {
407: }
408: }
409:
410: return (n == null) ? new Node[0] : new Node[] { n };
411: }
412:
413: public synchronized void refreshNodes() {
414: addNotify();
415: }
416:
417: private synchronized void addKey(FileObject key) {
418: if (VisibilityQuery.getDefault().isVisible(key)
419: && isSupported(key)) {
420: //System.out.println("Adding " + key.getPath());
421: keys.add(key);
422: doSetKeys();
423: }
424: key.addFileChangeListener(anyFileListener);
425: }
426:
427: private synchronized void removeKey(FileObject key) {
428: //System.out.println("Removing " + key.getPath());
429: key.removeFileChangeListener(anyFileListener);
430: keys.remove(key);
431: doSetKeys();
432: }
433:
434: private synchronized void createKeys() {
435: keys.clear();
436: addAllScripts();
437: }
438:
439: private void doSetKeys() {
440: final FileObject[] result = keys
441: .toArray(new FileObject[keys.size()]);
442: java.util.Arrays.sort(result, comparator);
443:
444: SwingUtilities.invokeLater(new Runnable() {
445:
446: public void run() {
447: setKeys(result);
448: }
449: });
450: }
451:
452: private void removeListeners() {
453: for (FileObject key : keys) {
454: key.removeFileChangeListener(anyFileListener);
455: }
456: }
457:
458: private void addAllScripts() {
459: FileObject rootDir = project.getProjectDirectory();
460: addAllScripts(rootDir);
461: }
462:
463: private void addAllScripts(FileObject forDir) {
464: if (forDir.isData()) {
465: return;
466: }
467: for (FileObject file : forDir.getChildren()) {
468: file.addFileChangeListener(WeakListeners
469: .create(FileChangeListener.class,
470: anyFileListener, file));
471: if (file.isFolder()) {
472: addAllScripts(file);
473: } else {
474: if (VisibilityQuery.getDefault().isVisible(file)
475: && isSupported(file)) {
476: keys.add(file);
477: }
478: }
479: }
480: }
481:
482: private boolean isSupported(FileObject fo) {
483: if (lgEngineManager == null)
484: return false;
485: return !lgEngineManager.findEngines(fo.getExt()).isEmpty();
486: }
487: }
488:
489: private static final class NodeComparator implements
490: java.util.Comparator<FileObject> {
491:
492: public int compare(FileObject fo1, FileObject fo2) {
493: int result = compareType(fo1, fo2);
494: if (result == 0) {
495: result = compareNames(fo1, fo2);
496: }
497: if (result == 0) {
498: return fo1.getPath().compareTo(fo2.getPath());
499: }
500: return result;
501: }
502:
503: private int compareType(FileObject fo1, FileObject fo2) {
504: int folder1 = fo1.isFolder() ? 0 : 1;
505: int folder2 = fo2.isFolder() ? 0 : 1;
506:
507: return folder1 - folder2;
508: }
509:
510: private int compareNames(FileObject do1, FileObject do2) {
511: return do1.getNameExt().compareTo(do2.getNameExt());
512: }
513:
514: public boolean equals(Object o) {
515: return o instanceof NodeComparator;
516: }
517: }
518:
519: private static class ScriptsRefreshAction extends AbstractAction {
520: private ScriptNode associatedNode;
521:
522: public ScriptsRefreshAction(ScriptNode node) {
523: super (NbBundle.getMessage(ScriptNodeFactory.class,
524: "LBL_Refresh")); //NOI18N
525: associatedNode = node;
526: }
527:
528: public void actionPerformed(ActionEvent e) {
529: ((ScriptChildren) associatedNode.getChildren())
530: .refreshNodes();
531: }
532: }
533: }
|