
net.officefloor.server.http.mock.MockStreamBufferPool Maven / Gradle / Ivy
/*-
* #%L
* HTTP Server
* %%
* Copyright (C) 2005 - 2020 Daniel Sagenschneider
* %%
* Licensed 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.
* #L%
*/
package net.officefloor.server.http.mock;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import net.officefloor.server.stream.BufferJvmFix;
import net.officefloor.server.stream.ByteBufferFactory;
import net.officefloor.server.stream.FileCompleteCallback;
import net.officefloor.server.stream.StreamBuffer;
import net.officefloor.server.stream.StreamBuffer.FileBuffer;
import net.officefloor.server.stream.StreamBufferPool;
import net.officefloor.test.JUnitAgnosticAssert;
/**
* Mock {@link StreamBufferPool}.
*
* @author Daniel Sagenschneider
*/
public class MockStreamBufferPool implements StreamBufferPool {
/**
* Releases the {@link StreamBuffer} instances.
*
* @param headBuffer Head {@link StreamBuffer} of linked list of
* {@link StreamBuffer} instances.
*/
public static void releaseStreamBuffers(StreamBuffer headBuffer) {
while (headBuffer != null) {
headBuffer.release();
headBuffer = headBuffer.next;
}
}
/**
* Creates an {@link InputStream} to the content of the {@link StreamBuffer}
* instances.
*
* @param headBuffer Head {@link StreamBuffer} of linked list of
* {@link StreamBuffer} instances.
* @return {@link InputStream} to read the data from the {@link StreamBuffer}
* instances.
*/
public static InputStream createInputStream(StreamBuffer headBuffer) {
return new MockBufferPoolInputStream(headBuffer);
}
/**
* Convenience method to obtain the contents of the buffers as a string.
*
* @param headBuffer Head {@link StreamBuffer} of linked list of
* {@link StreamBuffer} instances.
* @param charset {@link Charset} of underlying data.
* @return Content of buffers as string.
*/
public static String getContent(StreamBuffer headBuffer, Charset charset) {
try {
InputStream inputStream = createInputStream(headBuffer);
InputStreamReader reader = new InputStreamReader(inputStream, charset);
StringWriter content = new StringWriter();
for (int character = reader.read(); character != -1; character = reader.read()) {
content.write(character);
}
content.flush();
return content.toString();
} catch (IOException ex) {
return JUnitAgnosticAssert.fail(ex);
}
}
/**
* Identifier for the next {@link StreamBuffer}.
*/
private final AtomicInteger nextBufferId = new AtomicInteger(0);
/**
* {@link ByteBufferFactory}.
*/
private final ByteBufferFactory byteBufferFactory;
/**
* Registered {@link AbstractMockStreamBuffer} instances.
*/
private final Deque createdBuffers = new ConcurrentLinkedDeque<>();
/**
* Instantiate with default buffer size for testing.
*/
public MockStreamBufferPool() {
// small buffer to ensure handling filling buffer
this(() -> ByteBuffer.allocate(3));
}
/**
* Instantiate.
*
* @param byteBufferFactory {@link ByteBufferFactory}.
*/
public MockStreamBufferPool(ByteBufferFactory byteBufferFactory) {
this.byteBufferFactory = byteBufferFactory;
}
/**
* Indicates if there are active {@link StreamBuffer} instances.
*
* @return true
if there are active {@link StreamBuffer} instances
* not released back to this {@link StreamBufferPool}.
*/
public boolean isActiveBuffers() {
// Determine if non-released (active) buffer
for (AbstractMockStreamBuffer buffer : this.createdBuffers) {
if (!buffer.isReleased) {
return true; // not released buffer, so still active
}
}
// All buffers released, so none are active
return false;
}
/**
* Asserts that all {@link StreamBuffer} instances have been released.
*/
public void assertAllBuffersReturned() {
for (AbstractMockStreamBuffer buffer : this.createdBuffers) {
JUnitAgnosticAssert.assertTrue(buffer.isReleased, "Buffer " + buffer.id + " (of "
+ this.createdBuffers.size() + ") should be released" + buffer.getStackTrace());
}
}
/*
* =============== BufferPool =======================
*/
@Override
public StreamBuffer getPooledStreamBuffer() {
MockPooledStreamBuffer buffer = new MockPooledStreamBuffer(this.byteBufferFactory.createByteBuffer());
this.createdBuffers.add(buffer);
return buffer;
}
@Override
public StreamBuffer getUnpooledStreamBuffer(ByteBuffer byteBuffer) {
MockUnpooledStreamBuffer buffer = new MockUnpooledStreamBuffer(byteBuffer);
this.createdBuffers.add(buffer);
return buffer;
}
@Override
public StreamBuffer getFileStreamBuffer(FileChannel file, long position, long count,
FileCompleteCallback callback) {
MockFileStreamBuffer buffer = new MockFileStreamBuffer(new FileBuffer(file, position, count, callback));
this.createdBuffers.add(buffer);
return buffer;
}
@Override
public void close() {
// Nothing to close
}
/**
* Abstract mock {@link StreamBuffer}.
*/
private abstract class AbstractMockStreamBuffer extends StreamBuffer {
/**
* Id of this {@link StreamBuffer}.
*/
private int id;
/**
* Stack trace of the creation of this {@link StreamBuffer}.
*/
private StackTraceElement[] stackTrace;
/**
* Indicates if released.
*/
private volatile boolean isReleased = false;
/**
* Instantiate.
*
* @param pooledBuffer Pooled {@link ByteBuffer}.
* @param unpooledByteBuffer Unpooled {@link ByteBuffer}.
* @param fileBuffer {@link FileBuffer}.
*/
public AbstractMockStreamBuffer(ByteBuffer pooledBuffer, ByteBuffer unpooledByteBuffer, FileBuffer fileBuffer) {
super(pooledBuffer, unpooledByteBuffer, fileBuffer);
this.id = MockStreamBufferPool.this.nextBufferId.getAndIncrement();
// Obtain the stack trace to locate creation
this.stackTrace = new Exception().getStackTrace();
}
/**
* Obtains the stack trace.
*
* @return Stack trace.
*/
protected String getStackTrace() {
StringBuilder trace = new StringBuilder();
trace.append("\n\nStreamBuffer created at:\n");
for (int i = 0; i < this.stackTrace.length; i++) {
trace.append('\t');
trace.append(this.stackTrace[i].toString());
trace.append('\n');
}
return trace.toString();
}
@Override
public void release() {
JUnitAgnosticAssert.assertFalse(this.isReleased,
"StreamBuffer " + this.id + " should only be released once" + this.getStackTrace());
this.isReleased = true;
}
}
/**
* Mock pooled {@link StreamBuffer}.
*/
private class MockPooledStreamBuffer extends AbstractMockStreamBuffer {
/**
* Instantiate.
*
* @param buffer {@link ByteBuffer}.
*/
private MockPooledStreamBuffer(ByteBuffer buffer) {
super(buffer, null, null);
}
/*
* ================== PooledBuffer ======================
*/
@Override
public boolean write(byte datum) {
// Determine if buffer full
if (this.pooledBuffer.remaining() == 0) {
return false;
}
// Add the byte to the buffer
this.pooledBuffer.put(datum);
return true;
}
@Override
public int write(byte[] data, int offset, int length) {
// Obtain the length of data to write
int writeLength = Math.min(length, this.pooledBuffer.remaining());
// Write the data
this.pooledBuffer.put(data, offset, writeLength);
// Return the bytes written
return writeLength;
}
}
/**
* Mock unpooled {@link StreamBuffer}.
*/
private class MockUnpooledStreamBuffer extends AbstractMockStreamBuffer {
/**
* Instantiate.
*
* @param buffer Unpooled {@link ByteBuffer}.
*/
private MockUnpooledStreamBuffer(ByteBuffer buffer) {
super(null, buffer, null);
}
/*
* =================== PooledBuffer ======================
*/
@Override
public boolean write(byte datum) {
return JUnitAgnosticAssert.fail(this.getClass().getSimpleName() + " is unpooled" + this.getStackTrace());
}
@Override
public int write(byte[] data, int offset, int length) {
return JUnitAgnosticAssert.fail(this.getClass().getSimpleName() + " is unpooled" + this.getStackTrace());
}
}
/**
* Mock file {@link StreamBuffer}.
*/
private class MockFileStreamBuffer extends AbstractMockStreamBuffer {
/**
* Instantiate.
*
* @param buffer {@link FileBuffer}.
*/
private MockFileStreamBuffer(FileBuffer buffer) {
super(null, null, buffer);
}
/*
* =================== PooledBuffer ======================
*/
@Override
public boolean write(byte datum) {
return JUnitAgnosticAssert.fail(this.getClass().getSimpleName() + " is file" + this.getStackTrace());
}
@Override
public int write(byte[] data, int offset, int length) {
return JUnitAgnosticAssert.fail(this.getClass().getSimpleName() + " is file" + this.getStackTrace());
}
}
/**
* {@link InputStream} to read in the output content to {@link StreamBuffer}
* instances.
*/
private static class MockBufferPoolInputStream extends InputStream {
/**
* Translate the byte to an int value.
*
* @param value Byte value.
* @return Int value.
*/
private static int byteToInt(byte value) {
return value & 0xff;
}
/**
* Current {@link StreamBuffer} to read contents.
*/
private StreamBuffer currentBuffer = null;
/**
* Position to read next value from current pooled {@link StreamBuffer}
*/
private int currentBufferPosition = 0;
/**
* Used to read batch content from files for improved performance.
*/
private ByteBuffer fileContent = ByteBuffer.allocate(1024);
/**
* Instantiate.
*
* @param headBuffer Head {@link StreamBuffer} of linked list of
* {@link StreamBuffer} instances.
*/
private MockBufferPoolInputStream(StreamBuffer headBuffer) {
this.currentBuffer = headBuffer;
// Ensure nothing to read
BufferJvmFix.position(this.fileContent, BufferJvmFix.limit(this.fileContent));
}
/*
* =================== InputStream ==========================
*/
@Override
public int read() throws IOException {
// Loop until read byte (or end of stream)
for (;;) {
// Determine if completed stream
if (this.currentBuffer == null) {
return -1; // end of stream
}
// Attempt to obtain value from current buffer
if (this.currentBuffer.pooledBuffer != null) {
// Obtain the pooled data
ByteBuffer bufferData = this.currentBuffer.pooledBuffer;
// Determine if can read data from buffer
if (this.currentBufferPosition < BufferJvmFix.position(bufferData)) {
// Read the data from the buffer
return byteToInt(bufferData.get(this.currentBufferPosition++));
}
} else if (this.currentBuffer.unpooledByteBuffer != null) {
// Attempt to read from unpooled byte buffer
ByteBuffer byteBuffer = this.currentBuffer.unpooledByteBuffer;
// Determine if can read from byte buffer
if (byteBuffer.remaining() > 0) {
return byteToInt(byteBuffer.get());
}
} else {
// Attempt to read from file buffer
FileBuffer fileBuffer = this.currentBuffer.fileBuffer;
// Determine if can read data from buffer
long count = (fileBuffer.count < 0) ? fileBuffer.file.size() : fileBuffer.count;
if (this.currentBufferPosition < count) {
// Read data from the buffer
long position = fileBuffer.position + this.currentBufferPosition;
this.currentBufferPosition++;
// Handle batch read from file for performance
if (!this.fileContent.hasRemaining()) {
this.fileContent.clear();
fileBuffer.file.read(this.fileContent, position);
this.fileContent.flip();
}
return byteToInt(this.fileContent.get());
}
// As here, all file content read (so complete)
if (fileBuffer.callback != null) {
fileBuffer.callback.complete(fileBuffer.file, true);
}
}
// Obtain the next buffer to read
this.currentBuffer = this.currentBuffer.next;
this.currentBufferPosition = 0;
if (this.currentBuffer == null) {
this.currentBuffer = null;
return -1; // end of stream
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy