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.awt.Toolkit;
046: import java.beans.BeanInfo;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.net.URL;
050: import java.util.ArrayList;
051: import java.util.Iterator;
052: import java.util.List;
053: import java.util.Properties;
054: import java.util.Set;
055: import java.util.logging.Level;
056: import java.util.logging.Logger;
057: import javax.swing.Action;
058: import javax.swing.JSeparator;
059: import org.netbeans.api.java.classpath.ClassPath;
060: import org.netbeans.modules.apisupport.project.Util;
061: import org.openide.ErrorManager;
062: import org.openide.awt.Actions;
063: import org.openide.cookies.InstanceCookie;
064: import org.openide.filesystems.FileAttributeEvent;
065: import org.openide.filesystems.FileChangeListener;
066: import org.openide.filesystems.FileEvent;
067: import org.openide.filesystems.FileObject;
068: import org.openide.filesystems.FileRenameEvent;
069: import org.openide.filesystems.FileStateInvalidException;
070: import org.openide.filesystems.FileStatusEvent;
071: import org.openide.filesystems.FileStatusListener;
072: import org.openide.filesystems.FileSystem;
073: import org.openide.filesystems.FileUtil;
074: import org.openide.filesystems.URLMapper;
075: import org.openide.loaders.DataObject;
076: import org.openide.util.NbBundle;
077: import org.openide.util.RequestProcessor;
078: import org.openide.util.actions.Presenter;
079:
080: /**
081: * Handles addition of badges to a filesystem a la system filesystem.
082: * Specifically interprets SystemFileSystem.localizingBundle and
083: * SystemFileSystem.icon (and SystemFileSystem.icon32).
084: * Also tries to provide display labels for InstanceDataObject's.
085: * Parts copied from org.netbeans.core.projects.SystemFileSystem.
086: * @author Jesse Glick
087: */
088: final class BadgingSupport implements FileSystem.Status,
089: FileChangeListener {
090:
091: /** for branding/localization like "_f4j_ce_ja"; never null, but may be "" */
092: private String suffix = "";
093: /** classpath in which to look up resources; may be null but then nothing will be found... */
094: private ClassPath classpath;
095: private final FileSystem fs;
096: private final FileChangeListener fileChangeListener;
097: private final List<FileStatusListener> listeners = new ArrayList<FileStatusListener>();
098:
099: public BadgingSupport(FileSystem fs) {
100: this .fs = fs;
101: fileChangeListener = FileUtil
102: .weakFileChangeListener(this , null);
103: fs.addFileChangeListener(fileChangeListener);
104: }
105:
106: public void setClasspath(ClassPath classpath) {
107: this .classpath = classpath;
108: }
109:
110: public void setSuffix(String suffix) {
111: this .suffix = suffix;
112: }
113:
114: public void addFileStatusListener(FileStatusListener l) {
115: listeners.add(l);
116: }
117:
118: public void removeFileStatusListener(FileStatusListener l) {
119: listeners.remove(l);
120: }
121:
122: private void fireFileStatusChanged(FileStatusEvent e) {
123: Iterator it = listeners.iterator();
124: while (it.hasNext()) {
125: ((FileStatusListener) it.next()).annotationChanged(e);
126: }
127: }
128:
129: public String annotateName(String name, Set files) {
130: return annotateNameGeneral(name, files, suffix,
131: fileChangeListener, classpath);
132: }
133:
134: private static String annotateNameGeneral(String name, Set files,
135: String suffix, FileChangeListener fileChangeListener,
136: ClassPath cp) {
137: Iterator it = files.iterator();
138: while (it.hasNext()) {
139: FileObject fo = (FileObject) it.next();
140: String bundleName = (String) fo
141: .getAttribute("SystemFileSystem.localizingBundle"); // NOI18N
142: if (bundleName != null) {
143: try {
144: URL[] u = LayerUtils.currentify(new URL(
145: "nbresloc:/"
146: + // NOI18N
147: bundleName.replace('.', '/')
148: + ".properties"), // NOI18N
149: suffix, cp);
150: for (int i = 0; i < u.length; i++) {
151: InputStream is = u[i].openStream();
152: try {
153: Properties p = new Properties();
154: p.load(is);
155: String key = fo.getPath();
156: String val = p.getProperty(key);
157: // Listen to changes in the origin file if any...
158: FileObject ufo = URLMapper
159: .findFileObject(u[i]);
160: if (ufo != null) {
161: ufo
162: .removeFileChangeListener(fileChangeListener);
163: ufo
164: .addFileChangeListener(fileChangeListener);
165: // In case a sibling bundle is added, that may be relevant:
166: ufo.getParent()
167: .removeFileChangeListener(
168: fileChangeListener);
169: ufo.getParent().addFileChangeListener(
170: fileChangeListener);
171: }
172: if (val != null) {
173: if (fo.getPath().startsWith("Menu/")) { // NOI18N
174: // Special-case menu folders to trim the mnemonics, since they are ugly.
175: return Actions.cutAmpersand(val);
176: } else {
177: return val;
178: }
179: }
180: // if null, fine--normal for key to not be found
181: } finally {
182: is.close();
183: }
184: }
185: } catch (IOException ioe) {
186: // For debugging; SFS will rather notify a problem separately...
187: Util.err.notify(ErrorManager.INFORMATIONAL, ioe);
188: return NbBundle.getMessage(BadgingSupport.class,
189: "LBL_no_such_bundle", name, bundleName);
190: }
191: }
192: if (fo.hasExt("instance")) { // NOI18N
193: return getInstanceLabel(fo);
194: }
195: if (fo.hasExt("shadow")) { // NOI18N
196: Object originalFile = fo.getAttribute("originalFile"); // NOI18N
197: if (originalFile != null
198: && originalFile instanceof String) {
199: FileObject orig;
200: try {
201: orig = fo.getFileSystem().findResource(
202: (String) originalFile);
203: } catch (FileStateInvalidException e) {
204: orig = null;
205: }
206: if (orig != null && orig.hasExt("instance")) { // NOI18N
207: return getInstanceLabel(orig);
208: }
209: }
210: }
211: }
212: return name;
213: }
214:
215: private static String getInstanceLabel(FileObject fo) {
216: try {
217: // First try to load it in current IDE, as this handles most platform cases OK.
218: InstanceCookie ic = DataObject.find(fo).getCookie(
219: InstanceCookie.class);
220: if (ic != null) {
221: Object o;
222: Logger fslogger = Logger
223: .getLogger("org.openide.filesystems"); // NOI18N
224: Level oldLevel = fslogger.getLevel();
225: fslogger.setLevel(Level.OFF); // #99744
226: try {
227: o = ic.instanceCreate();
228: } finally {
229: fslogger.setLevel(oldLevel);
230: }
231: if (o instanceof Action) {
232: String name = (String) ((Action) o)
233: .getValue(Action.NAME);
234: if (name != null) {
235: return Actions.cutAmpersand(name);
236: } else {
237: return toStringOf(o);
238: }
239: } else if (o instanceof Presenter.Menu) {
240: return ((Presenter.Menu) o).getMenuPresenter()
241: .getText();
242: } else if (o instanceof JSeparator) {
243: return NbBundle.getMessage(BadgingSupport.class,
244: "LBL_separator");
245: } else {
246: return toStringOf(o);
247: }
248: }
249: } catch (IOException e) {
250: // ignore, OK
251: } catch (ClassNotFoundException e) {
252: // ignore, OK
253: }
254: // OK, probably a developed module, so take a guess.
255: String clazz = (String) fo.getAttribute("instanceClass"); // NOI18N
256: if (clazz == null) {
257: clazz = fo.getName().replace('-', '.');
258: }
259: String instanceCreate = (String) fo
260: .getAttribute("literal:instanceCreate"); // NOI18N
261: if (instanceCreate != null && instanceCreate.startsWith("new:")) { // NOI18N
262: clazz = instanceCreate.substring("new:".length()); // NOI18N
263: } else if (instanceCreate != null
264: && instanceCreate.startsWith("method:")) { // NOI18N
265: String factoryDisplayLabel = instanceCreate
266: .substring(instanceCreate.lastIndexOf('.',
267: instanceCreate.lastIndexOf('.') - 1) + 1);
268: return NbBundle.getMessage(BadgingSupport.class,
269: "LBL_instance_from", factoryDisplayLabel);
270: }
271: String clazzDisplayLabel = clazz.substring(clazz
272: .lastIndexOf('.') + 1);
273: return NbBundle.getMessage(BadgingSupport.class,
274: "LBL_instance_of", clazzDisplayLabel);
275: }
276:
277: private static String toStringOf(Object o) {
278: String s = o.toString();
279: if ((o.getClass().getName() + "@" + Integer.toHexString(o
280: .hashCode())).equals(s)) {
281: // Does not override toString, so no point in using pkg.Clazz@123456.
282: String clazz = o.getClass().getName();
283: String clazzDisplayLabel = clazz.substring(clazz
284: .lastIndexOf('.') + 1);
285: return NbBundle.getMessage(BadgingSupport.class,
286: "LBL_instance_of", clazzDisplayLabel);
287: } else {
288: return s;
289: }
290: }
291:
292: public Image annotateIcon(Image icon, int type, Set files) {
293: return annotateIconGeneral(icon, type, files, suffix,
294: fileChangeListener, classpath);
295: }
296:
297: private static Image annotateIconGeneral(Image icon, int type,
298: Set files, String suffix,
299: FileChangeListener fileChangeListener, ClassPath cp) {
300: String attr;
301: if (type == BeanInfo.ICON_COLOR_16x16) {
302: attr = "SystemFileSystem.icon"; // NOI18N
303: } else if (type == BeanInfo.ICON_COLOR_32x32) {
304: attr = "SystemFileSystem.icon32"; // NOI18N
305: } else {
306: return icon;
307: }
308: Iterator it = files.iterator();
309: while (it.hasNext()) {
310: FileObject fo = (FileObject) it.next();
311: Object value = fo.getAttribute(attr);
312: if (value instanceof Image) {
313: // #18832
314: return (Image) value;
315: }
316: if (value != null) {
317: try {
318: URL[] u = LayerUtils.currentify((URL) value,
319: suffix, cp);
320: FileObject ufo = URLMapper.findFileObject(u[0]);
321: if (ufo != null) {
322: ufo
323: .removeFileChangeListener(fileChangeListener);
324: ufo.addFileChangeListener(fileChangeListener);
325: }
326: return Toolkit.getDefaultToolkit().getImage(u[0]);
327: } catch (Exception e) {
328: //e.printStackTrace(LayerDataNode.getErr());
329: Util.err.notify(ErrorManager.INFORMATIONAL, e);
330: }
331: }
332: }
333: return icon;
334: }
335:
336: // Listen to changes in
337: // bundles & icons used to annotate names. If these change,
338: // the filesystem needs to show something else. Properly we would
339: // keep track of *which* file changed and thus which of our resources
340: // is affected. Practically this would be a lot of work and gain
341: // very little.
342: public void fileDeleted(FileEvent fe) {
343: // not ineresting here
344: }
345:
346: public void fileFolderCreated(FileEvent fe) {
347: // does not apply to us
348: }
349:
350: public void fileDataCreated(FileEvent fe) {
351: // In case a file was created that makes an annotation be available.
352: // We are listening to the parent folder, so if e.g. a new branded variant
353: // of a bundle is added, the display ought to be refreshed accordingly.
354: someFileChange();
355: }
356:
357: public void fileAttributeChanged(FileAttributeEvent fe) {
358: someFileChange();
359: }
360:
361: public void fileRenamed(FileRenameEvent fe) {
362: someFileChange();
363: }
364:
365: public void fileChanged(FileEvent fe) {
366: someFileChange();
367: }
368:
369: private void someFileChange() {
370: RequestProcessor.getDefault().post(new Runnable() {
371: public void run() {
372: // If used as nbres: annotation, fire status change.
373: fireFileStatusChanged(new FileStatusEvent(fs, true,
374: true));
375: }
376: });
377: }
378:
379: }
|