0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.vfs.provider;
0018:
0019: import org.apache.commons.vfs.Capability;
0020: import org.apache.commons.vfs.FileContent;
0021: import org.apache.commons.vfs.FileContentInfoFactory;
0022: import org.apache.commons.vfs.FileName;
0023: import org.apache.commons.vfs.FileObject;
0024: import org.apache.commons.vfs.FileSelector;
0025: import org.apache.commons.vfs.FileSystem;
0026: import org.apache.commons.vfs.FileSystemException;
0027: import org.apache.commons.vfs.FileType;
0028: import org.apache.commons.vfs.FileUtil;
0029: import org.apache.commons.vfs.NameScope;
0030: import org.apache.commons.vfs.RandomAccessContent;
0031: import org.apache.commons.vfs.Selectors;
0032: import org.apache.commons.vfs.operations.DefaultFileOperations;
0033: import org.apache.commons.vfs.operations.FileOperations;
0034: import org.apache.commons.vfs.util.FileObjectUtils;
0035: import org.apache.commons.vfs.util.RandomAccessMode;
0036:
0037: import java.io.IOException;
0038: import java.io.InputStream;
0039: import java.io.OutputStream;
0040: import java.net.MalformedURLException;
0041: import java.net.URL;
0042: import java.security.AccessController;
0043: import java.security.PrivilegedActionException;
0044: import java.security.PrivilegedExceptionAction;
0045: import java.security.cert.Certificate;
0046: import java.util.ArrayList;
0047: import java.util.Arrays;
0048: import java.util.Collections;
0049: import java.util.List;
0050: import java.util.Map;
0051:
0052: /**
0053: * A partial file object implementation.
0054: *
0055: * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
0056: * @author Gary D. Gregory
0057: * @version $Revision: 537944 $ $Date: 2007-05-14 11:41:51 -0700 (Mon, 14 May 2007) $
0058: * @todo Chop this class up - move all the protected methods to several
0059: * interfaces, so that structure and content can be separately overridden.
0060: * @todo Check caps in methods like getChildren(), etc, and give better error messages
0061: * (eg 'this file type does not support listing children', vs 'this is not a folder')
0062: */
0063: public abstract class AbstractFileObject implements FileObject {
0064: // private static final FileObject[] EMPTY_FILE_ARRAY = {};
0065: private static final FileName[] EMPTY_FILE_ARRAY = {};
0066:
0067: private final AbstractFileName name;
0068: private final AbstractFileSystem fs;
0069:
0070: private FileContent content;
0071:
0072: // Cached info
0073: private boolean attached;
0074: private FileType type;
0075: private FileObject parent;
0076:
0077: // Changed to hold only the name of the children and let the object
0078: // go into the global files cache
0079: // private FileObject[] children;
0080: private FileName[] children;
0081: private List objects;
0082:
0083: /**
0084: * FileServices instance.
0085: */
0086: private FileOperations operations;
0087:
0088: protected AbstractFileObject(final FileName name,
0089: final AbstractFileSystem fs) {
0090: this .name = (AbstractFileName) name;
0091: this .fs = fs;
0092: fs.fileObjectHanded(this );
0093: }
0094:
0095: /**
0096: * Attaches this file object to its file resource. This method is called
0097: * before any of the doBlah() or onBlah() methods. Sub-classes can use
0098: * this method to perform lazy initialisation.
0099: * <p/>
0100: * This implementation does nothing.
0101: */
0102: protected void doAttach() throws Exception {
0103: }
0104:
0105: /**
0106: * Detaches this file object from its file resource.
0107: * <p/>
0108: * <p>Called when this file is closed. Note that the file object may be
0109: * reused later, so should be able to be reattached.
0110: * <p/>
0111: * This implementation does nothing.
0112: */
0113: protected void doDetach() throws Exception {
0114: }
0115:
0116: /**
0117: * Determines the type of this file. Must not return null. The return
0118: * value of this method is cached, so the implementation can be expensive.
0119: */
0120: protected abstract FileType doGetType() throws Exception;
0121:
0122: /**
0123: * Determines if this file is hidden. Is only called if {@link #doGetType}
0124: * does not return {@link FileType#IMAGINARY}.
0125: * <p/>
0126: * This implementation always returns false.
0127: */
0128: protected boolean doIsHidden() throws Exception {
0129: return false;
0130: }
0131:
0132: /**
0133: * Determines if this file can be read. Is only called if {@link #doGetType}
0134: * does not return {@link FileType#IMAGINARY}.
0135: * <p/>
0136: * This implementation always returns true.
0137: */
0138: protected boolean doIsReadable() throws Exception {
0139: return true;
0140: }
0141:
0142: /**
0143: * Determines if this file can be written to. Is only called if
0144: * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
0145: * <p/>
0146: * This implementation always returns true.
0147: */
0148: protected boolean doIsWriteable() throws Exception {
0149: return true;
0150: }
0151:
0152: /**
0153: * Lists the children of this file. Is only called if {@link #doGetType}
0154: * returns {@link FileType#FOLDER}. The return value of this method
0155: * is cached, so the implementation can be expensive.
0156: */
0157: protected abstract String[] doListChildren() throws Exception;
0158:
0159: /**
0160: * Lists the children of this file. Is only called if {@link #doGetType}
0161: * returns {@link FileType#FOLDER}. The return value of this method
0162: * is cached, so the implementation can be expensive.<br>
0163: * Other than <code>doListChildren</code> you could return FileObject's to e.g. reinitialize the type of the file.<br>
0164: * (Introduced for Webdav: "permission denied on resource" during getType())
0165: */
0166: protected FileObject[] doListChildrenResolved() throws Exception {
0167: return null;
0168: }
0169:
0170: /**
0171: * Deletes the file. Is only called when:
0172: * <ul>
0173: * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.
0174: * <li>{@link #doIsWriteable} returns true.
0175: * <li>This file has no children, if a folder.
0176: * </ul>
0177: * <p/>
0178: * This implementation throws an exception.
0179: */
0180: protected void doDelete() throws Exception {
0181: throw new FileSystemException(
0182: "vfs.provider/delete-not-supported.error");
0183: }
0184:
0185: /**
0186: * Renames the file. Is only called when:
0187: * <ul>
0188: * <li>{@link #doIsWriteable} returns true.
0189: * </ul>
0190: * <p/>
0191: * This implementation throws an exception.
0192: */
0193: protected void doRename(FileObject newfile) throws Exception {
0194: throw new FileSystemException(
0195: "vfs.provider/rename-not-supported.error");
0196: }
0197:
0198: /**
0199: * Creates this file as a folder. Is only called when:
0200: * <ul>
0201: * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.
0202: * <li>The parent folder exists and is writeable, or this file is the
0203: * root of the file system.
0204: * </ul>
0205: * <p/>
0206: * This implementation throws an exception.
0207: */
0208: protected void doCreateFolder() throws Exception {
0209: throw new FileSystemException(
0210: "vfs.provider/create-folder-not-supported.error");
0211: }
0212:
0213: /**
0214: * Called when the children of this file change. Allows subclasses to
0215: * refresh any cached information about the children of this file.
0216: * <p/>
0217: * This implementation does nothing.
0218: */
0219: protected void onChildrenChanged(FileName child, FileType newType)
0220: throws Exception {
0221: }
0222:
0223: /**
0224: * Called when the type or content of this file changes.
0225: * <p/>
0226: * This implementation does nothing.
0227: */
0228: protected void onChange() throws Exception {
0229: }
0230:
0231: /**
0232: * Returns the last modified time of this file. Is only called if
0233: * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
0234: * <p/>
0235: * This implementation throws an exception.
0236: */
0237: protected long doGetLastModifiedTime() throws Exception {
0238: throw new FileSystemException(
0239: "vfs.provider/get-last-modified-not-supported.error");
0240: }
0241:
0242: /**
0243: * Sets the last modified time of this file. Is only called if
0244: * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
0245: * <p/>
0246: * This implementation throws an exception.
0247: *
0248: * @return false if it was not possible to change the time
0249: */
0250: protected boolean doSetLastModTime(final long modtime)
0251: throws Exception {
0252: doSetLastModifiedTime(modtime);
0253: return true;
0254: }
0255:
0256: /**
0257: * Sets the last modified time of this file. Is only called if
0258: * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
0259: * <p/>
0260: * This implementation throws an exception.
0261: *
0262: * @deprecated use {@link #doSetLastModTime}
0263: */
0264: protected void doSetLastModifiedTime(final long modtime)
0265: throws Exception {
0266: throw new FileSystemException(
0267: "vfs.provider/set-last-modified-not-supported.error");
0268: }
0269:
0270: /**
0271: * Returns the attributes of this file. Is only called if {@link #doGetType}
0272: * does not return {@link FileType#IMAGINARY}.
0273: * <p/>
0274: * This implementation always returns an empty map.
0275: */
0276: protected Map doGetAttributes() throws Exception {
0277: return Collections.EMPTY_MAP;
0278: }
0279:
0280: /**
0281: * Sets an attribute of this file. Is only called if {@link #doGetType}
0282: * does not return {@link FileType#IMAGINARY}.
0283: * <p/>
0284: * This implementation throws an exception.
0285: */
0286: protected void doSetAttribute(final String atttrName,
0287: final Object value) throws Exception {
0288: throw new FileSystemException(
0289: "vfs.provider/set-attribute-not-supported.error");
0290: }
0291:
0292: /**
0293: * Removes an attribute of this file. Is only called if {@link #doGetType}
0294: * does not return {@link FileType#IMAGINARY}.
0295: * <p/>
0296: * This implementation throws an exception.
0297: * @returns true if removing the attribute succeed. In this case we remove the attribute from
0298: * our cache
0299: */
0300: protected void doRemoveAttribute(final String atttrName)
0301: throws Exception {
0302: throw new FileSystemException(
0303: "vfs.provider/remove-attribute-not-supported.error");
0304: }
0305:
0306: /**
0307: * Returns the certificates used to sign this file. Is only called if
0308: * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
0309: * <p/>
0310: * This implementation always returns null.
0311: */
0312: protected Certificate[] doGetCertificates() throws Exception {
0313: return null;
0314: }
0315:
0316: /**
0317: * Returns the size of the file content (in bytes). Is only called if
0318: * {@link #doGetType} returns {@link FileType#FILE}.
0319: */
0320: protected abstract long doGetContentSize() throws Exception;
0321:
0322: /**
0323: * Creates an input stream to read the file content from. Is only called
0324: * if {@link #doGetType} returns {@link FileType#FILE}.
0325: * <p/>
0326: * <p>It is guaranteed that there are no open output streams for this file
0327: * when this method is called.
0328: * <p/>
0329: * <p>The returned stream does not have to be buffered.
0330: */
0331: protected abstract InputStream doGetInputStream() throws Exception;
0332:
0333: /**
0334: * Creates access to the file for random i/o. Is only called
0335: * if {@link #doGetType} returns {@link FileType#FILE}.
0336: * <p/>
0337: * <p>It is guaranteed that there are no open output streams for this file
0338: * when this method is called.
0339: * <p/>
0340: */
0341: protected RandomAccessContent doGetRandomAccessContent(
0342: final RandomAccessMode mode) throws Exception {
0343: throw new FileSystemException(
0344: "vfs.provider/random-access-not-supported.error");
0345: }
0346:
0347: /**
0348: * Creates an output stream to write the file content to. Is only
0349: * called if:
0350: * <ul>
0351: * <li>{@link #doIsWriteable} returns true.
0352: * <li>{@link #doGetType} returns {@link FileType#FILE}, or
0353: * {@link #doGetType} returns {@link FileType#IMAGINARY}, and the file's
0354: * parent exists and is a folder.
0355: * </ul>
0356: * <p/>
0357: * <p>It is guaranteed that there are no open stream (input or output) for
0358: * this file when this method is called.
0359: * <p/>
0360: * <p>The returned stream does not have to be buffered.
0361: * <p/>
0362: * This implementation throws an exception.
0363: */
0364: protected OutputStream doGetOutputStream(boolean bAppend)
0365: throws Exception {
0366: throw new FileSystemException(
0367: "vfs.provider/write-not-supported.error");
0368: }
0369:
0370: /**
0371: * Returns the URI of the file.
0372: */
0373: public String toString() {
0374: return name.getURI();
0375: }
0376:
0377: /**
0378: * Returns the name of the file.
0379: */
0380: public FileName getName() {
0381: return name;
0382: }
0383:
0384: /**
0385: * Returns the file system this file belongs to.
0386: */
0387: public FileSystem getFileSystem() {
0388: return fs;
0389: }
0390:
0391: /**
0392: * Returns a URL representation of the file.
0393: */
0394: public URL getURL() throws FileSystemException {
0395: final StringBuffer buf = new StringBuffer();
0396: try {
0397: return (URL) AccessController
0398: .doPrivileged(new PrivilegedExceptionAction() {
0399: public Object run()
0400: throws MalformedURLException {
0401: return new URL(UriParser.extractScheme(name
0402: .getURI(), buf), "", -1, buf
0403: .toString(),
0404: new DefaultURLStreamHandler(fs
0405: .getContext(), fs
0406: .getFileSystemOptions()));
0407: }
0408: });
0409: } catch (final PrivilegedActionException e) {
0410: throw new FileSystemException("vfs.provider/get-url.error",
0411: name, e.getException());
0412: }
0413: }
0414:
0415: /**
0416: * Determines if the file exists.
0417: */
0418: public boolean exists() throws FileSystemException {
0419: return (getType() != FileType.IMAGINARY);
0420: }
0421:
0422: /**
0423: * Returns the file's type.
0424: */
0425: public FileType getType() throws FileSystemException {
0426: synchronized (fs) {
0427: attach();
0428: return type;
0429: }
0430: }
0431:
0432: /**
0433: * Determines if this file can be read.
0434: */
0435: public boolean isHidden() throws FileSystemException {
0436: try {
0437: if (exists()) {
0438: return doIsHidden();
0439: } else {
0440: return false;
0441: }
0442: } catch (final Exception exc) {
0443: throw new FileSystemException(
0444: "vfs.provider/check-is-hidden.error", name, exc);
0445: }
0446: }
0447:
0448: /**
0449: * Determines if this file can be read.
0450: */
0451: public boolean isReadable() throws FileSystemException {
0452: try {
0453: if (exists()) {
0454: return doIsReadable();
0455: } else {
0456: return false;
0457: }
0458: } catch (final Exception exc) {
0459: throw new FileSystemException(
0460: "vfs.provider/check-is-readable.error", name, exc);
0461: }
0462: }
0463:
0464: /**
0465: * Determines if this file can be written to.
0466: */
0467: public boolean isWriteable() throws FileSystemException {
0468: try {
0469: if (exists()) {
0470: return doIsWriteable();
0471: } else {
0472: final FileObject parent = getParent();
0473: if (parent != null) {
0474: return parent.isWriteable();
0475: }
0476: return true;
0477: }
0478: } catch (final Exception exc) {
0479: throw new FileSystemException(
0480: "vfs.provider/check-is-writeable.error", name, exc);
0481: }
0482: }
0483:
0484: /**
0485: * Returns the parent of the file.
0486: */
0487: public FileObject getParent() throws FileSystemException {
0488: if (this == fs.getRoot()) {
0489: if (fs.getParentLayer() != null) {
0490: // Return the parent of the parent layer
0491: return fs.getParentLayer().getParent();
0492: } else {
0493: // Root file has no parent
0494: return null;
0495: }
0496: }
0497:
0498: synchronized (fs) {
0499: // Locate the parent of this file
0500: if (parent == null) {
0501: parent = (FileObject) fs.resolveFile(name.getParent());
0502: }
0503: }
0504: return parent;
0505: }
0506:
0507: /**
0508: * Returns the children of the file.
0509: */
0510: public FileObject[] getChildren() throws FileSystemException {
0511: synchronized (fs) {
0512: if (!getType().hasChildren()) {
0513: throw new FileSystemException(
0514: "vfs.provider/list-children-not-folder.error",
0515: name);
0516: }
0517:
0518: // Use cached info, if present
0519: if (children != null) {
0520: return resolveFiles(children);
0521: }
0522:
0523: // allow the filesystem to return resolved children. e.g. prefill type for webdav
0524: FileObject[] childrenObjects;
0525: try {
0526: childrenObjects = doListChildrenResolved();
0527: children = extractNames(childrenObjects);
0528: } catch (Exception exc) {
0529: throw new FileSystemException(
0530: "vfs.provider/list-children.error",
0531: new Object[] { name }, exc);
0532: }
0533:
0534: if (childrenObjects != null) {
0535: return childrenObjects;
0536: }
0537:
0538: // List the children
0539: final String[] files;
0540: try {
0541: files = doListChildren();
0542: } catch (Exception exc) {
0543: throw new FileSystemException(
0544: "vfs.provider/list-children.error",
0545: new Object[] { name }, exc);
0546: }
0547:
0548: if (files == null) {
0549: return null;
0550: } else if (files.length == 0) {
0551: // No children
0552: children = EMPTY_FILE_ARRAY;
0553: } else {
0554: // Create file objects for the children
0555: // children = new FileObject[files.length];
0556: children = new FileName[files.length];
0557: for (int i = 0; i < files.length; i++) {
0558: final String file = files[i];
0559: // children[i] = fs.resolveFile(name.resolveName(file, NameScope.CHILD));
0560: // children[i] = name.resolveName(file, NameScope.CHILD);
0561: children[i] = getFileSystem()
0562: .getFileSystemManager().resolveName(name,
0563: file, NameScope.CHILD);
0564: }
0565: }
0566:
0567: return resolveFiles(children);
0568: }
0569: }
0570:
0571: private FileName[] extractNames(FileObject[] objects) {
0572: if (objects == null) {
0573: return null;
0574: }
0575:
0576: FileName[] names = new FileName[objects.length];
0577: for (int iterObjects = 0; iterObjects < objects.length; iterObjects++) {
0578: names[iterObjects] = objects[iterObjects].getName();
0579: }
0580:
0581: return names;
0582: }
0583:
0584: private FileObject[] resolveFiles(FileName[] children)
0585: throws FileSystemException {
0586: if (children == null) {
0587: return null;
0588: }
0589:
0590: FileObject[] objects = new FileObject[children.length];
0591: for (int iterChildren = 0; iterChildren < children.length; iterChildren++) {
0592: objects[iterChildren] = resolveFile(children[iterChildren]);
0593: }
0594:
0595: return objects;
0596: }
0597:
0598: private FileObject resolveFile(FileName child)
0599: throws FileSystemException {
0600: return fs.resolveFile(child);
0601: }
0602:
0603: /**
0604: * Returns a child of this file.
0605: */
0606: public FileObject getChild(final String name)
0607: throws FileSystemException {
0608: // TODO - use a hashtable when there are a large number of children
0609: FileObject[] children = getChildren();
0610: for (int i = 0; i < children.length; i++) {
0611: // final FileObject child = children[i];
0612: final FileName child = children[i].getName();
0613: // TODO - use a comparator to compare names
0614: // if (child.getName().getBaseName().equals(name))
0615: if (child.getBaseName().equals(name)) {
0616: return resolveFile(child);
0617: }
0618: }
0619: return null;
0620: }
0621:
0622: /**
0623: * Returns a child by name.
0624: */
0625: public FileObject resolveFile(final String name,
0626: final NameScope scope) throws FileSystemException {
0627: // return fs.resolveFile(this.name.resolveName(name, scope));
0628: return fs.resolveFile(getFileSystem().getFileSystemManager()
0629: .resolveName(this .name, name, scope));
0630: }
0631:
0632: /**
0633: * Finds a file, relative to this file.
0634: *
0635: * @param path The path of the file to locate. Can either be a relative
0636: * path, which is resolved relative to this file, or an
0637: * absolute path, which is resolved relative to the file system
0638: * that contains this file.
0639: */
0640: public FileObject resolveFile(final String path)
0641: throws FileSystemException {
0642: final FileName otherName = getFileSystem()
0643: .getFileSystemManager().resolveName(name, path);
0644: return fs.resolveFile(otherName);
0645: }
0646:
0647: /**
0648: * Deletes this file, once all its children have been deleted
0649: *
0650: * @return true if this file has been deleted
0651: */
0652: private boolean deleteSelf() throws FileSystemException {
0653: synchronized (fs) {
0654: /* Its possible to delete a read-only file if you have write-execute access to the directory
0655: if (!isWriteable())
0656: {
0657: throw new FileSystemException("vfs.provider/delete-read-only.error", name);
0658: }
0659: */
0660:
0661: if (getType() == FileType.IMAGINARY) {
0662: // File does not exist
0663: return false;
0664: }
0665:
0666: try {
0667: // Delete the file
0668: doDelete();
0669:
0670: // Update cached info
0671: handleDelete();
0672: } catch (final RuntimeException re) {
0673: throw re;
0674: } catch (final Exception exc) {
0675: throw new FileSystemException(
0676: "vfs.provider/delete.error",
0677: new Object[] { name }, exc);
0678: }
0679:
0680: return true;
0681: }
0682: }
0683:
0684: /**
0685: * Deletes this file.
0686: *
0687: * @return true if this object has been deleted
0688: * @todo This will not fail if this is a non-empty folder.
0689: */
0690: public boolean delete() throws FileSystemException {
0691: return delete(Selectors.SELECT_SELF) > 0;
0692: }
0693:
0694: /**
0695: * Deletes this file, and all children.
0696: *
0697: * @return the number of deleted files
0698: */
0699: public int delete(final FileSelector selector)
0700: throws FileSystemException {
0701: int nuofDeleted = 0;
0702:
0703: if (getType() == FileType.IMAGINARY) {
0704: // File does not exist
0705: return nuofDeleted;
0706: }
0707:
0708: // Locate all the files to delete
0709: ArrayList files = new ArrayList();
0710: findFiles(selector, true, files);
0711:
0712: // Delete 'em
0713: final int count = files.size();
0714: for (int i = 0; i < count; i++) {
0715: final AbstractFileObject file = FileObjectUtils
0716: .getAbstractFileObject((FileObject) files.get(i));
0717: // file.attach();
0718:
0719: // If the file is a folder, make sure all its children have been deleted
0720: if (file.getType().hasChildren()
0721: && file.getChildren().length != 0) {
0722: // Skip - as the selector forced us not to delete all files
0723: continue;
0724: }
0725:
0726: // Delete the file
0727: boolean deleted = file.deleteSelf();
0728: if (deleted) {
0729: nuofDeleted++;
0730: }
0731: }
0732:
0733: return nuofDeleted;
0734: }
0735:
0736: /**
0737: * Creates this file, if it does not exist.
0738: */
0739: public void createFile() throws FileSystemException {
0740: synchronized (fs) {
0741: try {
0742: if (exists() && !FileType.FILE.equals(getType())) {
0743: throw new FileSystemException(
0744: "vfs.provider/create-file.error", name);
0745: }
0746:
0747: if (!exists()) {
0748: getOutputStream().close();
0749: endOutput();
0750: }
0751: } catch (final RuntimeException re) {
0752: throw re;
0753: } catch (final Exception e) {
0754: throw new FileSystemException(
0755: "vfs.provider/create-file.error", name, e);
0756: }
0757: }
0758: }
0759:
0760: /**
0761: * Creates this folder, if it does not exist. Also creates any ancestor
0762: * files which do not exist.
0763: */
0764: public void createFolder() throws FileSystemException {
0765: synchronized (fs) {
0766: if (getType().hasChildren()) {
0767: // Already exists as correct type
0768: return;
0769: }
0770: if (getType() != FileType.IMAGINARY) {
0771: throw new FileSystemException(
0772: "vfs.provider/create-folder-mismatched-type.error",
0773: name);
0774: }
0775: if (!isWriteable()) {
0776: throw new FileSystemException(
0777: "vfs.provider/create-folder-read-only.error",
0778: name);
0779: }
0780:
0781: // Traverse up the heirarchy and make sure everything is a folder
0782: final FileObject parent = getParent();
0783: if (parent != null) {
0784: parent.createFolder();
0785: }
0786:
0787: try {
0788: // Create the folder
0789: doCreateFolder();
0790:
0791: // Update cached info
0792: handleCreate(FileType.FOLDER);
0793: } catch (final RuntimeException re) {
0794: throw re;
0795: } catch (final Exception exc) {
0796: throw new FileSystemException(
0797: "vfs.provider/create-folder.error", name, exc);
0798: }
0799: }
0800: }
0801:
0802: /**
0803: * Copies another file to this file.
0804: */
0805: public void copyFrom(final FileObject file,
0806: final FileSelector selector) throws FileSystemException {
0807: if (!file.exists()) {
0808: throw new FileSystemException(
0809: "vfs.provider/copy-missing-file.error", file);
0810: }
0811: if (!isWriteable()) {
0812: throw new FileSystemException(
0813: "vfs.provider/copy-read-only.error", new Object[] {
0814: file.getType(), file.getName(), this },
0815: null);
0816: }
0817:
0818: // Locate the files to copy across
0819: final ArrayList files = new ArrayList();
0820: file.findFiles(selector, false, files);
0821:
0822: // Copy everything across
0823: final int count = files.size();
0824: for (int i = 0; i < count; i++) {
0825: final FileObject srcFile = (FileObject) files.get(i);
0826:
0827: // Determine the destination file
0828: final String relPath = file.getName().getRelativeName(
0829: srcFile.getName());
0830: final FileObject destFile = resolveFile(relPath,
0831: NameScope.DESCENDENT_OR_SELF);
0832:
0833: // Clean up the destination file, if necessary
0834: if (destFile.exists()
0835: && destFile.getType() != srcFile.getType()) {
0836: // The destination file exists, and is not of the same type,
0837: // so delete it
0838: // TODO - add a pluggable policy for deleting and overwriting existing files
0839: destFile.delete(Selectors.SELECT_ALL);
0840: }
0841:
0842: // Copy across
0843: try {
0844: if (srcFile.getType().hasContent()) {
0845: FileUtil.copyContent(srcFile, destFile);
0846: } else if (srcFile.getType().hasChildren()) {
0847: destFile.createFolder();
0848: }
0849: } catch (final IOException e) {
0850: throw new FileSystemException(
0851: "vfs.provider/copy-file.error", new Object[] {
0852: srcFile, destFile }, e);
0853: }
0854: }
0855: }
0856:
0857: /**
0858: * Moves (rename) the file to another one
0859: */
0860: public void moveTo(FileObject destFile) throws FileSystemException {
0861: if (canRenameTo(destFile)) {
0862: if (!getParent().isWriteable()) {
0863: throw new FileSystemException(
0864: "vfs.provider/rename-parent-read-only.error",
0865: new FileName[] { getName(),
0866: getParent().getName() });
0867: }
0868: } else {
0869: if (!isWriteable()) {
0870: throw new FileSystemException(
0871: "vfs.provider/rename-read-only.error",
0872: getName());
0873: }
0874: }
0875:
0876: if (destFile.exists() && !isSameFile(destFile)) {
0877: destFile.delete(Selectors.SELECT_ALL);
0878: // throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName());
0879: }
0880:
0881: if (canRenameTo(destFile)) {
0882: // issue rename on same filesystem
0883: try {
0884: attach();
0885: doRename(destFile);
0886:
0887: (FileObjectUtils.getAbstractFileObject(destFile))
0888: .handleCreate(getType());
0889:
0890: destFile.close(); // now the destFile is no longer imaginary. force reattach.
0891:
0892: handleDelete(); // fire delete-events. This file-object (src) is like deleted.
0893: } catch (final RuntimeException re) {
0894: throw re;
0895: } catch (final Exception exc) {
0896: throw new FileSystemException(
0897: "vfs.provider/rename.error", new Object[] {
0898: getName(), destFile.getName() }, exc);
0899: }
0900: } else {
0901: // different fs - do the copy/delete stuff
0902:
0903: destFile.copyFrom(this , Selectors.SELECT_SELF);
0904:
0905: if (((destFile.getType().hasContent() && destFile
0906: .getFileSystem().hasCapability(
0907: Capability.SET_LAST_MODIFIED_FILE)) || (destFile
0908: .getType().hasChildren() && destFile
0909: .getFileSystem().hasCapability(
0910: Capability.SET_LAST_MODIFIED_FOLDER)))
0911: && getFileSystem().hasCapability(
0912: Capability.GET_LAST_MODIFIED)) {
0913: destFile.getContent().setLastModifiedTime(
0914: this .getContent().getLastModifiedTime());
0915: }
0916:
0917: deleteSelf();
0918: }
0919:
0920: }
0921:
0922: /**
0923: * Checks if this fileObject is the same file as <code>destFile</code> just with a different
0924: * name.<br />
0925: * E.g. for case insensitive filesystems like windows.
0926: */
0927: protected boolean isSameFile(FileObject destFile)
0928: throws FileSystemException {
0929: attach();
0930: return doIsSameFile(destFile);
0931: }
0932:
0933: /**
0934: * Checks if this fileObject is the same file as <code>destFile</code> just with a different
0935: * name.<br />
0936: * E.g. for case insensitive filesystems like windows.
0937: */
0938: protected boolean doIsSameFile(FileObject destFile)
0939: throws FileSystemException {
0940: return false;
0941: }
0942:
0943: /**
0944: * Queries the object if a simple rename to the filename of <code>newfile</code>
0945: * is possible.
0946: *
0947: * @param newfile the new filename
0948: * @return true if rename is possible
0949: */
0950: public boolean canRenameTo(FileObject newfile) {
0951: if (getFileSystem() == newfile.getFileSystem()) {
0952: return true;
0953: }
0954:
0955: return false;
0956: }
0957:
0958: /**
0959: * Finds the set of matching descendents of this file, in depthwise
0960: * order.
0961: *
0962: * @return list of files or null if the base file (this object) do not exist
0963: */
0964: public FileObject[] findFiles(final FileSelector selector)
0965: throws FileSystemException {
0966: if (!exists()) {
0967: return null;
0968: }
0969:
0970: final ArrayList list = new ArrayList();
0971: findFiles(selector, true, list);
0972: return (FileObject[]) list.toArray(new FileObject[list.size()]);
0973: }
0974:
0975: /**
0976: * Returns the file's content.
0977: */
0978: public FileContent getContent() throws FileSystemException {
0979: synchronized (fs) {
0980: attach();
0981: if (content == null) {
0982: content = doCreateFileContent();
0983: }
0984: return content;
0985: }
0986: }
0987:
0988: /**
0989: * Create a FileContent implementation
0990: */
0991: protected FileContent doCreateFileContent()
0992: throws FileSystemException {
0993: return new DefaultFileContent(this , getFileContentInfoFactory());
0994: }
0995:
0996: /**
0997: * This will prepare the fileObject to get resynchronized with the underlaying filesystem if required
0998: */
0999: public void refresh() throws FileSystemException {
1000: // Detach from the file
1001: try {
1002: detach();
1003: } catch (final Exception e) {
1004: throw new FileSystemException("vfs.provider/resync.error",
1005: name, e);
1006: }
1007: }
1008:
1009: /**
1010: * Closes this file, and its content.
1011: */
1012: public void close() throws FileSystemException {
1013: FileSystemException exc = null;
1014:
1015: // Close the content
1016: if (content != null) {
1017: try {
1018: content.close();
1019: content = null;
1020: } catch (FileSystemException e) {
1021: exc = e;
1022: }
1023: }
1024:
1025: // Detach from the file
1026: try {
1027: detach();
1028: } catch (final Exception e) {
1029: exc = new FileSystemException("vfs.provider/close.error",
1030: name, e);
1031: }
1032:
1033: if (exc != null) {
1034: throw exc;
1035: }
1036: }
1037:
1038: /**
1039: * Returns an input stream to use to read the content of the file.
1040: */
1041: public InputStream getInputStream() throws FileSystemException {
1042: if (!getType().hasContent()) {
1043: throw new FileSystemException(
1044: "vfs.provider/read-not-file.error", name);
1045: }
1046: if (!isReadable()) {
1047: throw new FileSystemException(
1048: "vfs.provider/read-not-readable.error", name);
1049: }
1050:
1051: // Get the raw input stream
1052: try {
1053: return doGetInputStream();
1054: } catch (final Exception exc) {
1055: throw new FileSystemException("vfs.provider/read.error",
1056: name, exc);
1057: }
1058: }
1059:
1060: /**
1061: * Returns an input/output stream to use to read and write the content of the file in and
1062: * random manner.
1063: */
1064: public RandomAccessContent getRandomAccessContent(
1065: final RandomAccessMode mode) throws FileSystemException {
1066: if (!getType().hasContent()) {
1067: throw new FileSystemException(
1068: "vfs.provider/read-not-file.error", name);
1069: }
1070:
1071: if (mode.requestRead()) {
1072: if (!getFileSystem().hasCapability(
1073: Capability.RANDOM_ACCESS_READ)) {
1074: throw new FileSystemException(
1075: "vfs.provider/random-access-read-not-supported.error");
1076: }
1077: if (!isReadable()) {
1078: throw new FileSystemException(
1079: "vfs.provider/read-not-readable.error", name);
1080: }
1081: }
1082:
1083: if (mode.requestWrite()) {
1084: if (!getFileSystem().hasCapability(
1085: Capability.RANDOM_ACCESS_WRITE)) {
1086: throw new FileSystemException(
1087: "vfs.provider/random-access-write-not-supported.error");
1088: }
1089: if (!isWriteable()) {
1090: throw new FileSystemException(
1091: "vfs.provider/write-read-only.error", name);
1092: }
1093: }
1094:
1095: // Get the raw input stream
1096: try {
1097: return doGetRandomAccessContent(mode);
1098: } catch (final Exception exc) {
1099: throw new FileSystemException(
1100: "vfs.provider/random-access.error", name, exc);
1101: }
1102: }
1103:
1104: /**
1105: * Prepares this file for writing. Makes sure it is either a file,
1106: * or its parent folder exists. Returns an output stream to use to
1107: * write the content of the file to.
1108: */
1109: public OutputStream getOutputStream() throws FileSystemException {
1110: return getOutputStream(false);
1111: }
1112:
1113: /**
1114: * Prepares this file for writing. Makes sure it is either a file,
1115: * or its parent folder exists. Returns an output stream to use to
1116: * write the content of the file to.<br>
1117: *
1118: * @param bAppend true when append to the file.<br>
1119: * Note: If the underlaying filesystem do not support this, it wont work.
1120: */
1121: public OutputStream getOutputStream(boolean bAppend)
1122: throws FileSystemException {
1123: if (getType() != FileType.IMAGINARY && !getType().hasContent()) {
1124: throw new FileSystemException(
1125: "vfs.provider/write-not-file.error", name);
1126: }
1127: if (!isWriteable()) {
1128: throw new FileSystemException(
1129: "vfs.provider/write-read-only.error", name);
1130: }
1131: if (bAppend
1132: && !getFileSystem().hasCapability(
1133: Capability.APPEND_CONTENT)) {
1134: throw new FileSystemException(
1135: "vfs.provider/write-append-not-supported.error",
1136: name);
1137: }
1138:
1139: if (getType() == FileType.IMAGINARY) {
1140: // Does not exist - make sure parent does
1141: FileObject parent = getParent();
1142: if (parent != null) {
1143: parent.createFolder();
1144: }
1145: }
1146:
1147: // Get the raw output stream
1148: try {
1149: return doGetOutputStream(bAppend);
1150: } catch (RuntimeException re) {
1151: throw re;
1152: } catch (Exception exc) {
1153: throw new FileSystemException("vfs.provider/write.error",
1154: new Object[] { name }, exc);
1155: }
1156: }
1157:
1158: /**
1159: * Detaches this file, invaliating all cached info. This will force
1160: * a call to {@link #doAttach} next time this file is used.
1161: */
1162: private void detach() throws Exception {
1163: synchronized (fs) {
1164: if (attached) {
1165: try {
1166: doDetach();
1167: } finally {
1168: attached = false;
1169: setFileType(null);
1170: parent = null;
1171:
1172: // fs.fileDetached(this);
1173:
1174: removeChildrenCache();
1175: // children = null;
1176: }
1177: }
1178: }
1179: }
1180:
1181: private void removeChildrenCache() {
1182: /*
1183: if (children != null)
1184: {
1185: for (int iterChildren = 0; iterChildren < children.length; iterChildren++)
1186: {
1187: fs.removeFileFromCache(children[iterChildren].getName());
1188: }
1189:
1190: children = null;
1191: }
1192: */
1193: children = null;
1194: }
1195:
1196: /**
1197: * Attaches to the file.
1198: */
1199: private void attach() throws FileSystemException {
1200: synchronized (fs) {
1201: if (attached) {
1202: return;
1203: }
1204:
1205: try {
1206: // Attach and determine the file type
1207: doAttach();
1208: attached = true;
1209: // now the type could already be injected by doAttach (e.g from parent to child)
1210: if (type == null) {
1211: setFileType(doGetType());
1212: }
1213: if (type == null) {
1214: setFileType(FileType.IMAGINARY);
1215: }
1216: } catch (Exception exc) {
1217: throw new FileSystemException(
1218: "vfs.provider/get-type.error",
1219: new Object[] { name }, exc);
1220: }
1221:
1222: // fs.fileAttached(this);
1223: }
1224: }
1225:
1226: /**
1227: * Called when the ouput stream for this file is closed.
1228: */
1229: protected void endOutput() throws Exception {
1230: if (getType() == FileType.IMAGINARY) {
1231: // File was created
1232: handleCreate(FileType.FILE);
1233: } else {
1234: // File has changed
1235: onChange();
1236: }
1237: }
1238:
1239: /**
1240: * Called when this file is created. Updates cached info and notifies
1241: * the parent and file system.
1242: */
1243: protected void handleCreate(final FileType newType)
1244: throws Exception {
1245: synchronized (fs) {
1246: if (attached) {
1247: // Fix up state
1248: injectType(newType);
1249:
1250: removeChildrenCache();
1251: // children = EMPTY_FILE_ARRAY;
1252:
1253: // Notify subclass
1254: onChange();
1255: }
1256:
1257: // Notify parent that its child list may no longer be valid
1258: notifyParent(this .getName(), newType);
1259:
1260: // Notify the file system
1261: fs.fireFileCreated(this );
1262: }
1263: }
1264:
1265: /**
1266: * Called when this file is deleted. Updates cached info and notifies
1267: * subclasses, parent and file system.
1268: */
1269: protected void handleDelete() throws Exception {
1270: synchronized (fs) {
1271: if (attached) {
1272: // Fix up state
1273: injectType(FileType.IMAGINARY);
1274: removeChildrenCache();
1275: // children = null;
1276:
1277: // Notify subclass
1278: onChange();
1279: }
1280:
1281: // Notify parent that its child list may no longer be valid
1282: notifyParent(this .getName(), FileType.IMAGINARY);
1283:
1284: // Notify the file system
1285: fs.fireFileDeleted(this );
1286: }
1287: }
1288:
1289: /**
1290: * Called when this file is changed.<br />
1291: * This will only happen if you monitor the file using {@link org.apache.commons.vfs.FileMonitor}.
1292: */
1293: protected void handleChanged() throws Exception {
1294: // Notify the file system
1295: fs.fireFileChanged(this );
1296: }
1297:
1298: /**
1299: * Notifies the file that its children have changed.
1300: *
1301: * @deprecated use {@link #childrenChanged(FileName,FileType)}
1302: */
1303: protected void childrenChanged() throws Exception {
1304: childrenChanged(null, null);
1305: }
1306:
1307: /**
1308: * Notifies the file that its children have changed.
1309: */
1310: protected void childrenChanged(FileName childName, FileType newType)
1311: throws Exception {
1312: // TODO - this may be called when not attached
1313:
1314: if (children != null) {
1315: if (childName != null && newType != null) {
1316: // TODO - figure out if children[] can be replaced by list
1317: ArrayList list = new ArrayList(Arrays.asList(children));
1318: if (newType.equals(FileType.IMAGINARY)) {
1319: list.remove(childName);
1320: } else {
1321: list.add(childName);
1322: }
1323: children = new FileName[list.size()];
1324: list.toArray(children);
1325: }
1326: }
1327:
1328: // removeChildrenCache();
1329: onChildrenChanged(childName, newType);
1330: }
1331:
1332: /**
1333: * Notify the parent of a change to its children, when a child is created
1334: * or deleted.
1335: */
1336: private void notifyParent(FileName childName, FileType newType)
1337: throws Exception {
1338: if (parent == null) {
1339: FileName parentName = name.getParent();
1340: if (parentName != null) {
1341: // Locate the parent, if it is cached
1342: parent = fs.getFileFromCache(parentName);
1343: }
1344: }
1345:
1346: if (parent != null) {
1347: FileObjectUtils.getAbstractFileObject(parent)
1348: .childrenChanged(childName, newType);
1349: }
1350: }
1351:
1352: /**
1353: * Traverses the descendents of this file, and builds a list of selected
1354: * files.
1355: */
1356: public void findFiles(final FileSelector selector,
1357: final boolean depthwise, final List selected)
1358: throws FileSystemException {
1359: try {
1360: if (exists()) {
1361: // Traverse starting at this file
1362: final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo();
1363: info.setBaseFolder(this );
1364: info.setDepth(0);
1365: info.setFile(this );
1366: traverse(info, selector, depthwise, selected);
1367: }
1368: } catch (final Exception e) {
1369: throw new FileSystemException(
1370: "vfs.provider/find-files.error", name, e);
1371: }
1372: }
1373:
1374: /**
1375: * Traverses a file.
1376: */
1377: private static void traverse(
1378: final DefaultFileSelectorInfo fileInfo,
1379: final FileSelector selector, final boolean depthwise,
1380: final List selected) throws Exception {
1381: // Check the file itself
1382: final FileObject file = fileInfo.getFile();
1383: final int index = selected.size();
1384:
1385: // If the file is a folder, traverse it
1386: if (file.getType().hasChildren()
1387: && selector.traverseDescendents(fileInfo)) {
1388: final int curDepth = fileInfo.getDepth();
1389: fileInfo.setDepth(curDepth + 1);
1390:
1391: // Traverse the children
1392: final FileObject[] children = file.getChildren();
1393: for (int i = 0; i < children.length; i++) {
1394: final FileObject child = children[i];
1395: fileInfo.setFile(child);
1396: traverse(fileInfo, selector, depthwise, selected);
1397: }
1398:
1399: fileInfo.setFile(file);
1400: fileInfo.setDepth(curDepth);
1401: }
1402:
1403: // Add the file if doing depthwise traversal
1404: if (selector.includeFile(fileInfo)) {
1405: if (depthwise) {
1406: // Add this file after its descendents
1407: selected.add(file);
1408: } else {
1409: // Add this file before its descendents
1410: selected.add(index, file);
1411: }
1412: }
1413: }
1414:
1415: /**
1416: * Check if the content stream is open
1417: *
1418: * @return true if this is the case
1419: */
1420: public boolean isContentOpen() {
1421: if (content == null) {
1422: return false;
1423: }
1424:
1425: return content.isOpen();
1426: }
1427:
1428: /**
1429: * Check if the internal state is "attached"
1430: *
1431: * @return true if this is the case
1432: */
1433: public boolean isAttached() {
1434: return attached;
1435: }
1436:
1437: /**
1438: * create the filecontentinfo implementation
1439: */
1440: protected FileContentInfoFactory getFileContentInfoFactory() {
1441: return getFileSystem().getFileSystemManager()
1442: .getFileContentInfoFactory();
1443: }
1444:
1445: protected void injectType(FileType fileType) {
1446: setFileType(fileType);
1447: }
1448:
1449: private void setFileType(FileType type) {
1450: if (type != null && type != FileType.IMAGINARY) {
1451: try {
1452: name.setType(type);
1453: } catch (FileSystemException e) {
1454: throw new RuntimeException(e.getMessage());
1455: }
1456: }
1457: this .type = type;
1458: }
1459:
1460: /**
1461: * This method is meant to add a object where this object holds a strong reference then.
1462: * E.g. a archive-filesystem creates a list of all childs and they shouldnt get
1463: * garbage collected until the container is garbage collected
1464: *
1465: * @param strongRef
1466: */
1467: public void holdObject(Object strongRef) {
1468: if (objects == null) {
1469: objects = new ArrayList(5);
1470: }
1471: objects.add(strongRef);
1472: }
1473:
1474: /**
1475: * will be called after this file-object closed all its streams.
1476: */
1477: protected void notifyAllStreamsClosed() {
1478: }
1479:
1480: // --- OPERATIONS ---
1481:
1482: /**
1483: * @return FileOperations interface that provides access to the operations
1484: * API.
1485: * @throws FileSystemException
1486: */
1487: public FileOperations getFileOperations()
1488: throws FileSystemException {
1489: if (operations == null) {
1490: operations = new DefaultFileOperations(this );
1491: }
1492:
1493: return operations;
1494: }
1495:
1496: protected void finalize() throws Throwable {
1497: fs.fileObjectDestroyed(this);
1498:
1499: super.finalize();
1500: }
1501: }
|