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.Image;
0045: import java.awt.datatransfer.*;
0046: import java.beans.*;
0047: import java.io.*;
0048: import java.lang.reflect.InvocationTargetException;
0049: import java.net.URI;
0050: import java.util.*;
0051: import java.util.logging.Level;
0052: import java.util.logging.Logger;
0053: import javax.swing.*;
0054: import javax.swing.event.ChangeEvent;
0055: import org.openide.*;
0056: import org.openide.cookies.*;
0057: import org.openide.filesystems.*;
0058: import org.openide.nodes.*;
0059: import org.openide.util.*;
0060: import org.openide.util.datatransfer.*;
0061:
0062: /** A folder containing data objects.
0063: * Is actually itself a data object, whose primary (and only) file object
0064: * is a file folder.
0065: * <p>Has special support for determining the sorting of the folder,
0066: * or even explicit ordering of the children.
0067: *
0068: * @author Jaroslav Tulach, Petr Hamernik
0069: */
0070: public class DataFolder extends MultiDataObject implements
0071: DataObject.Container {
0072: /** generated Serialized Version UID */
0073: static final long serialVersionUID = -8244904281845488751L;
0074:
0075: /** Name of property that holds children of this node. */
0076: public static final String PROP_CHILDREN = DataObject.Container.PROP_CHILDREN;
0077:
0078: /** Name of property which decides sorting mode. */
0079: public static final String PROP_SORT_MODE = "sortMode"; // NOI18N
0080:
0081: /** name of extended attribute for order of children */
0082: static final String EA_SORT_MODE = "OpenIDE-Folder-SortMode"; // NOI18N
0083: /** name of extended attribute for order of children */
0084: static final String EA_ORDER = "OpenIDE-Folder-Order"; // NOI18N
0085:
0086: /** Name of property for order of children. */
0087: public static final String PROP_ORDER = "order"; // NOI18N
0088: /** Name of set with sorting options. */
0089: public static final String SET_SORTING = "sorting"; // NOI18N
0090:
0091: /** Icon resource string for folder node */
0092: private static final String FOLDER_ICON_BASE = "org/openide/loaders/defaultFolder.gif"; // NOI18N
0093:
0094: /** name of a shadow file for a root */
0095: private static final String ROOT_SHADOW_NAME = "Root"; // NOI18N
0096:
0097: /** Drag'n'drop DataFlavor used on Linux for file dragging */
0098: private static DataFlavor uriListDataFlavor;
0099:
0100: /** listener that contains array of children
0101: * Also represents the folder as the node delegate.
0102: */
0103: private FolderList list;
0104:
0105: /** Listener for changes in FolderList */
0106: private PropertyChangeListener pcl;
0107:
0108: private DataTransferSupport dataTransferSupport = new Paste();
0109:
0110: /** Create a data folder from a folder file object.
0111:
0112: * @deprecated This method should not be used in client code.
0113: * If you are searching for a <code>DataFolder</code> for
0114: * a FileObject use {@link DataFolder#findFolder} factory method.
0115: *
0116: * @param fo file folder to work on
0117: * @exception DataObjectExistsException if there is one already
0118: * @exception IllegalArgumentException if <code>fo</code> is not folder
0119: */
0120: @Deprecated
0121: public DataFolder(FileObject fo) throws DataObjectExistsException,
0122: IllegalArgumentException {
0123: this (fo, DataLoaderPool.getFolderLoader());
0124: }
0125:
0126: /** Create a data folder from a folder file object.
0127: *
0128: * @param fo file folder to work on
0129: * @param loader data loader for this data object
0130: * @exception DataObjectExistsException if there is one already
0131: * @exception IllegalArgumentException if <code>fo</code> is not folder
0132: */
0133: protected DataFolder(FileObject fo, MultiFileLoader loader)
0134: throws DataObjectExistsException, IllegalArgumentException {
0135: this (fo, loader, true);
0136: }
0137:
0138: /** Create a data folder from a folder file object.
0139: * @param fo file folder to work on
0140: * @param loader data loader for this data object
0141: * @exception DataObjectExistsException if there is one already
0142: * @exception IllegalArgumentException if <code>fo</code> is not folder
0143: * @deprecated Since 1.13 do not use this constructor, it is for backward compatibility only.
0144: */
0145: @Deprecated
0146: protected DataFolder(FileObject fo, DataLoader loader)
0147: throws DataObjectExistsException, IllegalArgumentException {
0148: super (fo, loader);
0149: init(fo, true);
0150: }
0151:
0152: /** Create a data folder from a folder file object.
0153: * @param fo file folder to work on
0154: * @param loader data loader for this data object
0155: * @param attach listen to changes?
0156: * @exception DataObjectExistsException if there is one already
0157: * @exception IllegalArgumentException if <code>fo</code> is not folder
0158: */
0159: private DataFolder(FileObject fo, MultiFileLoader loader,
0160: boolean attach) throws DataObjectExistsException,
0161: IllegalArgumentException {
0162: super (fo, loader);
0163: init(fo, attach);
0164: }
0165:
0166: /** Perform initialization after construction.
0167: * @param fo file folder to work on
0168: * @param attach listen to changes?
0169: */
0170: private void init(FileObject fo, boolean attach)
0171: throws IllegalArgumentException {
0172: if (!fo.isFolder()) {
0173: // not folder => throw an exception
0174: throw new IllegalArgumentException("Not folder: " + fo); // NOI18N
0175: }
0176: list = reassignList(fo, attach);
0177: }
0178:
0179: /** Attaches a listener to the folder list, removes any previous one if registered.
0180: * @param fo the new primary file we should listen on
0181: * @param attach really attache listener
0182: */
0183: private FolderList reassignList(FileObject fo, boolean attach) {
0184: // creates object that handles all elements in array and
0185: // assignes it to the
0186: FolderList list = FolderList.find(fo, true);
0187:
0188: if (attach) {
0189: pcl = new ListPCL();
0190: list
0191: .addPropertyChangeListener(org.openide.util.WeakListeners
0192: .propertyChange(pcl, list));
0193: }
0194:
0195: return list;
0196: }
0197:
0198: /** Helper method to find or create a folder of a given path.
0199: * Tries to find such a subfolder, or creates it if it needs to.
0200: *
0201: * @param folder the folder to start in
0202: * @param name a subfolder path (e.g. <code>com/mycom/testfolder</code>)
0203: * @return a folder with the given name
0204: * @exception IOException if the I/O fails
0205: */
0206: public static DataFolder create(DataFolder folder, String name)
0207: throws IOException {
0208: StringTokenizer tok = new StringTokenizer(name, "/"); // NOI18N
0209: while (tok.hasMoreTokens()) {
0210: String piece = tok.nextToken();
0211: if (!confirmName(piece)) {
0212: throw new IOException(NbBundle.getMessage(
0213: DataFolder.class, "EXC_WrongName", piece));
0214: }
0215: }
0216: return DataFolder.findFolder(FileUtil.createFolder(folder
0217: .getPrimaryFile(), name));
0218: }
0219:
0220: /** Set the sort mode for the folder.
0221: * @param mode an constant from {@link DataFolder.SortMode}
0222: * @exception IOException if the mode cannot be set
0223: */
0224: public synchronized final void setSortMode(SortMode mode)
0225: throws IOException {
0226: SortMode old = getOrder().getSortMode();
0227: getOrder().setSortMode(mode);
0228: firePropertyChange(PROP_SORT_MODE, old, getOrder()
0229: .getSortMode());
0230: }
0231:
0232: /** Get the sort mode of the folder.
0233: * @return the sort mode
0234: */
0235: public final SortMode getSortMode() {
0236: return getOrder().getSortMode();
0237: }
0238:
0239: /** Set the order of the children.
0240: * The provided array defines
0241: * the order of some children for the folder. Such children
0242: * will be returned at the beginning of the array returned from
0243: * {@link #getChildren}. If there are any other children, they
0244: * will be appended to the array.
0245: *
0246: * @param arr array of data objects (children of this
0247: * folder) to define the order; or <code>null</code> if any particular ordering should
0248: * be cancelled
0249: *
0250: * @exception IOException if the order cannot be set
0251: *
0252: */
0253: public synchronized final void setOrder(DataObject[] arr)
0254: throws IOException {
0255: getOrder().setOrder(arr);
0256: firePropertyChange(PROP_ORDER, null, null);
0257: }
0258:
0259: /** Getter for order object.
0260: * @return order of children
0261: */
0262: private FolderOrder getOrder() {
0263: return FolderOrder.findFor(getPrimaryFile());
0264: }
0265:
0266: @Override
0267: public Lookup getLookup() {
0268: if (DataFolder.class == getClass()) {
0269: return getCookieSet().getLookup();
0270: } else {
0271: return super .getLookup();
0272: }
0273: }
0274:
0275: /** Get the name of the data folder.
0276: * <p>This implementation uses the name and extension of the primary file.
0277: * @return the name
0278: */
0279: public String getName() {
0280: return getPrimaryFile().getNameExt();
0281: }
0282:
0283: /** Get the children of this folder.
0284: * @return array of children
0285: */
0286: public DataObject[] getChildren() {
0287: return list.getChildren();
0288: }
0289:
0290: /** Getter for list of children.
0291: * @param filter filter to notify about addition of new objects
0292: */
0293: final List<DataObject> getChildrenList() {
0294: return list.getChildrenList();
0295: }
0296:
0297: /** Computes list of children asynchronously
0298: * @param l listener to notify about the progress
0299: * @return task that will handle the computation
0300: */
0301: final RequestProcessor.Task computeChildrenList(FolderListListener l) {
0302: return list.computeChildrenList(l);
0303: }
0304:
0305: /** Get enumeration of children of this folder.
0306: * @return enumeration of {@link DataObject}s
0307: */
0308: public Enumeration<DataObject> children() {
0309: return Collections.enumeration(getChildrenList());
0310: }
0311:
0312: /** Enumerate all children of this folder. If the children should be enumerated
0313: * recursively, first all direct children are listed; then children of direct subfolders; and so on.
0314: *
0315: * @param rec whether to enumerate recursively
0316: * @return enumeration of type <code>DataObject</code>
0317: */
0318: public Enumeration<DataObject> children(final boolean rec) {
0319: if (!rec) {
0320: return children();
0321: }
0322:
0323: class Processor
0324: implements
0325: org.openide.util.Enumerations.Processor<DataObject, DataObject> {
0326: /** @param o processes object by adding its children to the queue */
0327: public DataObject process(DataObject dataObj,
0328: Collection<DataObject> toAdd) {
0329: if (rec && dataObj instanceof DataFolder) {
0330: toAdd.addAll(Arrays.asList(((DataFolder) dataObj)
0331: .getChildren()));
0332: }
0333: return dataObj;
0334: }
0335: }
0336: Enumeration<DataObject> en = org.openide.util.Enumerations
0337: .queue(org.openide.util.Enumerations
0338: .array(getChildren()), new Processor());
0339: return en;
0340: }
0341:
0342: /** Create node representative for this folder.
0343: */
0344: protected synchronized Node createNodeDelegate() {
0345: return new FolderNode();
0346: }
0347:
0348: private final class ClonedFilter extends FilterNode {
0349: private DataFilter filter;
0350: private int hashCode = -1; // We need to remember the hash code in
0351:
0352: // order to keep it constant fix for
0353:
0354: public ClonedFilter(Node n, DataFilter filter) {
0355: super (n, DataFolder.this .createNodeChildren(filter));
0356: this .filter = filter;
0357: }
0358:
0359: public ClonedFilter(DataFilter filter) {
0360: this (DataFolder.this .getNodeDelegate(), filter);
0361: }
0362:
0363: public Node cloneNode() {
0364: if (isValid()) {
0365: return new ClonedFilter(filter);
0366: } else {
0367: return super .cloneNode();
0368: }
0369: }
0370:
0371: public Node.Handle getHandle() {
0372: return new ClonedFilterHandle(DataFolder.this , filter);
0373: }
0374:
0375: public boolean equals(Object o) {
0376: if (o == null) {
0377: return false;
0378: } else if (o == this ) {
0379: return true;
0380: } else if (o instanceof FolderNode) {
0381: FolderNode fn = (FolderNode) o;
0382: if (fn.getCookie(DataFolder.class) != DataFolder.this )
0383: return false;
0384: org.openide.nodes.Children ch = fn.getChildren();
0385: return (ch instanceof FolderChildren)
0386: && ((FolderChildren) ch).getFilter().equals(
0387: filter);
0388: } else if (o instanceof ClonedFilter) {
0389: ClonedFilter cf = (ClonedFilter) o;
0390: return cf.getCookie(DataFolder.class) == DataFolder.this
0391: && cf.filter.equals(filter);
0392: } else {
0393: return false;
0394: }
0395: }
0396:
0397: public int hashCode() {
0398: if (hashCode == -1) {
0399: if (isValid()) {
0400: hashCode = getNodeDelegate().hashCode();
0401: } else {
0402: hashCode = super .hashCode();
0403: }
0404:
0405: if (hashCode == -1) {
0406: hashCode = -2;
0407: }
0408:
0409: }
0410: return hashCode;
0411:
0412: }
0413: }
0414:
0415: private final static class ClonedFilterHandle implements
0416: Node.Handle {
0417: private final static long serialVersionUID = 24234097765186L;
0418: private DataObject folder;
0419: private DataFilter filter;
0420:
0421: public ClonedFilterHandle(DataFolder folder, DataFilter filter) {
0422: this .folder = folder;
0423: this .filter = filter;
0424: }
0425:
0426: public Node getNode() throws IOException {
0427: if (folder instanceof DataFolder) {
0428: return ((DataFolder) folder).new ClonedFilter(filter);
0429: } else {
0430: throw new java.io.InvalidObjectException(
0431: folder == null ? "" : folder.toString() // NOI18N
0432: );
0433: }
0434: }
0435: }
0436:
0437: /** This method allows DataFolder to filter its nodes.
0438: *
0439: * @param filter filter for subdata objects
0440: * @return the node delegate (without parent) for this data object
0441: */
0442: Node getClonedNodeDelegate(DataFilter filter) {
0443: Node n = getNodeDelegate();
0444: Children c = n.getChildren();
0445: // #7362: relying on subclassers to override createNodeChildren is ugly...
0446: if (c.getClass() == FolderChildren.class) {
0447: DataFilter f = ((FolderChildren) c).getFilter();
0448: if (f == DataFilter.ALL) {
0449: // Either createNodeDelegate was not overridden; or
0450: // it provided some node with the same children as
0451: // DataFolder would have anyway. Filter the children.
0452: return new ClonedFilter(n, filter);
0453: } else if (filter != DataFilter.ALL && filter != f) {
0454: // Tricky. createNodeDelegate was overridden, and it is
0455: // producing FolderChildren with some special filter.
0456: // Apply both the subclass's filter and this additional one.
0457: return new ClonedFilter(n, filterCompose(f, filter));
0458: } else {
0459: // Subclass provided FolderChildren with some special filter,
0460: // and we are not trying to filter specially. Let the subclass
0461: // display as usual.
0462: return n.cloneNode();
0463: }
0464: } else {
0465: // We have some DataFolder subclass with idiosyncratic children.
0466: // Play it safe and let it display what it wants.
0467: return n.cloneNode();
0468: }
0469: }
0470:
0471: /** Logically compose two filters: accept the intersection. */
0472: private static DataFilter filterCompose(final DataFilter f1,
0473: final DataFilter f2) {
0474: if (f1.equals(f2)) {
0475: return f1;
0476: } else {
0477: return new DataFilter() {
0478: public boolean acceptDataObject(DataObject obj) {
0479: return f1.acceptDataObject(obj)
0480: && f2.acceptDataObject(obj);
0481: }
0482: };
0483: }
0484: }
0485:
0486: /** Support method to obtain a children object that
0487: * can be added to any {@link Node}. The provided filter can be
0488: * used to exclude some objects from the list.
0489: * <p><strong>Overriding this method is deprecated!</strong>
0490: * @param filter filter of data objects
0491: * @return children object representing content of this folder
0492: */
0493: public/* XXX final */Children createNodeChildren(DataFilter filter) {
0494: return new FolderChildren(this , filter);
0495: }
0496:
0497: /* Getter for delete action.
0498: * @return true if the object can be deleted
0499: */
0500: public boolean isDeleteAllowed() {
0501: return isRenameAllowed();
0502: }
0503:
0504: /* Getter for copy action.
0505: * @return true if the object can be copied
0506: */
0507: public boolean isCopyAllowed() {
0508: return true;
0509: }
0510:
0511: /* Getter for move action.
0512: * @return true if the object can be moved
0513: */
0514: public boolean isMoveAllowed() {
0515: return isRenameAllowed();
0516: }
0517:
0518: /* Getter for rename action.
0519: * @return true if the object can be renamed
0520: */
0521: public boolean isRenameAllowed() {
0522: FileObject fo = getPrimaryFile();
0523: return !fo.isRoot() && fo.canWrite();
0524: }
0525:
0526: /* Help context for this object.
0527: * @return help context
0528: */
0529: public HelpCtx getHelpCtx() {
0530: return null;
0531: }
0532:
0533: /** Create a folder for a specified file object.
0534: * @param fo file object
0535: * @return folder for the file object
0536: * @exception IllegalArgumentException if the file object is not folder
0537: */
0538: public static DataFolder findFolder(FileObject fo) {
0539: DataObject d;
0540: try {
0541: d = DataObject.find(fo);
0542: } catch (DataObjectNotFoundException e) {
0543: throw (IllegalArgumentException) new IllegalArgumentException(
0544: e.toString()).initCause(e);
0545: }
0546: if (!(d instanceof DataFolder)) {
0547: throw new IllegalArgumentException("Not a DataFolder: "
0548: + fo + " (was a " + d.getClass().getName()
0549: + ") (file is folder? " + fo.isFolder() + ")"); // NOI18N
0550: }
0551: return (DataFolder) d;
0552: }
0553:
0554: /** Finds a DataObject.Container representing given folder.
0555: * @param fo file object (must be folder)
0556: * @return the container for the file object
0557: * @exception IllegalArgumentException if the file object is not folder
0558: *
0559: * @since 1.11
0560: */
0561: public static DataObject.Container findContainer(FileObject fo) {
0562: if (fo.isFolder()) {
0563: return FolderList.find(fo, true);
0564: } else {
0565: throw new IllegalArgumentException("Not a folder: " + fo); // NOI18N
0566: }
0567: }
0568:
0569: /* Copy this object to a folder.
0570: * The copy of the object is required to
0571: * be deletable and movable.
0572: *
0573: * @param f the folder to copy object to
0574: * @exception IOException if something went wrong
0575: * @return the new object
0576: */
0577: protected DataObject handleCopy(DataFolder f) throws IOException {
0578: testNesting(this , f);
0579:
0580: Enumeration en = children();
0581:
0582: DataFolder newFolder = (DataFolder) super .handleCopy(f);
0583:
0584: while (en.hasMoreElements()) {
0585: try {
0586: DataObject obj = (DataObject) en.nextElement();
0587: if (obj.isCopyAllowed()) {
0588: obj.copy(newFolder);
0589: } else {
0590: // data object can not be copied, inform user
0591: DataObject.LOG.warning(NbBundle.getMessage(
0592: DataFolder.class, "FMT_CannotCopyDo", obj
0593: .getName()));
0594: }
0595: } catch (IOException ex) {
0596: Exceptions.printStackTrace(ex);
0597: }
0598: }
0599:
0600: return newFolder;
0601: }
0602:
0603: /**
0604: * Ensure that given folder is not parent of targetFolder. Also
0605: * ensure that they are not equal.
0606: */
0607: static void testNesting(DataFolder folder, DataFolder targetFolder)
0608: throws IOException {
0609: if (targetFolder.equals(folder)) {
0610: IOException ioe = new IOException(
0611: "Error Copying File or Folder"); //NOI18N
0612: Exceptions.attachLocalizedMessage(ioe, NbBundle.getMessage(
0613: DataFolder.class, "EXC_CannotCopyTheSame", folder
0614: .getName()));
0615: throw ioe;
0616: } else {
0617: DataFolder testFolder = targetFolder.getFolder();
0618: while (testFolder != null) {
0619: if (testFolder.equals(folder)) {
0620: IOException ioe = new IOException(
0621: "Error copying file or folder: "
0622: + folder.getPrimaryFile()
0623: + " cannot be copied to its subfolder "
0624: + targetFolder.getPrimaryFile());
0625: Exceptions.attachLocalizedMessage(ioe, NbBundle
0626: .getMessage(DataFolder.class,
0627: "EXC_CannotCopySubfolder", folder
0628: .getName()));
0629: throw ioe;
0630: }
0631: testFolder = testFolder.getFolder();
0632: }
0633: }
0634: }
0635:
0636: /* Deals with deleting of the object. Must be overriden in children.
0637: * @exception IOException if an error occures
0638: */
0639: protected void handleDelete() throws IOException {
0640: Enumeration en = children();
0641: FileLock lightWeightLock = null;//#43278
0642: try {
0643: lightWeightLock = createLightWeightLock(this );
0644: while (en.hasMoreElements()) {
0645: DataObject obj = (DataObject) en.nextElement();
0646: if (obj.isValid()) {
0647: obj.delete();
0648: }
0649: }
0650: } catch (IOException iex) {
0651: /** Annotates exception and throws again*/
0652: FileObject fo = getPrimaryFile();
0653: String message = NbBundle.getMessage(DataFolder.class,
0654: "EXC_CannotDelete2", FileUtil
0655: .getFileDisplayName(fo));
0656: Exceptions.attachLocalizedMessage(iex, message);
0657: throw iex;
0658: } finally {
0659: if (lightWeightLock != null) {
0660: lightWeightLock.releaseLock();
0661: }
0662: }
0663:
0664: super .handleDelete();
0665: }
0666:
0667: private static FileLock createLightWeightLock(DataFolder df) {//#43278
0668: FileObject fo = df.getPrimaryFile();
0669: assert fo != null;
0670: Object o = fo.getAttribute("LIGHTWEIGHT_LOCK_SET");//NOI18N
0671: assert o == null || (o instanceof FileLock) : fo.toString();
0672: return (FileLock) o;
0673: }
0674:
0675: /* Handles renaming of the object.
0676: * Must be overriden in children.
0677: *
0678: * @param name name to rename the object to
0679: * @return new primary file of the object
0680: * @exception IOException if an error occures
0681: */
0682: protected FileObject handleRename(final String name)
0683: throws IOException {
0684: if (!confirmName(name)) {
0685: IOException e = new IOException("bad name: " + name); // NOI18N
0686: Exceptions.attachLocalizedMessage(e, NbBundle.getMessage(
0687: DataFolder.class, "EXC_WrongName", name));
0688: throw e;
0689: }
0690: return super .handleRename(name);
0691: }
0692:
0693: private static final ThreadLocal<boolean[]> KEEP_ALIVE = new ThreadLocal<boolean[]>();
0694:
0695: /* Handles move of the object. Must be overriden in children. Since 1.13 move operation
0696: * behaves similar like copy, it merges folders whith existing folders in target location.
0697: * @param df target data folder
0698: * @return new primary file of the object
0699: * @exception IOException if an error occures
0700: */
0701: @Override
0702: protected FileObject handleMove(DataFolder df) throws IOException {
0703: FileObject originalFolder = getPrimaryFile();
0704: FileLock lock = originalFolder.lock();
0705: List<Pair> backup = saveEntries();
0706:
0707: boolean clearKeepAlive = false;
0708: try {
0709: // move entries (FolderEntry creates new folder when moved)
0710:
0711: FileObject newFile = super .handleMove(df);
0712:
0713: DataFolder newFolder = null;
0714: boolean dispose = false;
0715:
0716: boolean[] keepAlive = KEEP_ALIVE.get();
0717: if (keepAlive == null) {
0718: keepAlive = new boolean[] { false };
0719: KEEP_ALIVE.set(keepAlive);
0720: }
0721:
0722: /*
0723: * The following code is a partial bugfix of the issue #8705.
0724: * Please note that this problem is hardly reproducible by users,
0725: * but only by unit test.
0726: *
0727: * The root of the problem is that it is not possible to disable
0728: * recognizing of DataObjects for some time. Couple of lines above
0729: * the file object (destination folder) is created using
0730: * super.handleMove(df) and couple of lines below DataFolder if created
0731: * for this file object using createMultiObject.
0732: * The problems are:
0733: * 1) Temporary DataFolder created as destination folder is used only
0734: * during copying the original (this) DataFolder content.
0735: * Then is is marked as not valid using setValid(false). The original
0736: * datafolder switches its primary file to the destination file object.
0737: * The problem occurs, when some other thread takes the node representing
0738: * the temporary folder.
0739: * Solution: Special DataFolder that delegates nodeDelegate and
0740: * clonedNodeDelegate to the original folder.
0741: *
0742: * 2) There is still some sort time between creating of fileobject
0743: * and its datafolder. Another thread can ask for parent folder's
0744: * dataobjects and it forces creation of "normal" datafolder,
0745: * not the special one (with delegating nodes). Then it is necessary
0746: * to dispose the normal DataFolder and try to create our one.
0747: * To prevent infinite look there is a count down initialy set
0748: * to 20 repeats. Acording to results of DataFolderMoveTest it should
0749: * help. When this solution fails it only means that in some rare
0750: * cases some DataNode might represent invalid DataFolder. It is
0751: * not possible to delete such a node in explorer for instance.
0752: *
0753: * This is really strange hack (especially the 2nd part), and it is
0754: * necessary to think about better solution for NetBeans 4.0
0755: * data system architecture changes.
0756: *
0757: */
0758: final int COUNT_DOWN_INIT = 20;
0759: int countDown = COUNT_DOWN_INIT;
0760: while (countDown >= 0) {
0761: countDown--;
0762: try {
0763: // resolve temporary object for moving into
0764: Object loader = getMultiFileLoader();
0765: assert loader instanceof DataLoaderPool.FolderLoader : "This has to be FolderLoader: "
0766: + loader + " for " + getPrimaryFile(); // NOI18N
0767: DataLoaderPool.FolderLoader folderLoader = (DataLoaderPool.FolderLoader) loader;
0768: newFolder = (DataFolder) DataObjectPool
0769: .createMultiObject(folderLoader, newFile,
0770: this );
0771: dispose = false;
0772: break;
0773: } catch (DataObjectExistsException e) {
0774: // object already exists, get it and remember we should be discarded
0775: newFolder = (DataFolder) e.getDataObject();
0776: newFolder.dispose();
0777: dispose = true;
0778: }
0779: }
0780:
0781: // move all children
0782: Enumeration en = children();
0783:
0784: while (en.hasMoreElements()) {
0785: try {
0786: DataObject obj = (DataObject) en.nextElement();
0787: if (obj.isMoveAllowed()) {
0788: obj.move(newFolder);
0789: } else {
0790: keepAlive[0] = true;
0791:
0792: // data object can not be moved, inform user
0793: DataObject.LOG.warning(NbBundle.getMessage(
0794: DataFolder.class, "FMT_CannotMoveDo",
0795: obj.getName()));
0796: }
0797: } catch (IOException ex) {
0798: keepAlive[0] = true;
0799: Exceptions.printStackTrace(ex);
0800: }
0801: }
0802:
0803: if (keepAlive[0]) {
0804: // some children couldn't be moved -> folder shouldn't be moved
0805: restoreEntries(backup);
0806: list.refresh();
0807: assert newFolder.getClass().getName().indexOf(
0808: "NodeSharingDataFolder") >= 0;
0809: // and new folder is going to stay, so dispose this NodeSharingDataFolder
0810: newFolder.dispose();
0811: return originalFolder;
0812: }
0813:
0814: // remove original folder
0815: try {
0816: originalFolder.delete(lock);
0817: } catch (IOException e) {
0818: Throwable t = Exceptions.attachLocalizedMessage(e,
0819: org.openide.loaders.DataObject
0820: .getString("EXC_folder_delete_failed")); // NOI18N
0821: Exceptions.printStackTrace(t);
0822: }
0823:
0824: if (dispose) {
0825: // current object will be discarded, target already existed
0826: try {
0827: setValid(false);
0828: newFile = originalFolder;
0829: } catch (PropertyVetoException e) {
0830: // ignore, just repair entries
0831: restoreEntries(backup);
0832: newFile = getPrimaryEntry().getFile();
0833: }
0834: } else {
0835: // dispose temporary folder and place itself instead of it
0836: // call of changePrimaryFile and dispose must be in this order
0837: // to silently change DataFolders in the DataObjectPool
0838: item.changePrimaryFile(newFile);
0839: newFolder.dispose();
0840: list = reassignList(newFile, true);
0841: }
0842:
0843: return newFile;
0844: } finally {
0845: if (clearKeepAlive) {
0846: KEEP_ALIVE.remove();
0847: }
0848: lock.releaseLock();
0849: }
0850: }
0851:
0852: /* Creates new object from template.
0853: * @param f folder to create object in
0854: * @return new data object
0855: * @exception IOException if an error occured
0856: */
0857: protected DataObject handleCreateFromTemplate(DataFolder f,
0858: String name) throws IOException {
0859: DataFolder newFolder = (DataFolder) super
0860: .handleCreateFromTemplate(f, name);
0861: Enumeration en = children();
0862:
0863: while (en.hasMoreElements()) {
0864: try {
0865: DataObject obj = (DataObject) en.nextElement();
0866: obj.createFromTemplate(newFolder);
0867: } catch (IOException ex) {
0868: Exceptions.printStackTrace(ex);
0869: }
0870: }
0871:
0872: return newFolder;
0873: }
0874:
0875: /** Creates shadow for this object in specified folder (overridable in subclasses).
0876: * <p>The default
0877: * implementation creates a reference data shadow and pastes it into
0878: * the specified folder.
0879: *
0880: * @param f the folder to create a shortcut in
0881: * @return the shadow
0882: */
0883: protected DataShadow handleCreateShadow(DataFolder f)
0884: throws IOException {
0885: // #33871 - prevent creation of recursive folder structure
0886: testNesting(this , f);
0887:
0888: String name;
0889: if (getPrimaryFile().isRoot()) {
0890: name = FileUtil.findFreeFileName(f.getPrimaryFile(),
0891: ROOT_SHADOW_NAME, DataShadow.SHADOW_EXTENSION);
0892: } else {
0893: name = null;
0894: }
0895:
0896: return DataShadow.create(f, name, this );
0897: }
0898:
0899: /** Merge folder on move or copy when it exists in target location.
0900: * @returns <code>true</code>
0901: * @since 1.13
0902: */
0903: boolean isMergingFolders(FileObject who, FileObject targetFolder) {
0904: return !targetFolder.equals(who.getParent());
0905: }
0906:
0907: /** Support for index cookie for folder nodes.
0908: */
0909: public static class Index extends org.openide.nodes.Index.Support {
0910:
0911: /** Asociated data folder */
0912: private DataFolder df;
0913: /** node to be associated with */
0914: private Node node;
0915: /** change listener */
0916: private Listener listener;
0917:
0918: /** Create an index cookie associated with a data folder.
0919: * @param df the data folder
0920: * @deprecated Please explicitly specify a node to be safe.
0921: */
0922: @Deprecated
0923: public Index(final DataFolder df) {
0924: this (df, df.getNodeDelegate());
0925: }
0926:
0927: /** Create an index cookie associated with a data folder.
0928: * @param df the data folder
0929: * @param node node to be associated with. subnodes of this node will be returned, etc.
0930: */
0931: public Index(final DataFolder df, Node node) {
0932: this .df = df;
0933: this .node = node;
0934: listener = new Listener();
0935: node.addNodeListener(org.openide.nodes.NodeOp
0936: .weakNodeListener(listener, node));
0937: }
0938:
0939: /* Returns count of the nodes.
0940: */
0941: public int getNodesCount() {
0942: return node.getChildren().getNodes(
0943: FolderChildren.checkChildrenMutex()).length;
0944: }
0945:
0946: /* Returns array of subnodes
0947: * @return array of subnodes
0948: */
0949: public Node[] getNodes() {
0950: return node.getChildren().getNodes(
0951: FolderChildren.checkChildrenMutex());
0952: }
0953:
0954: /* Reorders all children with given permutation.
0955: * @param perm permutation with the length of current nodes
0956: * @exception IllegalArgumentException if the perm is not
0957: * valid permutation
0958: */
0959: public void reorder(int[] perm) {
0960: // #11809: the children of the node may not directly match the data folder
0961: // children. Specifically, it is legal to reorder a set of nodes where
0962: // each node has a distinct data object cookie, each object being a child of
0963: // this folder, but there are some objects missing. In such a case, the
0964: // specified objects are permuted according to the node permutation, while
0965: // other objects in the folder are left in their original positions and order.
0966: DataObject[] curObjs = df.getChildren();
0967: DataObject[] newObjs = new DataObject[curObjs.length];
0968: Node[] nodes = getNodes();
0969: if (nodes.length != perm.length) {
0970: throw new IllegalArgumentException(
0971: "permutation of incorrect length: "
0972: + perm.length + " rather than "
0973: + nodes.length); // NOI18N
0974: }
0975:
0976: // hashtable from names of nodes to their data objects for
0977: // nodes that do not express their data object as their cookie
0978: HashMap<String, DataObject> names = new HashMap<String, DataObject>(
0979: 2 * curObjs.length);
0980: for (int i = 0; i < curObjs.length; i++) {
0981: Node del = curObjs[i].getNodeDelegate();
0982: if (del.getCookie(DataObject.class) == null) {
0983: names.put(del.getName(), curObjs[i]);
0984: }
0985: }
0986:
0987: DataObject[] dperm = new DataObject[perm.length];
0988: for (int i = 0; i < perm.length; i++) {
0989: DataObject d = nodes[i].getCookie(DataObject.class);
0990:
0991: if (d == null) {
0992: // try to scan the names table too
0993: d = names.get(nodes[i].getName());
0994: }
0995:
0996: if (d == null) {
0997: throw new IllegalArgumentException(
0998: "cannot reorder node with no DataObject: "
0999: + nodes[i]); // NOI18N
1000: }
1001: if (d.getFolder() != df) {
1002: throw new IllegalArgumentException(
1003: "wrong folder for: " + d.getPrimaryFile()
1004: + " rather than "
1005: + df.getPrimaryFile()); // NOI18N
1006: }
1007: dperm[perm[i]] = d;
1008: }
1009: Set<DataObject> dpermSet = new HashSet<DataObject>(Arrays
1010: .asList(dperm));
1011: if (dpermSet.size() != dperm.length) {
1012: throw new IllegalArgumentException(
1013: "duplicate DataObject's among reordered childen"); // NOI18N
1014: }
1015: int dindex = 0;
1016: for (int i = 0; i < curObjs.length; i++) {
1017: if (dpermSet.remove(curObjs[i])) {
1018: newObjs[i] = dperm[dindex++];
1019: } else {
1020: // Not reordered, leave where it was.
1021: newObjs[i] = curObjs[i];
1022: }
1023: }
1024: try {
1025: df.setOrder(newObjs);
1026: } catch (IOException ex) {
1027: Exceptions.attachLocalizedMessage(ex,
1028: org.openide.loaders.DataObject
1029: .getString("EXC_ReorderFailed")); // NOI18N
1030: Exceptions.printStackTrace(ex);
1031: }
1032: }
1033:
1034: /* Invokes a dialog for reordering subnodes.
1035: */
1036: public void reorder() {
1037: Index.Support.showIndexedCustomizer(this );
1038: }
1039:
1040: /** Fires notification about reordering to all
1041: * registered listeners.
1042: */
1043: void fireChangeEventAccess() {
1044: fireChangeEvent(new ChangeEvent(this ));
1045: }
1046:
1047: /** Listener to change of children of the folder.
1048: */
1049: private final class Listener extends Object implements
1050: NodeListener {
1051: Listener() {
1052: }
1053:
1054: /** Change of children?
1055: */
1056: public void propertyChange(PropertyChangeEvent ev) {
1057: }
1058:
1059: /** Fired when the node is deleted.
1060: * @param ev event describing the node
1061: */
1062: public void nodeDestroyed(NodeEvent ev) {
1063: }
1064:
1065: /** Fired when the order of children is changed.
1066: * @param ev event describing the change
1067: */
1068: public void childrenReordered(NodeReorderEvent ev) {
1069: fireChangeEventAccess();
1070: }
1071:
1072: /** Fired when a set of children is removed.
1073: * @param ev event describing the action
1074: */
1075: public void childrenRemoved(NodeMemberEvent ev) {
1076: fireChangeEventAccess();
1077: }
1078:
1079: /** Fired when a set of new children is added.
1080: * @param ev event describing the action
1081: */
1082: public void childrenAdded(NodeMemberEvent ev) {
1083: fireChangeEventAccess();
1084: }
1085: } // end of Listener
1086:
1087: } // end of Index inner class
1088:
1089: /** Type-safe enumeration of sort modes for data folders.
1090: */
1091: public abstract static class SortMode implements
1092: Comparator<DataObject> {
1093: /** Objects are unsorted. */
1094: public static final SortMode NONE = new FolderComparator(
1095: FolderComparator.NONE);
1096:
1097: /** Objects are sorted by their names. */
1098: public static final SortMode NAMES = new FolderComparator(
1099: FolderComparator.NAMES);
1100:
1101: /** Objects are sorted by their types and then by names. */
1102: public static final SortMode CLASS = new FolderComparator(
1103: FolderComparator.CLASS);
1104:
1105: /** Folders go first (sorted by name) followed by non-folder
1106: * objects sorted by name.
1107: */
1108: public static final SortMode FOLDER_NAMES = new FolderComparator(
1109: FolderComparator.FOLDER_NAMES);
1110:
1111: /**
1112: * Folders go first (sorted by name) followed by files sorted by decreasing
1113: * last modification time.
1114: * @since org.openide.loaders 4.10
1115: */
1116: public static final SortMode LAST_MODIFIED = new FolderComparator(
1117: FolderComparator.LAST_MODIFIED);
1118:
1119: /**
1120: * Folders go first (sorted by name) followed by files sorted by decreasing size.
1121: * @since org.openide.loaders 4.10
1122: */
1123: public static final SortMode SIZE = new FolderComparator(
1124: FolderComparator.SIZE);
1125:
1126: /** Method to write the sort mode to a folder's attributes.
1127: * @param folder folder write this mode to
1128: */
1129: void write(FileObject f) throws IOException {
1130: // Let it throw the IOException:
1131: //if (f.getPrimaryFile ().getFileSystem ().isReadOnly ()) return; // cannot write to read-only FS
1132:
1133: String x;
1134: if (this == FOLDER_NAMES)
1135: x = "F"; // NOI18N
1136: else if (this == NAMES)
1137: x = "N"; // NOI18N
1138: else if (this == CLASS)
1139: x = "C"; // NOI18N
1140: else if (this == LAST_MODIFIED)
1141: x = "M"; // NOI18N
1142: else if (this == SIZE)
1143: x = "S"; // NOI18N
1144: else
1145: x = "O"; // NOI18N
1146:
1147: f.setAttribute(EA_SORT_MODE, x);
1148: }
1149:
1150: /** Reads sort mode for given folder.
1151: */
1152: static SortMode read(FileObject f) {
1153: String x = (String) f.getAttribute(EA_SORT_MODE);
1154: if (x == null || x.length() != 1)
1155: return FOLDER_NAMES;
1156:
1157: char c = x.charAt(0);
1158: switch (c) {
1159: case 'N':
1160: return NAMES;
1161: case 'C':
1162: return CLASS;
1163: case 'O':
1164: return NONE;
1165: case 'M':
1166: return LAST_MODIFIED;
1167: case 'S':
1168: return SIZE;
1169: case 'F':
1170: default:
1171: return FOLDER_NAMES;
1172: }
1173: }
1174: }
1175:
1176: /** true if the new folder name is acceptable */
1177: private static boolean confirmName(String folderName) {
1178: return folderName.indexOf('/') == -1
1179: && folderName.indexOf('\\') == -1;
1180: }
1181:
1182: /** Gets an icon from UIManager and converts it to Image
1183: */
1184: private static Image icon2image(String key) {
1185: Object obj = UIManager.get(key);
1186: if (obj instanceof Image) {
1187: return (Image) obj;
1188: }
1189:
1190: if (obj instanceof Icon) {
1191: Icon icon = (Icon) obj;
1192: return Utilities.icon2Image(icon);
1193: }
1194:
1195: return null;
1196: }
1197:
1198: /** array to cache images in */
1199: private static Image[] IMGS = new Image[2];
1200:
1201: static Image findIcon(int index, String k1, String k2) {
1202: if (IMGS[index] != null) {
1203: return IMGS[index];
1204: }
1205:
1206: Image i1 = icon2image(k1);
1207: if (i1 == null) {
1208: i1 = icon2image(k2);
1209: }
1210:
1211: IMGS[index] = i1;
1212: return i1;
1213: }
1214:
1215: /** Node for a folder.
1216: */
1217: public class FolderNode extends DataNode {
1218: /** Create a folder node with some children.
1219: * @param ch children to use for the node
1220: */
1221: public FolderNode(Children ch) {
1222: super (DataFolder.this , ch);
1223: setIconBaseWithExtension(FOLDER_ICON_BASE);
1224: }
1225:
1226: /** Create a folder node with default folder children.
1227: */
1228: protected FolderNode() {
1229: super (DataFolder.this , new FolderChildren(DataFolder.this ));
1230: setIconBaseWithExtension(FOLDER_ICON_BASE);
1231: }
1232:
1233: /** Overrides folder icon to search for icon in UIManager table for
1234: * BeanInfo.ICON_COLOR_16x16 type, to allow for different icons
1235: * across Look and Feels.
1236: * Keeps possibility of icon annotations.
1237: */
1238: public Image getIcon(int type) {
1239: Image img = null;
1240: if (type == BeanInfo.ICON_COLOR_16x16) {
1241: // search for proper folder icon installed by core/windows module
1242: img = findIcon(0, "Nb.Explorer.Folder.icon",
1243: "Tree.closedIcon"); // NOI18N
1244: }
1245: if (img == null) {
1246: img = super .getIcon(type);
1247: } else {
1248: // give chance to annotate icon returned from UIManeger
1249: // copied from DataNode to keep the contract
1250: try {
1251: DataObject obj = getDataObject();
1252: img = obj.getPrimaryFile().getFileSystem()
1253: .getStatus().annotateIcon(img, type,
1254: obj.files());
1255: } catch (FileStateInvalidException e) {
1256: // no fs, do nothing
1257: }
1258: }
1259: return img;
1260: }
1261:
1262: /** Overrides folder icon to search for icon in UIManager table for
1263: * BeanInfo.ICON_COLOR_16x16 type, to allow for different icons
1264: * across Look and Feels.
1265: * Keeps possibility of icon annotations.
1266: */
1267: public Image getOpenedIcon(int type) {
1268: Image img = null;
1269: if (type == BeanInfo.ICON_COLOR_16x16) {
1270: // search for proper folder icon installed by core/windows module
1271: img = findIcon(1, "Nb.Explorer.Folder.openedIcon",
1272: "Tree.openIcon"); // NOI18N
1273: }
1274: if (img == null) {
1275: img = super .getOpenedIcon(type);
1276: } else {
1277: // give chance to annotate icon returned from UIManeger
1278: // copied from DataNode to keep the contract
1279: try {
1280: DataObject obj = getDataObject();
1281: img = obj.getPrimaryFile().getFileSystem()
1282: .getStatus().annotateIcon(img, type,
1283: obj.files());
1284: } catch (FileStateInvalidException e) {
1285: // no fs, do nothing
1286: }
1287: }
1288: return img;
1289: }
1290:
1291: @SuppressWarnings("unchecked")
1292: public Node.Cookie getCookie(Class clazz) {
1293: if (clazz == org.openide.nodes.Index.class
1294: || clazz == Index.class) {
1295: //#33130 - enable IndexCookie only on SystemFileSystem
1296: // (also on apisupport layers...)
1297: try {
1298: if (DataFolder.this .getPrimaryFile()
1299: .getFileSystem() == Repository.getDefault()
1300: .getDefaultFileSystem()
1301: || Boolean.TRUE
1302: .equals(DataFolder.this
1303: .getPrimaryFile()
1304: .getAttribute(
1305: "DataFolder.Index.reorderable"))) { // NOI18N
1306: return new Index(DataFolder.this , this );
1307: }
1308: } catch (FileStateInvalidException ex) {
1309: Logger.getLogger(DataFolder.class.getName()).log(
1310: Level.WARNING, null, ex);
1311: }
1312: }
1313: return super .getCookie(clazz);
1314: }
1315:
1316: /* Adds properties for sorting.
1317: * @return the augmented property sheet
1318: */
1319: protected Sheet createSheet() {
1320: Sheet s = super .createSheet();
1321:
1322: Sheet.Set ss = new Sheet.Set();
1323: ss.setName(SET_SORTING);
1324: ss.setDisplayName(DataObject.getString("PROP_sorting"));
1325: ss
1326: .setShortDescription(DataObject
1327: .getString("HINT_sorting"));
1328:
1329: Node.Property p;
1330:
1331: p = new PropertySupport.ReadWrite<SortMode>(PROP_SORT_MODE,
1332: SortMode.class, DataObject.getString("PROP_sort"),
1333: DataObject.getString("HINT_sort")) {
1334: public SortMode getValue() {
1335: return DataFolder.this .getSortMode();
1336: }
1337:
1338: public void setValue(SortMode o)
1339: throws InvocationTargetException {
1340: try {
1341: DataFolder.this .setSortMode(o);
1342: } catch (IOException ex) {
1343: throw new InvocationTargetException(ex);
1344: }
1345: }
1346:
1347: public java.beans.PropertyEditor getPropertyEditor() {
1348: return new SortModeEditor();
1349: }
1350: };
1351: ss.put(p);
1352:
1353: s.put(ss);
1354: return s;
1355: }
1356:
1357: /* No default action on data folder node.
1358: * @return null
1359: */
1360: public Action getPreferredAction() {
1361: return null;
1362: }
1363:
1364: /* New type for creating new subfolder.
1365: * @return array with one element
1366: */
1367: public NewType[] getNewTypes() {
1368: return new NewType[0];
1369: /* Commented out. Folder is now created via template.
1370:
1371: if (getPrimaryFile ().isReadOnly ()) {
1372: // no new types
1373: return new NewType[0];
1374: } else {
1375: return new NewType[] { new NewFolder () };
1376: }
1377: */
1378: }
1379:
1380: private synchronized FolderRenameHandler getRenameHandler() {
1381: Collection handlers = Lookup.getDefault().lookupAll(
1382: FolderRenameHandler.class);
1383: if (handlers.size() == 0)
1384: return null;
1385: if (handlers.size() > 1)
1386: DataObject.LOG
1387: .warning("Multiple instances of FolderRenameHandler found in Lookup; only using first one: "
1388: + handlers); //NOI18N
1389: return (FolderRenameHandler) handlers.iterator().next();
1390: }
1391:
1392: public void setName(String name) {
1393: FolderRenameHandler handler = getRenameHandler();
1394: if (handler == null) {
1395: super .setName(name);
1396: } else {
1397: handler.handleRename(DataFolder.this , name);
1398: }
1399: }
1400:
1401: /* May add some paste types for objects being added to folders.
1402: * May move data objects; copy them; create links for them; instantiate
1403: * them as templates; serialize instances; or create instance data objects
1404: * from instances, according to the abilities of the transferable.
1405: *
1406: * @param t transferable to use
1407: * @param s list of {@link PasteType}s
1408: */
1409: protected void createPasteTypes(Transferable t,
1410: java.util.List<PasteType> s) {
1411: super .createPasteTypes(t, s);
1412: if (getPrimaryFile().canWrite()) {
1413: dataTransferSupport.createPasteTypes(t, s);
1414: }
1415:
1416: List<File> files = getDraggedFilesList(t);
1417: if (null != files && !files.isEmpty() && s.isEmpty()) {
1418: //there are some files in the Transferable so let's try to
1419: //convert them to DataObjects and create PasteTypes for them
1420: List<Transferable> transferables = new ArrayList<Transferable>(
1421: files.size());
1422: for (File f : files) {
1423: Transferable nodeTransferable = createNodeTransferable(f);
1424: if (null != nodeTransferable)
1425: transferables.add(nodeTransferable);
1426: }
1427: ExTransferable.Multi multi = new ExTransferable.Multi(
1428: transferables
1429: .toArray(new Transferable[transferables
1430: .size()]));
1431: super .createPasteTypes(multi, s);
1432: if (getPrimaryFile().canWrite()) {
1433: dataTransferSupport.createPasteTypes(multi, s);
1434: }
1435: }
1436: }
1437:
1438: Transferable createNodeTransferable(File f) {
1439: Transferable result = null;
1440: FileObject fo = FileUtil.toFileObject(FileUtil
1441: .normalizeFile(f));
1442: if (null != fo) {
1443: try {
1444: DataObject dob = DataObject.find(fo);
1445: if (null != dob) {
1446: Node delegate = dob.getNodeDelegate();
1447: //cannot paste a node to itself
1448: if (!delegate.equals(this )) {
1449: result = dob.getNodeDelegate()
1450: .clipboardCopy();
1451: ExClipboard exClipboard = Lookup
1452: .getDefault().lookup(
1453: ExClipboard.class);
1454: if (exClipboard != null) {
1455: //let refactoring and others to add their own paste wrappers
1456: result = exClipboard.convert(result);
1457: }
1458: }
1459: }
1460: } catch (IOException ioE) {
1461: Logger.getLogger(DataFolder.class.getName()).log(
1462: Level.WARNING, null, ioE);
1463: }
1464: }
1465: return result;
1466: }
1467:
1468: private List<File> getDraggedFilesList(Transferable t) {
1469: try {
1470: if (t
1471: .isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
1472: //windows & mac
1473: List fileList = (List) t
1474: .getTransferData(DataFlavor.javaFileListFlavor);
1475: //#92812 - make sure mac os does not return null value
1476: if (null != fileList)
1477: return NbCollections
1478: .checkedListByCopy(
1479: (List) t
1480: .getTransferData(DataFlavor.javaFileListFlavor),
1481: File.class, true);
1482: } else if (t
1483: .isDataFlavorSupported(getUriListDataFlavor())) {
1484: //linux
1485: String uriList = (String) t
1486: .getTransferData(getUriListDataFlavor());
1487: return textURIListToFileList(uriList);
1488: }
1489: } catch (UnsupportedFlavorException ex) {
1490: Logger.getLogger(DataFolder.class.getName()).log(
1491: Level.WARNING, null, ex);
1492: } catch (IOException ex) {
1493: // Ignore. Can be just "Owner timed out" from sun.awt.X11.XSelection.getData.
1494: Logger.getLogger(DataFlavor.class.getName()).log(
1495: Level.FINE, null, ex);
1496: }
1497: return null;
1498: }
1499:
1500: private DataFlavor getUriListDataFlavor() {
1501: if (null == uriListDataFlavor) {
1502: try {
1503: uriListDataFlavor = new DataFlavor(
1504: "text/uri-list;class=java.lang.String");
1505: } catch (ClassNotFoundException cnfE) {
1506: //cannot happen
1507: throw new AssertionError(cnfE);
1508: }
1509: }
1510: return uriListDataFlavor;
1511: }
1512:
1513: private List<File> textURIListToFileList(String data) {
1514: List<File> list = new ArrayList<File>(1);
1515: // XXX consider using BufferedReader(StringReader) instead
1516: for (StringTokenizer st = new StringTokenizer(data, "\r\n"); st
1517: .hasMoreTokens();) {
1518: String s = st.nextToken();
1519: if (s.startsWith("#")) {
1520: // the line is a comment (as per the RFC 2483)
1521: continue;
1522: }
1523: try {
1524: URI uri = new URI(s);
1525: File file = new File(uri);
1526: list.add(file);
1527: } catch (java.net.URISyntaxException e) {
1528: // malformed URI
1529: } catch (IllegalArgumentException e) {
1530: // the URI is not a valid 'file:' URI
1531: }
1532: }
1533: return list;
1534: }
1535:
1536: } // end of FolderNode
1537:
1538: /** New type for creation of new folder.
1539: */
1540: private final class NewFolder extends NewType {
1541: NewFolder() {
1542: }
1543:
1544: /** Display name for the creation action. This should be
1545: * presented as an item in a menu.
1546: *
1547: * @return the name of the action
1548: */
1549: public String getName() {
1550: return DataObject.getString("CTL_NewFolder");
1551: }
1552:
1553: /** Help context for the creation action.
1554: * @return the help context
1555: */
1556: public HelpCtx getHelpCtx() {
1557: return HelpCtx.DEFAULT_HELP;
1558: }
1559:
1560: /** Create the object.
1561: * @exception IOException if something fails
1562: */
1563: public void create() throws IOException {
1564: NotifyDescriptor.InputLine input = new NotifyDescriptor.InputLine(
1565: DataObject.getString("CTL_NewFolderName"),
1566: DataObject.getString("CTL_NewFolderTitle"));
1567: input.setInputText(DataObject
1568: .getString("CTL_NewFolderValue"));
1569: if (DialogDisplayer.getDefault().notify(input) == NotifyDescriptor.OK_OPTION) {
1570: String folderName = input.getInputText();
1571: if ("".equals(folderName))
1572: return; // empty name = cancel // NOI18N
1573:
1574: FileObject folder = getPrimaryFile();
1575: int dotPos = -1;
1576:
1577: while ((dotPos = folderName.indexOf(".")) != -1) { // NOI18N
1578: String subFolder = folderName.substring(0, dotPos);
1579: folderName = folderName.substring(dotPos + 1);
1580:
1581: FileObject existingFile = folder
1582: .getFileObject(subFolder);
1583: if (existingFile != null) {
1584: if (!existingFile.isFolder()) {
1585: DialogDisplayer
1586: .getDefault()
1587: .notify(
1588: new NotifyDescriptor.Message(
1589: NbBundle
1590: .getMessage(
1591: DataObject.class,
1592: "MSG_FMT_FileExists",
1593: subFolder,
1594: folder
1595: .getName()),
1596: NotifyDescriptor.WARNING_MESSAGE));
1597: return;
1598: }
1599: folder = existingFile;
1600: } else {
1601: if (!confirmName(subFolder)) {
1602: throw new IOException(NbBundle.getMessage(
1603: DataObject.class, "EXC_WrongName",
1604: subFolder));
1605: }
1606: folder = folder.createFolder(subFolder);
1607: }
1608: }
1609: if (!"".equals(folderName)) { // NOI18N
1610: FileObject existingFile = folder
1611: .getFileObject(folderName);
1612: if (existingFile != null) {
1613: if (existingFile.isFolder()) {
1614: DialogDisplayer
1615: .getDefault()
1616: .notify(
1617: new NotifyDescriptor.Message(
1618: NbBundle
1619: .getMessage(
1620: DataObject.class,
1621: "MSG_FMT_FolderExists",
1622: folderName,
1623: folder
1624: .getName()),
1625: NotifyDescriptor.INFORMATION_MESSAGE));
1626: } else {
1627: DialogDisplayer
1628: .getDefault()
1629: .notify(
1630: new NotifyDescriptor.Message(
1631: NbBundle
1632: .getMessage(
1633: DataObject.class,
1634: "MSG_FMT_FileExists",
1635: folderName,
1636: folder
1637: .getName()),
1638: NotifyDescriptor.WARNING_MESSAGE));
1639: }
1640: return;
1641: }
1642:
1643: if (!confirmName(folderName)) {
1644: throw new IOException(NbBundle.getMessage(
1645: DataObject.class, "EXC_WrongName",
1646: folderName));
1647: }
1648:
1649: DataObject created = DataObject.find(folder
1650: .createFolder(folderName));
1651: if (created != null) {
1652: DataLoaderPool.getDefault().fireOperationEvent(
1653: new OperationEvent.Copy(created,
1654: DataFolder.this ),
1655: OperationEvent.TEMPL);
1656: }
1657: }
1658: }
1659: }
1660: }
1661:
1662: private class Paste extends DataTransferSupport {
1663: Paste() {
1664: }
1665:
1666: /** Defines array of classes implementing paste for specified clipboard operation.
1667: * @param op clopboard operation to specify paste types for
1668: * @return array of classes extending PasteTypeExt class
1669: */
1670: protected DataTransferSupport.PasteTypeExt[] definePasteTypes(
1671: int op) {
1672: switch (op) {
1673: case LoaderTransfer.CLIPBOARD_CUT:
1674: return new DataTransferSupport.PasteTypeExt[] { new DataTransferSupport.PasteTypeExt() {
1675: public String getName() {
1676: return DataObject.getString("PT_move"); // NOI18N
1677: }
1678:
1679: public HelpCtx getHelpCtx() {
1680: return new HelpCtx(Paste.class.getName()
1681: + ".move"); // NOI18N
1682: }
1683:
1684: protected boolean handleCanPaste(DataObject obj) {
1685: return obj.isMoveAllowed()
1686: && !isParent(getPrimaryFile(), obj
1687: .getPrimaryFile());
1688: }
1689:
1690: protected void handlePaste(DataObject obj)
1691: throws IOException {
1692: obj.move(DataFolder.this );
1693: }
1694:
1695: /** Cleans clipboard after paste. Overrides superclass method. */
1696: protected boolean cleanClipboard() {
1697: return true;
1698: }
1699:
1700: /** Check if one file object has another as a parent.
1701: * @param fo the file object to check
1702: * @param parent
1703: * @return true if parent is fo's (indirect) parent
1704: */
1705: /*not private called from FolderNode*/
1706: private boolean isParent(FileObject fo,
1707: FileObject parent) {
1708: File parentFile = FileUtil.toFile(parent);
1709: File foFile = FileUtil.toFile(fo);
1710:
1711: if (foFile != null && parentFile != null) {
1712: return isParentFile(foFile, parentFile);
1713: }
1714:
1715: try {
1716: if (fo.getFileSystem() != parent
1717: .getFileSystem()) {
1718: return false;
1719: }
1720: } catch (IOException ex) {
1721: }
1722:
1723: while (fo != null) {
1724: if (fo.equals(parent)) {
1725: return true;
1726: }
1727:
1728: fo = fo.getParent();
1729: }
1730:
1731: return false;
1732: }
1733: } };
1734:
1735: case LoaderTransfer.CLIPBOARD_COPY:
1736: return new DataTransferSupport.PasteTypeExt[] {
1737: new DataTransferSupport.PasteTypeExt() {
1738: public String getName() {
1739: return DataObject.getString("PT_copy"); // NOI18N
1740: }
1741:
1742: public HelpCtx getHelpCtx() {
1743: return new HelpCtx(Paste.class
1744: .getName()
1745: + ".copy"); // NOI18N
1746: }
1747:
1748: protected boolean handleCanPaste(
1749: DataObject obj) {
1750: return obj.isCopyAllowed();
1751: }
1752:
1753: protected void handlePaste(DataObject obj)
1754: throws IOException {
1755: saveIfModified(obj);
1756: obj.copy(DataFolder.this );
1757: }
1758:
1759: private void saveIfModified(DataObject obj)
1760: throws IOException {
1761: if (obj.isModified()) {
1762: SaveCookie sc = obj
1763: .getCookie(SaveCookie.class);
1764: if (sc != null) {
1765: sc.save();
1766: }
1767: }
1768: }
1769: }, new DataTransferSupport.PasteTypeExt() {
1770: public String getName() {
1771: return DataObject
1772: .getString("PT_instantiate"); // NOI18N
1773: }
1774:
1775: public HelpCtx getHelpCtx() {
1776: return new HelpCtx(Paste.class
1777: .getName()
1778: + ".instantiate"); // NOI18N
1779: }
1780:
1781: protected boolean handleCanPaste(
1782: DataObject obj) {
1783: return obj.isTemplate();
1784: }
1785:
1786: protected void handlePaste(DataObject obj)
1787: throws IOException {
1788: obj.createFromTemplate(DataFolder.this );
1789: }
1790: }, new DataTransferSupport.PasteTypeExt() {
1791: public String getName() {
1792: return DataObject
1793: .getString("PT_shadow"); // NOI18N
1794: }
1795:
1796: public HelpCtx getHelpCtx() {
1797: return new HelpCtx(Paste.class
1798: .getName()
1799: + ".shadow"); // NOI18N
1800: }
1801:
1802: protected boolean handleCanPaste(
1803: DataObject obj) {
1804: // #42888 - disable "Create as Link" action on non-SystemFileSystem
1805: try {
1806: if (!DataFolder.this
1807: .getPrimaryFile()
1808: .getFileSystem()
1809: .equals(
1810: Repository
1811: .getDefault()
1812: .getDefaultFileSystem())) {
1813: return false;
1814: }
1815: } catch (FileStateInvalidException ex) {
1816: // something wrong. disable.
1817: return false;
1818: }
1819: return obj.isShadowAllowed();
1820: }
1821:
1822: protected void handlePaste(DataObject obj)
1823: throws IOException {
1824: obj.createShadow(DataFolder.this );
1825: }
1826: } };
1827: }
1828: return new DataTransferSupport.PasteTypeExt[0];
1829: }
1830:
1831: private boolean isParentFile(File foFile, File parentFile) {
1832: boolean retVal = false;
1833: while (foFile != null) {
1834: if (foFile.equals(parentFile)) {
1835: retVal = true;
1836: break;
1837: }
1838: foFile = foFile.getParentFile();
1839: }
1840: return retVal;
1841: }
1842:
1843: /** Defines array of data clipboard operations recognized by this paste support.
1844: * @return array of DataFlavors
1845: */
1846: protected int[] defineOperations() {
1847: return new int[] { LoaderTransfer.CLIPBOARD_CUT,
1848: LoaderTransfer.CLIPBOARD_COPY };
1849: }
1850:
1851: protected void handleCreatePasteTypes(Transferable t,
1852: java.util.List<PasteType> s) {
1853: // These should only accept single-node transfers, since they require dialogs.
1854: Node node = NodeTransfer.node(t,
1855: NodeTransfer.CLIPBOARD_COPY);
1856:
1857: // lastly try special cookies
1858: if (node != null) {
1859: try {
1860: InstanceCookie cookie = node
1861: .getCookie(InstanceCookie.class);
1862: if (cookie != null
1863: && java.io.Serializable.class
1864: .isAssignableFrom(cookie
1865: .instanceClass())) {
1866: s.add(new DataTransferSupport.SerializePaste(
1867: DataFolder.this , cookie));
1868: s.add(new DataTransferSupport.InstantiatePaste(
1869: DataFolder.this , cookie));
1870: }
1871: } catch (IOException e) {
1872: } catch (ClassNotFoundException e) {
1873: }
1874: }
1875: }
1876: }
1877:
1878: /** Listener on changes in FolderList that delegates to our PCL.
1879: */
1880: private final class ListPCL extends Object implements
1881: PropertyChangeListener {
1882: ListPCL() {
1883: }
1884:
1885: public void propertyChange(java.beans.PropertyChangeEvent ev) {
1886: if (this == DataFolder.this .pcl) {
1887: // if I am still folder's correct listener
1888: DataFolder.this.firePropertyChange(PROP_CHILDREN, null,
1889: null);
1890: }
1891: }
1892:
1893: }
1894: }
|