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.filesystems;
0043:
0044: import java.io.File;
0045: import java.io.FileNotFoundException;
0046: import java.io.FilterOutputStream;
0047: import java.io.IOException;
0048: import java.io.InputStream;
0049: import java.io.OutputStream;
0050: import java.io.Serializable;
0051: import java.net.URL;
0052: import java.util.ArrayList;
0053: import java.util.Arrays;
0054: import java.util.Collection;
0055: import java.util.Collections;
0056: import java.util.Enumeration;
0057: import java.util.LinkedList;
0058: import java.util.List;
0059: import java.util.StringTokenizer;
0060: import org.openide.util.Enumerations;
0061: import org.openide.util.Exceptions;
0062: import org.openide.util.NbBundle;
0063:
0064: /** This is the base for all implementations of file objects on a filesystem.
0065: * Provides basic information about the object (its name, parent,
0066: * whether it exists, etc.) and operations on it (move, delete, etc.).
0067: *
0068: * @author Jaroslav Tulach, Petr Hamernik, Ian Formanek
0069: */
0070: public abstract class FileObject extends Object implements Serializable {
0071: /** generated Serialized Version UID */
0072: static final long serialVersionUID = 85305031923497718L;
0073:
0074: /** Get the name without extension of this file or folder.
0075: * Period at first position is not considered as extension-separator
0076: * For the root folder of a filesystem, this will be the empty
0077: * string (the extension will also be the empty string, and the
0078: * fully qualified name with any delimiters will also be the
0079: * empty string).
0080: * @return name of the file or folder(in its enclosing folder)
0081: */
0082: public abstract String getName();
0083:
0084: /** Get the extension of this file or folder.
0085: * Period at first position is not considered as extension-separator
0086: * This is the string after the last dot of the full name, if any.
0087: *
0088: * @return extension of the file or folder (if any) or empty string if there is none
0089: */
0090: public abstract String getExt();
0091:
0092: /** Renames this file (or folder).
0093: * Both the new basename and new extension should be specified.
0094: * <p>
0095: * Note that using this call, it is currently only possible to rename <em>within</em>
0096: * a parent folder, and not to do moves <em>across</em> folders.
0097: * Conversely, implementing filesystems need only implement "simple" renames.
0098: * If you wish to move a file across folders, you should call {@link FileUtil#moveFile}.
0099: * @param lock File must be locked before renaming.
0100: * @param name new basename of file
0101: * @param ext new extension of file (ignored for folders)
0102: */
0103: public abstract void rename(FileLock lock, String name, String ext)
0104: throws IOException;
0105:
0106: /** Copies this file. This allows the filesystem to perform any additional
0107: * operation associated with the copy. But the default implementation is simple
0108: * copy of the file and its attributes
0109: *
0110: * @param target target folder to move this file to
0111: * @param name new basename of file
0112: * @param ext new extension of file (ignored for folders)
0113: * @return the newly created file object representing the moved file
0114: */
0115: public FileObject copy(FileObject target, String name, String ext)
0116: throws IOException {
0117: if (isFolder()) {
0118: throw new IOException(NbBundle.getBundle(FileObject.class)
0119: .getString("EXC_FolderCopy"));
0120: }
0121:
0122: FileObject dest = FileUtil
0123: .copyFileImpl(this , target, name, ext);
0124:
0125: return dest;
0126: }
0127:
0128: /** Moves this file. This allows the filesystem to perform any additional
0129: * operation associated with the move. But the default implementation is encapsulated
0130: * as copy and delete.
0131: *
0132: * @param lock File must be locked before renaming.
0133: * @param target target folder to move this file to
0134: * @param name new basename of file
0135: * @param ext new extension of file (ignored for folders)
0136: * @return the newly created file object representing the moved file
0137: */
0138: public FileObject move(FileLock lock, FileObject target,
0139: String name, String ext) throws IOException {
0140: if (getParent().equals(target)) {
0141: // it is possible to do only rename
0142: rename(lock, name, ext);
0143:
0144: return this ;
0145: } else {
0146: // have to do copy
0147: FileObject dest = copy(target, name, ext);
0148: delete(lock);
0149:
0150: return dest;
0151: }
0152: }
0153:
0154: /**
0155: * Gets a textual represtentation of this <code>FileObject</code>.
0156: * The precise format is not defined. In particular it is probably
0157: * <em>not</em> a resource path.
0158: * For that purpose use {@link #getPath} directly.
0159: * <p>Typically it is useful for debugging purposes. Example of correct usage:
0160: * <pre>
0161: * <font class="type">FileObject</font> <font class="variable-name">fo</font> = getSomeFileObject();
0162: * ErrorManager.getDefault().log(<font class="string">"Got a change from "</font> + fo);
0163: * </pre>
0164: * @return some representation of this file object
0165: */
0166: public String toString() {
0167: String cname = getClass().getName();
0168: String cnameShort = cname.substring(cname.lastIndexOf('.') + 1);
0169:
0170: try {
0171: return cnameShort
0172: + '@'
0173: + Integer
0174: .toHexString(System.identityHashCode(this ))
0175: + '['
0176: + (isRoot() ? "root of " + getFileSystem()
0177: : getPath()) + ']'; // NOI18N
0178: } catch (FileStateInvalidException x) {
0179: return cnameShort
0180: + '@'
0181: + Integer
0182: .toHexString(System.identityHashCode(this ))
0183: + "[???]"; // NOI18N
0184: }
0185: }
0186:
0187: /** Get the full resource path of this file object starting from the filesystem root.
0188: * Folders are separated with forward slashes. File extensions, if present,
0189: * are included. The root folder's path is the empty string. The path of a folder
0190: * never ends with a slash.
0191: * <p>Subclasses are strongly encouraged to override this method.
0192: * <p>Never use this to get a display name for the file! Use {@link FileUtil#getFileDisplayName}.
0193: * <p>Do not use this method to find a file path on disk! Use {@link FileUtil#toFile}.
0194: * @return the path, for example <samp>path/from/root.ext</samp>
0195: * @see FileSystem#findResource
0196: * @since 3.7
0197: */
0198: public String getPath() {
0199: StringBuilder sb = new StringBuilder();
0200: constructName(sb, '/');
0201:
0202: return sb.toString();
0203: }
0204:
0205: /** Get fully-qualified filename. Does so by walking through all folders
0206: * to the root of the filesystem. Separates files with provided <code>separatorChar</code>.
0207: * The extension, if present, is separated from the basename with <code>extSepChar</code>.
0208: * <p><strong>Note:</strong> <samp>fo.getPath()</samp> will have the
0209: * same effect as using this method with <samp>/</samp> and <samp>.</samp> (the standard
0210: * path and extension delimiters).
0211: * @param separatorChar char to separate folders and files
0212: * @param extSepChar char to separate extension
0213: * @return the fully-qualified filename
0214: * @deprecated Please use the <a href="@org-netbeans-api-java@/org/netbeans/api/java/classpath/ClassPath.html">ClassPath API</a> instead.
0215: */
0216: @Deprecated
0217: public String getPackageNameExt(char separatorChar, char extSepChar) {
0218: assert FileUtil.assertDeprecatedMethod();
0219:
0220: StringBuilder sb = new StringBuilder();
0221:
0222: if (isRoot() || getParent().isRoot()) {
0223: return getNameExt();
0224: }
0225:
0226: getParent().constructName(sb, separatorChar);
0227:
0228: String ext = getExt();
0229:
0230: if ((ext == null) || ext.equals("")) { // NOI18N
0231: sb.append(separatorChar).append(getNameExt());
0232: } else {
0233: sb.append(separatorChar).append(getName()).append(
0234: extSepChar).append(getExt());
0235: }
0236:
0237: return sb.toString();
0238: }
0239:
0240: /** Get fully-qualified filename, but without extension.
0241: * Like {@link #getPackageNameExt} but omits the extension.
0242: * @param separatorChar char to separate folders and files
0243: * @return the fully-qualified filename
0244: * @deprecated Please use the <a href="@org-netbeans-api-java@/org/netbeans/api/java/classpath/ClassPath.html">ClassPath API</a> instead.
0245: */
0246: @Deprecated
0247: public String getPackageName(char separatorChar) {
0248: assert FileUtil.assertDeprecatedMethod();
0249:
0250: StringBuilder sb = new StringBuilder();
0251:
0252: if (isRoot() || getParent().isRoot()) {
0253: return (isFolder()) ? getNameExt() : getName();
0254: }
0255:
0256: getParent().constructName(sb, separatorChar);
0257:
0258: //sb.append (separatorChar).append ((isFolder ()) ? getNameExt() : getName ());
0259: sb.append(separatorChar).append(getName());
0260:
0261: return sb.toString();
0262: }
0263:
0264: /** Getter for name and extension of a file object. Dot is used
0265: * as separator between name and ext.
0266: * @return string name of the file in the folder (with extension)
0267: */
0268: public String getNameExt() {
0269: String n = getName();
0270: String e = getExt();
0271:
0272: return ((e == null) || (e.length() == 0)) ? n : (n + '.' + e);
0273: }
0274:
0275: /** Constructs path of file.
0276: * @param sb string buffer
0277: * @param sepChar separator character
0278: */
0279: private void constructName(StringBuilder sb, char sepChar) {
0280: FileObject parent = getParent();
0281:
0282: if ((parent != null) && !parent.isRoot()) {
0283: parent.constructName(sb, sepChar);
0284: sb.append(sepChar);
0285: }
0286:
0287: sb.append(getNameExt());
0288: }
0289:
0290: /** Get the filesystem containing this file.
0291: * <p>
0292: * Note that it may be possible for a stale file object to exist which refers to a now-defunct filesystem.
0293: * If this is the case, this method will throw an exception.
0294: * @return the filesystem
0295: * @exception FileStateInvalidException if the reference to the file
0296: * system has been lost (e.g., if the filesystem was deleted)
0297: */
0298: public abstract FileSystem getFileSystem()
0299: throws FileStateInvalidException;
0300:
0301: /** Get parent folder.
0302: * The returned object will satisfy {@link #isFolder}.
0303: *
0304: * @return the parent folder or <code>null</code> if this object {@link #isRoot}.
0305: */
0306: public abstract FileObject getParent();
0307:
0308: /** Test whether this object is a folder.
0309: * @return true if the file object is a folder (i.e., can have children)
0310: */
0311: public abstract boolean isFolder();
0312:
0313: /**
0314: * Get last modification time.
0315: * @return the date
0316: */
0317: public abstract java.util.Date lastModified();
0318:
0319: /** Test whether this object is the root folder.
0320: * The root should always be a folder.
0321: * @return true if the object is the root of a filesystem
0322: */
0323: public abstract boolean isRoot();
0324:
0325: /** Test whether this object is a data object.
0326: * This is exclusive with {@link #isFolder}.
0327: * @return true if the file object represents data (i.e., can be read and written)
0328: */
0329: public abstract boolean isData();
0330:
0331: /** Test whether the file is valid. The file can be invalid if it has been deserialized
0332: * and the file no longer exists on disk; or if the file has been deleted.
0333: *
0334: * @return true if the file object is valid
0335: */
0336: public abstract boolean isValid();
0337:
0338: /** Test whether there is a file with the same basename and only a changed extension in the same folder.
0339: * The default implementation asks this file's parent using {@link #getFileObject(String name, String ext)}.
0340: *
0341: * @param ext the alternate extension
0342: * @return true if there is such a file
0343: */
0344: public boolean existsExt(String ext) {
0345: FileObject parent = getParent();
0346:
0347: return (parent != null)
0348: && (parent.getFileObject(getName(), ext) != null);
0349: }
0350:
0351: /** Delete this file. If the file is a folder and it is not empty then
0352: * all of its contents are also recursively deleted.
0353: *
0354: * @param lock the lock obtained by a call to {@link #lock}
0355: * @exception IOException if the file could not be deleted
0356: */
0357: public abstract void delete(FileLock lock) throws IOException;
0358:
0359: /** Delete this file. If the file is a folder and it is not empty then
0360: * all of its contents are also recursively deleted. FileObject is locked
0361: * before delete and finally is this lock released.
0362: *
0363: * @exception IOException if the file could not be deleted or
0364: * FileAlreadyLockedException if the file is already locked {@link #lock}
0365: * @since 1.15
0366: */
0367: public final void delete() throws IOException {
0368: FileLock lock = lock();
0369:
0370: try {
0371: delete(lock);
0372: } finally {
0373: lock.releaseLock();
0374: }
0375: }
0376:
0377: /** Get the file attribute with the specified name.
0378: * @param attrName name of the attribute
0379: * @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason)
0380: */
0381: abstract public Object getAttribute(String attrName);
0382:
0383: /** Set the file attribute with the specified name.
0384: * @param attrName name of the attribute
0385: * @param value new value or <code>null</code> to clear the attribute. Must be serializable, although particular filesystems may or may not use serialization to store attribute values.
0386: * @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link java.io.NotSerializableException}.
0387: */
0388: abstract public void setAttribute(String attrName, Object value)
0389: throws IOException;
0390:
0391: /** Get all file attribute names for this file.
0392: * @return enumeration of keys (as strings)
0393: */
0394: abstract public Enumeration<String> getAttributes();
0395:
0396: /** Test whether this file has the specified extension.
0397: * @param ext the extension the file should have
0398: * @return true if the text after the last period (<code>.</code>) is equal to the given extension
0399: */
0400: public final boolean hasExt(String ext) {
0401: if (isHasExtOverride()) {
0402: return hasExtOverride(ext);
0403: }
0404:
0405: return getExt().equals(ext);
0406: }
0407:
0408: /** Overriden in AbstractFolder */
0409: boolean isHasExtOverride() {
0410: return false;
0411: }
0412:
0413: /** Overridden in AbstractFolder */
0414: boolean hasExtOverride(String ext) {
0415: return false;
0416: }
0417:
0418: /** Add new listener to this object.
0419: * @param fcl the listener
0420: */
0421: public abstract void addFileChangeListener(FileChangeListener fcl);
0422:
0423: /** Remove listener from this object.
0424: * @param fcl the listener
0425: */
0426: public abstract void removeFileChangeListener(FileChangeListener fcl);
0427:
0428: /** Fire data creation event.
0429: * @param en listeners that should receive the event
0430: * @param fe the event to fire in this object
0431: */
0432: protected void fireFileDataCreatedEvent(
0433: Enumeration<FileChangeListener> en, FileEvent fe) {
0434: dispatchEvent(FCLSupport.Op.DATA_CREATED, en, fe);
0435: }
0436:
0437: /** Fire folder creation event.
0438: * @param en listeners that should receive the event
0439: * @param fe the event to fire in this object
0440: */
0441: protected void fireFileFolderCreatedEvent(
0442: Enumeration<FileChangeListener> en, FileEvent fe) {
0443: dispatchEvent(FCLSupport.Op.FOLDER_CREATED, en, fe);
0444: }
0445:
0446: /** Fire file change event.
0447: * @param en listeners that should receive the event
0448: * @param fe the event to fire in this object
0449: */
0450: protected void fireFileChangedEvent(
0451: Enumeration<FileChangeListener> en, FileEvent fe) {
0452: dispatchEvent(FCLSupport.Op.FILE_CHANGED, en, fe);
0453: }
0454:
0455: /** Fire file deletion event.
0456: * @param en listeners that should receive the event
0457: * @param fe the event to fire in this object
0458: */
0459: protected void fireFileDeletedEvent(
0460: Enumeration<FileChangeListener> en, FileEvent fe) {
0461: dispatchEvent(FCLSupport.Op.FILE_DELETED, en, fe);
0462: }
0463:
0464: /** Fire file attribute change event.
0465: * @param en listeners that should receive the event
0466: * @param fe the event to fire in this object
0467: */
0468: protected void fireFileAttributeChangedEvent(
0469: Enumeration<FileChangeListener> en, FileAttributeEvent fe) {
0470: dispatchEvent(FCLSupport.Op.ATTR_CHANGED, en, fe);
0471: }
0472:
0473: /** Fire file rename event.
0474: * @param en listeners that should receive the event
0475: * @param fe the event to fire in this object
0476: */
0477: protected void fireFileRenamedEvent(
0478: Enumeration<FileChangeListener> en, FileRenameEvent fe) {
0479: dispatchEvent(FCLSupport.Op.FILE_RENAMED, en, fe);
0480: }
0481:
0482: /** Puts the dispatch event into the filesystem.
0483: */
0484: private final void dispatchEvent(FCLSupport.Op op,
0485: Enumeration<FileChangeListener> en, FileEvent fe) {
0486: try {
0487: FileSystem fs = getFileSystem();
0488: fs.dispatchEvent(new ED(op, en, fe));
0489: } catch (FileStateInvalidException ex) {
0490: // no filesystem, no notification
0491: }
0492: }
0493:
0494: final void dispatchEvent(Enumeration<FileChangeListener> en,
0495: FileEvent fe) {
0496: try {
0497: getFileSystem().dispatchEvent(new ED(en, fe));
0498: } catch (FileStateInvalidException ex) {
0499: // no filesystem, no notification
0500: }
0501: }
0502:
0503: /** Get the MIME type of this file.
0504: * The MIME type identifies the type of the file's contents and should be used in the same way as in the <B>Java
0505: * Activation Framework</B> or in the {@link java.awt.datatransfer} package.
0506: * <P>
0507: * The default implementation calls {@link FileUtil#getMIMEType}.
0508: * (As a fallback return value, <code>content/unknown</code> is used.)
0509: * @return the MIME type textual representation, e.g. <code>"text/plain"</code>; never <code>null</code>
0510: */
0511: public String getMIMEType() {
0512: return FileUtil.getMIMETypeOrDefault(this );
0513: }
0514:
0515: /** Get the size of the file.
0516: * @return the size of the file in bytes or zero if the file does not contain data (does not
0517: * exist or is a folder).
0518: */
0519: public abstract long getSize();
0520:
0521: /** Get input stream.
0522: * @return an input stream to read the contents of this file
0523: * @exception FileNotFoundException if the file does not exists, is a folder
0524: * rather than a regular file or is invalid
0525: */
0526: public abstract InputStream getInputStream()
0527: throws FileNotFoundException;
0528:
0529: /** Get output stream.
0530: * @param lock the lock that belongs to this file (obtained by a call to
0531: * {@link #lock})
0532: * @return output stream to overwrite the contents of this file
0533: * @exception IOException if an error occures (the file is invalid, etc.)
0534: */
0535: public abstract OutputStream getOutputStream(FileLock lock)
0536: throws IOException;
0537:
0538: /** Get output stream.
0539: * @return output stream to overwrite the contents of this file
0540: * @throws IOException if an error occurs (the file is invalid, etc.)
0541: * @throws FileAlreadyLockedException if the file is already locked
0542: * @since 6.6
0543: */
0544: public final OutputStream getOutputStream()
0545: throws FileAlreadyLockedException, IOException {
0546: final FileLock lock = lock();
0547: final OutputStream os;
0548: try {
0549: os = getOutputStream(lock);
0550: return new FilterOutputStream(os) {
0551: public void close() throws IOException {
0552: try {
0553: super .close();
0554: lock.releaseLock();
0555: } catch (IOException iex) {
0556: if (lock.isValid()) {
0557: lock.releaseLock();
0558: }
0559: throw iex;
0560: }
0561: }
0562: };
0563: } catch (IOException iex) {
0564: if (lock.isValid()) {
0565: lock.releaseLock();
0566: }
0567: throw iex;
0568: }
0569: }
0570:
0571: /** Lock this file.
0572: * @return lock that can be used to perform various modifications on the file
0573: * @throws FileAlreadyLockedException if the file is already locked
0574: * @throws UserQuestionException in case when the lock cannot be obtained now,
0575: * but the underlaying implementation is able to do it after some
0576: * complex/dangerous/long-lasting operation and request confirmation
0577: * from the user
0578: *
0579: */
0580: public abstract FileLock lock() throws IOException;
0581:
0582: /**
0583: * Test if file is locked
0584: * @return true if file is locked
0585: * @since 7.3
0586: */
0587: public boolean isLocked() {
0588: FileLock fLock = null;
0589: try {
0590: fLock = lock();
0591: } catch (FileAlreadyLockedException fax) {
0592: return true;
0593: } catch (IOException ex) {
0594: return false;
0595: } finally {
0596: if (fLock != null) {
0597: fLock.releaseLock();
0598: }
0599: }
0600: return fLock == null;
0601: }
0602:
0603: /** Indicate whether this file is important from a user perspective.
0604: * This method allows a filesystem to distingush between important and
0605: * unimportant files when this distinction is possible.
0606: * <P>
0607: * <em>For example:</em> Java sources have important <code>.java</code> files and
0608: * unimportant <code>.class</code> files. If the filesystem provides
0609: * an "archive" feature it should archive only <code>.java</code> files.
0610: * @param b true if the file should be considered important
0611: * @deprecated No longer used. Instead use
0612: * <a href="@PROJECTS/QUERIES@/org/netbeans/api/queries/SharabilityQuery.html"><code>SharabilityQuery</code></a>.
0613: */
0614: @Deprecated
0615: public abstract void setImportant(boolean b);
0616:
0617: /** Get all children of this folder (files and subfolders). If the file does not have children
0618: * (does not exist or is not a folder) then an empty array should be returned. No particular order is assumed.
0619: *
0620: * @return array of direct children
0621: * @see #getChildren(boolean)
0622: * @see #getFolders
0623: * @see #getData
0624: */
0625: public abstract FileObject[] getChildren();
0626:
0627: /** Enumerate all children of this folder. If the children should be enumerated
0628: * recursively, first all direct children are listed; then children of direct subfolders; and so on.
0629: *
0630: * @param rec whether to enumerate recursively
0631: * @return enumeration of type <code>FileObject</code>
0632: */
0633: public Enumeration<? extends FileObject> getChildren(
0634: final boolean rec) {
0635: class WithChildren implements
0636: Enumerations.Processor<FileObject, FileObject> {
0637: public FileObject process(FileObject fo,
0638: Collection<FileObject> toAdd) {
0639: if (rec && fo.isFolder()) {
0640: toAdd.addAll(Arrays.asList(fo.getChildren()));
0641: }
0642:
0643: return fo;
0644: }
0645: }
0646:
0647: return Enumerations.queue(Enumerations.array(getChildren()),
0648: new WithChildren());
0649: }
0650:
0651: /** Enumerate the subfolders of this folder.
0652: * @param rec whether to recursively list subfolders
0653: * @return enumeration of type <code>FileObject</code> (satisfying {@link #isFolder})
0654: */
0655: public Enumeration<? extends FileObject> getFolders(boolean rec) {
0656: return Enumerations.filter(getChildren(rec), new OnlyFolders(
0657: true));
0658: }
0659:
0660: /** Enumerate all data files in this folder.
0661: * @param rec whether to recursively search subfolders
0662: * @return enumeration of type <code>FileObject</code> (satisfying {@link #isData})
0663: */
0664: public Enumeration<? extends FileObject> getData(boolean rec) {
0665: return Enumerations.filter(getChildren(rec), new OnlyFolders(
0666: false));
0667: }
0668:
0669: /** Retrieve file or folder contained in this folder by name.
0670: * <em>Note</em> that neither file nor folder is created on disk.
0671: * @param name basename of the file or folder (in this folder)
0672: * @param ext extension of the file; <CODE>null</CODE> or <code>""</code>
0673: * if the file should have no extension or if folder is requested
0674: * @return the object representing this file or <CODE>null</CODE> if the file
0675: * or folder does not exist
0676: * @exception IllegalArgumentException if <code>this</code> is not a folder
0677: */
0678: public abstract FileObject getFileObject(String name, String ext);
0679:
0680: /** Retrieve file or folder relative to a current folder, with a given relative path.
0681: * <em>Note</em> that neither file nor folder is created on disk. This method isn't final since revision 1.93.
0682: * @param relativePath is just basename of the file or (since 4.16) the relative path delimited by '/'
0683: * @return the object representing this file or <CODE>null</CODE> if the file
0684: * or folder does not exist
0685: * @exception IllegalArgumentException if <code>this</code> is not a folder
0686: */
0687: public FileObject getFileObject(String relativePath) {
0688: if (relativePath.startsWith("/")) {
0689: relativePath = relativePath.substring(1);
0690: }
0691:
0692: FileObject myObj = this ;
0693: StringTokenizer st = new StringTokenizer(relativePath, "/");
0694:
0695: while ((myObj != null) && st.hasMoreTokens()) {
0696: String nameExt = st.nextToken();
0697: myObj = myObj.getFileObject(nameExt, null);
0698: }
0699:
0700: return myObj;
0701: }
0702:
0703: /**
0704: * Create a new folder below this one with the specified name.
0705: * Fires {@link FileChangeListener#fileFolderCreated}.
0706: *
0707: * @param name the name of folder to create. Periods in name are allowed (but no slashes).
0708: * @return the new folder
0709: * @exception IOException if the folder cannot be created (e.g. already exists), or if <code>this</code> is not a folder
0710: * @see FileUtil#createFolder
0711: */
0712: public abstract FileObject createFolder(String name)
0713: throws IOException;
0714:
0715: /**
0716: * Create new data file in this folder with the specified name.
0717: * Fires {@link FileChangeListener#fileDataCreated}.
0718: *
0719: * @param name the name of data object to create (can contain a period, but no slashes)
0720: * @param ext the extension of the file (or <code>null</code> or <code>""</code>)
0721: * @return the new data file object
0722: * @exception IOException if the file cannot be created (e.g. already exists), or if <code>this</code> is not a folder
0723: * @see FileUtil#createData
0724: */
0725: public abstract FileObject createData(String name, String ext)
0726: throws IOException;
0727:
0728: /**
0729: * Create new data file in this folder with the specified name.
0730: * Fires {@link FileChangeListener#fileDataCreated}.
0731: *
0732: * @param name the name of data object to create (can contain a period, but no slashes)
0733: * @return the new data file object
0734: * @exception IOException if the file cannot be created (e.g. already exists), or if <code>this</code> is not a folder
0735: * @since 1.17
0736: * @see FileUtil#createData
0737: */
0738: public FileObject createData(String name) throws IOException {
0739: return createData(name, ""); // NOI18N
0740: }
0741:
0742: /** Test whether this file can be written to or not.
0743: * <P>
0744: * The value returned from this method should indicate the capabilities of the
0745: * file from the point of view of users of the FileObject's API, the actual
0746: * state of the file on a disk does not matter if the implementation of the
0747: * filesystem can change it when requested.
0748: * <P>
0749: * The result returned from this method should be tight together with
0750: * the expected behaviour of <code>getOutputStream</code>. If it is
0751: * likely that the method successfully returns a stream that can be
0752: * written to, let the <code>isReadOnly</code> return <code>false</code>.
0753: * <P>
0754: * Also other fileobject methods like <code>delete</code>
0755: * are suggested to be connected to result of this method. If not
0756: * read only, then it can be deleted, etc.
0757: * <p>
0758: * It is a good idea to call this method before attempting to perform any
0759: * operation on the FileObject that might throw an IOException simply
0760: * because it is read-only. If isReadOnly returns true, the operation may
0761: * be skipped, or the user notified that it cannot be done.
0762: * <em>However</em> it is often desirable for the user to be able to
0763: * continue the operation in case the filesystem supports making a file
0764: * writable. In this case calling code should:
0765: * <ol>
0766: * <li>Call {@link #lock} and catch any exception thrown.
0767: * <li>Then:
0768: * <ul>
0769: * <li>If no exception is thrown, proceed with the operation.
0770: * <li>If a <code>UserQuestionException</code> is thrown,
0771: * call {@link org.openide.util.UserQuestionException#confirmed} on it
0772: * (asynchronously - do not block any important threads). If <code>true</code>,
0773: * proceed with the operation. If <code>false</code>, exit.
0774: * If an <code>IOException</code> is thrown, notify it and exit.
0775: * <li>If another <code>IOException</code> is thrown, call {@link #isReadOnly}.
0776: * If <code>true</code>, ignore the exception (it is expected).
0777: * If <code>false</code>, notify it.
0778: * </ul>
0779: * In either case, exit.
0780: * </ol>
0781: * <p>
0782: *
0783: * @return <CODE>true</CODE> if file is read-only
0784: * @deprecated Please use the {@link #canWrite}.
0785: */
0786: @Deprecated
0787: public abstract boolean isReadOnly();
0788:
0789: /**
0790: * Tests if this file can be written to.
0791: * <P>
0792: * The default implementation simply uses <code> java.io.File.canWrite </code>
0793: * if there exists conversion to <code> java.io.File</code> (see {@link FileUtil#toFile}).
0794: * If conversion is not possible, then deprecated method {@link #isReadOnly} is used.
0795: * @return true if this file can be written, false if not.
0796: * @since 3.31
0797: */
0798: public boolean canWrite() {
0799: File f = FileUtil.toFile(this );
0800:
0801: if (f != null) {
0802: return f.canWrite();
0803: }
0804:
0805: return !isReadOnly();
0806: }
0807:
0808: /**
0809: * Tests if this file can be read.
0810: * <P>
0811: * The default implementation simply uses <code> java.io.File.canRead </code>
0812: * if there exists conversion to <code> java.io.File</code> (see {@link FileUtil#toFile}).
0813: * If conversion is not possible, then <code>true </code> is returned.
0814: * @return true if this file can be read, false if not.
0815: * @since 3.31
0816: */
0817: public boolean canRead() {
0818: File f = FileUtil.toFile(this );
0819:
0820: if (f != null) {
0821: return f.canRead();
0822: }
0823:
0824: return true;
0825: }
0826:
0827: /** Should check for external modifications. For folders it should reread
0828: * the content of disk, for data file it should check for the last
0829: * time the file has been modified.
0830: *
0831: * @param expected should the file events be marked as expected change or not?
0832: * @see FileEvent#isExpected
0833: */
0834: public void refresh(boolean expected) {
0835: }
0836:
0837: /** Should check for external modifications. For folders it should reread
0838: * the content of disk, for data file it should check for the last
0839: * time the file has been modified.
0840: * <P>
0841: * The file events are marked as unexpected.
0842: */
0843: public void refresh() {
0844: refresh(false);
0845: }
0846:
0847: /** Get URL that can be used to access this file.
0848: * If the file object does not correspond to a disk file or JAR entry,
0849: * the URL will only be usable within NetBeans as it uses a special protocol handler.
0850: * Otherwise an attempt is made to produce an external URL.
0851: * @return URL of this file object
0852: * @exception FileStateInvalidException if the file is not valid
0853: * @see URLMapper#findURL
0854: * @see URLMapper#INTERNAL
0855: */
0856: public final URL getURL() throws FileStateInvalidException {
0857: // XXX why does this still throw FSIE? need not
0858: return URLMapper.findURL(this , URLMapper.INTERNAL);
0859: }
0860:
0861: /**
0862: * Tests if file really exists or is missing. Some operation on it may be restricted.
0863: * @return true indicates that the file is missing.
0864: * @since 1.9
0865: */
0866: public boolean isVirtual() {
0867: return false;
0868: }
0869:
0870: /** Listeners registered from MultiFileObject are considered as priority
0871: * listeners.
0872: */
0873: static boolean isPriorityListener(FileChangeListener fcl) {
0874: if (fcl instanceof PriorityFileChangeListener) {
0875: return true;
0876: } else {
0877: return false;
0878: }
0879: }
0880:
0881: interface PriorityFileChangeListener extends FileChangeListener {
0882: }
0883:
0884: private class ED extends FileSystem.EventDispatcher {
0885: private FCLSupport.Op op;
0886: private Enumeration<FileChangeListener> en;
0887: final private List<FileChangeListener> fsList;
0888: final private List<FileChangeListener> repList;
0889:
0890: private FileEvent fe;
0891:
0892: public ED(FCLSupport.Op op, Enumeration<FileChangeListener> en,
0893: FileEvent fe) {
0894: this .op = op;
0895: this .en = en;
0896: this .fe = fe;
0897: FileSystem fs = null;
0898: try {
0899: fs = this .fe.getFile().getFileSystem();
0900: } catch (FileStateInvalidException ex) {
0901: ExternalUtil.exception(ex);
0902: }
0903: ListenerList<FileChangeListener> fsll = (fs != null) ? fs
0904: .getFCLSupport().listeners : null;
0905: ListenerList<FileChangeListener> repll = (fs != null && fs
0906: .getRepository() != null) ? fs.getRepository()
0907: .getFCLSupport().listeners : null;
0908: fsList = (fsll != null) ? new ArrayList<FileChangeListener>(
0909: fsll.getAllListeners())
0910: : new ArrayList<FileChangeListener>();
0911: repList = (repll != null) ? new ArrayList<FileChangeListener>(
0912: repll.getAllListeners())
0913: : new ArrayList<FileChangeListener>();
0914:
0915: }
0916:
0917: public ED(Enumeration<FileChangeListener> en, FileEvent fe) {
0918: this (null, en, fe);
0919: }
0920:
0921: /** @param onlyPriority if true then invokes only priority listeners
0922: * else all listeners are invoked.
0923: */
0924: protected void dispatch(boolean onlyPriority) {
0925: if (this .op == null) {
0926: this .op = fe.getFile().isFolder() ? FCLSupport.Op.FOLDER_CREATED
0927: : FCLSupport.Op.DATA_CREATED;
0928: }
0929:
0930: LinkedList<FileChangeListener> newEnum = new LinkedList<FileChangeListener>(); // later lazy
0931:
0932: while (en.hasMoreElements()) {
0933: FileChangeListener fcl = en.nextElement();
0934:
0935: if (onlyPriority && !isPriorityListener(fcl)) {
0936: newEnum.add(fcl);
0937:
0938: continue;
0939: }
0940: FCLSupport.dispatchEvent(fcl, fe, op);
0941: }
0942:
0943: if (onlyPriority) {
0944: this .en = Collections.enumeration(newEnum);
0945: }
0946:
0947: /** FileEvents are forked in may cases. But FileEvents fired from
0948: * FileSystem and from Repository mustn`t be forked.
0949: */
0950: FileObject fo = fe.getFile();
0951: boolean transmit = false;
0952: if (fo != null) {
0953: switch (op) {
0954: case FILE_CHANGED:
0955: transmit = fo.equals(fe.getSource());
0956: break;
0957: default:
0958: transmit = !fo.equals(fe.getSource());
0959: if (!transmit && fe instanceof Enumeration
0960: && !((Enumeration) fe).hasMoreElements()) {
0961: transmit = true;
0962: }
0963: }
0964:
0965: }
0966:
0967: if (!en.hasMoreElements() && transmit && !onlyPriority) {
0968: FileSystem fs = null;
0969: Repository rep = null;
0970:
0971: try {
0972: fs = fe.getFile().getFileSystem();
0973: rep = fs.getRepository();
0974: } catch (FileStateInvalidException fsix) {
0975: return;
0976: }
0977: if (fs != null && fsList != null) {
0978: for (FileChangeListener fcl : fsList) {
0979: fs.getFCLSupport().dispatchEvent(fcl, fe, op);
0980: }
0981: }
0982:
0983: if (rep != null && repList != null) {
0984: for (FileChangeListener fcl : repList) {
0985: rep.getFCLSupport().dispatchEvent(fcl, fe, op);
0986: }
0987: }
0988: }
0989: }
0990:
0991: protected void setAtomicActionLink(
0992: EventControl.AtomicActionLink propID) {
0993: fe.setAtomicActionLink(propID);
0994: }
0995: }
0996:
0997: /** Filters folders or data files.
0998: */
0999: private static final class OnlyFolders implements
1000: Enumerations.Processor<FileObject, FileObject> {
1001: private boolean folders;
1002:
1003: public OnlyFolders(boolean folders) {
1004: this .folders = folders;
1005: }
1006:
1007: public FileObject process(FileObject obj,
1008: Collection<FileObject> coll) {
1009: FileObject fo = obj;
1010:
1011: if (folders) {
1012: return fo.isFolder() ? fo : null;
1013: } else {
1014: return fo.isData() ? fo : null;
1015: }
1016: }
1017: }
1018: // end of OnlyFolders
1019: }
|