/* * JBoss, Home of Professional Open Source * Copyright 2005-2008, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.messaging.tests.unit.core.journal.impl.fakes; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.jboss.messaging.core.journal.BufferCallback; import org.jboss.messaging.core.journal.IOCallback; import org.jboss.messaging.core.journal.SequentialFile; import org.jboss.messaging.core.journal.SequentialFileFactory; import org.jboss.messaging.core.logging.Logger; /** * * A FakeSequentialFileFactory * * @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a> * @author <a href="mailto:clebert.suconic@jboss.com">Clebert Suconic</a> * */ public class FakeSequentialFileFactory implements SequentialFileFactory { private static final Logger log = Logger.getLogger(FakeSequentialFileFactory.class); // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- private final Map<String, FakeSequentialFile> fileMap = new ConcurrentHashMap<String, FakeSequentialFile>(); private final int alignment; private final boolean supportsCallback; private volatile boolean holdCallbacks; private volatile boolean generateErrors; private final List<CallbackRunnable> callbacksInHold; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- public FakeSequentialFileFactory(final int alignment, final boolean supportsCallback) { this.alignment = alignment; this.supportsCallback = supportsCallback; callbacksInHold = new ArrayList<CallbackRunnable>(); } public FakeSequentialFileFactory() { this(1, false); } // Public -------------------------------------------------------- public SequentialFile createSequentialFile(final String fileName, final int maxAIO) throws Exception { FakeSequentialFile sf = fileMap.get(fileName); if (sf == null) { sf = newSequentialFile(fileName); fileMap.put(fileName, sf); } else { sf.getData().position(0); // log.debug("positioning data to 0"); } return sf; } public void clearBuffer(final ByteBuffer buffer) { final int limit = buffer.limit(); buffer.rewind(); for (int i = 0; i < limit; i++) { buffer.put((byte)0); } buffer.rewind(); } public List<String> listFiles(final String extension) { List<String> files = new ArrayList<String>(); for (String s : fileMap.keySet()) { if (s.endsWith("." + extension)) { files.add(s); } } return files; } public Map<String, FakeSequentialFile> getFileMap() { return fileMap; } public void clear() { fileMap.clear(); } public boolean isSupportsCallbacks() { return supportsCallback; } public ByteBuffer newBuffer(int size) { if (size % alignment != 0) { size = (size / alignment + 1) * alignment; } return ByteBuffer.allocateDirect(size); } public int calculateBlockSize(final int position) { int alignment = getAlignment(); int pos = (position / alignment + (position % alignment != 0 ? 1 : 0)) * alignment; return pos; } public ByteBuffer wrapBuffer(final byte[] bytes) { return ByteBuffer.wrap(bytes); } public boolean isHoldCallbacks() { return holdCallbacks; } public void setHoldCallbacks(final boolean holdCallbacks) { this.holdCallbacks = holdCallbacks; } public boolean isGenerateErrors() { return generateErrors; } public void setGenerateErrors(final boolean generateErrors) { this.generateErrors = generateErrors; } public void flushAllCallbacks() { for (Runnable action : callbacksInHold) { action.run(); } callbacksInHold.clear(); } public void flushCallback(final int position) { Runnable run = callbacksInHold.get(position); run.run(); callbacksInHold.remove(run); } public void setCallbackAsError(final int position) { callbacksInHold.get(position).setSendError(true); } public int getNumberOfCallbacks() { return callbacksInHold.size(); } public int getAlignment() { return alignment; } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- protected FakeSequentialFile newSequentialFile(final String fileName) { return new FakeSequentialFile(fileName); } // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- private class CallbackRunnable implements Runnable { final FakeSequentialFile file; final ByteBuffer bytes; final IOCallback callback; volatile boolean sendError; CallbackRunnable(final FakeSequentialFile file, final ByteBuffer bytes, final IOCallback callback) { this.file = file; this.bytes = bytes; this.callback = callback; } public void run() { if (sendError) { callback.onError(1, "Fake aio error"); } else { try { file.data.put(bytes); if (callback != null) { callback.done(); } if (file.bufferCallback != null) { file.bufferCallback.bufferDone(bytes); } } catch (Throwable e) { e.printStackTrace(); callback.onError(-1, e.getMessage()); } } } public boolean isSendError() { return sendError; } public void setSendError(final boolean sendError) { this.sendError = sendError; } } public class FakeSequentialFile implements SequentialFile { private volatile boolean open; private final String fileName; private ByteBuffer data; private BufferCallback bufferCallback; public ByteBuffer getData() { return data; } public boolean isOpen() { // log.debug("is open" + System.identityHashCode(this) +" open is now " // + open); return open; } public FakeSequentialFile(final String fileName) { this.fileName = fileName; } public void close() throws Exception { open = false; if (data != null) { data.position(0); } } public void delete() throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } close(); fileMap.remove(fileName); } public String getFileName() { return fileName; } public void open() throws Exception { open(0); } public synchronized void open(final int currentMaxIO) throws Exception { open = true; checkAndResize(0); } public void setBufferCallback(final BufferCallback callback) { bufferCallback = callback; } public void fill(final int pos, final int size, final byte fillCharacter) throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } checkAndResize(pos + size); // log.debug("size is " + size + " pos is " + pos); for (int i = pos; i < size + pos; i++) { data.array()[i] = fillCharacter; // log.debug("Filling " + pos + " with char " + fillCharacter); } } public int read(final ByteBuffer bytes) throws Exception { return read(bytes, null); } public int read(final ByteBuffer bytes, final IOCallback callback) throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } byte[] bytesRead = new byte[bytes.limit()]; data.get(bytesRead); bytes.put(bytesRead); bytes.rewind(); if (callback != null) { callback.done(); } return bytesRead.length; } public void position(final int pos) throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } checkAlignment(pos); data.position(pos); } public int position() throws Exception { return data.position(); } public synchronized int write(final ByteBuffer bytes, final IOCallback callback) throws Exception { if (!open) { throw new IllegalStateException("Is closed"); } final int position = data == null ? 0 : data.position(); checkAlignment(position); checkAlignment(bytes.limit()); checkAndResize(bytes.limit() + position); CallbackRunnable action = new CallbackRunnable(this, bytes, callback); if (generateErrors) { action.setSendError(true); } if (holdCallbacks) { callbacksInHold.add(action); } else { action.run(); } return bytes.limit(); } public void sync() throws Exception { if (supportsCallback) { throw new IllegalStateException("sync is not supported when supportsCallback=true"); } } public long size() throws Exception { if (data == null) { return 0; } else { return data.limit(); } } public int write(final ByteBuffer bytes, final boolean sync) throws Exception { return write(bytes, null); } private void checkAndResize(final int size) { int oldpos = data == null ? 0 : data.position(); if (data == null || data.array().length < size) { byte[] newBytes = new byte[size]; if (data != null) { System.arraycopy(data.array(), 0, newBytes, 0, data.array().length); } data = ByteBuffer.wrap(newBytes); data.position(oldpos); } } public int getAlignment() throws Exception { return alignment; } public int calculateBlockStart(final int position) throws Exception { int pos = (position / alignment + (position % alignment != 0 ? 1 : 0)) * alignment; return pos; } @Override public String toString() { return "FakeSequentialFile:" + fileName; } private void checkAlignment(final int position) { if (position % alignment != 0) { throw new IllegalStateException("Position is not aligned to " + alignment); } } } }