Java tutorial
/* * 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}. * <p> * 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. * <p> * 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. * * <p> * 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; } }