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/20 12:26:25 UTC

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

Author: thomasm
Date: Tue Sep 20 10:26:24 2011
New Revision: 1173067

URL: http://svn.apache.org/viewvc?rev=1173067&view=rev
Log:
New file system abstraction that is similar to the Java 7 abstraction in the package java.nio.file.

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java
    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/
    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/FileBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java?rev=1173067&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java Tue Sep 20 10:26:24 2011
@@ -0,0 +1,87 @@
+/*
+ * 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.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * The base class for file implementations.
+ */
+public abstract class FileBase extends FileChannel {
+
+    public void force(boolean metaData) throws IOException {
+        // ignore
+    }
+
+    public FileLock lock(long position, long size, boolean shared) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public abstract long position() throws IOException;
+
+    public abstract FileChannel position(long newPosition) throws IOException;
+
+    public abstract int read(ByteBuffer dst) throws IOException;
+
+    public int read(ByteBuffer dst, long position) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public abstract long size() throws IOException;
+
+    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public long transferTo(long position, long count, WritableByteChannel target)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public abstract FileChannel truncate(long size) throws IOException;
+
+    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    public abstract int write(ByteBuffer src) throws IOException;
+
+    public int write(ByteBuffer src, long position) throws IOException {
+        throw new UnsupportedOperationException();    }
+
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+        throw new UnsupportedOperationException();    }
+
+    protected void implCloseChannel() throws IOException {
+        // ignore
+    }
+
+}

Added: 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=1173067&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java Tue Sep 20 10:26:24 2011
@@ -0,0 +1,331 @@
+/*
+ * 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.io.InputStream;
+import java.io.OutputStream;
+import java.nio.channels.FileChannel;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import org.apache.jackrabbit.mk.util.StringUtils;
+
+/**
+ * A path to a file. It similar to the Java 7 <code>java.nio.file.Path</code>,
+ * but simpler, and works with older versions of Java. It also implements the
+ * relevant methods found in <code>java.nio.file.FileSystem</code> and
+ * <code>FileSystems</code>
+ */
+public abstract class FilePath {
+
+    private static final FilePath DEFAULT = new FilePathDisk();
+
+    private static Map<String, FilePath> providers;
+
+    /**
+     * The prefix for temporary files.
+     */
+    private static String tempRandom;
+    private static long tempSequence;
+
+    /**
+     * The complete path (which may be absolute or relative, depending on the
+     * file system).
+     */
+    protected String name;
+
+    /**
+     * Get the file path object for the given path.
+     * This method is similar to Java 7 <code>java.nio.file.FileSystem.getPath</code>.
+     * Windows-style '\' is replaced with '/'.
+     *
+     * @param path the path
+     * @return the file path object
+     */
+    public static FilePath get(String path) {
+        path = path.replace('\\', '/');
+        int index = path.indexOf(':');
+        if (index < 2) {
+            // use the default provider if no prefix or
+            // only a single character (drive name)
+            return DEFAULT.getPath(path);
+        }
+        String scheme = path.substring(0, index);
+        registerDefaultProviders();
+        FilePath p = providers.get(scheme);
+        if (p == null) {
+            // provider not found - use the default
+            p = DEFAULT;
+        }
+        return p.getPath(path);
+    }
+
+    private static void registerDefaultProviders() {
+        if (providers == null) {
+            Map<String, FilePath> map = Collections.synchronizedMap(new HashMap<String, FilePath>());
+            for (String c : new String[] {
+                    "org.apache.jackrabbit.mk.fs.FilePathDisk"
+            }) {
+                try {
+                    FilePath p = (FilePath) Class.forName(c).newInstance();
+                    map.put(p.getScheme(), p);
+                } catch (Exception e) {
+                    // ignore - the files may be excluded in purpose
+                }
+            }
+            providers = map;
+        }
+    }
+
+    /**
+     * Register a file provider.
+     *
+     * @param provider the file provider
+     */
+    public static void register(FilePath provider) {
+        registerDefaultProviders();
+        providers.put(provider.getScheme(), provider);
+    }
+
+    /**
+     * Unregister a file provider.
+     *
+     * @param provider the file provider
+     */
+    public static void unregister(FilePath provider) {
+        registerDefaultProviders();
+        providers.remove(provider.getScheme());
+    }
+
+    /**
+     * Get the size of a file in bytes
+     *
+     * @return the size in bytes
+     */
+    public abstract long size();
+
+    /**
+     * Rename a file if this is allowed.
+     *
+     * @param newName the new fully qualified file name
+     */
+    public abstract void moveTo(FilePath newName) throws IOException;
+
+    /**
+     * Create a new file.
+     *
+     * @return true if creating was successful
+     */
+    public abstract boolean createFile();
+
+    /**
+     * Checks if a file exists.
+     *
+     * @return true if it exists
+     */
+    public abstract boolean exists();
+
+    /**
+     * Delete a file or directory if it exists.
+     * Directories may only be deleted if they are empty.
+     */
+    public abstract void delete() throws IOException;
+
+    /**
+     * List the files and directories in the given directory.
+     *
+     * @return the list of fully qualified file names
+     */
+    public abstract List<FilePath> newDirectoryStream() throws IOException;
+
+    /**
+     * Normalize a file name.
+     *
+     * @return the normalized file name
+     */
+    public abstract FilePath toRealPath() throws IOException;
+
+    /**
+     * Get the parent directory of a file or directory.
+     *
+     * @return the parent directory name
+     */
+    public abstract FilePath getParent();
+
+    /**
+     * Check if it is a file or a directory.
+     *
+     * @return true if it is a directory
+     */
+    public abstract boolean isDirectory();
+
+    /**
+     * Check if the file name includes a path.
+     *
+     * @return if the file name is absolute
+     */
+    public abstract boolean isAbsolute();
+
+    /**
+     * Get the last modified date of a file
+     *
+     * @return the last modified date
+     */
+    public abstract long lastModified();
+
+    /**
+     * Check if the file is writable.
+     *
+     * @return if the file is writable
+     */
+    public abstract boolean canWrite();
+
+    /**
+     * Create a directory (all required parent directories already exist).
+     */
+    public abstract void createDirectory() throws IOException;
+
+    /**
+     * Get the file or directory name (the last element of the path).
+     *
+     * @return the last element of the path
+     */
+    public String getName() {
+        int idx = Math.max(name.indexOf(':'), name.lastIndexOf('/'));
+        return idx < 0 ? name : name.substring(idx + 1);
+    }
+
+    /**
+     * Create an output stream to write into the file.
+     *
+     * @param append if true, the file will grow, if false, the file will be
+     *            truncated first
+     * @return the output stream
+     */
+    public abstract OutputStream newOutputStream(boolean append) throws IOException;
+
+    /**
+     * Open a random access file object.
+     *
+     * @param mode the access mode. Supported are r, rw, rws, rwd
+     * @return the file object
+     */
+    public abstract FileChannel open(String mode) throws IOException;
+
+    /**
+     * Create an input stream to read from the file.
+     *
+     * @return the input stream
+     */
+    public abstract InputStream newInputStream() throws IOException;
+
+    /**
+     * Disable the ability to write.
+     *
+     * @return true if the call was successful
+     */
+    public abstract boolean setReadOnly();
+
+    /**
+     * Create a new temporary file.
+     *
+     * @param suffix the suffix
+     * @param deleteOnExit if the file should be deleted when the virtual
+     *            machine exists
+     * @param inTempDir if the file should be stored in the temporary directory
+     * @return the name of the created file
+     */
+    public FilePath createTempFile(String suffix, boolean deleteOnExit, boolean inTempDir) throws IOException {
+        while (true) {
+            FilePath p = getPath(name + getNextTempFileNamePart(false) + suffix);
+            if (p.exists() || !p.createFile()) {
+                // in theory, the random number could collide
+                getNextTempFileNamePart(true);
+                continue;
+            }
+            p.open("rw").close();
+            return p;
+        }
+    }
+
+    /**
+     * Get the next temporary file name part (the part in the middle).
+     *
+     * @param newRandom if the random part of the filename should change
+     * @return the file name part
+     */
+    protected static synchronized String getNextTempFileNamePart(boolean newRandom) {
+        if (newRandom || tempRandom == null) {
+            byte[] prefix = new byte[8];
+            new Random().nextBytes(prefix);
+            tempRandom = StringUtils.convertBytesToHex(prefix) + ".";
+        }
+        return tempRandom + tempSequence++;
+    }
+
+    /**
+     * Get the string representation. The returned string can be used to
+     * construct a new object.
+     *
+     * @return the path as a string
+     */
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * Get the scheme (prefix) for this file provider.
+     * This is similar to <code>java.nio.file.spi.FileSystemProvider.getScheme</code>.
+     *
+     * @return the scheme
+     */
+    public abstract String getScheme();
+
+    /**
+     * Convert a file to a path. This is similar to
+     * <code>java.nio.file.spi.FileSystemProvider.getPath</code>, but may
+     * return an object even if the scheme doesn't match in case of the the
+     * default file provider.
+     *
+     * @param path the path
+     * @return the file path object
+     */
+    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>.
+     *
+     * @param other the relative path (might be null)
+     * @return the resolved path
+     */
+    public abstract FilePath resolve(String other);
+
+}

