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);
+ }
+
+}