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;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.text.MessageFormat;
047: import java.util.ArrayList;
048: import java.util.Collections;
049: import java.util.StringTokenizer;
050: import javax.swing.Action;
051: import javax.swing.event.ChangeEvent;
052: import javax.swing.event.ChangeListener;
053: import org.netbeans.api.project.Project;
054: import org.netbeans.api.project.ProjectUtils;
055: import org.netbeans.api.project.ProjectInformation;
056: import org.netbeans.api.project.SourceGroup;
057: import org.netbeans.api.project.Sources;
058: import org.netbeans.api.queries.VisibilityQuery;
059: import org.netbeans.spi.project.ui.support.CommonProjectActions;
060: import org.openide.ErrorManager;
061: import org.openide.filesystems.FileObject;
062: import org.openide.filesystems.FileUtil;
063: import org.openide.loaders.ChangeableDataFilter;
064: import org.openide.loaders.DataFilter;
065: import org.openide.loaders.DataFolder;
066: import org.openide.loaders.DataObject;
067: import org.openide.loaders.DataObjectNotFoundException;
068: import org.openide.nodes.FilterNode;
069: import org.openide.nodes.Node;
070: import org.openide.nodes.NodeNotFoundException;
071: import org.openide.nodes.NodeOp;
072: import org.openide.util.ChangeSupport;
073: import org.openide.util.NbBundle;
074: import org.openide.util.Lookup;
075: import org.openide.util.WeakListeners;
076: import org.openide.util.lookup.Lookups;
077: import org.openide.util.lookup.ProxyLookup;
078:
079: /**
080: * Support for creating logical views.
081: * @author Jesse Glick, Petr Hrebejk
082: */
083: public class PhysicalView {
084:
085: public static boolean isProjectDirNode(Node n) {
086: return n instanceof GroupNode && ((GroupNode) n).isProjectDir;
087: }
088:
089: public static Node[] createNodesForProject(Project p) {
090: Sources s = ProjectUtils.getSources(p);
091: SourceGroup[] groups = s.getSourceGroups(Sources.TYPE_GENERIC);
092:
093: FileObject projectDirectory = p.getProjectDirectory();
094: SourceGroup projectDirGroup = null;
095:
096: // First find the source group which will represent the project
097: for (int i = 0; i < groups.length; i++) {
098: FileObject groupRoot = groups[i].getRootFolder();
099: if (projectDirectory.equals(groupRoot)
100: || FileUtil.isParentOf(groupRoot, projectDirectory)) {
101: if (projectDirGroup != null) {
102: // more than once => Illegal
103: projectDirGroup = null;
104: break;
105: } else {
106: projectDirGroup = groups[i];
107: }
108: }
109: }
110:
111: if (projectDirGroup == null) {
112: // Illegal project
113: ErrorManager.getDefault().log(
114: ErrorManager.WARNING,
115: "Project "
116: + p
117: + // NOI18N
118: "either does not contain it's project directory under the "
119: + // NOI18N
120: "Generic source groups or the project directory is under "
121: + // NOI18N
122: "more than one source group"); // NOI18N
123: return new Node[0];
124: }
125:
126: // Create the nodes
127: ArrayList<Node> nodesList = new ArrayList<Node>(groups.length);
128: nodesList
129: .add(/*new GroupContainmentFilterNode(*/new GroupNode(
130: p, projectDirGroup, true, DataFolder
131: .findFolder(projectDirGroup
132: .getRootFolder()))/*, projectDirGroup)*/);
133:
134: for (int i = 0; i < groups.length; i++) {
135:
136: if (groups[i] == projectDirGroup) {
137: continue;
138: }
139:
140: nodesList
141: .add(/*new GroupContainmentFilterNode(*/new GroupNode(
142: p, groups[i], false, DataFolder
143: .findFolder(groups[i]
144: .getRootFolder()))/*, groups[i])*/);
145: }
146:
147: Node nodes[] = new Node[nodesList.size()];
148: nodesList.toArray(nodes);
149: return nodes;
150: }
151:
152: static final class VisibilityQueryDataFilter implements
153: ChangeListener, ChangeableDataFilter {
154:
155: private final ChangeSupport changeSupport = new ChangeSupport(
156: this );
157:
158: public VisibilityQueryDataFilter() {
159: VisibilityQuery.getDefault().addChangeListener(this );
160: }
161:
162: public boolean acceptDataObject(DataObject obj) {
163: FileObject fo = obj.getPrimaryFile();
164: return VisibilityQuery.getDefault().isVisible(fo);
165: }
166:
167: public void stateChanged(ChangeEvent e) {
168: changeSupport.fireChange();
169: }
170:
171: public void addChangeListener(ChangeListener listener) {
172: changeSupport.addChangeListener(listener);
173: }
174:
175: public void removeChangeListener(ChangeListener listener) {
176: changeSupport.removeChangeListener(listener);
177: }
178:
179: }
180:
181: static final class GroupNode extends FilterNode implements
182: PropertyChangeListener {
183:
184: private static final DataFilter VISIBILITY_QUERY_FILTER = new VisibilityQueryDataFilter();
185:
186: static final String GROUP_NAME_PATTERN = NbBundle.getMessage(
187: PhysicalView.class, "FMT_PhysicalView_GroupName"); // NOI18N
188:
189: private ProjectInformation pi;
190: private SourceGroup group;
191: private boolean isProjectDir;
192:
193: public GroupNode(Project project, SourceGroup group,
194: boolean isProjectDir, DataFolder dataFolder) {
195: super (dataFolder.getNodeDelegate(), dataFolder
196: .createNodeChildren(VISIBILITY_QUERY_FILTER),
197: createLookup(project, group, dataFolder));
198:
199: this .pi = ProjectUtils.getInformation(project);
200: this .group = group;
201: this .isProjectDir = isProjectDir;
202: pi.addPropertyChangeListener(WeakListeners.propertyChange(
203: this , pi));
204: group.addPropertyChangeListener(WeakListeners
205: .propertyChange(this , group));
206: }
207:
208: // XXX May need to change icons as well
209:
210: public String getName() {
211: if (isProjectDir) {
212: return pi.getName();
213: } else {
214: String n = group.getName();
215: if (n == null) {
216: n = "???"; // NOI18N
217: ErrorManager
218: .getDefault()
219: .log(
220: ErrorManager.WARNING,
221: "SourceGroup impl of type "
222: + group.getClass()
223: .getName()
224: + " specified a null getName(); this is illegal");
225: }
226: return n;
227: }
228: }
229:
230: public String getDisplayName() {
231: if (isProjectDir) {
232: return pi.getDisplayName();
233: } else {
234: return MessageFormat.format(GROUP_NAME_PATTERN,
235: new Object[] { group.getDisplayName(),
236: pi.getDisplayName(),
237: getOriginal().getDisplayName() });
238: }
239: }
240:
241: public String getShortDescription() {
242: FileObject gdir = group.getRootFolder();
243: String dir = FileUtil.getFileDisplayName(gdir);
244: return NbBundle.getMessage(PhysicalView.class,
245: isProjectDir ? "HINT_project" : "HINT_group", // NOI18N
246: dir);
247: }
248:
249: public boolean canRename() {
250: return false;
251: }
252:
253: public boolean canCut() {
254: return false;
255: }
256:
257: public boolean canCopy() {
258: // At least for now.
259: return false;
260: }
261:
262: public boolean canDestroy() {
263: return false;
264: }
265:
266: public Action[] getActions(boolean context) {
267:
268: if (context) {
269: return super .getActions(true);
270: } else {
271: Action[] folderActions = super .getActions(false);
272: Action[] projectActions;
273:
274: if (isProjectDir) {
275: // If this is project dir then the properties action
276: // has to be replaced to invoke project customizer
277: projectActions = new Action[folderActions.length];
278: for (int i = 0; i < folderActions.length; i++) {
279: if (folderActions[i] instanceof org.openide.actions.PropertiesAction) {
280: projectActions[i] = CommonProjectActions
281: .customizeProjectAction();
282: } else {
283: projectActions[i] = folderActions[i];
284: }
285: }
286: } else {
287: projectActions = folderActions;
288: }
289:
290: return projectActions;
291: }
292: }
293:
294: // Private methods -------------------------------------------------
295:
296: public void propertyChange(PropertyChangeEvent evt) {
297: String prop = evt.getPropertyName();
298: if (ProjectInformation.PROP_DISPLAY_NAME.equals(prop)) {
299: fireDisplayNameChange(null, null);
300: } else if (ProjectInformation.PROP_NAME.equals(prop)) {
301: fireNameChange(null, null);
302: } else if (ProjectInformation.PROP_ICON.equals(prop)) {
303: // OK, ignore
304: } else if ("name".equals(prop)) { // NOI18N
305: fireNameChange(null, null);
306: } else if ("displayName".equals(prop)) { // NOI18N
307: fireDisplayNameChange(null, null);
308: } else if ("icon".equals(prop)) { // NOI18N
309: // OK, ignore
310: } else if ("rootFolder".equals(prop)) { // NOI18N
311: // XXX Do something to children and lookup
312: fireNameChange(null, null);
313: fireDisplayNameChange(null, null);
314: fireShortDescriptionChange(null, null);
315: } else if (SourceGroup.PROP_CONTAINERSHIP.equals(prop)) {
316: // OK, ignore
317: } else {
318: assert false : "Attempt to fire an unsupported property change event from "
319: + pi.getClass().getName() + ": " + prop;
320: }
321: }
322:
323: private static Lookup createLookup(Project p,
324: SourceGroup group, DataFolder dataFolder) {
325: return new ProxyLookup(new Lookup[] {
326: dataFolder.getNodeDelegate().getLookup(),
327: Lookups.fixed(new Object[] { p,
328: new PathFinder(group) }), p.getLookup(), });
329: }
330:
331: }
332:
333: /* XXX disabled for now pending resolution of interaction with planned VCS annotations (color only):
334: /**
335: * Specially displays nodes corresponding to files which are not contained in this source group.
336: * /
337: private static final class GroupContainmentFilterNode extends FilterNode {
338:
339: private final SourceGroup g;
340:
341: public GroupContainmentFilterNode(Node orig, SourceGroup g) {
342: super(orig, orig.isLeaf() ? Children.LEAF : new GroupContainmentFilterChildren(orig, g));
343: this.g = g;
344: }
345:
346: public String getHtmlDisplayName() {
347: Node orig = getOriginal();
348: DataObject d = (DataObject) orig.getCookie(DataObject.class);
349: assert d != null : orig;
350: FileObject f = d.getPrimaryFile();
351: String barename = orig.getHtmlDisplayName();
352: if (!FileUtil.isParentOf(g.getRootFolder(), f) || g.contains(f)) {
353: // Leave it alone.
354: return barename;
355: }
356: // Try to grey it out.
357: if (barename == null) {
358: try {
359: barename = XMLUtil.toElementContent(orig.getDisplayName());
360: } catch (CharConversionException e) {
361: // Never mind.
362: return null;
363: }
364: }
365: return "<font color='!Label.disabledForeground'>" + barename + "</font>"; // NOI18N
366: }
367:
368: private static final class GroupContainmentFilterChildren extends FilterNode.Children {
369:
370: private final SourceGroup g;
371:
372: public GroupContainmentFilterChildren(Node orig, SourceGroup g) {
373: super(orig);
374: this.g = g;
375: }
376:
377: protected Node copyNode(Node node) {
378: if (original.getCookie(DataFolder.class) != null && node.getCookie(DataObject.class) != null) {
379: return new GroupContainmentFilterNode(node, g);
380: } else {
381: return super.copyNode(node);
382: }
383: }
384:
385: }
386:
387: }
388: */
389:
390: public static class PathFinder {
391:
392: private SourceGroup group;
393:
394: public PathFinder(SourceGroup group) {
395: this .group = group;
396: }
397:
398: public Node findPath(Node root, Object object) {
399:
400: if (!(object instanceof FileObject)) {
401: return null;
402: }
403:
404: FileObject fo = (FileObject) object;
405: FileObject groupRoot = group.getRootFolder();
406: if (FileUtil.isParentOf(groupRoot, fo) /* && group.contains( fo ) */) {
407: // The group contains the object
408:
409: String relPath = FileUtil
410: .getRelativePath(groupRoot, fo);
411:
412: ArrayList<String> path = new ArrayList<String>();
413: StringTokenizer strtok = new StringTokenizer(relPath,
414: "/");
415: while (strtok.hasMoreTokens()) {
416: path.add(strtok.nextToken());
417: }
418:
419: if (path.size() > 0) {
420: path.remove(path.size() - 1);
421: } else {
422: return null;
423: }
424: try {
425: //#75205
426: Node parent = NodeOp.findPath(root, Collections
427: .enumeration(path));
428: if (parent != null) {
429: //not nice but there isn't a findNodes(name) method.
430: Node[] nds = parent.getChildren()
431: .getNodes(true);
432: for (int i = 0; i < nds.length; i++) {
433: DataObject dobj = nds[i].getLookup()
434: .lookup(DataObject.class);
435: if (dobj != null
436: && fo.equals(dobj.getPrimaryFile())) {
437: return nds[i];
438: }
439: }
440: String name = fo.getName();
441: try {
442: DataObject dobj = DataObject.find(fo);
443: name = dobj.getNodeDelegate().getName();
444: } catch (DataObjectNotFoundException ex) {
445: }
446: return parent.getChildren().findChild(name);
447: }
448: } catch (NodeNotFoundException e) {
449: return null;
450: }
451: } else if (groupRoot.equals(fo)) {
452: return root;
453: }
454:
455: return null;
456: }
457:
458: }
459:
460: }
|