You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2011/09/21 16:05:29 UTC

svn commit: r1173655 - in /jackrabbit/sandbox/microkernel/src: main/java/org/apache/jackrabbit/mk/fs/ test/java/org/apache/jackrabbit/mk/fs/

Author: thomasm
Date: Wed Sep 21 14:05:29 2011
New Revision: 1173655

URL: http://svn.apache.org/viewvc?rev=1173655&view=rev
Log:
Improved test coverage and a few new features in the file system abstraction.

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java
Modified:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java
    jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java?rev=1173655&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java Wed Sep 21 14:05:29 2011
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk.fs;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.Map;
+import org.apache.jackrabbit.mk.util.SimpleLRUCache;
+
+/**
+ * A file that has a simple read cache.
+ */
+public class FileCache extends FileBase {
+
+    private static final int BLOCK_SIZE = 4 * 1024;
+    private final Map<Long, ByteBuffer> readCache = SimpleLRUCache.newInstance(16);
+    private final FileChannel base;
+    private long pos, size;
+
+    FileCache(FileChannel base) throws IOException {
+        this.base = base;
+        this.size = base.size();
+    }
+
+    public long position() throws IOException {
+        return pos;
+    }
+
+    public FileChannel position(long newPosition) throws IOException {
+        this.pos = newPosition;
+        return this;
+    }
+
+    public int read(ByteBuffer dst) throws IOException {
+        long readPos = (pos / BLOCK_SIZE) * BLOCK_SIZE;
+        int off = (int) (pos - readPos);
+        int len = BLOCK_SIZE - off;
+        ByteBuffer buff = readCache.get(readPos);
+        if (buff == null) {
+            base.position(readPos);
+            buff = ByteBuffer.allocate(BLOCK_SIZE);
+            int read = base.read(buff);
+            if (read == BLOCK_SIZE) {
+                readCache.put(readPos, buff);
+            } else {
+                if (read < 0) {
+                    return -1;
+                }
+                len = Math.min(len, read);
+            }
+        }
+        len = Math.min(len, dst.remaining());
+        System.arraycopy(buff.array(), off, dst.array(), dst.position(), len);
+        dst.position(dst.position() + len);
+        pos += len;
+        return len;
+    }
+
+    public long size() throws IOException {
+        return size;
+    }
+
+    public FileChannel truncate(long newSize) throws IOException {
+        readCache.clear();
+        base.truncate(newSize);
+        pos = Math.min(pos, newSize);
+        size = Math.min(size, newSize);
+        return this;
+    }
+
+    public int write(ByteBuffer src) throws IOException {
+        base.position(pos);
+        readCache.clear();
+        int len = base.write(src);
+        pos += len;
+        size = Math.max(size, pos);
+        return len;
+    }
+
+    protected void implCloseChannel() throws IOException {
+        base.close();
+    }
+
+    public void force(boolean metaData) throws IOException {
+        base.force(metaData);
+    }
+
+    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+        return base.tryLock(position, size, shared);
+    }
+
+    public String toString() {
+        return "cache:" + base.toString();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java?rev=1173655&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java Wed Sep 21 14:05:29 2011
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
+ * Version 1.0, and under the Eclipse Public License, Version 1.0
+ * (http://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.apache.jackrabbit.mk.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Allows to read from a file channel like an input stream.
+ */
+public class FileChannelInputStream extends InputStream {
+
+    private final FileChannel channel;
+    private final byte[] buffer = { 0 };
+    private final boolean closeChannel;
+
+    /**
+     * Create a new file object input stream from the file channel.
+     *
+     * @param channel the file channel
+     */
+    public FileChannelInputStream(FileChannel channel, boolean closeChannel) {
+        this.channel = channel;
+        this.closeChannel = closeChannel;
+    }
+
+    public int read() throws IOException {
+        if (channel.position() >= channel.size()) {
+            return -1;
+        }
+        FileUtils.readFully(channel, ByteBuffer.wrap(buffer));
+        return buffer[0] & 0xff;
+    }
+
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (channel.position() + len < channel.size()) {
+            FileUtils.readFully(channel, ByteBuffer.wrap(b, off, len));
+            return len;
+        }
+        return super.read(b, off, len);
+    }
+
+    public long skip(long n) throws IOException {
+        n = Math.min(channel.size() - channel.position(), n);
+        channel.position(channel.position() + n);
+        return n;
+    }
+
+    public void close() throws IOException {
+        if (closeChannel) {
+            channel.close();
+        }
+    }
+
+}

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java?rev=1173655&r1=1173654&r2=1173655&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java Wed Sep 21 14:05:29 2011
@@ -81,7 +81,8 @@ public abstract class FilePath {
         if (providers == null) {
             Map<String, FilePath> map = Collections.synchronizedMap(new HashMap<String, FilePath>());
             for (String c : new String[] {
-                    "org.apache.jackrabbit.mk.fs.FilePathDisk"
+                    "org.apache.jackrabbit.mk.fs.FilePathDisk",
+                    "org.apache.jackrabbit.mk.fs.FilePathCache"
             }) {
                 try {
                     FilePath p = (FilePath) Class.forName(c).newInstance();
@@ -310,16 +311,6 @@ public abstract class FilePath {
     public abstract FilePath getPath(String path);
 
     /**
-     * Get the unwrapped file name (without wrapper prefixes if wrapping /
-     * delegating file systems are used).
-     *
-     * @return the unwrapped path
-     */
-    public FilePath unwrap() {
-        return this;
-    }
-
-    /**
      * Append an element to the path.
      * This is similar to <code>java.nio.file.spi.FileSystemProvider.resolve</code>.
      *

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java?rev=1173655&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java Wed Sep 21 14:05:29 2011
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.mk.fs;
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+/**
+ * A file system with a small read cache.
+ */
+public class FilePathCache extends FilePathWrapper {
+
+    public FileChannel open(String mode) throws IOException {
+        return new FileCache(getBase().open(mode));
+    }
+
+    public String getScheme() {
+        return "cache";
+    }
+
+}

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java?rev=1173655&r1=1173654&r2=1173655&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java Wed Sep 21 14:05:29 2011
@@ -60,6 +60,9 @@ public class FilePathDisk extends FilePa
      */
     protected static String translateFileName(String fileName) {
         fileName = fileName.replace('\\', '/');
+        if (fileName.startsWith("file:")) {
+            fileName = fileName.substring("file:".length());
+        }
         return expandUserHomeDirectory(fileName);
     }
 
@@ -71,19 +74,11 @@ public class FilePathDisk extends FilePa
      * @return the native file name
      */
     public static String expandUserHomeDirectory(String fileName) {
-        if (fileName == null) {
-            return null;
-        }
-        boolean prefix = false;
-        if (fileName.startsWith("file:")) {
-            prefix = true;
-            fileName = fileName.substring("file:".length());
-        }
-        if (fileName.startsWith("~") && (fileName.length() == 1 || fileName.startsWith("~/") || fileName.startsWith("~\\"))) {
+        if (fileName.startsWith("~") && (fileName.length() == 1 || fileName.startsWith("~/"))) {
             String userDir = System.getProperty("user.home", "");
             fileName = userDir + fileName.substring(1);
         }
-        return prefix ? "file:" + fileName : fileName;
+        return fileName;
     }
 
     public void moveTo(FilePath newName) throws IOException {
@@ -231,16 +226,20 @@ public class FilePathDisk extends FilePa
 
     public void createDirectory() throws IOException {
         File f = new File(name);
-        if (!f.exists()) {
-            File dir = new File(name);
-            for (int i = 0; i < MAX_FILE_RETRY; i++) {
-                if ((dir.exists() && dir.isDirectory()) || dir.mkdir()) {
-                    return;
-                }
-                wait(i);
+        if (f.exists()) {
+            if (f.isDirectory()) {
+                return;
             }
-            throw new IOException("Could not create " + name);
+            throw new IOException("A file with this name already exists: " + name);
         }
+        File dir = new File(name);
+        for (int i = 0; i < MAX_FILE_RETRY; i++) {
+            if ((dir.exists() && dir.isDirectory()) || dir.mkdir()) {
+                return;
+            }
+            wait(i);
+        }
+        throw new IOException("Could not create " + name);
     }
 
     public OutputStream newOutputStream(boolean append) throws IOException {
@@ -266,7 +265,7 @@ public class FilePathDisk extends FilePa
                     Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
                 }
                 if (in == null) {
-                    throw new FileNotFoundException("resource " + fileName);
+                    throw new FileNotFoundException("Resource " + fileName);
                 }
                 return in;
             }
@@ -298,18 +297,7 @@ public class FilePathDisk extends FilePa
     }
 
     public FileChannel open(String mode) throws IOException {
-        FileDisk f;
-        try {
-            f = new FileDisk(name, mode);
-        } catch (IOException e) {
-            freeMemoryAndFinalize();
-            try {
-                f = new FileDisk(name, mode);
-            } catch (IOException e2) {
-                throw e;
-            }
-        }
-        return f;
+        return new FileDisk(name, mode);
     }
 
     public String getScheme() {
@@ -347,14 +335,6 @@ public class FilePathDisk extends FilePa
         }
     }
 
-
-    /**
-     * Append an element to the path.
-     * This is similar to <code>java.nio.file.spi.FileSystemProvider.resolve</code>.
-     *
-     * @param other the relative path (might be null)
-     * @return the resolved path
-     */
     public FilePath resolve(String other) {
         return other == null ? this : getPath(name + "/" + other);
     }

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java?rev=1173655&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java Wed Sep 21 14:05:29 2011
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
+ * Version 1.0, and under the Eclipse Public License, Version 1.0
+ * (http://h2database.com/html/license.html).
+ * Initial Developer: H2 Group
+ */
+package org.apache.jackrabbit.mk.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.util.List;
+import org.h2.message.DbException;
+
+/**
+ * The base class for wrapping / delegating file systems such as
+ * the split file system.
+ */
+public abstract class FilePathWrapper extends FilePath {
+
+    private FilePath base;
+
+    public FilePathWrapper getPath(String path) {
+        return create(path, unwrap(path));
+    }
+
+    /**
+     * Create a wrapped path instance for the given base path.
+     *
+     * @param base the base path
+     * @return the wrapped path
+     */
+    public FilePathWrapper wrap(FilePath base) {
+        return base == null ? null : create(getPrefix() + base.name, base);
+    }
+
+    public FilePath unwrap() {
+        return unwrap(name);
+    }
+
+    private FilePathWrapper create(String path, FilePath base) {
+        try {
+            FilePathWrapper p = getClass().newInstance();
+            p.name = path;
+            p.base = base;
+            return p;
+        } catch (Exception e) {
+            throw DbException.convert(e);
+        }
+    }
+
+    protected String getPrefix() {
+        return getScheme() + ":";
+    }
+
+    /**
+     * Get the base path for the given wrapped path.
+     *
+     * @param path the path including the scheme prefix
+     * @return the base file path
+     */
+    protected FilePath unwrap(String path) {
+        return FilePath.get(path.substring(getScheme().length() + 1));
+    }
+
+    protected FilePath getBase() {
+        return base;
+    }
+
+    public boolean canWrite() {
+        return base.canWrite();
+    }
+
+    public void createDirectory() throws IOException {
+        base.createDirectory();
+    }
+
+    public boolean createFile() {
+        return base.createFile();
+    }
+
+    public void delete() throws IOException {
+        base.delete();
+    }
+
+    public boolean exists() {
+        return base.exists();
+    }
+
+    public FilePath getParent() {
+        return wrap(base.getParent());
+    }
+
+    public boolean isAbsolute() {
+        return base.isAbsolute();
+    }
+
+    public boolean isDirectory() {
+        return base.isDirectory();
+    }
+
+    public long lastModified() {
+        return base.lastModified();
+    }
+
+    public FilePath toRealPath() throws IOException {
+        return wrap(base.toRealPath());
+    }
+
+    public List<FilePath> newDirectoryStream() throws IOException {
+        List<FilePath> list = base.newDirectoryStream();
+        for (int i = 0, len = list.size(); i < len; i++) {
+            list.set(i, wrap(list.get(i)));
+        }
+        return list;
+    }
+
+    public void moveTo(FilePath newName) throws IOException {
+        base.moveTo(((FilePathWrapper) newName).base);
+    }
+
+    public InputStream newInputStream() throws IOException {
+        return base.newInputStream();
+    }
+
+    public OutputStream newOutputStream(boolean append) throws IOException {
+        return base.newOutputStream(append);
+    }
+
+    public FileChannel open(String mode) throws IOException {
+        return base.open(mode);
+    }
+
+    public boolean setReadOnly() {
+        return base.setReadOnly();
+    }
+
+    public long size() {
+        return base.size();
+    }
+
+    public FilePath createTempFile(String suffix, boolean deleteOnExit, boolean inTempDir)
+            throws IOException {
+        return wrap(base.createTempFile(suffix, deleteOnExit, inTempDir));
+    }
+
+    public FilePath resolve(String other) {
+        return other == null ? this : wrap(base.resolve(other));
+    }
+
+}

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java?rev=1173655&r1=1173654&r2=1173655&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java Wed Sep 21 14:05:29 2011
@@ -245,17 +245,6 @@ public class FileUtils {
         return FilePath.get(fileName).setReadOnly();
     }
 
-    /**
-     * Get the unwrapped file name (without wrapper prefixes if wrapping /
-     * delegating file systems are used).
-     *
-     * @param fileName the file name
-     * @return the unwrapped
-     */
-    public static String unwrap(String fileName) {
-        return FilePath.get(fileName).unwrap().toString();
-    }
-
     // utility methods =======================================
 
     /**

Modified: jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java?rev=1173655&r1=1173654&r2=1173655&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java (original)
+++ jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java Wed Sep 21 14:05:29 2011
@@ -25,6 +25,7 @@ import java.io.RandomAccessFile;
 import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
+import java.nio.channels.FileChannel.MapMode;
 import java.util.List;
 import java.util.Random;
 import org.apache.jackrabbit.mk.util.IOUtilsTest;
@@ -41,6 +42,13 @@ public class TestFileSystem extends Test
 
     public void test() throws Exception {
         testFileSystem(getBaseDir() + "/fs");
+        testFileSystem("cache:" + getBaseDir() + "/fs");
+    }
+
+    public void testAbsoluteRelative() {
+        assertTrue(FileUtils.isAbsolute("/test/abc"));
+        assertFalse(FileUtils.isAbsolute("test/abc"));
+        assertTrue(FileUtils.isAbsolute("~/test/abc"));
     }
 
     public void testClasspath() throws IOException {
@@ -75,15 +83,106 @@ public class TestFileSystem extends Test
     }
 
     public void testUserHome() throws IOException {
-        String fileName = FileUtils.toRealPath("~/test");
         String userDir = System.getProperty("user.home").replace('\\', '/');
-        assertTrue(fileName.startsWith(userDir));
+        assertTrue(FileUtils.toRealPath("~/test").startsWith(userDir));
+        assertTrue(FileUtils.toRealPath("file:~/test").startsWith(userDir));
     }
 
     private void testFileSystem(String fsBase) throws Exception {
+        testDirectories(fsBase);
+        testMoveTo(fsBase);
+        testParentEventuallyReturnsNull(fsBase);
+        testRandomAccess(fsBase);
+        testResolve(fsBase);
+        testSetReadOnly(fsBase);
         testSimple(fsBase);
         testTempFile(fsBase);
-        testRandomAccess(fsBase);
+        testUnsupportedFeatures(fsBase);
+    }
+
+    private void testDirectories(String fsBase) throws IOException {
+        final String fileName = fsBase + "/testFile";
+        if (FileUtils.exists(fileName)) {
+            FileUtils.delete(fileName);
+        }
+        if (FileUtils.createFile(fileName)) {
+            try {
+                FileUtils.createDirectory(fileName);
+                fail();
+            } catch (IOException e) {
+                // expected
+            }
+            try {
+                FileUtils.createDirectories(fileName + "/test");
+                fail();
+            } catch (IOException e) {
+                // expected
+            }
+            FileUtils.delete(fileName);
+        }
+    }
+
+    private void testMoveTo(String fsBase) throws IOException {
+        final String fileName = fsBase + "/testFile";
+        final String fileName2 = fsBase + "/testFile2";
+        if (FileUtils.exists(fileName)) {
+            FileUtils.delete(fileName);
+        }
+        if (FileUtils.createFile(fileName)) {
+            FileUtils.moveTo(fileName, fileName2);
+            FileUtils.createFile(fileName);
+            try {
+                FileUtils.moveTo(fileName2, fileName);
+                fail();
+            } catch (IOException e) {
+                // expected
+            }
+            FileUtils.delete(fileName);
+            FileUtils.delete(fileName2);
+            try {
+                FileUtils.moveTo(fileName, fileName2);
+                fail();
+            } catch (IOException e) {
+                // expected
+            }
+        }
+    }
+
+    private void testParentEventuallyReturnsNull(String fsBase) {
+        FilePath p = FilePath.get(fsBase + "/testFile");
+        assertTrue(p.getScheme().length() > 0);
+        for (int i = 0; i < 100; i++) {
+            if (p == null) {
+                return;
+            }
+            p = p.getParent();
+        }
+        fail("Parent is not null: " + p);
+        String path = fsBase + "/testFile";
+        for (int i = 0; i < 100; i++) {
+            if (path == null) {
+                return;
+            }
+            path = FileUtils.getParent(path);
+        }
+        fail("Parent is not null: " + path);
+    }
+
+    private void testResolve(String fsBase) {
+        String fileName = fsBase + "/testFile";
+        assertEquals(fileName, FilePath.get(fsBase).resolve("testFile").toString());
+    }
+
+    private void testSetReadOnly(String fsBase) throws IOException {
+        String fileName = fsBase + "/testFile";
+        if (FileUtils.exists(fileName)) {
+            FileUtils.delete(fileName);
+        }
+        if (FileUtils.createFile(fileName)) {
+            FileUtils.setReadOnly(fileName);
+            assertFalse(FileUtils.canWrite(fileName));
+            FileUtils.delete(fileName);
+        }
     }
 
     private void testSimple(final String fsBase) throws Exception {
@@ -280,10 +379,6 @@ public class TestFileSystem extends Test
         }
     }
 
-    private void trace(String s) {
-        // System.out.println(s);
-    }
-
     private void testTempFile(String fsBase) throws Exception {
         int len = 10000;
         String s = FileUtils.createTempFile(fsBase + "/tmp", ".tmp", false, false);
@@ -305,4 +400,62 @@ public class TestFileSystem extends Test
         FileUtils.delete(s);
     }
 
+    private void testUnsupportedFeatures(String fsBase) throws IOException {
+        final String fileName = fsBase + "/testFile";
+        if (FileUtils.exists(fileName)) {
+            FileUtils.delete(fileName);
+        }
+        if (FileUtils.createFile(fileName)) {
+            final FileChannel channel = FileUtils.open(fileName, "rw");
+            try {
+                channel.map(MapMode.PRIVATE, 0, channel.size());
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            try {
+                channel.read(ByteBuffer.allocate(10), 0);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            try {
+                channel.read(new ByteBuffer[]{ByteBuffer.allocate(10)}, 0, 0);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            try {
+                channel.write(ByteBuffer.allocate(10), 0);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            try {
+                channel.write(new ByteBuffer[]{ByteBuffer.allocate(10)}, 0, 0);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            try {
+                channel.transferFrom(channel, 0, 0);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            try {
+                channel.transferTo(0, 0, channel);
+                fail();
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+            channel.close();
+            FileUtils.delete(fileName);
+        }
+    }
+
+    private void trace(String s) {
+        // System.out.println(s);
+    }
+
 }