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

dorkbox.network.pipeline.ByteBufOutput Maven / Gradle / Ivy

/*
 * Copyright 2010 dorkbox, llc
 *
 * 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.
 *
 * Copyright (c) 2008, Nathan Sweet
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the distribution.
 * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package dorkbox.network.pipeline;

import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Output;
import io.netty.buffer.ByteBuf;

import java.io.DataOutput;
import java.io.OutputStream;

/**
 * An {@link OutputStream} which writes data to a {@link ByteBuf}.
 * 

* A write operation against this stream will occur at the {@code writerIndex} * of its underlying buffer and the {@code writerIndex} will increase during * the write operation. *

* This stream implements {@link DataOutput} for your convenience. * The endianness of the stream is not always big endian but depends on * the endianness of the underlying buffer. * *

* Utility methods are provided for efficiently reading primitive types and strings. * * Modified from KRYO to use ByteBuf. */ public class ByteBufOutput extends Output { private ByteBuf byteBuf; private int startIndex; /** Creates an uninitialized Output. {@link #setBuffer(ByteBuf)} must be called before the Output is used. */ public ByteBufOutput () { } public ByteBufOutput(ByteBuf buffer) { setBuffer(buffer); } public final void setBuffer(ByteBuf byteBuf) { this.byteBuf = byteBuf; if (byteBuf != null) { this.byteBuf.readerIndex(0); startIndex = byteBuf.writerIndex(); } else { startIndex = 0; } } public ByteBuf getByteBuf() { return byteBuf; } @Override @Deprecated public OutputStream getOutputStream () { throw new RuntimeException("Cannot access this method!"); } /** Sets a new OutputStream. The position and total are reset, discarding any buffered bytes. * @param outputStream May be null. */ @Override @Deprecated public void setOutputStream (OutputStream outputStream) { throw new RuntimeException("Cannot access this method!"); } /** Sets the buffer that will be written to. {@link #setBuffer(byte[], int)} is called with the specified buffer's length as the * maxBufferSize. */ @Override @Deprecated public void setBuffer (byte[] buffer) { throw new RuntimeException("Cannot access this method!"); } /** Sets the buffer that will be written to. The position and total are reset, discarding any buffered bytes. The * {@link #setOutputStream(OutputStream) OutputStream} is set to null. * @param maxBufferSize The buffer is doubled as needed until it exceeds maxBufferSize and an exception is thrown. */ @Override @Deprecated public void setBuffer (byte[] buffer, int maxBufferSize) { throw new RuntimeException("Cannot access this method!"); } /** Returns the buffer. The bytes between zero and {@link #position()} are the data that has been written. */ @Override @Deprecated public byte[] getBuffer () { throw new RuntimeException("Cannot access this method!"); } /** Returns a new byte array containing the bytes currently in the buffer between zero and {@link #position()}. */ @Override @Deprecated public byte[] toBytes () { throw new RuntimeException("Cannot access this method!"); } /** Returns the current position in the buffer. This is the number of bytes that have not been flushed. */ @Override public int position () { return byteBuf.writerIndex(); } /** Sets the current position in the buffer. */ @Override @Deprecated public void setPosition (int position) { throw new RuntimeException("Cannot access this method!"); } /** Returns the total number of bytes written. This may include bytes that have not been flushed. */ @Override public long total () { return byteBuf.writerIndex() - startIndex; } /** Sets the position and total to zero. */ @Override public void clear () { byteBuf.readerIndex(0); byteBuf.writerIndex(startIndex); } // OutputStream /** Writes the buffered bytes to the underlying OutputStream, if any. */ @Override @Deprecated public void flush () throws KryoException { // do nothing... } /** Flushes any buffered bytes and closes the underlying OutputStream, if any. */ @Override @Deprecated public void close () throws KryoException { // do nothing... } /** Writes a byte. */ @Override public void write (int value) throws KryoException { byteBuf.writeByte(value); } /** Writes the bytes. Note the byte[] length is not written. */ @Override public void write (byte[] bytes) throws KryoException { if (bytes == null) { throw new IllegalArgumentException("bytes cannot be null."); } writeBytes(bytes, 0, bytes.length); } /** Writes the bytes. Note the byte[] length is not written. */ @Override public void write (byte[] bytes, int offset, int length) throws KryoException { writeBytes(bytes, offset, length); } // byte @Override public void writeByte (byte value) throws KryoException { byteBuf.writeByte(value); } @Override public void writeByte (int value) throws KryoException { byteBuf.writeByte((byte)value); } /** Writes the bytes. Note the byte[] length is not written. */ @Override public void writeBytes (byte[] bytes) throws KryoException { if (bytes == null) { throw new IllegalArgumentException("bytes cannot be null."); } writeBytes(bytes, 0, bytes.length); } /** Writes the bytes. Note the byte[] length is not written. */ @Override public void writeBytes (byte[] bytes, int offset, int count) throws KryoException { if (bytes == null) { throw new IllegalArgumentException("bytes cannot be null."); } byteBuf.writeBytes(bytes, offset, count); } // int /** Writes a 4 byte int. */ @Override public void writeInt (int value) throws KryoException { byteBuf.writeInt(value); } /** Writes a 1-5 byte int. This stream may consider such a variable length encoding request as a hint. It is not guaranteed that * a variable length encoding will be really used. The stream may decide to use native-sized integer representation for * efficiency reasons. * * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (5 bytes). */ @Override public int writeInt (int value, boolean optimizePositive) throws KryoException { return writeVarInt(value, optimizePositive); } /** Writes a 1-5 byte int. It is guaranteed that a varible length encoding will be used. * * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (5 bytes). */ @Override public int writeVarInt (int value, boolean optimizePositive) throws KryoException { ByteBuf buffer = byteBuf; if (!optimizePositive) { value = value << 1 ^ value >> 31; } if (value >>> 7 == 0) { buffer.writeByte((byte)value); return 1; } if (value >>> 14 == 0) { buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7)); return 2; } if (value >>> 21 == 0) { buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14)); return 3; } if (value >>> 28 == 0) { buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21)); return 4; } buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21 | 0x80)); buffer.writeByte((byte)(value >>> 28)); return 5; } // string /** Writes the length and string, or null. Short strings are checked and if ASCII they are written more efficiently, else they * are written as UTF8. If a string is known to be ASCII, {@link #writeAscii(String)} may be used. The string can be read using * {@link ByteBufInput#readString()} or {@link ByteBufInput#readStringBuilder()}. * @param value May be null. */ @Override public void writeString (String value) throws KryoException { if (value == null) { writeByte(0x80); // 0 means null, bit 8 means UTF8. return; } int charCount = value.length(); if (charCount == 0) { writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8. return; } // Detect ASCII. boolean ascii = false; if (charCount > 1 && charCount < 64) { // only snoop 64 chars in ascii = true; for (int i = 0; i < charCount; i++) { int c = value.charAt(i); if (c > 127) { ascii = false; break; } } } ByteBuf buffer = byteBuf; if (buffer.writableBytes() < charCount) { buffer.capacity(buffer.capacity() + charCount + 1); } if (!ascii) { writeUtf8Length(charCount + 1); } int charIndex = 0; // Try to write 8 bit chars. for (; charIndex < charCount; charIndex++) { int c = value.charAt(charIndex); if (c > 127) { break; // whoops! detect ascii. have to continue with a slower method! } buffer.writeByte((byte)c); } if (charIndex < charCount) { writeString_slow(value, charCount, charIndex); } else if (ascii) { // specify it's ASCII int i = buffer.writerIndex() - 1; buffer.setByte(i, buffer.getByte(i) | 0x80); // Bit 8 means end of ASCII. } } /** Writes the length and CharSequence as UTF8, or null. The string can be read using {@link ByteBufInput#readString()} or * {@link ByteBufInput#readStringBuilder()}. * @param value May be null. */ @Override public void writeString (CharSequence value) throws KryoException { if (value == null) { writeByte(0x80); // 0 means null, bit 8 means UTF8. return; } int charCount = value.length(); if (charCount == 0) { writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8. return; } writeUtf8Length(charCount + 1); ByteBuf buffer = byteBuf; if (buffer.writableBytes() < charCount) { buffer.capacity(buffer.capacity() + charCount + 1); } int charIndex = 0; // Try to write 8 bit chars. for (; charIndex < charCount; charIndex++) { int c = value.charAt(charIndex); if (c > 127) { break; // whoops! have to continue with a slower method! } buffer.writeByte((byte)c); } if (charIndex < charCount) { writeString_slow(value, charCount, charIndex); } } /** Writes a string that is known to contain only ASCII characters. Non-ASCII strings passed to this method will be corrupted. * Each byte is a 7 bit character with the remaining byte denoting if another character is available. This is slightly more * efficient than {@link #writeString(String)}. The string can be read using {@link ByteBufInput#readString()} or * {@link ByteBufInput#readStringBuilder()}. * @param value May be null. */ @Override public void writeAscii (String value) throws KryoException { if (value == null) { writeByte(0x80); // 0 means null, bit 8 means UTF8. return; } int charCount = value.length(); if (charCount == 0) { writeByte(1 | 0x80); // 1 means empty string, bit 8 means UTF8. return; } ByteBuf buffer = byteBuf; if (buffer.writableBytes() < charCount) { buffer.capacity(buffer.capacity() + charCount + 1); } int charIndex = 0; // Try to write 8 bit chars. for (; charIndex < charCount; charIndex++) { int c = value.charAt(charIndex); buffer.writeByte((byte)c); } // specify it's ASCII int i = buffer.writerIndex() - 1; buffer.setByte(i, buffer.getByte(i) | 0x80); // Bit 8 means end of ASCII. } /** Writes the length of a string, which is a variable length encoded int except the first byte uses bit 8 to denote UTF8 and * bit 7 to denote if another byte is present. */ private void writeUtf8Length (int value) { if (value >>> 6 == 0) { byteBuf.writeByte((byte)(value | 0x80)); // Set bit 8. } else if (value >>> 13 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value | 0x40 | 0x80)); // Set bit 7 and 8. buffer.writeByte((byte)(value >>> 6)); } else if (value >>> 20 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value | 0x40 | 0x80)); // Set bit 7 and 8. buffer.writeByte((byte)(value >>> 6 | 0x80)); // Set bit 8. buffer.writeByte((byte)(value >>> 13)); } else if (value >>> 27 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value | 0x40 | 0x80)); // Set bit 7 and 8. buffer.writeByte((byte)(value >>> 6 | 0x80)); // Set bit 8. buffer.writeByte((byte)(value >>> 13 | 0x80)); // Set bit 8. buffer.writeByte((byte)(value >>> 20)); } else { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value | 0x40 | 0x80)); // Set bit 7 and 8. buffer.writeByte((byte)(value >>> 6 | 0x80)); // Set bit 8. buffer.writeByte((byte)(value >>> 13 | 0x80)); // Set bit 8. buffer.writeByte((byte)(value >>> 20 | 0x80)); // Set bit 8. buffer.writeByte((byte)(value >>> 27)); } } private void writeString_slow (CharSequence value, int charCount, int charIndex) { ByteBuf buffer = byteBuf; for (; charIndex < charCount; charIndex++) { int c = value.charAt(charIndex); if (c <= 0x007F) { buffer.writeByte((byte)c); } else if (c > 0x07FF) { buffer.writeByte((byte)(0xE0 | c >> 12 & 0x0F)); buffer.writeByte((byte)(0x80 | c >> 6 & 0x3F)); buffer.writeByte((byte)(0x80 | c & 0x3F)); } else { buffer.writeByte((byte)(0xC0 | c >> 6 & 0x1F)); buffer.writeByte((byte)(0x80 | c & 0x3F)); } } } // float /** Writes a 4 byte float. */ @Override public void writeFloat (float value) throws KryoException { writeInt(Float.floatToIntBits(value)); } /** Writes a 1-5 byte float with reduced precision. * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (5 bytes). */ @Override public int writeFloat (float value, float precision, boolean optimizePositive) throws KryoException { return writeInt((int)(value * precision), optimizePositive); } // short /** Writes a 2 byte short. */ @Override public void writeShort (int value) throws KryoException { byteBuf.writeShort(value); } // long /** Writes an 8 byte long. */ @Override public void writeLong (long value) throws KryoException { byteBuf.writeLong(value); } /** Writes a 1-9 byte long. This stream may consider such a variable length encoding request as a hint. It is not guaranteed * that a variable length encoding will be really used. The stream may decide to use native-sized integer representation for * efficiency reasons. * * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (9 bytes). */ @Override public int writeLong (long value, boolean optimizePositive) throws KryoException { return writeVarLong(value, optimizePositive); } /** Writes a 1-9 byte long. It is guaranteed that a varible length encoding will be used. * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (9 bytes). */ @Override public int writeVarLong (long value, boolean optimizePositive) throws KryoException { if (!optimizePositive) { value = value << 1 ^ value >> 63; } if (value >>> 7 == 0) { byteBuf.writeByte((byte)value); return 1; } if (value >>> 14 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7)); return 2; } if (value >>> 21 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14)); return 3; } if (value >>> 28 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21)); return 4; } if (value >>> 35 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21 | 0x80)); buffer.writeByte((byte)(value >>> 28)); return 5; } if (value >>> 42 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21 | 0x80)); buffer.writeByte((byte)(value >>> 28 | 0x80)); buffer.writeByte((byte)(value >>> 35)); return 6; } if (value >>> 49 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21 | 0x80)); buffer.writeByte((byte)(value >>> 28 | 0x80)); buffer.writeByte((byte)(value >>> 35 | 0x80)); buffer.writeByte((byte)(value >>> 42)); return 7; } if (value >>> 56 == 0) { ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21 | 0x80)); buffer.writeByte((byte)(value >>> 28 | 0x80)); buffer.writeByte((byte)(value >>> 35 | 0x80)); buffer.writeByte((byte)(value >>> 42 | 0x80)); buffer.writeByte((byte)(value >>> 49)); return 8; } ByteBuf buffer = byteBuf; buffer.writeByte((byte)(value & 0x7F | 0x80)); buffer.writeByte((byte)(value >>> 7 | 0x80)); buffer.writeByte((byte)(value >>> 14 | 0x80)); buffer.writeByte((byte)(value >>> 21 | 0x80)); buffer.writeByte((byte)(value >>> 28 | 0x80)); buffer.writeByte((byte)(value >>> 35 | 0x80)); buffer.writeByte((byte)(value >>> 42 | 0x80)); buffer.writeByte((byte)(value >>> 49 | 0x80)); buffer.writeByte((byte)(value >>> 56)); return 9; } // boolean /** Writes a 1 byte boolean. */ @Override public void writeBoolean (boolean value) throws KryoException { byteBuf.writeBoolean(value); } // char /** Writes a 2 byte char. */ @Override public void writeChar (char value) throws KryoException { byteBuf.writeChar(value); } // double /** Writes an 8 byte double. */ @Override public void writeDouble (double value) throws KryoException { writeLong(Double.doubleToLongBits(value)); } /** Writes a 1-9 byte double with reduced precision. * @param optimizePositive If true, small positive numbers will be more efficient (1 byte) and small negative numbers will be * inefficient (9 bytes). */ @Override public int writeDouble (double value, double precision, boolean optimizePositive) throws KryoException { return writeLong((long)(value * precision), optimizePositive); } /** Returns the number of bytes that would be written with {@link #writeInt(int, boolean)}. */ static public int intLength (int value, boolean optimizePositive) { if (!optimizePositive) { value = value << 1 ^ value >> 31; } if (value >>> 7 == 0) { return 1; } if (value >>> 14 == 0) { return 2; } if (value >>> 21 == 0) { return 3; } if (value >>> 28 == 0) { return 4; } return 5; } /** Returns the number of bytes that would be written with {@link #writeLong(long, boolean)}. */ static public int longLength (long value, boolean optimizePositive) { if (!optimizePositive) { value = value << 1 ^ value >> 63; } if (value >>> 7 == 0) { return 1; } if (value >>> 14 == 0) { return 2; } if (value >>> 21 == 0) { return 3; } if (value >>> 28 == 0) { return 4; } if (value >>> 35 == 0) { return 5; } if (value >>> 42 == 0) { return 6; } if (value >>> 49 == 0) { return 7; } if (value >>> 56 == 0) { return 8; } return 9; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy