You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by st...@apache.org on 2012/03/23 18:24:13 UTC

svn commit: r1304506 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/mk/fs/ test/java/org/apache/jackrabbit/mk/fs/

Author: stefan
Date: Fri Mar 23 17:24:13 2012
New Revision: 1304506

URL: http://svn.apache.org/viewvc?rev=1304506&view=rev
Log:
cleanup mk

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/fs/FileSystemTest.java

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileBase.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,104 @@
+/*
+ * 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 {
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+        // ignore
+    }
+
+    @Override
+    public FileLock lock(long position, long size, boolean shared) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public abstract long position() throws IOException;
+
+    @Override
+    public abstract FileChannel position(long newPosition) throws IOException;
+
+    @Override
+    public abstract int read(ByteBuffer dst) throws IOException;
+
+    @Override
+    public int read(ByteBuffer dst, long position) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public abstract long size() throws IOException;
+
+    @Override
+    public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public long transferTo(long position, long count, WritableByteChannel target)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public abstract FileChannel truncate(long size) throws IOException;
+
+    @Override
+    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public abstract int write(ByteBuffer src) throws IOException;
+
+    @Override
+    public int write(ByteBuffer src, long position) throws IOException {
+        throw new UnsupportedOperationException();    }
+
+    @Override
+    public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
+        throw new UnsupportedOperationException();    }
+
+    @Override
+    protected void implCloseChannel() throws IOException {
+        // ignore
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileCache.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,211 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.jackrabbit.mk.util.SimpleLRUCache;
+
+/**
+ * A file that has a simple read cache.
+ */
+public class FileCache extends FileBase {
+
+    private static final boolean APPEND_BUFFER = !Boolean.getBoolean("mk.disableAppendBuffer");
+    private static final int APPEND_BUFFER_SIZE_INIT = 8 * 1024;
+    private static final int APPEND_BUFFER_SIZE = 8 * 1024;
+
+    private static final int BLOCK_SIZE = 4 * 1024;
+    private final String name;
+    private final Map<Long, ByteBuffer> readCache = SimpleLRUCache.newInstance(16);
+    private final FileChannel base;
+    private long pos, size;
+
+    private AtomicReference<ByteArrayOutputStream> appendBuffer;
+    private int appendOperations;
+    private Thread appendFlushThread;
+
+    FileCache(String name, FileChannel base) throws IOException {
+        this.name = name;
+        this.base = base;
+        this.size = base.size();
+    }
+
+    @Override
+    public long position() throws IOException {
+        return pos;
+    }
+
+    @Override
+    public FileChannel position(long newPosition) throws IOException {
+        this.pos = newPosition;
+        return this;
+    }
+
+    boolean flush() throws IOException {
+        if (appendBuffer == null) {
+            return false;
+        }
+        synchronized (this) {
+            ByteArrayOutputStream newBuff = new ByteArrayOutputStream(APPEND_BUFFER_SIZE_INIT);
+            ByteArrayOutputStream buff = appendBuffer.getAndSet(newBuff);
+            if (buff.size() > 0) {
+                try {
+                    base.position(size - buff.size());
+                    base.write(ByteBuffer.wrap(buff.toByteArray()));
+                } catch (IOException e) {
+                    close();
+                    throw e;
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+        flush();
+        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;
+    }
+
+    @Override
+    public long size() throws IOException {
+        return size;
+    }
+
+    @Override
+    public FileChannel truncate(long newSize) throws IOException {
+        flush();
+        readCache.clear();
+        base.truncate(newSize);
+        pos = Math.min(pos, newSize);
+        size = Math.min(size, newSize);
+        return this;
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+        if (readCache.size() > 0) {
+            readCache.clear();
+        }
+        // append operations are buffered, but
+        // only if there was at least one successful write operation
+        // (to detect trying to write to a read-only file and such early on)
+        // (in addition to that, the first few append operations are not buffered
+        // to avoid starting a thread unnecessarily)
+        if (APPEND_BUFFER && pos == size && ++appendOperations >= 4) {
+            int len = src.remaining();
+            if (len > APPEND_BUFFER_SIZE) {
+                flush();
+            } else {
+                if (appendBuffer == null) {
+                    ByteArrayOutputStream buff = new ByteArrayOutputStream(APPEND_BUFFER_SIZE_INIT);
+                    appendBuffer = new AtomicReference<ByteArrayOutputStream>(buff);
+                    appendFlushThread = new Thread("Flush " + name) {
+                        @Override
+                        public void run() {
+                            try {
+                                do {
+                                    Thread.sleep(500);
+                                    if (flush()) {
+                                        continue;
+                                    }
+                                } while (!Thread.interrupted());
+                            } catch (Exception e) {
+                                // ignore
+                            }
+                        }
+                    };
+                    appendFlushThread.setDaemon(true);
+                    appendFlushThread.start();
+                }
+                ByteArrayOutputStream buff = appendBuffer.get();
+                if (buff.size() > APPEND_BUFFER_SIZE) {
+                    flush();
+                    buff = appendBuffer.get();
+                }
+                buff.write(src.array(), src.position(), len);
+                pos += len;
+                size += len;
+                return len;
+            }
+        }
+        base.position(pos);
+        int len = base.write(src);
+        pos += len;
+        size = Math.max(size, pos);
+        return len;
+    }
+
+    @Override
+    protected void implCloseChannel() throws IOException {
+        if (appendBuffer != null) {
+            appendFlushThread.interrupt();
+            try {
+                appendFlushThread.join();
+            } catch (InterruptedException e) {
+                // ignore
+            }
+            flush();
+        }
+        base.close();
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+        flush();
+        base.force(metaData);
+    }
+
+    @Override
+    public FileLock tryLock(long position, long size, boolean shared) throws IOException {
+        flush();
+        return base.tryLock(position, size, shared);
+    }
+
+    @Override
+    public String toString() {
+        return "cache:" + base.toString();
+    }
+
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileChannelInputStream.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,70 @@
+/*
+ * 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;
+    }
+
+    @Override
+    public int read() throws IOException {
+        if (channel.position() >= channel.size()) {
+            return -1;
+        }
+        FileUtils.readFully(channel, ByteBuffer.wrap(buffer));
+        return buffer[0] & 0xff;
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        return read(b, 0, b.length);
+    }
+
+    @Override
+    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);
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        n = Math.min(channel.size() - channel.position(), n);
+        channel.position(channel.position() + n);
+        return n;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (closeChannel) {
+            channel.close();
+        }
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePath.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,323 @@
+/*
+ * 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",
+                    "org.apache.jackrabbit.mk.fs.FilePathCache"
+            }) {
+                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
+     */
+    @Override
+    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);
+
+    /**
+     * 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/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathCache.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,37 @@
+/*
+ * 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 {
+
+    @Override
+    public FileChannel open(String mode) throws IOException {
+        return new FileCache(getBase().name, getBase().open(mode));
+    }
+
+    @Override
+    public String getScheme() {
+        return "cache";
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathDisk.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,448 @@
+/*
+ * 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;
+
+    @Override
+    public FilePathDisk getPath(String path) {
+        FilePathDisk p = new FilePathDisk();
+        p.name = translateFileName(path);
+        return p;
+    }
+
+    @Override
+    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('\\', '/');
+        if (fileName.startsWith("file:")) {
+            fileName = fileName.substring("file:".length());
+        }
+        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.startsWith("~") && (fileName.length() == 1 || fileName.startsWith("~/"))) {
+            String userDir = System.getProperty("user.home", "");
+            fileName = userDir + fileName.substring(1);
+        }
+        return fileName;
+    }
+
+    @Override
+    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
+        }
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    public boolean exists() {
+        return new File(name).exists();
+    }
+
+    @Override
+    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);
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    public boolean canWrite() {
+        return canWriteInternal(new File(name));
+    }
+
+    @Override
+    public boolean setReadOnly() {
+        File f = new File(name);
+        return f.setReadOnly();
+    }
+
+    @Override
+    public FilePathDisk toRealPath() throws IOException {
+        String fileName = new File(name).getCanonicalPath();
+        return getPath(fileName);
+    }
+
+    @Override
+    public FilePath getParent() {
+        String p = new File(name).getParent();
+        return p == null ? null : getPath(p);
+    }
+
+    @Override
+    public boolean isDirectory() {
+        return new File(name).isDirectory();
+    }
+
+    @Override
+    public boolean isAbsolute() {
+        return new File(name).isAbsolute();
+    }
+
+    @Override
+    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
+                }
+            }
+        }
+    }
+
+    @Override
+    public void createDirectory() throws IOException {
+        File f = new File(name);
+        if (f.exists()) {
+            if (f.isDirectory()) {
+                return;
+            }
+            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);
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    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;
+        }
+    }
+
+    @Override
+    public FileChannel open(String mode) throws IOException {
+        return new FileDisk(name, mode);
+    }
+
+    @Override
+    public String getScheme() {
+        return "file";
+    }
+
+    @Override
+    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());
+        }
+    }
+
+    @Override
+    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;
+
+    private long pos;
+
+    FileDisk(String fileName, String mode) throws FileNotFoundException {
+        this.file = new RandomAccessFile(fileName, mode);
+        this.name = fileName;
+    }
+
+    @Override
+    public void force(boolean metaData) throws IOException {
+        file.getFD().sync();
+    }
+
+    @Override
+    public FileChannel truncate(long newLength) throws IOException {
+        if (newLength < file.length()) {
+            // some implementations actually only support truncate
+            file.setLength(newLength);
+            pos = Math.min(pos, newLength);
+        }
+        return this;
+    }
+
+    @Override
+    public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
+        return file.getChannel().tryLock();
+    }
+
+    @Override
+    public void implCloseChannel() throws IOException {
+        file.close();
+    }
+
+    @Override
+    public long position() throws IOException {
+        return pos;
+    }
+
+    @Override
+    public long size() throws IOException {
+        return file.length();
+    }
+
+    @Override
+    public int read(ByteBuffer dst) throws IOException {
+        int len = file.read(dst.array(), dst.position(), dst.remaining());
+        if (len > 0) {
+            pos += len;
+            dst.position(dst.position() + len);
+        }
+        return len;
+    }
+
+    @Override
+    public FileChannel position(long pos) throws IOException {
+        if (this.pos != pos) {
+            file.seek(pos);
+            this.pos = pos;
+        }
+        return this;
+    }
+
+    @Override
+    public int write(ByteBuffer src) throws IOException {
+        int len = src.remaining();
+        file.write(src.array(), src.position(), len);
+        src.position(src.position() + len);
+        pos += len;
+        return len;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FilePathWrapper.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,171 @@
+/*
+ * 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;
+
+/**
+ * The base class for wrapping / delegating file systems such as
+ * the split file system.
+ */
+public abstract class FilePathWrapper extends FilePath {
+
+    private FilePath base;
+
+    @Override
+    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 new RuntimeException(e.getMessage(), 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;
+    }
+
+    @Override
+    public boolean canWrite() {
+        return base.canWrite();
+    }
+
+    @Override
+    public void createDirectory() throws IOException {
+        base.createDirectory();
+    }
+
+    @Override
+    public boolean createFile() {
+        return base.createFile();
+    }
+
+    @Override
+    public void delete() throws IOException {
+        base.delete();
+    }
+
+    @Override
+    public boolean exists() {
+        return base.exists();
+    }
+
+    @Override
+    public FilePath getParent() {
+        return wrap(base.getParent());
+    }
+
+    @Override
+    public boolean isAbsolute() {
+        return base.isAbsolute();
+    }
+
+    @Override
+    public boolean isDirectory() {
+        return base.isDirectory();
+    }
+
+    @Override
+    public long lastModified() {
+        return base.lastModified();
+    }
+
+    @Override
+    public FilePath toRealPath() throws IOException {
+        return wrap(base.toRealPath());
+    }
+
+    @Override
+    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;
+    }
+
+    @Override
+    public void moveTo(FilePath newName) throws IOException {
+        base.moveTo(((FilePathWrapper) newName).base);
+    }
+
+    @Override
+    public InputStream newInputStream() throws IOException {
+        return base.newInputStream();
+    }
+
+    @Override
+    public OutputStream newOutputStream(boolean append) throws IOException {
+        return base.newOutputStream(append);
+    }
+
+    @Override
+    public FileChannel open(String mode) throws IOException {
+        return base.open(mode);
+    }
+
+    @Override
+    public boolean setReadOnly() {
+        return base.setReadOnly();
+    }
+
+    @Override
+    public long size() {
+        return base.size();
+    }
+
+    @Override
+    public FilePath createTempFile(String suffix, boolean deleteOnExit, boolean inTempDir)
+            throws IOException {
+        return wrap(base.createTempFile(suffix, deleteOnExit, inTempDir));
+    }
+
+    @Override
+    public FilePath resolve(String other) {
+        return other == null ? this : wrap(base.resolve(other));
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/mk/fs/FileUtils.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,370 @@
+/*
+ * 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();
+    }
+
+    // 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/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/fs/FileSystemTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/fs/FileSystemTest.java?rev=1304506&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/fs/FileSystemTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/mk/fs/FileSystemTest.java Fri Mar 23 17:24:13 2012
@@ -0,0 +1,483 @@
+/*
+ * 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.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.nio.channels.FileChannel.MapMode;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import junit.framework.TestCase;
+
+/**
+ * Tests various file system.
+ */
+public class FileSystemTest extends TestCase {
+
+    private String getBaseDir() {
+        return "target/temp";
+    }
+
+    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 {
+        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 userDir = System.getProperty("user.home").replace('\\', '/');
+        assertTrue(FileUtils.toRealPath("~/test").startsWith(userDir));
+        assertTrue(FileUtils.toRealPath("file:~/test").startsWith(userDir));
+    }
+
+    private void testFileSystem(String fsBase) throws Exception {
+        testAppend(fsBase);
+        testDirectories(fsBase);
+        testMoveTo(fsBase);
+        testParentEventuallyReturnsNull(fsBase);
+        testRandomAccess(fsBase);
+        testResolve(fsBase);
+        testSetReadOnly(fsBase);
+        testSimple(fsBase);
+        testTempFile(fsBase);
+        testUnsupportedFeatures(fsBase);
+    }
+
+    public void testAppend(String fsBase) throws IOException {
+        String fileName = fsBase + "/testFile.txt";
+        if (FileUtils.exists(fileName)) {
+            FileUtils.delete(fileName);
+        }
+        FileUtils.createDirectories(FileUtils.getParent(fileName));
+        FileUtils.createFile(fileName);
+        // Profiler prof = new Profiler();
+        // prof.interval = 1;
+        // prof.startCollecting();
+        FileChannel c = FileUtils.open(fileName, "rw");
+        c.position(0);
+        // long t = System.currentTimeMillis();
+        byte[] array = new byte[100];
+        ByteBuffer buff = ByteBuffer.wrap(array);
+        for (int i = 0; i < 100000; i++) {
+            array[0] = (byte) i;
+            c.write(buff);
+            buff.rewind();
+        }
+        c.close();
+        // System.out.println(fsBase + ": " + (System.currentTimeMillis() - t));
+        // System.out.println(prof.getTop(10));
+        FileUtils.delete(fileName);
+    }
+
+    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 {
+        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));
+        assertTrue(Arrays.equals(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);
+        assertTrue(Arrays.equals(buffer, buffer2));
+
+        assertTrue(FileUtils.tryDelete(fsBase + "/test2"));
+        FileUtils.delete(fsBase + "/test");
+        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("truncate " + pos, 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);
+                    FileUtils.readFully(f, ByteBuffer.wrap(b2, 0, len));
+                    buff.append("readFully " + len + "\n");
+                    assertTrue(Arrays.equals(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 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);
+    }
+
+    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);
+    }
+
+}