All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.officefloor.server.stream.StreamBuffer 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.stream;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.time.format.DateTimeFormatter;

import net.officefloor.server.http.ServerHttpConnection;

/**
 * Buffer that is part of a stream.
 * 
 * @param  Type of buffer.
 * @author Daniel Sagenschneider
 */
public abstract class StreamBuffer {

	/**
	 * {@link FileChannel} content buffer.
	 */
	public static class FileBuffer {

		/**
		 * {@link FileChannel}.
		 */
		public final FileChannel file;

		/**
		 * Position within the {@link FileChannel} to write content.
		 */
		public final long position;

		/**
		 * Count of bytes after the position to write from the {@link FileChannel}.
		 * Negative number (eg -1) will write remaining content of
		 * {@link FileChannel}.
		 */
		public final long count;

		/**
		 * Optional {@link FileCompleteCallback}. May be null.
		 */
		public final FileCompleteCallback callback;

		/**
		 * Bytes written. This is used by server implementations to track the number of
		 * bytes written from the {@link FileChannel}.
		 */
		public long bytesWritten = 0;

		/**
		 * Instantiate.
		 * 
		 * @param file     {@link FileChannel}.
		 * @param position Position.
		 * @param count    Count.
		 * @param callback Optional {@link FileCompleteCallback}. May be
		 *                 null.
		 */
		public FileBuffer(FileChannel file, long position, long count, FileCompleteCallback callback) {
			this.file = file;
			this.position = position;
			this.count = count;
			this.callback = callback;
		}

		/**
		 * Instantiate to write the entire {@link FileChannel} content.
		 * 
		 * @param file     {@link FileChannel}.
		 * @param callback Optional {@link FileCompleteCallback}. May be
		 *                 null.
		 */
		public FileBuffer(FileChannel file, FileCompleteCallback callback) {
			this(file, 0, -1, callback);
		}
	}

	/**
	 * Writes all the bytes to the {@link StreamBuffer} stream.
	 * 
	 * @param         Buffer type.
	 * @param bytes      Bytes to be written to the {@link StreamBuffer} stream.
	 * @param headBuffer Head {@link StreamBuffer} in the linked list of
	 *                   {@link StreamBuffer} instances.
	 * @param bufferPool {@link StreamBufferPool} should additional
	 *                   {@link StreamBuffer} instances be required in writing the
	 *                   bytes.
	 */
	public static  void write(byte[] bytes, StreamBuffer headBuffer, StreamBufferPool bufferPool) {
		write(bytes, 0, bytes.length, headBuffer, bufferPool);
	}

	/**
	 * Writes the bytes to the {@link StreamBuffer} stream.
	 * 
	 * @param         Buffer type.
	 * @param bytes      Bytes to be written to the {@link StreamBuffer} stream.
	 * @param offset     Offset into the bytes to start writing.
	 * @param length     Length of bytes to write.
	 * @param headBuffer Head {@link StreamBuffer} in the linked list of
	 *                   {@link StreamBuffer} instances.
	 * @param bufferPool {@link StreamBufferPool} should additional
	 *                   {@link StreamBuffer} instances be required in writing the
	 *                   bytes.
	 */
	public static  void write(byte[] bytes, int offset, int length, StreamBuffer headBuffer,
			StreamBufferPool bufferPool) {

		// Obtain the write stream buffer
		headBuffer = getWriteStreamBuffer(headBuffer, bufferPool);

		// Write the data to the buffer
		int bytesWritten = headBuffer.write(bytes, offset, length);
		length -= bytesWritten;
		while (length > 0) {
			offset += bytesWritten;

			// Append another buffer for remaining content
			headBuffer.next = bufferPool.getPooledStreamBuffer();
			headBuffer = headBuffer.next;

			// Attempt to complete writing bytes
			bytesWritten = headBuffer.write(bytes, offset, length);
			length -= bytesWritten;
		}
	}

	/**
	 * Writes all the {@link CharSequence} to the {@link StreamBuffer} stream.
	 * 
	 * @param         Buffer type.
	 * @param characters Characters to be written to the {@link StreamBuffer}
	 *                   stream.
	 * @param headBuffer Head {@link StreamBuffer} in the linked list of
	 *                   {@link StreamBuffer} instances.
	 * @param bufferPool {@link StreamBufferPool} should additional
	 *                   {@link StreamBuffer} instances be required in writing the
	 *                   bytes.
	 */
	public static  void write(CharSequence characters, StreamBuffer headBuffer, StreamBufferPool bufferPool) {
		write(characters, 0, characters.length(), headBuffer, bufferPool);
	}

	/**
	 * Writes the {@link CharSequence} to the {@link StreamBuffer} stream.
	 *
	 * @param         Buffer type.
	 * @param characters Characters to be written to the {@link StreamBuffer}
	 *                   stream.
	 * @param offset     Offset into the {@link CharSequence} to start writing.
	 * @param length     Length of characters to write.
	 * @param headBuffer Head {@link StreamBuffer} in the linked list of
	 *                   {@link StreamBuffer} instances.
	 * @param bufferPool {@link StreamBufferPool} should additional
	 *                   {@link StreamBuffer} instances be required in writing the
	 *                   bytes.
	 */
	public static  void write(CharSequence characters, int offset, int length, StreamBuffer headBuffer,
			StreamBufferPool bufferPool) {

		// Obtain the write stream buffer
		headBuffer = getWriteStreamBuffer(headBuffer, bufferPool);

		// Write the characters to the buffer
		for (int i = 0; i < length; i++) {
			byte character = (byte) characters.charAt(offset + i);
			headBuffer = writeByte(character, headBuffer, bufferPool);
		}
	}

	/**
	 * HTTP - (minus) value.
	 */
	private static final byte MINUS = "-".getBytes(ServerHttpConnection.HTTP_CHARSET)[0];

	/**
	 * HTTP 0 value.
	 */
	private static final byte ZERO = "0".getBytes(ServerHttpConnection.HTTP_CHARSET)[0];

	/**
	 * HTTP {@link Long#MIN_VALUE} value.
	 */
	private static final byte[] MIN_VALUE = String.valueOf(Long.MIN_VALUE).getBytes(ServerHttpConnection.HTTP_CHARSET);

	/**
	 * Writes a long value to the {@link StreamBuffer}.
	 *
	 * @param         Buffer type.
	 * @param value      Long value to write to the {@link StreamBuffer}.
	 * @param head       Head {@link StreamBuffer} of linked list of
	 *                   {@link StreamBuffer} instances.
	 * @param bufferPool {@link StreamBufferPool}.
	 */
	public static  void write(long value, StreamBuffer head, StreamBufferPool bufferPool) {

		// Determine if min value (as can not make positive)
		if (value == Long.MIN_VALUE) {
			StreamBuffer.write(MIN_VALUE, head, bufferPool);
			return;
		}

		// Obtain the write buffer
		StreamBuffer writeBuffer = StreamBuffer.getWriteStreamBuffer(head, bufferPool);

		// Write sign
		if (value < 0) {
			writeByte(MINUS, writeBuffer, bufferPool);

			// Make positive to write digits
			value = -value;
		}

		// Obtain the one's digit
		byte onesDigit = (byte) (value % 10);
		onesDigit += ZERO;

		// Write the value
		long lessMagnitude = value / 10;
		writeBuffer = recusiveWriteInteger(lessMagnitude, writeBuffer, bufferPool);

		// Always write the first digit
		writeByte(onesDigit, writeBuffer, bufferPool);
	}

	/**
	 * Uses recursion to write the long digits.
	 * 
	 * @param value       Value to write.
	 * @param writeBuffer Write {@link StreamBuffer}.
	 * @param bufferPool  {@link StreamBufferPool}.
	 * @return Next write {@link StreamBuffer}.
	 */
	private static  StreamBuffer recusiveWriteInteger(long value, StreamBuffer writeBuffer,
			StreamBufferPool bufferPool) {

		// Drop out when value at zero
		if (value == 0) {
			return writeBuffer;
		}

		// Not complete, so continue writing the next digit
		long lessMagnitude = value / 10;
		writeBuffer = recusiveWriteInteger(lessMagnitude, writeBuffer, bufferPool);

		// Now write the current digit
		byte currentDigit = (byte) (value % 10);
		currentDigit += ZERO;
		writeBuffer = writeByte(currentDigit, writeBuffer, bufferPool);

		// Return the write buffer
		return writeBuffer;
	}

