/* * 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.h2.test.utils; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.util.List; import org.h2.store.fs.FileBase; import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePathWrapper; /** * A debugging file system that logs all operations. */ public class FilePathDebug extends FilePathWrapper { private static final FilePathDebug INSTANCE = new FilePathDebug(); private static final IOException POWER_OFF = new IOException("Simulated power failure"); private int powerOffCount; private boolean trace; /** * Register the file system. * * @return the instance */ public static FilePathDebug register() { FilePath.register(INSTANCE); return INSTANCE; } /** * Check if the simulated power failure occurred. * This call will decrement the countdown. * * @throws IOException if the simulated power failure occurred */ void checkPowerOff() throws IOException { if (powerOffCount == 0) { return; } if (powerOffCount > 1) { powerOffCount--; return; } powerOffCount = -1; // throw new IOException("Simulated power failure"); throw POWER_OFF; } public void createDirectory() { trace(name, "createDirectory"); super.createDirectory(); } public boolean createFile() { trace(name, "createFile"); return super.createFile(); } public void delete() { trace(name, "fileName"); super.delete(); } public boolean exists() { trace(name, "exists"); return super.exists(); } public String getName() { trace(name, "getName"); return super.getName(); } public long lastModified() { trace(name, "lastModified"); return super.lastModified(); } public FilePath getParent() { trace(name, "getParent"); return super.getParent(); } public boolean isAbsolute() { trace(name, "isAbsolute"); return super.isAbsolute(); } public boolean isDirectory() { trace(name, "isDirectory"); return super.isDirectory(); } public boolean canWrite() { trace(name, "canWrite"); return super.canWrite(); } public boolean setReadOnly() { trace(name, "setReadOnly"); return super.setReadOnly(); } public long size() { trace(name, "size"); return super.size(); } public List<FilePath> newDirectoryStream() { trace(name, "newDirectoryStream"); return super.newDirectoryStream(); } public FilePath toRealPath() { trace(name, "toRealPath"); return super.toRealPath(); } public InputStream newInputStream() throws IOException { trace(name, "newInputStream"); InputStream in = super.newInputStream(); if (!trace) { return in; } final String fileName = name; return new FilterInputStream(in) { public int read(byte[] b) throws IOException { trace(fileName, "in.read(b)"); return super.read(b); } public int read(byte[] b, int off, int len) throws IOException { trace(fileName, "in.read(b)", "in.read(b, " + off + ", " + len + ")"); return super.read(b, off, len); } public long skip(long n) throws IOException { trace(fileName, "in.read(b)", "in.skip(" + n + ")"); return super.skip(n); } }; } public FileChannel open(String mode) throws IOException { trace(name, "open", mode); return new FileDebug(this, super.open(mode), name); } public OutputStream newOutputStream(boolean append) { trace(name, "newOutputStream", append); return super.newOutputStream(append); } public void moveTo(FilePath newName) { trace(name, "moveTo", unwrap(((FilePathDebug) newName).name)); super.moveTo(newName); } public FilePath createTempFile(String suffix, boolean deleteOnExit, boolean inTempDir) throws IOException { trace(name, "createTempFile", suffix, deleteOnExit, inTempDir); return super.createTempFile(suffix, deleteOnExit, inTempDir); } /** * Print a debug message. * * @param fileName the (wrapped) file name * @param method the method name * @param params parameters if any */ void trace(String fileName, String method, Object... params) { if (trace) { StringBuilder buff = new StringBuilder(" "); buff.append(unwrap(fileName)).append(' ').append(method); for (Object s : params) { buff.append(' ').append(s); } System.out.println(buff); } } public void setPowerOffCount(int count) { this.powerOffCount = count; } public int getPowerOffCount() { return powerOffCount; } public boolean isTrace() { return trace; } public void setTrace(boolean trace) { this.trace = trace; } public String getScheme() { return "debug"; } } /** * A debugging file that logs all operations. */ class FileDebug extends FileBase { private final FilePathDebug debug; private final FileChannel channel; private final String name; FileDebug(FilePathDebug debug, FileChannel channel, String name) { this.debug = debug; this.channel = channel; this.name = debug.getScheme() + ":" + name; } public void implCloseChannel() throws IOException { debug("close"); channel.close(); } public long position() throws IOException { debug("getFilePointer"); return channel.position(); } public long size() throws IOException { debug("length"); return channel.size(); } public int read(ByteBuffer dst) throws IOException { debug("read", channel.position(), dst.position(), dst.remaining()); return channel.read(dst); } public FileChannel position(long pos) throws IOException { debug("seek", pos); channel.position(pos); return this; } public FileChannel truncate(long newLength) throws IOException { checkPowerOff(); debug("truncate", newLength); channel.truncate(newLength); return this; } public void force(boolean metaData) throws IOException { debug("force"); channel.force(metaData); } public int write(ByteBuffer src) throws IOException { checkPowerOff(); debug("write", channel.position(), src.position(), src.remaining()); return channel.write(src); } private void debug(String method, Object... params) { debug.trace(name, method, params); } private void checkPowerOff() throws IOException { try { debug.checkPowerOff(); } catch (IOException e) { try { channel.close(); } catch (IOException e2) { // ignore } throw e; } } public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { debug("tryLock"); return channel.tryLock(); } }