0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.openide.loaders;
0043:
0044: import java.awt.datatransfer.*;
0045: import java.beans.*;
0046: import java.io.*;
0047: import java.util.*;
0048: import javax.swing.Action;
0049: import org.netbeans.modules.openide.loaders.UIException;
0050: import org.openide.filesystems.*;
0051: import org.openide.nodes.*;
0052: import org.openide.util.*;
0053: import org.openide.util.actions.SystemAction;
0054: import org.openide.util.datatransfer.ExTransferable;
0055:
0056: /** Standard node representing a data object.
0057: *
0058: * @author Jaroslav Tulach
0059: */
0060: public class DataNode extends AbstractNode {
0061:
0062: /** generated Serialized Version UID */
0063: static final long serialVersionUID = -7882925922830244768L;
0064:
0065: /** DataObject of this node. */
0066: private DataObject obj;
0067:
0068: /** property change listener */
0069: private PropL propL;
0070:
0071: /** should file extensions be displayed? */
0072: private static boolean showFileExtensions = true;
0073:
0074: /** Create a data node with the given children set for the given data object.
0075: * @param obj object to work with
0076: * @param ch children container for the node
0077: * @see #getShowFileExtensions
0078: */
0079: public DataNode(DataObject obj, Children ch) {
0080: this (obj, ch, null);
0081: }
0082:
0083: /** Create a data node for a given data object.
0084: * The provided children object will be used to hold all child nodes.
0085: * The name is always set to the base name of the primary file;
0086: * the display name may instead be set to the base name with extension.
0087: * @param obj object to work with
0088: * @param ch children container for the node
0089: * @param lookup the lookup to provide content of {@link #getLookup}
0090: * and also {@link #getCookie}
0091: * @see #getShowFileExtensions
0092: *
0093: * @since 5.6
0094: * @author Libor Kotouc
0095: */
0096: public DataNode(DataObject obj, Children ch, Lookup lookup) {
0097: super (ch, lookup);
0098: this .obj = obj;
0099:
0100: propL = new PropL();
0101: if (lookup == null) {
0102: setCookieSet(CookieSet.createGeneric(propL));
0103: }
0104:
0105: obj.addPropertyChangeListener(org.openide.util.WeakListeners
0106: .propertyChange(propL, obj));
0107:
0108: super .setName(obj.getName());
0109: updateDisplayName();
0110: }
0111:
0112: private void updateDisplayName() {
0113: FileObject prim = obj.getPrimaryFile();
0114: String newDisplayName;
0115:
0116: if (prim.isRoot()) {
0117: // Special case - getName{,Ext} will just return "".
0118: // Used to be handled by org.netbeans.core.RootFolderNode
0119: // but might as well do it here.
0120: // XXX replace with #37549
0121: File f = FileUtil.toFile(prim);
0122: if (f == null) {
0123: // Check for a JAR root explicitly.
0124: FileObject archiveFile = FileUtil.getArchiveFile(prim);
0125: if (archiveFile != null) {
0126: f = FileUtil.toFile(archiveFile);
0127: }
0128: }
0129: if (f != null) {
0130: // E.g. /tmp/foo or /tmp/foo.jar
0131: newDisplayName = f.getAbsolutePath();
0132: } else {
0133: try {
0134: // E.g. http://webdavhost.nowhere.net/mystuff/
0135: newDisplayName = prim.getURL().toExternalForm();
0136: } catch (FileStateInvalidException e) {
0137: // Should not happen in practice.
0138: newDisplayName = "???"; // NOI18N
0139: }
0140: }
0141: } else if (showFileExtensions || obj instanceof DataFolder
0142: || obj instanceof DefaultDataObject) {
0143: newDisplayName = prim.getNameExt();
0144: } else {
0145: newDisplayName = prim.getName();
0146: }
0147:
0148: if (displayFormat != null)
0149: setDisplayName(displayFormat
0150: .format(new Object[] { newDisplayName }));
0151: else
0152: setDisplayName(newDisplayName);
0153: }
0154:
0155: /** Get the represented data object.
0156: * @return the data object
0157: */
0158: public DataObject getDataObject() {
0159: return obj;
0160: }
0161:
0162: /** Changes the name of the node and may also rename the data object.
0163: * If the object is renamed and file extensions are to be shown,
0164: * the display name is also updated accordingly.
0165: *
0166: * @param name new name for the object
0167: * @param rename rename the data object?
0168: * @exception IllegalArgumentException if the rename failed
0169: */
0170: public void setName(String name, boolean rename) {
0171: try {
0172: if (rename) {
0173: obj.rename(name);
0174: }
0175:
0176: super .setName(name);
0177: updateDisplayName();
0178: } catch (IOException ex) {
0179: String msg = null;
0180: if ((ex.getLocalizedMessage() == null)
0181: || (ex.getLocalizedMessage()
0182: .equals(ex.getMessage()))) {
0183: msg = NbBundle.getMessage(DataNode.class,
0184: "MSG_renameError", getName(), name); // NOI18N
0185: } else {
0186: msg = ex.getLocalizedMessage();
0187: }
0188:
0189: RuntimeException e = new IllegalArgumentException();
0190: UIException.annotateUser(e, null, msg, ex, null);
0191: throw e;
0192: }
0193: }
0194:
0195: /* Rename the data object.
0196: * @param name new name for the object
0197: * @exception IllegalArgumentException if the rename failed
0198: */
0199: public void setName(String name) {
0200: setName(name, true);
0201: }
0202:
0203: /** Get the display name for the node.
0204: * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this.
0205: * Subclassers overriding this method should consider the recommendations
0206: * in {@link DataObject#createNodeDelegate}.
0207: * @return the desired name
0208: */
0209: public String getDisplayName() {
0210: String s = super .getDisplayName();
0211:
0212: try {
0213: s = obj.getPrimaryFile().getFileSystem().getStatus()
0214: .annotateName(s, new LazyFilesSet());
0215: } catch (FileStateInvalidException e) {
0216: // no fs, do nothing
0217: }
0218:
0219: return s;
0220: }
0221:
0222: /** Get a display name formatted using the limited HTML subset supported
0223: * by <code>HtmlRenderer</code>. If the underlying
0224: * <code>FileSystem.Status</code> is an instance of HmlStatus,
0225: * this method will return non-null if status information is added.
0226: *
0227: * @return a string containing compliant HTML markup or null
0228: * @see org.openide.awt.HtmlRenderer
0229: * @see org.openide.nodes.Node#getHtmlDisplayName
0230: * @since 4.13
0231: */
0232: public String getHtmlDisplayName() {
0233: try {
0234: FileSystem.Status stat = obj.getPrimaryFile()
0235: .getFileSystem().getStatus();
0236: if (stat instanceof FileSystem.HtmlStatus) {
0237: FileSystem.HtmlStatus hstat = (FileSystem.HtmlStatus) stat;
0238:
0239: String result = hstat.annotateNameHtml(super
0240: .getDisplayName(), new LazyFilesSet());
0241:
0242: //Make sure the super string was really modified
0243: if (!super .getDisplayName().equals(result)) {
0244: return result;
0245: }
0246: }
0247: } catch (FileStateInvalidException e) {
0248: //do nothing and fall through
0249: }
0250: return super .getHtmlDisplayName();
0251: }
0252:
0253: /** Get the displayed icon for this node.
0254: * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this.
0255: * Subclassers overriding this method should consider the recommendations
0256: * in {@link DataObject#createNodeDelegate}.
0257: * @param type the icon type from {@link java.beans.BeanInfo}
0258: * @return the desired icon
0259: */
0260: public java.awt.Image getIcon(int type) {
0261: java.awt.Image img = super .getIcon(type);
0262:
0263: try {
0264: img = obj.getPrimaryFile().getFileSystem().getStatus()
0265: .annotateIcon(img, type, new LazyFilesSet());
0266: } catch (FileStateInvalidException e) {
0267: // no fs, do nothing
0268: }
0269:
0270: return img;
0271: }
0272:
0273: /** Get the displayed icon for this node.
0274: * A filesystem may {@link org.openide.filesystems.FileSystem#getStatus specially alter} this.
0275: * Subclassers overriding this method should consider the recommendations
0276: * in {@link DataObject#createNodeDelegate}.
0277: * @param type the icon type from {@link java.beans.BeanInfo}
0278: * @return the desired icon
0279: */
0280: public java.awt.Image getOpenedIcon(int type) {
0281: java.awt.Image img = super .getOpenedIcon(type);
0282:
0283: try {
0284: img = obj.getPrimaryFile().getFileSystem().getStatus()
0285: .annotateIcon(img, type, new LazyFilesSet());
0286: } catch (FileStateInvalidException e) {
0287: // no fs, do nothing
0288: }
0289:
0290: return img;
0291: }
0292:
0293: public HelpCtx getHelpCtx() {
0294: return obj.getHelpCtx();
0295: }
0296:
0297: /** Indicate whether the node may be renamed.
0298: * @return tests {@link DataObject#isRenameAllowed}
0299: */
0300: public boolean canRename() {
0301: return obj.isRenameAllowed();
0302: }
0303:
0304: /** Indicate whether the node may be destroyed.
0305: * @return tests {@link DataObject#isDeleteAllowed}
0306: */
0307: public boolean canDestroy() {
0308: return obj.isDeleteAllowed();
0309: }
0310:
0311: /* Destroyes the node
0312: */
0313: public void destroy() throws IOException {
0314: if (obj.isDeleteAllowed()) {
0315: obj.delete();
0316: }
0317: super .destroy();
0318: }
0319:
0320: /* Returns true if this object allows copying.
0321: * @returns true if this object allows copying.
0322: */
0323: public boolean canCopy() {
0324: return obj.isCopyAllowed();
0325: }
0326:
0327: /* Returns true if this object allows cutting.
0328: * @returns true if this object allows cutting.
0329: */
0330: public boolean canCut() {
0331: return obj.isMoveAllowed();
0332: }
0333:
0334: /** This method returns null to signal that actions
0335: * provide by DataLoader.getActions should be returned from
0336: * method getActions. If overriden to provide some actions,
0337: * then these actions will be preferred to the loader's ones.
0338: *
0339: * @return null
0340: * @deprecated Use {@link #getActions(boolean)} or do nothing and let the
0341: * data loader specify actions.
0342: */
0343: @Deprecated
0344: protected SystemAction[] createActions() {
0345: return null;
0346: }
0347:
0348: /** Get actions for this data object.
0349: * @see DataLoader#getActions
0350: * @return array of actions or <code>null</code>
0351: */
0352: public Action[] getActions(boolean context) {
0353: if (systemActions == null) {
0354: systemActions = createActions();
0355: }
0356:
0357: if (systemActions != null) {
0358: return systemActions;
0359: }
0360:
0361: return obj.getLoader().getSwingActions();
0362: }
0363:
0364: /** Get actions for this data object.
0365: * @deprecated Use getActions(boolean)
0366: * @return array of actions or <code>null</code>
0367: */
0368: @Deprecated
0369: public SystemAction[] getActions() {
0370: if (systemActions == null) {
0371: systemActions = createActions();
0372: }
0373:
0374: if (systemActions != null) {
0375: return systemActions;
0376: }
0377:
0378: return obj.getLoader().getActions();
0379: }
0380:
0381: /** Get default action. In the current implementation the
0382: *<code>null</code> is returned in case the underlying data
0383: * object is a template. The templates should not have any default
0384: * action.
0385: * @return no action if the underlying data object is a template.
0386: * Otherwise the abstract node's default action is returned, if <code>null</code> then
0387: * the first action returned from getActions (false) method is used.
0388: */
0389: public Action getPreferredAction() {
0390: if (obj.isTemplate()) {
0391: return null;
0392: } else {
0393: Action action = super .getPreferredAction();
0394: if (action != null) {
0395: return action;
0396: }
0397: Action[] arr = getActions(false);
0398: if (arr != null && arr.length > 0) {
0399: return arr[0];
0400: }
0401: return null;
0402: }
0403: }
0404:
0405: /** Get a cookie.
0406: * First of all {@link DataObject#getCookie} is
0407: * called. If it produces non-<code>null</code> result, that is returned.
0408: * Otherwise the superclass is tried.
0409: * Subclassers overriding this method should consider the recommendations
0410: * in {@link DataObject#createNodeDelegate}. Since version 5.6, if
0411: * non-null {@link Lookup} is passed to the constructor, then this
0412: * method directly delegates to <a href="@org-openide-nodes@/org/openide/nodes/Node.html">super.getCookie</a> and does
0413: * not query data object at all. This is supposed to provide consistency
0414: * between results in <code>getLookup().lookup</code> and <code>getCookie</code>.
0415: *
0416: * @return the cookie or <code>null</code>
0417: */
0418: @Override
0419: public <T extends Node.Cookie> T getCookie(Class<T> cl) {
0420: if (ownLookup()) {
0421: return super .getCookie(cl);
0422: }
0423: T c = obj.getCookie(cl);
0424: if (c != null) {
0425: return c;
0426: } else {
0427: return super .getCookie(cl);
0428: }
0429: }
0430:
0431: /* Initializes sheet of properties. Allow subclasses to
0432: * overwrite it.
0433: * @return the default sheet to use
0434: */
0435: protected Sheet createSheet() {
0436: Sheet s = Sheet.createDefault();
0437: Sheet.Set ss = s.get(Sheet.PROPERTIES);
0438:
0439: Node.Property p;
0440:
0441: p = createNameProperty(obj);
0442: ss.put(p);
0443:
0444: FileObject fo = getDataObject().getPrimaryFile();
0445: if (couldBeTemplate(fo) && fo.canWrite()) {
0446: try {
0447: p = new PropertySupport.Reflection<Boolean>(obj,
0448: Boolean.TYPE, "isTemplate", "setTemplate"); // NOI18N
0449: p.setName(DataObject.PROP_TEMPLATE);
0450: p.setDisplayName(DataObject.getString("PROP_template"));
0451: p.setShortDescription(DataObject
0452: .getString("HINT_template"));
0453: ss.put(p);
0454: } catch (Exception ex) {
0455: throw new InternalError();
0456: }
0457: }
0458:
0459: if (fo.isData()) {
0460: ss.put(new AllFilesProperty());
0461: ss.put(new SizeProperty());
0462: ss.put(new LastModifiedProperty());
0463: }
0464:
0465: return s;
0466: }
0467:
0468: private static boolean couldBeTemplate(FileObject fo) {
0469: FileSystem fs;
0470: try {
0471: fs = fo.getFileSystem();
0472: } catch (FileStateInvalidException e) {
0473: return false;
0474: }
0475: return fs.isDefault() && fo.getPath().startsWith("Templates/"); // NOI18N
0476: }
0477:
0478: /**
0479: * A property with a list of all contained files.
0480: * Sorted to first show primary file, then all secondary files alphabetically.
0481: * Shows absolute file path or the closest equivalent.
0482: */
0483: private final class AllFilesProperty extends
0484: PropertySupport.ReadOnly<String[]> {
0485:
0486: public AllFilesProperty() {
0487: super (DataObject.PROP_FILES, String[].class, DataObject
0488: .getString("PROP_files"), DataObject
0489: .getString("HINT_files"));
0490: }
0491:
0492: public String[] getValue() {
0493: Set<FileObject> files = obj.files();
0494: FileObject primary = obj.getPrimaryFile();
0495: String[] res = new String[files.size()];
0496: assert files.contains(primary);
0497:
0498: int i = 1;
0499: for (Iterator<FileObject> it = files.iterator(); it
0500: .hasNext();) {
0501: FileObject next = it.next();
0502: res[next == primary ? 0 : i++] = name(next);
0503: }
0504:
0505: Arrays.sort(res, 1, res.length);
0506: return res;
0507: }
0508:
0509: private String name(FileObject fo) {
0510: return FileUtil.getFileDisplayName(fo);
0511: }
0512:
0513: }
0514:
0515: private final class SizeProperty extends
0516: PropertySupport.ReadOnly<Long> {
0517:
0518: public SizeProperty() {
0519: super ("size", Long.TYPE, DataObject.getString("PROP_size"),
0520: DataObject.getString("HINT_size"));
0521: }
0522:
0523: public Long getValue() {
0524: return new Long(getDataObject().getPrimaryFile().getSize());
0525: }
0526:
0527: }
0528:
0529: private final class LastModifiedProperty extends
0530: PropertySupport.ReadOnly<Date> {
0531:
0532: public LastModifiedProperty() {
0533: super ("lastModified", Date.class, DataObject
0534: .getString("PROP_lastModified"), DataObject
0535: .getString("HINT_lastModified"));
0536: }
0537:
0538: public Date getValue() {
0539: return getDataObject().getPrimaryFile().lastModified();
0540: }
0541:
0542: }
0543:
0544: /** Copy this node to the clipboard.
0545: *
0546: * @return {@link org.openide.util.datatransfer.ExTransferable.Single} with one copy flavor
0547: * @throws IOException if it could not copy
0548: * @see org.openide.nodes.NodeTransfer
0549: */
0550: public Transferable clipboardCopy() throws IOException {
0551: ExTransferable t = ExTransferable.create(super .clipboardCopy());
0552: t.put(LoaderTransfer.transferable(getDataObject(),
0553: LoaderTransfer.CLIPBOARD_COPY));
0554: //add extra data flavors to allow dragging the file outside the IDE window
0555: addExternalFileTransferable(t, getDataObject());
0556: return t;
0557: }
0558:
0559: /** Cut this node to the clipboard.
0560: *
0561: * @return {@link org.openide.util.datatransfer.ExTransferable.Single} with one cut flavor
0562: * @throws IOException if it could not cut
0563: * @see org.openide.nodes.NodeTransfer
0564: */
0565: public Transferable clipboardCut() throws IOException {
0566: ExTransferable t = ExTransferable.create(super .clipboardCut());
0567: t.put(LoaderTransfer.transferable(getDataObject(),
0568: LoaderTransfer.CLIPBOARD_CUT));
0569: //add extra data flavors to allow dragging the file outside the IDE window
0570: addExternalFileTransferable(t, getDataObject());
0571: return t;
0572: }
0573:
0574: private void addExternalFileTransferable(ExTransferable t,
0575: DataObject d) {
0576: FileObject fo = d.getPrimaryFile();
0577: File file = FileUtil.toFile(fo);
0578: if (null != file) {
0579: //windows & mac
0580: final ArrayList<File> list = new ArrayList<File>(1);
0581: list.add(file);
0582: t.put(new ExTransferable.Single(
0583: DataFlavor.javaFileListFlavor) {
0584: public Object getData() {
0585: return list;
0586: }
0587: });
0588: //linux
0589: final String uriList = file.toURI().toString() + "\r\n";
0590: t.put(new ExTransferable.Single(createUriListFlavor()) {
0591: public Object getData() {
0592: return uriList;
0593: }
0594: });
0595: }
0596: }
0597:
0598: private DataFlavor createUriListFlavor() {
0599: try {
0600: return new DataFlavor(
0601: "text/uri-list;class=java.lang.String");
0602: } catch (ClassNotFoundException ex) {
0603: //cannot happen
0604: throw new AssertionError(ex);
0605: }
0606: }
0607:
0608: /** Creates a name property for given data object.
0609: */
0610: static Node.Property createNameProperty(final DataObject obj) {
0611: Node.Property p = new org.openide.nodes.PropertySupport.ReadWrite<String>(
0612: org.openide.loaders.DataObject.PROP_NAME, String.class,
0613: org.openide.loaders.DataObject.getString("PROP_name"),
0614: org.openide.loaders.DataObject.getString("HINT_name")) {
0615:
0616: public String getValue() {
0617: return obj.getName();
0618: }
0619:
0620: public void setValue(String val)
0621: throws IllegalAccessException,
0622: IllegalArgumentException,
0623: java.lang.reflect.InvocationTargetException {
0624: if (!canWrite())
0625: throw new java.lang.IllegalAccessException();
0626: if (!(val instanceof java.lang.String))
0627: throw new java.lang.IllegalArgumentException();
0628: try {
0629: obj.rename((java.lang.String) val);
0630: } catch (java.io.IOException ex) {
0631: java.lang.String msg = null;
0632:
0633: if ((ex.getLocalizedMessage() == null)
0634: || (ex.getLocalizedMessage().equals(ex
0635: .getMessage()))) {
0636: msg = org.openide.util.NbBundle.getMessage(
0637: org.openide.loaders.DataNode.class,
0638: "MSG_renameError", obj.getName(), val);
0639: } else {
0640: msg = ex.getLocalizedMessage();
0641: }
0642: UIException.annotateUser(ex, null, msg, null, null);
0643: throw new java.lang.reflect.InvocationTargetException(
0644: ex);
0645: }
0646: }
0647:
0648: public boolean canWrite() {
0649: return obj.isRenameAllowed();
0650: }
0651:
0652: // #33296 - suppress custom editor
0653:
0654: public Object getValue(String key) {
0655: if ("suppressCustomEditor".equals(key)) {
0656: return Boolean.TRUE;
0657: } else {
0658: return super .getValue(key);
0659: }
0660: }
0661: };
0662:
0663: return p;
0664: }
0665:
0666: /** Update files, if we are using CookieSet
0667: */
0668: private void updateFilesInCookieSet(Set<FileObject> obj) {
0669: if (ownLookup()) {
0670: return;
0671: }
0672: getCookieSet().assign(FileObject.class,
0673: obj.toArray(new FileObject[0]));
0674: }
0675:
0676: /** Support for firing property change.
0677: * @param ev event describing the change
0678: */
0679: void fireChange(final PropertyChangeEvent ev) {
0680: Mutex.EVENT.writeAccess(new Runnable() {
0681: public void run() {
0682:
0683: if (DataFolder.PROP_CHILDREN.equals(ev
0684: .getPropertyName())) {
0685: // the node is not interested in children changes
0686: return;
0687: }
0688:
0689: if (DataObject.PROP_PRIMARY_FILE.equals(ev
0690: .getPropertyName())) {
0691: propL.updateStatusListener();
0692: setName(obj.getName(), false);
0693: updateFilesInCookieSet(obj.files());
0694: return;
0695: }
0696:
0697: if (DataObject.PROP_FILES.equals(ev.getPropertyName())) {
0698: updateFilesInCookieSet(obj.files());
0699: }
0700:
0701: if (DataObject.PROP_NAME.equals(ev.getPropertyName())) {
0702: DataNode.super .setName(obj.getName());
0703: updateDisplayName();
0704: }
0705: if (DataObject.PROP_COOKIE.equals(ev.getPropertyName())) {
0706: fireCookieChange();
0707: //return;
0708: }
0709:
0710: // if the DataOjbect is not valid the node should be
0711: // removed
0712: if (DataObject.PROP_VALID.equals(ev.getPropertyName())) {
0713: Object newVal = ev.getNewValue();
0714: if ((newVal instanceof Boolean)
0715: && (!((Boolean) newVal).booleanValue())) {
0716: fireNodeDestroyed();
0717: }
0718: return;
0719: }
0720:
0721: /*See #31413*/
0722: List transmitProperties = Arrays.asList(new String[] {
0723: DataObject.PROP_NAME, DataObject.PROP_FILES,
0724: DataObject.PROP_TEMPLATE });
0725: if (transmitProperties.contains(ev.getPropertyName())) {
0726: firePropertyChange(ev.getPropertyName(), ev
0727: .getOldValue(), ev.getNewValue());
0728: }
0729: }
0730: });
0731: }
0732:
0733: /** Handle for location of given data object.
0734: * @return handle that remembers the data object.
0735: */
0736: public Node.Handle getHandle() {
0737: return new ObjectHandle(obj, obj.isValid() ? (this != obj
0738: .getNodeDelegate()) : /* to be safe */true);
0739: }
0740:
0741: /** Access method to fire icon change.
0742: */
0743: final void fireChangeAccess(boolean icon, boolean name) {
0744: if (name) {
0745: fireDisplayNameChange(null, null);
0746: }
0747: if (icon) {
0748: fireIconChange();
0749: }
0750: }
0751:
0752: /** Determine whether file extensions should be shown by default.
0753: * By default, no.
0754: * @return <code>true</code> if so
0755: */
0756: public static boolean getShowFileExtensions() {
0757: return showFileExtensions;
0758: }
0759:
0760: /** Set whether file extensions should be shown by default.
0761: *
0762: * <p>Note that this method affects all <code>DataNode</code>s.</p>
0763: *
0764: * @param s <code>true</code> if so
0765: */
0766: public static void setShowFileExtensions(boolean s) {
0767: boolean refresh = (showFileExtensions != s);
0768: showFileExtensions = s;
0769:
0770: if (refresh) {
0771: // refresh current nodes display name
0772: RequestProcessor.getDefault().post(new Runnable() {
0773: public void run() {
0774: Iterator it = DataObjectPool.getPOOL()
0775: .getActiveDataObjects();
0776: while (it.hasNext()) {
0777: DataObject obj = ((DataObjectPool.Item) it
0778: .next()).getDataObjectOrNull();
0779: if (obj != null
0780: && obj.getNodeDelegate() instanceof DataNode) {
0781: ((DataNode) obj.getNodeDelegate())
0782: .updateDisplayName();
0783: }
0784: }
0785: }
0786: }, 300, Thread.MIN_PRIORITY);
0787: }
0788:
0789: }
0790:
0791: private static Class defaultLookup;
0792:
0793: /** Returns true if this node is using own lookup and not the standard one.
0794: */
0795: private boolean ownLookup() {
0796: if (defaultLookup == null) {
0797: try {
0798: defaultLookup = Class.forName(
0799: "org.openide.nodes.NodeLookup", false,
0800: Node.class.getClassLoader());
0801: } catch (ClassNotFoundException ex) {
0802: Exceptions.printStackTrace(ex);
0803: return false;
0804: }
0805: }
0806: return !defaultLookup.isInstance(getLookup());
0807: }
0808:
0809: /** Request processor task to update a bunch of names/icons.
0810: * Potentially faster to do many nodes at once; see #16478.
0811: */
0812: private static RequestProcessor.Task refreshNamesIconsTask = null;
0813: /** nodes which should be refreshed */
0814: private static Set<DataNode> refreshNameNodes = null;
0815: private static Set<DataNode> refreshIconNodes = null;
0816: /** whether the task is current scheduled and will still look in above sets */
0817: private static boolean refreshNamesIconsRunning = false;
0818: private static final Object refreshNameIconLock = "DataNode.refreshNameIconLock"; // NOI18N
0819:
0820: /** Property listener on data object that delegates all changes of
0821: * properties to this node.
0822: */
0823: private class PropL extends Object implements
0824: PropertyChangeListener, FileStatusListener,
0825: CookieSet.Before {
0826: /** weak version of this listener */
0827: private FileStatusListener weakL;
0828: /** previous filesystem we were attached to */
0829: private FileSystem previous;
0830:
0831: public PropL() {
0832: updateStatusListener();
0833: }
0834:
0835: public void propertyChange(PropertyChangeEvent ev) {
0836: fireChange(ev);
0837: }
0838:
0839: /** Updates listening on a status of filesystem.
0840: */
0841: private void updateStatusListener() {
0842: if (previous != null) {
0843: previous.removeFileStatusListener(weakL);
0844: }
0845: try {
0846: previous = obj.getPrimaryFile().getFileSystem();
0847:
0848: if (weakL == null) {
0849: weakL = org.openide.filesystems.FileUtil
0850: .weakFileStatusListener(this , null);
0851: }
0852:
0853: previous.addFileStatusListener(weakL);
0854: } catch (FileStateInvalidException ex) {
0855: previous = null;
0856: }
0857: }
0858:
0859: /** Notifies listener about change in annotataion of a few files.
0860: * @param ev event describing the change
0861: */
0862: public void annotationChanged(FileStatusEvent ev) {
0863: // #16541: listen for changes in both primary and secondary files
0864: boolean this Changed = false;
0865: Iterator it = obj.files().iterator();
0866: while (it.hasNext()) {
0867: FileObject fo = (FileObject) it.next();
0868: if (ev.hasChanged(fo)) {
0869: this Changed = true;
0870: break;
0871: }
0872: }
0873: if (this Changed) {
0874: // #12368: fire display name & icon changes asynch
0875: synchronized (refreshNameIconLock) {
0876: boolean post = false;
0877: if (ev.isNameChange()) {
0878: if (refreshNameNodes == null) {
0879: refreshNameNodes = new HashSet<DataNode>();
0880: }
0881: post |= refreshNameNodes.add(DataNode.this );
0882: }
0883: if (ev.isIconChange()) {
0884: if (refreshIconNodes == null) {
0885: refreshIconNodes = new HashSet<DataNode>();
0886: }
0887: post |= refreshIconNodes.add(DataNode.this );
0888: }
0889: if (post && !refreshNamesIconsRunning) {
0890: refreshNamesIconsRunning = true;
0891: if (refreshNamesIconsTask == null) {
0892: refreshNamesIconsTask = RequestProcessor
0893: .getDefault().post(
0894: new NamesUpdater());
0895: } else {
0896: // Should be OK even if it is running right now.
0897: // (Cf. RequestProcessorTest.testScheduleWhileRunning.)
0898: refreshNamesIconsTask.schedule(0);
0899: }
0900: }
0901: }
0902: }
0903: }
0904:
0905: public void beforeLookup(Class<?> clazz) {
0906: if (clazz.isAssignableFrom(FileObject.class)) {
0907: updateFilesInCookieSet(obj.files());
0908: }
0909: }
0910: }
0911:
0912: private static class NamesUpdater implements Runnable {
0913: /** Refreshes names and icons for a whole batch of data nodes at once.
0914: */
0915: public void run() {
0916: DataNode[] _refreshNameNodes, _refreshIconNodes;
0917: synchronized (refreshNameIconLock) {
0918: if (refreshNameNodes != null) {
0919: _refreshNameNodes = refreshNameNodes
0920: .toArray(new DataNode[refreshNameNodes
0921: .size()]);
0922: refreshNameNodes.clear();
0923: } else {
0924: _refreshNameNodes = new DataNode[0];
0925: }
0926: if (refreshIconNodes != null) {
0927: _refreshIconNodes = refreshIconNodes
0928: .toArray(new DataNode[refreshIconNodes
0929: .size()]);
0930: refreshIconNodes.clear();
0931: } else {
0932: _refreshIconNodes = new DataNode[0];
0933: }
0934: refreshNamesIconsRunning = false;
0935: }
0936: for (int i = 0; i < _refreshNameNodes.length; i++) {
0937: _refreshNameNodes[i].fireChangeAccess(false, true);
0938: }
0939: for (int i = 0; i < _refreshIconNodes.length; i++) {
0940: _refreshIconNodes[i].fireChangeAccess(true, false);
0941: }
0942: }
0943:
0944: }
0945:
0946: /** Handle for data object nodes */
0947: private static class ObjectHandle implements Node.Handle {
0948: private FileObject obj;
0949: private boolean clone;
0950:
0951: static final long serialVersionUID = 6616060729084681518L;
0952:
0953: public ObjectHandle(DataObject obj, boolean clone) {
0954: this .obj = obj.getPrimaryFile();
0955: this .clone = clone;
0956: }
0957:
0958: public Node getNode() throws IOException {
0959: if (obj == null) {
0960: // Serialization problem? Seems to occur frequently with connection support:
0961: // java.lang.IllegalArgumentException: Called DataObject.find on null
0962: // at org.openide.loaders.DataObject.find(DataObject.java:435)
0963: // at org.openide.loaders.DataNode$ObjectHandle.getNode(DataNode.java:757)
0964: // at org.netbeans.modules.java.JavaDataObject$PersistentConnectionHandle.getNode(JavaDataObject.java:977)
0965: // at org.openide.loaders.ConnectionSupport$Pair.getNode(ConnectionSupport.java:357)
0966: // at org.openide.loaders.ConnectionSupport.register(ConnectionSupport.java:94)
0967: // at org.netbeans.modules.java.codesync.SourceConnectionSupport.registerDependency(SourceConnectionSupport.java:475)
0968: // at org.netbeans.modules.java.codesync.SourceConnectionSupport.addDependency(SourceConnectionSupport.java:554)
0969: // at org.netbeans.modules.java.codesync.ClassDependencyImpl.supertypesAdded(ClassDependencyImpl.java:241)
0970: // at org.netbeans.modules.java.codesync.ClassDependencyImpl.refreshClass(ClassDependencyImpl.java:121)
0971: // at org.netbeans.modules.java.codesync.SourceConnectionSupport.refreshLinks(SourceConnectionSupport.java:357)
0972: // at org.netbeans.modules.java.codesync.SourceConnectionSupport.access$000(SourceConnectionSupport.java:44)
0973: // at org.netbeans.modules.java.codesync.SourceConnectionSupport$2.run(SourceConnectionSupport.java:223)
0974: throw new IOException("File could not be restored"); // NOI18N
0975: }
0976: Node n = DataObject.find(obj).getNodeDelegate();
0977: return clone ? n.cloneNode() : n;
0978: }
0979: }
0980:
0981: /** Wrapping class for obj.files(). Used in getIcon() and getDisplayName()
0982: to have something lazy to pass to annotateIcon() and annotateName()
0983: instead of calling obj.files() immediately. */
0984: private class LazyFilesSet implements Set<FileObject> {
0985:
0986: private Set<FileObject> obj_files;
0987:
0988: synchronized private void lazyInitialization() {
0989: obj_files = obj.files();
0990: }
0991:
0992: public boolean add(FileObject o) {
0993: lazyInitialization();
0994: return obj_files.add(o);
0995: }
0996:
0997: public boolean addAll(Collection<? extends FileObject> c) {
0998: lazyInitialization();
0999: return obj_files.addAll(c);
1000: }
1001:
1002: public void clear() {
1003: lazyInitialization();
1004: obj_files.clear();
1005: }
1006:
1007: public boolean contains(Object o) {
1008: lazyInitialization();
1009: return obj_files.contains(o);
1010: }
1011:
1012: public boolean containsAll(Collection c) {
1013: lazyInitialization();
1014: return obj_files.containsAll(c);
1015: }
1016:
1017: public boolean isEmpty() {
1018: lazyInitialization();
1019: return obj_files.isEmpty();
1020: }
1021:
1022: public Iterator<FileObject> iterator() {
1023: return new FilesIterator();
1024: }
1025:
1026: public boolean remove(Object o) {
1027: lazyInitialization();
1028: return obj_files.remove(o);
1029: }
1030:
1031: public boolean removeAll(Collection c) {
1032: lazyInitialization();
1033: return obj_files.removeAll(c);
1034: }
1035:
1036: public boolean retainAll(Collection c) {
1037: lazyInitialization();
1038: return obj_files.retainAll(c);
1039: }
1040:
1041: public int size() {
1042: lazyInitialization();
1043: return obj_files.size();
1044: }
1045:
1046: public Object[] toArray() {
1047: lazyInitialization();
1048: return obj_files.toArray();
1049: }
1050:
1051: public <FileObject> FileObject[] toArray(FileObject[] a) {
1052: lazyInitialization();
1053: return obj_files.toArray(a);
1054: }
1055:
1056: public boolean equals(Object obj) {
1057: lazyInitialization();
1058: return obj_files.equals(obj);
1059: }
1060:
1061: public String toString() {
1062: lazyInitialization();
1063: return obj_files.toString();
1064: }
1065:
1066: public int hashCode() {
1067: lazyInitialization();
1068: return obj_files.hashCode();
1069: }
1070:
1071: /** Iterator for FilesSet. It returns the primaryFile first and
1072: * then initialize the delegate iterator for secondary files.
1073: */
1074: private final class FilesIterator implements
1075: Iterator<FileObject> {
1076: /** Was the first element (primary file) already returned?
1077: */
1078: private boolean first = true;
1079:
1080: /** Delegation iterator for secondary files. It is lazy initialized after
1081: * the first element is returned.
1082: */
1083: private Iterator<FileObject> itDelegate = null;
1084:
1085: FilesIterator() {
1086: }
1087:
1088: public boolean hasNext() {
1089: return first ? true : getIteratorDelegate().hasNext();
1090: }
1091:
1092: public FileObject next() {
1093: if (first) {
1094: first = false;
1095: return obj.getPrimaryFile();
1096: } else {
1097: return getIteratorDelegate().next();
1098: }
1099: }
1100:
1101: public void remove() {
1102: getIteratorDelegate().remove();
1103: }
1104:
1105: /** Initialize the delegation iterator.
1106: */
1107: private Iterator<FileObject> getIteratorDelegate() {
1108: if (itDelegate == null) {
1109: lazyInitialization();
1110: // this should return iterator of all files of the MultiDataObject...
1111: itDelegate = obj_files.iterator();
1112: // ..., so it is necessary to skip the primary file
1113: itDelegate.next();
1114: }
1115: return itDelegate;
1116: }
1117: }
1118: }
1119:
1120: }
|