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.apisupport.project.layers;
043:
044: import java.awt.Image;
045: import java.io.CharConversionException;
046: import java.io.File;
047: import java.io.IOException;
048: import java.util.Arrays;
049: import java.util.Collections;
050: import java.util.Iterator;
051: import java.util.Set;
052: import org.netbeans.api.java.classpath.ClassPath;
053: import org.netbeans.api.project.FileOwnerQuery;
054: import org.netbeans.api.project.Project;
055: import org.netbeans.modules.apisupport.project.NbModuleProject;
056: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
057: import org.netbeans.modules.apisupport.project.Util;
058: import org.netbeans.modules.apisupport.project.suite.SuiteProject;
059: import org.netbeans.modules.apisupport.project.ui.customizer.SuiteUtils;
060: import org.openide.ErrorManager;
061: import org.openide.filesystems.FileAttributeEvent;
062: import org.openide.filesystems.FileChangeListener;
063: import org.openide.filesystems.FileEvent;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileRenameEvent;
066: import org.openide.filesystems.FileStatusEvent;
067: import org.openide.filesystems.FileStatusListener;
068: import org.openide.filesystems.FileSystem;
069: import org.openide.filesystems.FileUtil;
070: import org.openide.filesystems.MultiFileSystem;
071: import org.openide.loaders.DataNode;
072: import org.openide.loaders.DataObject;
073: import org.openide.loaders.DataObjectNotFoundException;
074: import org.openide.loaders.Environment;
075: import org.openide.nodes.AbstractNode;
076: import org.openide.nodes.FilterNode;
077: import org.openide.nodes.Node;
078: import org.openide.util.Lookup;
079: import org.openide.util.NbBundle;
080: import org.openide.util.RequestProcessor;
081: import org.openide.util.actions.SystemAction;
082: import org.openide.util.lookup.Lookups;
083: import org.openide.xml.XMLUtil;
084:
085: /**
086: * Displays two views of a layer.
087: * @author Jesse Glick
088: */
089: public final class LayerNode extends FilterNode implements Node.Cookie {
090:
091: public LayerNode(LayerUtils.LayerHandle handle) {
092: this (getDataNode(handle), handle);
093: }
094:
095: LayerNode(Node delegate, LayerUtils.LayerHandle handle) {
096: super (delegate, new LayerChildren(handle));
097: }
098:
099: private static Node getDataNode(LayerUtils.LayerHandle handle) {
100: FileObject layer = handle.getLayerFile();
101: try {
102: return DataObject.find(layer).getNodeDelegate();
103: } catch (DataObjectNotFoundException e) {
104: assert false : e;
105: return Node.EMPTY;
106: }
107: }
108:
109: public static Environment.Provider createProvider() {
110: class EP implements Environment.Provider {
111: public Lookup getEnvironment(DataObject obj) {
112: Project p = FileOwnerQuery.getOwner(obj
113: .getPrimaryFile());
114: if (p == null) {
115: return Lookup.EMPTY;
116: }
117: LayerUtils.LayerHandle h = LayerUtils
118: .layerForProject(p);
119: if (h == null) {
120: return Lookup.EMPTY;
121: }
122: if (obj.getPrimaryFile().equals(h.getLayerFile())) {
123: DataNode dn = new DataNode(obj, Children.LEAF);
124: dn
125: .setIconBaseWithExtension("org/netbeans/modules/apisupport/project/ui/resources/layerObject.gif"); // NOI18N
126: LayerNode ln = new LayerNode(dn, h);
127: return Lookups.singleton(ln);
128: }
129: return Lookup.EMPTY;
130: }
131: }
132: return new EP();
133: }
134:
135: private static final class LayerChildren extends
136: Children.Keys<LayerChildren.KeyType> {
137:
138: enum KeyType {
139: WAIT, RAW, CONTEXTUALIZED
140: }
141:
142: private final LayerUtils.LayerHandle handle;
143: private ClassPath cp;
144: private Project p;
145: private FileSystem layerfs;
146: private FileSystem sfs;
147:
148: public LayerChildren(LayerUtils.LayerHandle handle) {
149: this .handle = handle;
150: }
151:
152: protected void addNotify() {
153: super .addNotify();
154: handle.setAutosave(true);
155: setKeys(Collections.singleton(KeyType.WAIT));
156: RequestProcessor.getDefault().post(new Runnable() {
157: public void run() {
158: try {
159: FileObject layer = handle.getLayerFile();
160: p = FileOwnerQuery.getOwner(layer);
161: assert p != null : layer;
162: try {
163: cp = createClasspath(p);
164: } catch (IOException e) {
165: Util.err.notify(ErrorManager.INFORMATIONAL,
166: e);
167: }
168: layerfs = handle.layer(false);
169: setKeys(Arrays
170: .asList(KeyType.RAW, KeyType.WAIT));
171: FileSystem _sfs = LayerUtils
172: .getEffectiveSystemFilesystem(p);
173: if (cp != null) { // has not been removeNotify()d yet
174: sfs = _sfs;
175: setKeys(Arrays.asList(KeyType.RAW,
176: KeyType.CONTEXTUALIZED));
177: }
178: } catch (IOException e) {
179: Util.err.notify(ErrorManager.INFORMATIONAL, e);
180: }
181: }
182: });
183: }
184:
185: protected void removeNotify() {
186: setKeys(Collections.<KeyType> emptySet());
187: cp = null;
188: p = null;
189: layerfs = null;
190: sfs = null;
191: super .removeNotify();
192: }
193:
194: protected Node[] createNodes(KeyType key) {
195: try {
196: switch (key) {
197: case RAW:
198: FileSystem fs = badge(layerfs, cp, handle
199: .getLayerFile(), NbBundle.getMessage(
200: LayerNode.class, "LBL_this_layer"), null);
201: return new Node[] { DataObject.find(fs.getRoot())
202: .getNodeDelegate() };
203: case CONTEXTUALIZED:
204: fs = badge(sfs, cp, handle.getLayerFile(), NbBundle
205: .getMessage(LayerNode.class,
206: "LBL_this_layer_in_context"),
207: handle.layer(false));
208: return new Node[] { DataObject.find(fs.getRoot())
209: .getNodeDelegate() };
210: case WAIT:
211: return new Node[] { new AbstractNode(Children.LEAF) {
212: public @Override
213: String getDisplayName() {
214: return NbBundle.getMessage(LayerNode.class,
215: "LayerNode_please_wait");
216: }
217: } };
218: default:
219: throw new AssertionError(key);
220: }
221: } catch (IOException e) {
222: Util.err.notify(ErrorManager.INFORMATIONAL, e);
223: return new Node[0];
224: }
225: }
226:
227: }
228:
229: /**
230: * Add badging support to the plain layer.
231: */
232: private static FileSystem badge(final FileSystem base,
233: final ClassPath cp, final FileObject layer,
234: final String rootLabel, final FileSystem highlighted) {
235: class BadgingMergedFileSystem extends MultiFileSystem {
236: private final BadgingSupport status;
237:
238: public BadgingMergedFileSystem() {
239: super (new FileSystem[] { base });
240: status = new BadgingSupport(this );
241: status.addFileStatusListener(new FileStatusListener() {
242: public void annotationChanged(FileStatusEvent ev) {
243: fireFileStatusChanged(ev);
244: }
245: });
246: status.setClasspath(cp);
247: // XXX loc/branding suffix?
248: addFileChangeListener(new FileChangeListener() { // #65564
249: private void fire() {
250: fireFileStatusChanged(new FileStatusEvent(
251: BadgingMergedFileSystem.this , true,
252: true));
253: }
254:
255: public void fileAttributeChanged(
256: FileAttributeEvent fe) {
257: fire();
258: }
259:
260: public void fileChanged(FileEvent fe) {
261: fire();
262: }
263:
264: public void fileDataCreated(FileEvent fe) {
265: fire();
266: }
267:
268: public void fileDeleted(FileEvent fe) {
269: fire();
270: }
271:
272: public void fileFolderCreated(FileEvent fe) {
273: fire();
274: }
275:
276: public void fileRenamed(FileRenameEvent fe) {
277: fire();
278: }
279: });
280: }
281:
282: public FileSystem.Status getStatus() {
283: return new FileSystem.HtmlStatus() {
284: public String annotateNameHtml(String name,
285: Set files) {
286: String nonHtmlLabel = status.annotateName(name,
287: files);
288: if (files.size() == 1
289: && ((FileObject) files.iterator()
290: .next()).isRoot()) {
291: nonHtmlLabel = rootLabel;
292: }
293: String htmlLabel;
294: try {
295: htmlLabel = XMLUtil
296: .toElementContent(nonHtmlLabel);
297: } catch (CharConversionException e) {
298: Util.err.notify(ErrorManager.INFORMATIONAL,
299: e);
300: htmlLabel = nonHtmlLabel;
301: }
302: if (highlighted != null) {
303: // Boldface resources which do come from this project.
304: boolean local = false;
305: Iterator it = files.iterator();
306: while (it.hasNext()) {
307: FileObject f = (FileObject) it.next();
308: if (!f.isRoot()
309: && highlighted.findResource(f
310: .getPath()) != null) {
311: local = true;
312: break;
313: }
314: }
315: if (local) {
316: htmlLabel = "<b>" + htmlLabel + "</b>"; // NOI18N
317: }
318: }
319: return htmlLabel;
320: }
321:
322: public String annotateName(String name, Set files) {
323: // Complex to explain why this is even called, but it is.
324: // Weird b/c hacks in the way DataNode.getHtmlDisplayName works.
325: return name;
326: }
327:
328: public Image annotateIcon(Image icon, int iconType,
329: Set files) {
330: return status.annotateIcon(icon, iconType,
331: files);
332: }
333: };
334: }
335:
336: public String getDisplayName() {
337: return FileUtil.getFileDisplayName(layer);
338: }
339:
340: public SystemAction[] getActions(Set<FileObject> foSet) {
341: return new SystemAction[] {
342: SystemAction.get(PickNameAction.class),
343: SystemAction.get(PickIconAction.class),
344: SystemAction.get(OpenLayerFilesAction.class), };
345: }
346: }
347: return new BadgingMergedFileSystem();
348: /* XXX loc/branding suffix possibilities:
349: Matcher m = Pattern.compile("(.*" + "/)?[^_/.]+(_[^/.]+)?(\\.[^/]+)?").matcher(u);
350: assert m.matches() : u;
351: suffix = m.group(2);
352: if (suffix == null) {
353: suffix = "";
354: }
355: status.setSuffix(suffix);
356: */
357: }
358:
359: public String getDisplayName() {
360: return NbBundle.getMessage(LayerNode.class, "LayerNode_label");
361: }
362:
363: /**
364: * Make a runtime classpath indicative of what is accessible from a sample resource.
365: */
366: private static ClassPath createClasspath(Project p)
367: throws IOException {
368: NbModuleProvider.NbModuleType type = Util.getModuleType(p);
369: if (type == NbModuleProvider.STANDALONE) {
370: return LayerUtils.createLayerClasspath(Collections
371: .singleton(p), LayerUtils
372: .getPlatformJarsForStandaloneProject(p));
373: } else if (type == NbModuleProvider.SUITE_COMPONENT) {
374: SuiteProject suite = SuiteUtils.findSuite(p);
375: if (suite == null) {
376: throw new IOException("Could not load suite for " + p); // NOI18N
377: }
378: Set<NbModuleProject> modules = SuiteUtils
379: .getSubProjects(suite);
380: return LayerUtils.createLayerClasspath(modules, LayerUtils
381: .getPlatformJarsForSuiteComponentProject(p, suite));
382: } else if (type == NbModuleProvider.NETBEANS_ORG) {
383: //Can cast to NbModuleProject here..
384: return LayerUtils
385: .createLayerClasspath(
386: LayerUtils
387: .getProjectsForNetBeansOrgProject((NbModuleProject) p),
388: Collections.<File> emptySet());
389: } else {
390: throw new AssertionError(type);
391: }
392: }
393:
394: }
|