/*
* JCarder -- cards Java programs to keep threads disentangled
*
* Copyright (C) 2006-2007 Enea AB
* Copyright (C) 2007 Ulrik Svensson
* Copyright (C) 2007 Joel Rosdahl
*
* This program is made available under the GNU GPL version 2, with a special
* exception for linking with JUnit. See the accompanying file LICENSE.txt for
* details.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
package com.enea.jcarder.common.contexts;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import net.jcip.annotations.ThreadSafe;
import com.enea.jcarder.common.Lock;
import com.enea.jcarder.common.LockingContext;
import com.enea.jcarder.util.logging.Logger;
@ThreadSafe
public final class ContextFileWriter
implements ContextWriterIfc {
private final FileChannel mChannel;
private int mNextFilePosition = 0;
private final Logger mLogger;
private ByteBuffer mBuffer = ByteBuffer.allocateDirect(8192);
private boolean mShutdownHookExecuted = false;
public ContextFileWriter(Logger logger, File file) throws IOException {
mLogger = logger;
mLogger.info("Opening for writing: " + file.getAbsolutePath());
RandomAccessFile raFile = new RandomAccessFile(file, "rw");
raFile.setLength(0);
mChannel = raFile.getChannel();
writeHeader();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() { shutdownHook(); }
});
}
private synchronized void shutdownHook() {
try {
if (mChannel.isOpen()) {
writeBuffer();
}
} catch (IOException e) {
e.printStackTrace();
}
mShutdownHookExecuted = true;
}
private void writeBuffer() throws IOException {
mBuffer.flip();
mChannel.write(mBuffer);
while (mBuffer.hasRemaining()) {
Thread.yield();
mChannel.write(mBuffer);
}
mBuffer.clear();
}
private void writeHeader() throws IOException {
mBuffer.putLong(ContextFileReader.MAGIC_COOKIE);
mBuffer.putInt(ContextFileReader.MAJOR_VERSION);
mBuffer.putInt(ContextFileReader.MINOR_VERSION);
mNextFilePosition += 8 + 4 + 4;
}
public synchronized void close() throws IOException {
writeBuffer();
mChannel.close();
}
private void writeString(String s) throws IOException {
ByteBuffer encodedString = ContextFileReader.CHARSET.encode(s);
final int length = encodedString.remaining();
assureBufferCapacity(4 + length);
mBuffer.putInt(length);
mBuffer.put(encodedString);
mNextFilePosition += 4 + length;
}
private void writeInteger(int i) throws IOException {
assureBufferCapacity(4);
mBuffer.putInt(i);
mNextFilePosition += 4;
}
private void assureBufferCapacity(int size) throws IOException {
if (mBuffer.remaining() < size || mShutdownHookExecuted) {
writeBuffer();
}
// Grow buffer if it can't hold the requested size.
while (mBuffer.capacity() < size) {
mBuffer = ByteBuffer.allocateDirect(2 * mBuffer.capacity());
}
}
public synchronized int writeLock(Lock lock) throws IOException {
final int startPosition = mNextFilePosition;
writeString(lock.getClassName());
writeInteger(lock.getObjectId());
flushBufferIfNeeded();
return startPosition;
}
public synchronized int writeContext(LockingContext context)
throws IOException {
final int startPosition = mNextFilePosition;
writeString(context.getThreadName());
writeString(context.getLockReference());
writeString(context.getMethodWithClass());
flushBufferIfNeeded();
return startPosition;
}
private void flushBufferIfNeeded() throws IOException {
if (mShutdownHookExecuted) {
writeBuffer();
}
}
}