/* * Copyright 2014 WANdisco * * WANdisco 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 c5db.log; import com.google.common.io.CountingInputStream; import com.google.common.primitives.Ints; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; /** * A BytePersistence implementation entirely within memory, for testing. This implementation * does not write anything to disk. It is not designed with efficiency in mind, either; * it freely does full copies of the underlying byte array. */ class ByteArrayPersistence implements LogPersistenceService.BytePersistence { private ByteArrayOutputStream stream; private volatile boolean closed; public ByteArrayPersistence() { stream = new ByteArrayOutputStream(); } /** * Alter the byte at the specified position. The purpose of this method is to test e.g. CRCs. */ public void corrupt(int position) throws IOException { byte[] bytes = stream.toByteArray(); bytes[position] = (byte) (bytes[position] ^ 0x01); stream = new ByteArrayOutputStream(bytes.length); stream.write(bytes); } @Override public boolean isEmpty() throws IOException { return stream.size() == 0; } @Override public long size() throws IOException { return stream.size(); } @Override public void append(ByteBuffer[] buffers) throws IOException { ensureNotClosed(); for (ByteBuffer buffer : buffers) { byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); stream.write(bytes); } } @Override public LogPersistenceService.PersistenceReader getReader() { return new ByteArrayPersistenceReader(stream.toByteArray()); } @Override public void truncate(long size) throws IOException { ensureNotClosed(); int intSize = Ints.checkedCast(size); byte[] bytes = stream.toByteArray(); stream = new ByteArrayOutputStream(intSize); stream.write(bytes, 0, intSize); } @Override public void sync() throws IOException { ensureNotClosed(); } @Override public void close() throws IOException { // With ByteArrayOutputStream, this is actually a no-op: stream.close(); closed = true; } public static class ByteArrayPersistenceReader implements LogPersistenceService.PersistenceReader { private final CountingInputStream stream; private final ReadableByteChannel channel; public ByteArrayPersistenceReader(byte[] bytes) { stream = new CountingInputStream(new ByteArrayInputStream(bytes)); stream.mark(bytes.length); channel = Channels.newChannel(stream); } @Override public long position() throws IOException { return stream.getCount(); } @Override public void position(long newPos) throws IOException { stream.reset(); long actualSkipped = stream.skip(newPos); if (actualSkipped != newPos) { throw new IllegalArgumentException("Trying to set the reader position beyond the end of the readable bytes"); } } @Override public int read(ByteBuffer dst) throws IOException { return channel.read(dst); } @Override public boolean isOpen() { return channel.isOpen(); } @Override public void close() throws IOException { channel.close(); } } private void ensureNotClosed() throws IOException { if (closed) { throw new IOException("attempted to use ByteArrayPersistence, but it is closed"); } } }