	/**
	 * 

* Obtains an {@link Appendable} to write to the {@link StreamBuffer} stream. *

* Typical use of this is for the {@link DateTimeFormatter}. * * @param Buffer type. * @param headBuffer Head {@link StreamBuffer} in the linked list of * {@link StreamBuffer} instances. * @param bufferPool {@link StreamBufferPool} should additional * {@link StreamBuffer} instances be required in writing the * bytes. * @return {@link Appendable} to write to the {@link StreamBuffer} stream. */ public static Appendable getAppendable(StreamBuffer headBuffer, StreamBufferPool bufferPool) { StreamBuffer initialWriteBuffer = StreamBuffer.getWriteStreamBuffer(headBuffer, bufferPool); return new Appendable() { private StreamBuffer writeBuffer = initialWriteBuffer; @Override public Appendable append(CharSequence csq, int start, int end) throws IOException { for (int i = start; i < end; i++) { char character = csq.charAt(i); this.append(character); } return this; } @Override public Appendable append(char c) throws IOException { this.writeBuffer = writeByte((byte) c, this.writeBuffer, bufferPool); return this; } @Override public Appendable append(CharSequence csq) throws IOException { return this.append(csq, 0, csq.length()); } }; } /** * Obtains the {@link StreamBuffer} to use for writing. * * @param Buffer type. * @param headBuffer Head {@link StreamBuffer} in the linked list of * {@link StreamBuffer} instances. * @param bufferPool {@link StreamBufferPool} should additional * {@link StreamBuffer} instances be required in writing the * bytes. * @return {@link StreamBuffer} within the linked list to next write data. */ public static StreamBuffer getWriteStreamBuffer(StreamBuffer headBuffer, StreamBufferPool bufferPool) { // Only append to end of linked list while (headBuffer.next != null) { headBuffer = headBuffer.next; } // Ensure writing to pooled buffer if (headBuffer.pooledBuffer == null) { // Not pooled, so append pooled headBuffer.next = bufferPool.getPooledStreamBuffer(); headBuffer = headBuffer.next; } // Return the write stream buffer return headBuffer; } /** * Writes a HTTP encoded character. * * @param Buffer type. * @param character Character to write. * @param writeBuffer Write {@link StreamBuffer}. * @param bufferPool {@link StreamBufferPool}. * @return Next write {@link StreamBuffer}. */ public static StreamBuffer writeByte(byte character, StreamBuffer writeBuffer, StreamBufferPool bufferPool) { // Attempt to write to buffer if (!writeBuffer.write(character)) { // Buffer full, so write to new buffer writeBuffer.next = bufferPool.getPooledStreamBuffer(); writeBuffer = writeBuffer.next; if (!writeBuffer.write(character)) { throw new IllegalStateException("New pooled space buffer should always have space"); } } return writeBuffer; } /** * Obtains the pooled buffer. Will be null if not pooled. */ public final B pooledBuffer; /** * Obtains the non-pooled {@link ByteBuffer}. Will be null if * non-pooled. */ public final ByteBuffer unpooledByteBuffer; /** * Obtains the {@link FileBuffer}. Will be null if not file. */ public final FileBuffer fileBuffer; /** *

* Next {@link StreamBuffer} in the stream. *

* This allows chaining {@link StreamBuffer} instances into a linked list (and * avoids memory management overheads of creating/destroying lists). */ public StreamBuffer next = null; /** * Instantiate. * * @param pooledBuffer Pooled buffer. Must be null if another * buffer provided. * @param unpooledByteBuffer Unpooled {@link ByteBuffer}. Must be * null if another buffer provided. * @param fileBuffer {@link FileBuffer}. Must be null if * another buffer provided. * @throws IllegalArgumentException If not providing the one buffer. */ public StreamBuffer(B pooledBuffer, ByteBuffer unpooledByteBuffer, FileBuffer fileBuffer) { if (pooledBuffer != null) { // Pooled buffer, so ensure only pooled buffer if ((unpooledByteBuffer != null) || (fileBuffer != null)) { throw new IllegalArgumentException("Must provide either a pooled, unpooled or file buffer"); } // Setup for pooled buffer this.pooledBuffer = pooledBuffer; this.unpooledByteBuffer = null; this.fileBuffer = null; } else if (unpooledByteBuffer != null) { // Unpooled buffer, so ensure only unpooled buffer // (pooled checked above) if (fileBuffer != null) { throw new IllegalArgumentException("Must provide either a pooled, unpooled or file buffer"); } // Setup for unpooled buffer this.pooledBuffer = null; this.unpooledByteBuffer = unpooledByteBuffer; this.fileBuffer = null; } else if (fileBuffer != null) { // Above checks others are null // Setup for file buffer this.pooledBuffer = null; this.unpooledByteBuffer = null; this.fileBuffer = fileBuffer; } else { // No buffer provided throw new IllegalArgumentException("Must provide a pooled, unpooled or file buffer"); } } /** * Writes a byte to the pooled buffer. * * @param datum Byte value. * @return true if written value to buffer. false * indicates the pooled buffer is full. */ public abstract boolean write(byte datum); /** * Writes the data to the pooled buffer. * * @param data Data to write to the pooled buffer. * @param offset Offset within the data to write the data. * @param length Length of data to write the data. * @return Number of bytes written. */ public abstract int write(byte[] data, int offset, int length); /** * Writes all the data to the pooled buffer. * * @param data Data to write to the pooled buffer. * @return Number of bytes written. */ public int write(byte[] data) { return this.write(data, 0, data.length); } /** * Releases this {@link StreamBuffer} for re-use. */ public abstract void release(); }