Added: 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=1173067&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java Tue Sep 20 10:26:24 2011
@@ -0,0 +1,429 @@
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.net.URL;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This file system stores files on disk.
+ * This is the most common file system.
+ */
+public class FilePathDisk extends FilePath {
+
+    private static final String CLASSPATH_PREFIX = "classpath:";
+    private static final String FILE_SEPARATOR = System.getProperty("file.separator", "/");
+    private static final int MAX_FILE_RETRY = 16;
+
+    public FilePathDisk getPath(String path) {
+        FilePathDisk p = new FilePathDisk();
+        p.name = translateFileName(path);
+        return p;
+    }
+
+    public long size() {
+        return new File(name).length();
+    }
+
+    /**
+     * Translate the file name to the native format. This will replace '\' with
+     * '/' and expand the home directory ('~').
+     *
+     * @param fileName the file name
+     * @return the native file name
+     */
+    protected static String translateFileName(String fileName) {
+        fileName = fileName.replace('\\', '/');
+        return expandUserHomeDirectory(fileName);
+    }
+
+    /**
+     * Expand '~' to the user home directory. It is only be expanded if the '~'
+     * stands alone, or is followed by '/' or '\'.
+     *
+     * @param fileName the file name
+     * @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("~\\"))) {
+            String userDir = System.getProperty("user.home", "");
+            fileName = userDir + fileName.substring(1);
+        }
+        return prefix ? "file:" + fileName : fileName;
+    }
+
+    public void moveTo(FilePath newName) throws IOException {
+        File oldFile = new File(name);
+        File newFile = new File(newName.name);
+        if (oldFile.getAbsolutePath().equals(newFile.getAbsolutePath())) {
+            return;
+        }
+        if (!oldFile.exists()) {
+            throw new IOException("Could not rename " +
+                    name + " (not found) to " + newName.name);
+        }
+        if (newFile.exists()) {
+            throw new IOException("Could not rename " +
+                    name + " to " + newName + " (already exists)");
+        }
+        for (int i = 0; i < MAX_FILE_RETRY; i++) {
+            boolean ok = oldFile.renameTo(newFile);
+            if (ok) {
+                return;
+            }
+            wait(i);
+        }
+        throw new IOException("Could not rename " + name + " to " + newName.name);
+    }
+
+    private static void wait(int i) {
+        if (i == 8) {
+            System.gc();
+        }
+        try {
+            // sleep at most 256 ms
+            long sleep = Math.min(256, i * i);
+            Thread.sleep(sleep);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    public boolean createFile() {
+        File file = new File(name);
+        for (int i = 0; i < MAX_FILE_RETRY; i++) {
+            try {
+                return file.createNewFile();
+            } catch (IOException e) {
+                // 'access denied' is really a concurrent access problem
+                wait(i);
+            }
+        }
+        return false;
+    }
+
+    public boolean exists() {
+        return new File(name).exists();
+    }
+
+    public void delete() throws IOException {
+        File file = new File(name);
+        for (int i = 0; i < MAX_FILE_RETRY; i++) {
+            boolean ok = file.delete();
+            if (ok || !file.exists()) {
+                return;
+            }
+            wait(i);
+        }
+        throw new IOException("Could not delete " + name);
+    }
+
+    public List<FilePath> newDirectoryStream() throws IOException {
+        ArrayList<FilePath> list = new ArrayList<FilePath>();
+        File f = new File(name);
+        String[] files = f.list();
+        if (files != null) {
+            String base = f.getCanonicalPath();
+            if (!base.endsWith(FILE_SEPARATOR)) {
+                base += FILE_SEPARATOR;
+            }
+            for (int i = 0, len = files.length; i < len; i++) {
+                list.add(getPath(base + files[i]));
+            }
+        }
+        return list;
+    }
+
+    public boolean canWrite() {
+        return canWriteInternal(new File(name));
+    }
+
+    public boolean setReadOnly() {
+        File f = new File(name);
+        return f.setReadOnly();
+    }
+
+    public FilePathDisk toRealPath() throws IOException {
+        String fileName = new File(name).getCanonicalPath();
+        return getPath(fileName);
+    }
+
+    public FilePath getParent() {
+        String p = new File(name).getParent();
+        return p == null ? null : getPath(p);
+    }
+
+    public boolean isDirectory() {
+        return new File(name).isDirectory();
+    }
+
+    public boolean isAbsolute() {
+        return new File(name).isAbsolute();
+    }
+
+    public long lastModified() {
+        return new File(name).lastModified();
+    }
+
+    private static boolean canWriteInternal(File file) {
+        try {
+            if (!file.canWrite()) {
+                return false;
+            }
+        } catch (Exception e) {
+            // workaround for GAE which throws a
+            // java.security.AccessControlException
+            return false;
+        }
+        // File.canWrite() does not respect windows user permissions,
+        // so we must try to open it using the mode "rw".
+        // See also http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4420020
+        RandomAccessFile r = null;
+        try {
+            r = new RandomAccessFile(file, "rw");
+            return true;
+        } catch (FileNotFoundException e) {
+            return false;
+        } finally {
+            if (r != null) {
+                try {
+                    r.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    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);
+            }
+            throw new IOException("Could not create " + name);
+        }
+    }
+
+    public OutputStream newOutputStream(boolean append) throws IOException {
+        File file = new File(name);
+        File parent = file.getParentFile();
+        if (parent != null) {
+            FileUtils.createDirectories(parent.getAbsolutePath());
+        }
+        FileOutputStream out = new FileOutputStream(name, append);
+        return out;
+    }
+
+    public InputStream newInputStream() throws IOException {
+        if (name.indexOf(':') > 1) {
+            // if the : is in position 1, a windows file access is assumed: C:.. or D:
+            if (name.startsWith(CLASSPATH_PREFIX)) {
+                String fileName = name.substring(CLASSPATH_PREFIX.length());
+                if (!fileName.startsWith("/")) {
+                    fileName = "/" + fileName;
+                }
+                InputStream in = getClass().getResourceAsStream(fileName);
+                if (in == null) {
+                    Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
+                }
+                if (in == null) {
+                    throw new FileNotFoundException("resource " + fileName);
+                }
+                return in;
+            }
+            // otherwise an URL is assumed
+            URL url = new URL(name);
+            InputStream in = url.openStream();
+            return in;
+        }
+        FileInputStream in = new FileInputStream(name);
+        return in;
+    }
+
+    /**
+     * Call the garbage collection and run finalization. This close all files that
+     * were not closed, and are no longer referenced.
+     */
+    static void freeMemoryAndFinalize() {
+        Runtime rt = Runtime.getRuntime();
+        long mem = rt.freeMemory();
+        for (int i = 0; i < 16; i++) {
+            rt.gc();
+            long now = rt.freeMemory();
+            rt.runFinalization();
+            if (now == mem) {
+                break;
+            }
+            mem = now;
+        }
+    }
+
+    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;
+    }
+
+    public String getScheme() {
+        return "file";
+    }
+
+    public FilePath createTempFile(String suffix, boolean deleteOnExit, boolean inTempDir)
+            throws IOException {
+        String fileName = name + ".";
+        String prefix = new File(fileName).getName();
+        File dir;
+        if (inTempDir) {
+            dir = new File(System.getProperty("java.io.tmpdir", "."));
+        } else {
+            dir = new File(fileName).getAbsoluteFile().getParentFile();
+        }
+        FileUtils.createDirectories(dir.getAbsolutePath());
+        while (true) {
+            File f = new File(dir, prefix + getNextTempFileNamePart(false) + suffix);
+            if (f.exists() || !f.createNewFile()) {
+                // in theory, the random number could collide
+                getNextTempFileNamePart(true);
+                continue;
+            }
+            if (deleteOnExit) {
+                try {
+                    f.deleteOnExit();
+                } catch (Throwable e) {
+                    // sometimes this throws a NullPointerException
+                    // at java.io.DeleteOnExitHook.add(DeleteOnExitHook.java:33)
+                    // we can ignore it
+                }
+            }
+            return get(f.getCanonicalPath());
+        }
+    }
+
+
+    /**
+     * 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);
+    }
+
+}
+
+/**
+ * Uses java.io.RandomAccessFile to access a file.
+ */
+class FileDisk extends FileBase {
+
+    private final RandomAccessFile file;
+    private final String name;
+
+    FileDisk(String fileName, String mode) throws FileNotFoundException {
+        this.file = new RandomAccessFile(fileName, mode);
+        this.name = fileName;
+    }
+
+    public void force(boolean metaData) throws IOException {
+        file.getFD().sync();
+    }
+
+    public FileChannel truncate(long newLength) throws IOException {
+        if (newLength < file.length()) {
+            // some implementations actually only support truncate
+            file.setLength(newLength);
+        }
+        return this;
+    }
+
+    public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
+        return file.getChannel().tryLock();
+    }
+
+    public void implCloseChannel() throws IOException {
+        file.close();
+    }
+
+    public long position() throws IOException {
+        return file.getFilePointer();
+    }
+
+    public long size() throws IOException {
+        return file.length();
+    }
+
+    public int read(ByteBuffer dst) throws IOException {
+        int len = file.read(dst.array(), dst.position(), dst.remaining());
+        if (len > 0) {
+            dst.position(dst.position() + len);
+        }
+        return len;
+    }
+
+    public FileChannel position(long pos) throws IOException {
+        file.seek(pos);
+        return this;
+    }
+
+    public int write(ByteBuffer src) throws IOException {
+        int len = src.remaining();
+        file.write(src.array(), src.position(), len);
+        src.position(src.position() + len);
+        return len;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+}

Added: 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=1173067&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java Tue Sep 20 10:26:24 2011
@@ -0,0 +1,382 @@
+/*
+ * 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.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.jackrabbit.mk.util.IOUtils;
+
+/**
+ * This utility class contains utility functions that use the file system
+ * abstraction.
+ */
+public class FileUtils {
+
+    /**
+     * Checks if a file exists.
+     * This method is similar to Java 7 <code>java.nio.file.Path.exists</code>.
+     *
+     * @param fileName the file name
+     * @return true if it exists
+     */
+    public static boolean exists(String fileName) {
+        return FilePath.get(fileName).exists();
+    }
+
+    /**
+     * Create a directory (all required parent directories must already exist).
+     * This method is similar to Java 7 <code>java.nio.file.Path.createDirectory</code>.
+     *
+     * @param directoryName the directory name
+     */
+    public static void createDirectory(String directoryName) throws IOException {
+        FilePath.get(directoryName).createDirectory();
+    }
+
+    /**
+     * Create a new file.
+     * This method is similar to Java 7 <code>java.nio.file.Path.createFile</code>, but returns
+     * false instead of throwing a exception if the file already existed.
+     *
+     * @param fileName the file name
+     * @return true if creating was successful
+     */
+    public static boolean createFile(String fileName) {
+        return FilePath.get(fileName).createFile();
+    }
+
+    /**
+     * Delete a file or directory if it exists.
+     * Directories may only be deleted if they are empty.
+     * This method is similar to Java 7 <code>java.nio.file.Path.deleteIfExists</code>.
+     *
+     * @param path the file or directory name
+     */
+    public static void delete(String path) throws IOException {
+        FilePath.get(path).delete();
+    }
+
+    /**
+     * Get the canonical file or directory name.
+     * This method is similar to Java 7 <code>java.nio.file.Path.toRealPath</code>.
+     *
+     * @param fileName the file name
+     * @return the normalized file name
+     */
+    public static String toRealPath(String fileName) throws IOException {
+        return FilePath.get(fileName).toRealPath().toString();
+    }
+
+    /**
+     * Get the parent directory of a file or directory.
+     * This method returns null if there is no parent.
+     * This method is similar to Java 7 <code>java.nio.file.Path.getParent</code>.
+     *
+     * @param fileName the file or directory name
+     * @return the parent directory name
+     */
+    public static String getParent(String fileName) {
+        FilePath p = FilePath.get(fileName).getParent();
+        return p == null ? null : p.toString();
+    }
+
+    /**
+     * Check if the file name includes a path.
+     * This method is similar to Java 7 <code>java.nio.file.Path.isAbsolute</code>.
+     *
+     * @param fileName the file name
+     * @return if the file name is absolute
+     */
+    public static boolean isAbsolute(String fileName) {
+        return FilePath.get(fileName).isAbsolute();
+    }
+
+    /**
+     * Rename a file if this is allowed.
+     * This method is similar to Java 7 <code>java.nio.file.Path.moveTo</code>.
+     *
+     * @param oldName the old fully qualified file name
+     * @param newName the new fully qualified file name
+     */
+    public static void moveTo(String oldName, String newName) throws IOException {
+        FilePath.get(oldName).moveTo(FilePath.get(newName));
+    }
+
+    /**
+     * Get the file or directory name (the last element of the path).
+     * This method is similar to Java 7 <code>java.nio.file.Path.getName</code>.
+     *
+     * @param path the directory and file name
+     * @return just the file name
+     */
+    public static String getName(String path) {
+        return FilePath.get(path).getName();
+    }
+
+    /**
+     * List the files and directories in the given directory.
+     * This method is similar to Java 7 <code>java.nio.file.Path.newDirectoryStream</code>.
+     *
+     * @param path the directory
+     * @return the list of fully qualified file names
+     */
+    public static List<String> newDirectoryStream(String path) throws IOException {
+        List<FilePath> list = FilePath.get(path).newDirectoryStream();
+        int len = list.size();
+        List<String> result = new ArrayList<String>(len);
+        for (int i = 0; i < len; i++) {
+            result.add(list.get(i).toString());
+        }
+        return result;
+    }
+
+    /**
+     * Get the last modified date of a file.
+     * This method is similar to Java 7
+     * <code>java.nio.file.attribute.Attributes.readBasicFileAttributes(file).lastModified().toMillis()</code>
+     *
+     * @param fileName the file name
+     * @return the last modified date
+     */
+    public static long lastModified(String fileName) {
+        return FilePath.get(fileName).lastModified();
+    }
+
+    /**
+     * Get the size of a file in bytes
+     * This method is similar to Java 7
+     * <code>java.nio.file.attribute.Attributes.readBasicFileAttributes(file).size()</code>
+     *
+     * @param fileName the file name
+     * @return the size in bytes
+     */
+    public static long size(String fileName) {
+        return FilePath.get(fileName).size();
+    }
+
+    /**
+     * Check if it is a file or a directory.
+     * <code>java.nio.file.attribute.Attributes.readBasicFileAttributes(file).isDirectory()</code>
+     *
+     * @param fileName the file or directory name
+     * @return true if it is a directory
+     */
+    public static boolean isDirectory(String fileName) {
+        return FilePath.get(fileName).isDirectory();
+    }
+
+    /**
+     * Open a random access file object.
+     * This method is similar to Java 7 <code>java.nio.channels.FileChannel.open</code>.
+     *
+     * @param fileName the file name
+     * @param mode the access mode. Supported are r, rw, rws, rwd
+     * @return the file object
+     */
+    public static FileChannel open(String fileName, String mode) throws IOException {
+        return FilePath.get(fileName).open(mode);
+    }
+
+    /**
+     * Create an input stream to read from the file.
+     * This method is similar to Java 7 <code>java.nio.file.Path.newInputStream</code>.
+     *
+     * @param fileName the file name
+     * @return the input stream
+     */
+    public static InputStream newInputStream(String fileName) throws IOException {
+        return FilePath.get(fileName).newInputStream();
+    }
+
+    /**
+     * Create an output stream to write into the file.
+     * This method is similar to Java 7 <code>java.nio.file.Path.newOutputStream</code>.
+     *
+     * @param fileName the file name
+     * @param append if true, the file will grow, if false, the file will be
+     *            truncated first
+     * @return the output stream
+     */
+    public static OutputStream newOutputStream(String fileName, boolean append) throws IOException {
+        return FilePath.get(fileName).newOutputStream(append);
+    }
+
+    /**
+     * Check if the file is writable.
+     * This method is similar to Java 7
+     * <code>java.nio.file.Path.checkAccess(AccessMode.WRITE)</code>
+     *
+     * @param fileName the file name
+     * @return if the file is writable
+     */
+    public static boolean canWrite(String fileName) {
+        return FilePath.get(fileName).canWrite();
+    }
+
+    // special methods =======================================
+
+    /**
+     * Disable the ability to write. The file can still be deleted afterwards.
+     *
+     * @param fileName the file name
+     * @return true if the call was successful
+     */
+    public static boolean setReadOnly(String fileName) {
+        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 =======================================
+
+    /**
+     * Delete a directory or file and all subdirectories and files.
+     *
+     * @param path the path
+     * @param tryOnly whether errors should  be ignored
+     */
+    public static void deleteRecursive(String path, boolean tryOnly) throws IOException {
+        if (exists(path)) {
+            if (isDirectory(path)) {
+                for (String s : newDirectoryStream(path)) {
+                    deleteRecursive(s, tryOnly);
+                }
+            }
+            if (tryOnly) {
+                tryDelete(path);
+            } else {
+                delete(path);
+            }
+        }
+    }
+
+    /**
+     * Create the directory and all required parent directories.
+     *
+     * @param dir the directory name
+     */
+    public static void createDirectories(String dir) throws IOException {
+        if (dir != null) {
+            if (exists(dir)) {
+                if (!isDirectory(dir)) {
+                    throw new IOException("Could not create directory, " +
+                            "because a file with the same name already exists: " + dir);
+                }
+            } else {
+                String parent = getParent(dir);
+                createDirectories(parent);
+                createDirectory(dir);
+            }
+        }
+    }
+
+    /**
+     * Copy a file from one directory to another, or to another file.
+     *
+     * @param original the original file name
+     * @param copy the file name of the copy
+     */
+    public static void copy(String original, String copy) throws IOException {
+        InputStream in = newInputStream(original);
+        try {
+            OutputStream out = newOutputStream(copy, false);
+            try {
+                IOUtils.copy(in, out);
+            } finally {
+                out.close();
+            }
+        } finally {
+            in.close();
+        }
+    }
+
+    /**
+     * Try to delete a file (ignore errors).
+     *
+     * @param fileName the file name
+     * @return true if it worked
+     */
+    public static boolean tryDelete(String fileName) {
+        try {
+            FilePath.get(fileName).delete();
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * Create a new temporary file.
+     *
+     * @param prefix the prefix of the file name (including directory name if
+     *            required)
+     * @param suffix the suffix
+     * @param deleteOnExit if the file should be deleted when the virtual
+     *            machine exists
+     * @param inTempDir if the file should be stored in the temporary directory
+     * @return the name of the created file
+     */
+    public static String createTempFile(String prefix, String suffix, boolean deleteOnExit, boolean inTempDir)
+            throws IOException {
+        return FilePath.get(prefix).createTempFile(suffix, deleteOnExit, inTempDir).toString();
+    }
+
+    /**
+     * Fully read from the file. This will read all remaining bytes,
+     * or throw an EOFException if not successful.
+     *
+     * @param channel the file channel
+     * @param dst the byte buffer
+     */
+    public static void readFully(FileChannel channel, ByteBuffer dst) throws IOException {
+        do {
+            int r = channel.read(dst);
+            if (r < 0) {
+                throw new EOFException();
+            }
+        } while (dst.remaining() > 0);
+    }
+
+    /**
+     * Fully write to the file. This will write all remaining bytes.
+     *
+     * @param channel the file channel
+     * @param src the byte buffer
+     */
+    public static void writeFully(FileChannel channel, ByteBuffer src) throws IOException {
+        do {
+            channel.write(src);
+        } while (src.remaining() > 0);
+    }
+
+
+}

Added: 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=1173067&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java (added)
+++ jackrabbit/sandbox/microkernel/src/test/java/org/apache/jackrabbit/mk/fs/TestFileSystem.java Tue Sep 20 10:26:24 2011
@@ -0,0 +1,310 @@
+/*
+ * 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.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.List;
+import java.util.Random;
+import org.apache.jackrabbit.mk.util.IOUtilsTest;
+import junit.framework.TestCase;
+
+/**
+ * Tests various file system.
+ */
+public class TestFileSystem extends TestCase {
+
+    private String getBaseDir() {
+        return "target/temp";
+    }
+
+    public void test() throws Exception {
+        testFileSystem(getBaseDir() + "/fs");
+    }
+
+    public void testClasspath() throws IOException {
+        String resource = getClass().getName().replace('.', '/') + ".class";
+        InputStream in;
+        in = getClass().getResourceAsStream("/" + resource);
+        assertTrue(in != null);
+        in.close();
+        in = getClass().getClassLoader().getResourceAsStream(resource);
+        assertTrue(in != null);
+        in.close();
+        in = FileUtils.newInputStream("classpath:" + resource);
+        assertTrue(in != null);
+        in.close();
+        in = FileUtils.newInputStream("classpath:/" + resource);
+        assertTrue(in != null);
+        in.close();
+    }
+
+    public void testSimpleExpandTruncateSize() throws Exception {
+        String f = getBaseDir() + "/fs/test.data";
+        FileUtils.createDirectories(getBaseDir() + "/fs");
+        FileChannel c = FileUtils.open(f, "rw");
+        c.position(4000);
+        c.write(ByteBuffer.wrap(new byte[1]));
+        FileLock lock = c.tryLock();
+        c.truncate(0);
+        if (lock != null) {
+            lock.release();
+        }
+        c.close();
+    }
+
+    public void testUserHome() throws IOException {
+        String fileName = FileUtils.toRealPath("~/test");
+        String userDir = System.getProperty("user.home").replace('\\', '/');
+        assertTrue(fileName.startsWith(userDir));
+    }
+
+    private void testFileSystem(String fsBase) throws Exception {
+        testSimple(fsBase);
+        testTempFile(fsBase);
+        testRandomAccess(fsBase);
+    }
+
+    private void testSimple(final String fsBase) throws Exception {
+        long time = System.currentTimeMillis();
+        for (String s : FileUtils.newDirectoryStream(fsBase)) {
+            FileUtils.delete(s);
+        }
+        FileUtils.createDirectories(fsBase + "/test");
+        FileUtils.delete(fsBase + "/test");
+        FileUtils.delete(fsBase + "/test2");
+        assertTrue(FileUtils.createFile(fsBase + "/test"));
+        List<FilePath> p = FilePath.get(fsBase).newDirectoryStream();
+        assertEquals(1, p.size());
+        String can = FilePath.get(fsBase + "/test").toRealPath().toString();
+        assertEquals(can, p.get(0).toString());
+        assertTrue(FileUtils.canWrite(fsBase + "/test"));
+        FileChannel channel = FileUtils.open(fsBase + "/test", "rw");
+        byte[] buffer = new byte[10000];
+        Random random = new Random(1);
+        random.nextBytes(buffer);
+        channel.write(ByteBuffer.wrap(buffer));
+        assertEquals(10000, channel.size());
+        channel.position(20000);
+        assertEquals(20000, channel.position());
+        assertEquals(-1, channel.read(ByteBuffer.wrap(buffer, 0, 1)));
+        String path = fsBase + "/test";
+        assertEquals("test", FileUtils.getName(path));
+        can = FilePath.get(fsBase).toRealPath().toString();
+        String can2 = FileUtils.toRealPath(FileUtils.getParent(path));
+        assertEquals(can, can2);
+        FileLock lock = channel.tryLock();
+        if (lock != null) {
+            lock.release();
+        }
+        assertEquals(10000, channel.size());
+        channel.close();
+        assertEquals(10000, FileUtils.size(fsBase + "/test"));
+        channel = FileUtils.open(fsBase + "/test", "r");
+        final byte[] test = new byte[10000];
+        FileUtils.readFully(channel, ByteBuffer.wrap(test, 0, 10000));
+        IOUtilsTest.assertEquals(buffer, test);
+        final FileChannel fc = channel;
+        try {
+            fc.write(ByteBuffer.wrap(test, 0, 10));
+            fail();
+        } catch (IOException e) {
+            // expected
+        }
+        try {
+            fc.truncate(10);
+            fail();
+        } catch (IOException e) {
+            // expected
+        }
+        channel.close();
+        long lastMod = FileUtils.lastModified(fsBase + "/test");
+        if (lastMod < time - 1999) {
+            // at most 2 seconds difference
+            assertEquals(time, lastMod);
+        }
+        assertEquals(10000, FileUtils.size(fsBase + "/test"));
+        List<String> list = FileUtils.newDirectoryStream(fsBase);
+        assertEquals(1, list.size());
+        assertTrue(list.get(0).endsWith("test"));
+        FileUtils.copy(fsBase + "/test", fsBase + "/test3");
+        FileUtils.moveTo(fsBase + "/test3", fsBase + "/test2");
+        assertTrue(!FileUtils.exists(fsBase + "/test3"));
+        assertTrue(FileUtils.exists(fsBase + "/test2"));
+        assertEquals(10000, FileUtils.size(fsBase + "/test2"));
+        byte[] buffer2 = new byte[10000];
+        InputStream in = FileUtils.newInputStream(fsBase + "/test2");
+        int pos = 0;
+        while (true) {
+            int l = in.read(buffer2, pos, Math.min(10000 - pos, 1000));
+            if (l <= 0) {
+                break;
+            }
+            pos += l;
+        }
+        in.close();
+        assertEquals(10000, pos);
+        IOUtilsTest.assertEquals(buffer, buffer2);
+
+        assertTrue(FileUtils.tryDelete(fsBase + "/test2"));
+        FileUtils.delete(fsBase + "/test");
+        if (fsBase.indexOf("memFS:") < 0 && fsBase.indexOf("memLZF:") < 0) {
+            FileUtils.createDirectories(fsBase + "/testDir");
+            assertTrue(FileUtils.isDirectory(fsBase + "/testDir"));
+            if (!fsBase.startsWith("jdbc:")) {
+                FileUtils.deleteRecursive(fsBase + "/testDir", false);
+                assertTrue(!FileUtils.exists(fsBase + "/testDir"));
+            }
+        }
+    }
+
+    private void testRandomAccess(String fsBase) throws Exception {
+        testRandomAccess(fsBase, 1);
+    }
+
+    private void testRandomAccess(String fsBase, int seed) throws Exception {
+        StringBuilder buff = new StringBuilder();
+        String s = FileUtils.createTempFile(fsBase + "/tmp", ".tmp", false, false);
+        File file = new File(getBaseDir() + "/tmp");
+        file.getParentFile().mkdirs();
+        file.delete();
+        RandomAccessFile ra = new RandomAccessFile(file, "rw");
+        FileUtils.delete(s);
+        FileChannel f = FileUtils.open(s, "rw");
+        assertEquals(-1, f.read(ByteBuffer.wrap(new byte[1])));
+        f.force(true);
+        Random random = new Random(seed);
+        int size = 500;
+        try {
+            for (int i = 0; i < size; i++) {
+                trace("op " + i);
+                int pos = random.nextInt(10000);
+                switch(random.nextInt(7)) {
+                case 0: {
+                    pos = (int) Math.min(pos, ra.length());
+                    trace("seek " + pos);
+                    buff.append("seek " + pos + "\n");
+                    f.position(pos);
+                    ra.seek(pos);
+                    break;
+                }
+                case 1: {
+                    byte[] buffer = new byte[random.nextInt(1000)];
+                    random.nextBytes(buffer);
+                    trace("write " + buffer.length);
+                    buff.append("write " + buffer.length + "\n");
+                    f.write(ByteBuffer.wrap(buffer));
+                    ra.write(buffer, 0, buffer.length);
+                    break;
+                }
+                case 2: {
+                    trace("truncate " + pos);
+                    f.truncate(pos);
+                    if (pos < ra.length()) {
+                        // truncate is supposed to have no effect if the
+                        // position is larger than the current size
+                        ra.setLength(pos);
+                    }
+                    assertEquals(ra.getFilePointer(), f.position());
+                    buff.append("truncate " + pos + "\n");
+                    break;
+                }
+                case 3: {
+                    int len = random.nextInt(1000);
+                    len = (int) Math.min(len, ra.length() - ra.getFilePointer());
+                    byte[] b1 = new byte[len];
+                    byte[] b2 = new byte[len];
+                    trace("readFully " + len);
+                    ra.readFully(b1, 0, len);
+                    try {
+                        FileUtils.readFully(f, ByteBuffer.wrap(b2, 0, len));
+                    } catch (EOFException e) {
+                        e.printStackTrace();
+                    }
+                    buff.append("readFully " + len + "\n");
+                    IOUtilsTest.assertEquals(b1, b2);
+                    break;
+                }
+                case 4: {
+                    trace("getFilePointer " + ra.getFilePointer());
+                    buff.append("getFilePointer " + ra.getFilePointer() + "\n");
+                    assertEquals(ra.getFilePointer(), f.position());
+                    break;
+                }
+                case 5: {
+                    trace("length " + ra.length());
+                    buff.append("length " + ra.length() + "\n");
+                    assertEquals(ra.length(), f.size());
+                    break;
+                }
+                case 6: {
+                    trace("reopen");
+                    buff.append("reopen\n");
+                    f.close();
+                    ra.close();
+                    ra = new RandomAccessFile(file, "rw");
+                    f = FileUtils.open(s, "rw");
+                    assertEquals(ra.length(), f.size());
+                    break;
+                }
+                default:
+                }
+            }
+        } catch (Throwable e) {
+            e.printStackTrace();
+            fail("Exception: " + e + "\n"+ buff.toString());
+        } finally {
+            f.close();
+            ra.close();
+            file.delete();
+            FileUtils.delete(s);
+        }
+    }
+
+    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);
+        OutputStream out = FileUtils.newOutputStream(s, false);
+        byte[] buffer = new byte[len];
+        out.write(buffer);
+        out.close();
+        out = FileUtils.newOutputStream(s, true);
+        out.write(1);
+        out.close();
+        InputStream in = FileUtils.newInputStream(s);
+        for (int i = 0; i < len; i++) {
+            assertEquals(0, in.read());
+        }
+        assertEquals(1, in.read());
+        assertEquals(-1, in.read());
+        in.close();
+        out.close();
+        FileUtils.delete(s);
+    }
+
+}