001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019:
020: package org.netbeans.modules.bpel.project.ui;
021:
022: import java.awt.Component;
023: import java.awt.Image;
024: import java.awt.Panel;
025: import java.awt.datatransfer.Transferable;
026: import java.awt.datatransfer.DataFlavor;
027: import java.awt.datatransfer.UnsupportedFlavorException;
028: import java.awt.image.BufferedImage;
029: import java.io.IOException;
030: import java.util.Arrays;
031: import java.util.Collections;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.ArrayList;
035: import java.util.Set;
036: import javax.swing.Action;
037: import javax.swing.Icon;
038: import javax.swing.ImageIcon;
039: import org.netbeans.api.project.SourceGroup;
040: import org.netbeans.spi.project.ui.support.CommonProjectActions;
041: import org.openide.ErrorManager;
042: import org.openide.filesystems.FileObject;
043: import org.openide.filesystems.FileStateInvalidException;
044: import org.openide.filesystems.FileStatusEvent;
045: import org.openide.filesystems.FileStatusListener;
046: import org.openide.filesystems.FileSystem;
047: import org.openide.filesystems.FileUtil;
048: import org.openide.loaders.DataFolder;
049: import org.openide.loaders.DataObject;
050: import org.openide.nodes.AbstractNode;
051: import org.openide.nodes.Node;
052: import org.openide.nodes.NodeNotFoundException;
053: import org.openide.nodes.NodeOp;
054: import org.openide.nodes.PropertySupport;
055: import org.openide.nodes.Sheet;
056: import org.openide.util.Lookup;
057: import org.openide.util.NbBundle;
058: import org.openide.util.RequestProcessor;
059: import org.openide.util.Utilities;
060: import org.openide.util.datatransfer.ExTransferable;
061: import org.openide.util.datatransfer.MultiTransferObject;
062: import org.openide.util.datatransfer.PasteType;
063: import org.openide.util.lookup.AbstractLookup;
064: import org.openide.util.lookup.InstanceContent;
065: import org.openide.util.lookup.Lookups;
066: import org.openide.util.lookup.ProxyLookup;
067: import org.openidex.search.SearchInfo;
068: import org.openidex.search.SearchInfoFactory;
069:
070: /** Node displaying a packages in given SourceGroup
071: * @author Petr Hrebejk
072: */
073: final class PackageRootNode extends AbstractNode implements Runnable,
074: FileStatusListener {
075:
076: static Image PACKAGE_BADGE = Utilities
077: .loadImage("org/netbeans/spi/java/project/support/ui/packageBadge.gif"); // NOI18N
078:
079: private static Action actions[];
080:
081: private SourceGroup group;
082:
083: private final FileObject file;
084: private final Set files;
085: private FileStatusListener fileSystemListener;
086: private RequestProcessor.Task task;
087: private volatile boolean iconChange;
088: private volatile boolean nameChange;
089:
090: PackageRootNode(SourceGroup group) {
091: this (group, new InstanceContent());
092: }
093:
094: private PackageRootNode(SourceGroup group, InstanceContent ic) {
095: super (new PackageViewChildren(group.getRootFolder()),
096: new ProxyLookup(new Lookup[] { createLookup(group),
097: new AbstractLookup(ic) }));
098:
099: ic.add(alwaysSearchableSearchInfo(SearchInfoFactory
100: .createSearchInfoBySubnodes(this )));
101: this .group = group;
102: file = group.getRootFolder();
103: files = Collections.singleton(file);
104: try {
105: FileSystem fs = file.getFileSystem();
106: fileSystemListener = FileUtil.weakFileStatusListener(this ,
107: fs);
108: fs.addFileStatusListener(fileSystemListener);
109: } catch (FileStateInvalidException e) {
110: ErrorManager err = ErrorManager.getDefault();
111: err.annotate(e, "Can not get " + file
112: + " filesystem, ignoring..."); // NO18N
113: err.notify(ErrorManager.INFORMATIONAL, e);
114: }
115: setName(group.getName());
116: setDisplayName(group.getDisplayName());
117: // setIconBase("org/netbeans/modules/java/j2seproject/ui/resources/packageRoot");
118: }
119:
120: public Image getIcon(int type) {
121: return computeIcon(false, type);
122: }
123:
124: public Image getOpenedIcon(int type) {
125: return computeIcon(true, type);
126: }
127:
128: public String getDisplayName() {
129: String s = super .getDisplayName();
130:
131: try {
132: s = file.getFileSystem().getStatus().annotateName(s, files);
133: } catch (FileStateInvalidException e) {
134: ErrorManager.getDefault().notify(
135: ErrorManager.INFORMATIONAL, e);
136: }
137:
138: return s;
139: }
140:
141: public String getHtmlDisplayName() {
142: try {
143: FileSystem.Status stat = file.getFileSystem().getStatus();
144: if (stat instanceof FileSystem.HtmlStatus) {
145: FileSystem.HtmlStatus hstat = (FileSystem.HtmlStatus) stat;
146:
147: String result = hstat.annotateNameHtml(super
148: .getDisplayName(), files);
149:
150: //Make sure the super string was really modified
151: if (!super .getDisplayName().equals(result)) {
152: return result;
153: }
154: }
155: } catch (FileStateInvalidException e) {
156: ErrorManager.getDefault().notify(
157: ErrorManager.INFORMATIONAL, e);
158: }
159: return super .getHtmlDisplayName();
160: }
161:
162: public void run() {
163: if (iconChange) {
164: fireIconChange();
165: fireOpenedIconChange();
166: iconChange = false;
167: }
168: if (nameChange) {
169: fireDisplayNameChange(null, null);
170: nameChange = false;
171: }
172: }
173:
174: public void annotationChanged(FileStatusEvent event) {
175: if (task == null) {
176: task = RequestProcessor.getDefault().create(this );
177: }
178:
179: if ((iconChange == false && event.isIconChange())
180: || (nameChange == false && event.isNameChange())) {
181: if (event.hasChanged(file)) {
182: iconChange |= event.isIconChange();
183: nameChange |= event.isNameChange();
184: }
185: }
186:
187: task.schedule(50); // batch by 50 ms
188: }
189:
190: public Action[] getActions(boolean context) {
191:
192: if (actions == null) {
193: actions = new Action[] {
194: CommonProjectActions.newFileAction(),
195: null,
196: org.openide.util.actions.SystemAction
197: .get(org.openide.actions.FileSystemAction.class),
198: null,
199: org.openide.util.actions.SystemAction
200: .get(org.openide.actions.FindAction.class),
201: null,
202: org.openide.util.actions.SystemAction
203: .get(org.openide.actions.PasteAction.class),
204: null,
205: org.openide.util.actions.SystemAction
206: .get(org.openide.actions.ToolsAction.class), };
207: }
208: return actions;
209: }
210:
211: // Show reasonable properties of the DataFolder,
212: //it shows the sorting names as rw property, the name as ro property and the path to root as ro property
213: public PropertySet[] getPropertySets() {
214: PropertySet[] properties = getDataFolderNodeDelegate()
215: .getPropertySets();
216: for (int i = 0; i < properties.length; i++) {
217: if (Sheet.PROPERTIES.equals(properties[i].getName())) {
218: //Replace the Sheet.PROPERTIES by the new one
219: //having the ro name property and ro path property
220: properties[i] = Sheet.createPropertiesSet();
221: ((Sheet.Set) properties[i])
222: .put(new PropertySupport.ReadOnly(
223: DataObject.PROP_NAME, String.class,
224: NbBundle.getMessage(
225: PackageRootNode.class,
226: "PROP_name"), NbBundle
227: .getMessage(
228: PackageRootNode.class,
229: "HINT_name")) {
230:
231: public/*@Override*/Object getValue() {
232: return PackageRootNode.this
233: .getDisplayName();
234: }
235: });
236: ((Sheet.Set) properties[i])
237: .put(new PropertySupport.ReadOnly("ROOT_PATH",
238: String.class, //NOI18N
239: NbBundle.getMessage(
240: PackageRootNode.class,
241: "PROP_rootpath"), NbBundle
242: .getMessage(
243: PackageRootNode.class,
244: "HINT_rootpath")) {
245:
246: public/*@Override*/Object getValue() {
247: return FileUtil
248: .getFileDisplayName(PackageRootNode.this .file);
249: }
250: });
251: }
252: }
253: return properties;
254: }
255:
256: // XXX Paste types - probably not very nice
257: public void createPasteTypes(Transferable t, List list) {
258: if (t.isDataFlavorSupported(ExTransferable.multiFlavor)) {
259: try {
260: MultiTransferObject mto = (MultiTransferObject) t
261: .getTransferData(ExTransferable.multiFlavor);
262: List l = new ArrayList();
263: boolean isPackageFlavor = false;
264: boolean hasTheSameRoot = false;
265: int op = -1;
266: for (int i = 0; i < mto.getCount(); i++) {
267: Transferable pt = mto.getTransferableAt(i);
268: DataFlavor[] flavors = mto
269: .getTransferDataFlavors(i);
270: for (int j = 0; j < flavors.length; j++) {
271: if (PackageViewChildren.SUBTYPE
272: .equals(flavors[j].getSubType())
273: && PackageViewChildren.PRIMARY_TYPE
274: .equals(flavors[j]
275: .getPrimaryType())) {
276: if (op == -1) {
277: op = Integer
278: .valueOf(
279: flavors[j]
280: .getParameter(PackageViewChildren.MASK))
281: .intValue();
282: }
283: PackageViewChildren.PackageNode pkgNode = (PackageViewChildren.PackageNode) pt
284: .getTransferData(flavors[j]);
285: if (!((PackageViewChildren) getChildren())
286: .getRoot()
287: .equals(pkgNode.getRoot())) {
288: l.add(pkgNode);
289: } else {
290: hasTheSameRoot = true;
291: }
292: isPackageFlavor = true;
293: }
294: }
295: }
296: if (isPackageFlavor && !hasTheSameRoot) {
297: list
298: .add(new PackageViewChildren.PackagePasteType(
299: this .group.getRootFolder(),
300: (PackageViewChildren.PackageNode[]) l
301: .toArray(new PackageViewChildren.PackageNode[l
302: .size()]), op));
303: } else if (!isPackageFlavor) {
304: list.addAll(Arrays
305: .asList(getDataFolderNodeDelegate()
306: .getPasteTypes(t)));
307: }
308: } catch (UnsupportedFlavorException e) {
309: ErrorManager.getDefault().notify(e);
310: } catch (IOException e) {
311: ErrorManager.getDefault().notify(e);
312: }
313: } else {
314: DataFlavor[] flavors = t.getTransferDataFlavors();
315: FileObject root = this .group.getRootFolder();
316: boolean isPackageFlavor = false;
317: if (root != null && root.canWrite()) {
318: for (int i = 0; i < flavors.length; i++) {
319: if (PackageViewChildren.SUBTYPE.equals(flavors[i]
320: .getSubType())
321: && PackageViewChildren.PRIMARY_TYPE
322: .equals(flavors[i].getPrimaryType())) {
323: isPackageFlavor = true;
324: try {
325: int op = Integer
326: .valueOf(
327: flavors[i]
328: .getParameter(PackageViewChildren.MASK))
329: .intValue();
330: PackageViewChildren.PackageNode pkgNode = (PackageViewChildren.PackageNode) t
331: .getTransferData(flavors[i]);
332: if (!((PackageViewChildren) getChildren())
333: .getRoot()
334: .equals(pkgNode.getRoot())) {
335: list
336: .add(new PackageViewChildren.PackagePasteType(
337: root,
338: new PackageViewChildren.PackageNode[] { pkgNode },
339: op));
340: }
341: } catch (IOException ioe) {
342: ErrorManager.getDefault().notify(ioe);
343: } catch (UnsupportedFlavorException ufe) {
344: ErrorManager.getDefault().notify(ufe);
345: }
346: }
347: }
348: }
349: if (!isPackageFlavor) {
350: list.addAll(Arrays.asList(getDataFolderNodeDelegate()
351: .getPasteTypes(t)));
352: }
353: }
354: }
355:
356: public/*@Override*/PasteType getDropType(Transferable t,
357: int action, int index) {
358: PasteType pasteType = super .getDropType(t, action, index);
359: //The pasteType can be:
360: // 1) PackagePasteType - the t.flavor is package flavor
361: // 2) null or DataPasteType - the t.flavor in not package flavor
362: if (pasteType instanceof PackageViewChildren.PackagePasteType) {
363: ((PackageViewChildren.PackagePasteType) pasteType)
364: .setOperation(action);
365: }
366: return pasteType;
367: }
368:
369: // Private methods ---------------------------------------------------------
370:
371: private Node getDataFolderNodeDelegate() {
372: return ((DataFolder) getLookup().lookup(DataFolder.class))
373: .getNodeDelegate();
374: }
375:
376: private Image computeIcon(boolean opened, int type) {
377: Image image;
378: Icon icon = group.getIcon(opened);
379:
380: if (icon == null) {
381: image = opened ? getDataFolderNodeDelegate().getOpenedIcon(
382: type) : getDataFolderNodeDelegate().getIcon(type);
383: image = Utilities.mergeImages(image, PACKAGE_BADGE, 7, 7);
384: } else {
385: if (icon instanceof ImageIcon) {
386: image = ((ImageIcon) icon).getImage();
387: } else {
388: image = icon2image(icon);
389: }
390: }
391:
392: return image;
393: }
394:
395: private static Component CONVERTOR_COMPONENT = new Panel();
396:
397: static Image icon2image(Icon icon) {
398: int height = icon.getIconHeight();
399: int width = icon.getIconWidth();
400:
401: BufferedImage bImage = new BufferedImage(width, height,
402: BufferedImage.TYPE_INT_ARGB);
403: icon.paintIcon(CONVERTOR_COMPONENT, bImage.getGraphics(), 0, 0);
404:
405: return bImage;
406: }
407:
408: private static Lookup createLookup(SourceGroup group) {
409: // XXX Remove DataFolder when paste, find and refresh are reimplemented
410: FileObject rootFolder = group.getRootFolder();
411: DataFolder dataFolder = DataFolder.findFolder(rootFolder);
412: return Lookups.fixed(new Object[] { dataFolder,
413: new PathFinder(group) });
414: }
415:
416: /** If contained in the lookup can perform the search for a node
417: */
418: public static class PathFinder {
419:
420: private SourceGroup group;
421:
422: public PathFinder(SourceGroup group) {
423: this .group = group;
424: }
425:
426: public Node findPath(Node root, Object object) {
427: FileObject fo;
428: if (object instanceof FileObject) {
429: fo = (FileObject) object;
430: } else if (object instanceof DataObject) {
431: fo = ((DataObject) object).getPrimaryFile();
432: } else {
433: return null;
434: }
435:
436: FileObject groupRoot = group.getRootFolder();
437: if (FileUtil.isParentOf(groupRoot, fo) /* && group.contains( fo ) */) {
438: // The group contains the object
439:
440: String relPath = FileUtil
441: .getRelativePath(groupRoot, fo);
442: int lastSlashIndex = relPath.lastIndexOf('/'); // NOI18N
443:
444: String[] path = null;
445: if (fo.isFolder()) {
446: String packageName = relPath.replace('/', '.'); // NOI18N
447: path = new String[] { packageName };
448: } else if (lastSlashIndex == -1) {
449: path = new String[] { "", fo.getName() };
450: } else {
451: String packageName = relPath.substring(0,
452: lastSlashIndex).replace('/', '.'); // NOI18N
453: path = new String[] { packageName, fo.getName() };
454: }
455: try {
456: // XXX if there are two files differing only by extension in the package,
457: // this will be wrong...
458: return NodeOp.findPath(root, path);
459: } catch (NodeNotFoundException e) {
460: if (!fo.isFolder()) {
461: // If it is a DefaultDataObject, the node name contains the extension.
462: if (lastSlashIndex == -1) {
463: path = new String[] { "", fo.getNameExt() };
464: } else {
465: String packageName = relPath.substring(0,
466: lastSlashIndex).replace('/', '.'); // NOI18N
467: path = new String[] { packageName,
468: fo.getNameExt() };
469: }
470: try {
471: return NodeOp.findPath(root, path);
472: } catch (NodeNotFoundException e2) {
473: // already handled
474: }
475: }
476: // did not manage to find it after all... why?
477: return null;
478: }
479: } else if (groupRoot.equals(fo)) {
480: // First try to find default package
481: try {
482: return NodeOp.findPath(root, new String[] { "" }); // NOI18N
483: } catch (NodeNotFoundException e) {
484: // If it does not exists return this node
485: }
486: return root;
487: }
488:
489: return null;
490: }
491:
492: public String toString() {
493: return "PathFinder[" + group + "]"; // NOI18N
494: }
495:
496: }
497:
498: /**
499: * Produce a {@link SearchInfo} variant that is always searchable, for speed.
500: * @see "#48685"
501: */
502: static SearchInfo alwaysSearchableSearchInfo(SearchInfo i) {
503: return new AlwaysSearchableSearchInfo(i);
504: }
505:
506: private static final class AlwaysSearchableSearchInfo implements
507: SearchInfo {
508:
509: private final SearchInfo delegate;
510:
511: public AlwaysSearchableSearchInfo(SearchInfo delegate) {
512: this .delegate = delegate;
513: }
514:
515: public boolean canSearch() {
516: return true;
517: }
518:
519: public Iterator/*<DataObject>*/objectsToSearch() {
520: return delegate.objectsToSearch();
521: }
522:
523: }
524:
525: }
|