| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF 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 org.apache.ignite.internal.util.io; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.Arrays; |
| import org.apache.ignite.internal.util.GridUnsafe; |
| import org.apache.ignite.internal.util.typedef.internal.S; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| |
| import static org.apache.ignite.IgniteSystemProperties.IGNITE_MARSHAL_BUFFERS_RECHECK; |
| import static org.apache.ignite.internal.binary.streams.BinaryMemoryAllocator.DFLT_MARSHAL_BUFFERS_RECHECK; |
| import static org.apache.ignite.internal.util.GridUnsafe.BIG_ENDIAN; |
| import static org.apache.ignite.internal.util.GridUnsafe.BYTE_ARR_OFF; |
| import static org.apache.ignite.internal.util.GridUnsafe.CHAR_ARR_OFF; |
| import static org.apache.ignite.internal.util.GridUnsafe.DOUBLE_ARR_OFF; |
| import static org.apache.ignite.internal.util.GridUnsafe.FLOAT_ARR_OFF; |
| import static org.apache.ignite.internal.util.GridUnsafe.INT_ARR_OFF; |
| import static org.apache.ignite.internal.util.GridUnsafe.LONG_ARR_OFF; |
| import static org.apache.ignite.internal.util.GridUnsafe.SHORT_ARR_OFF; |
| |
| /** |
| * Data output based on {@code Unsafe} operations. |
| */ |
| public class GridUnsafeDataOutput extends OutputStream implements GridDataOutput { |
| /** |
| * Based on {@link ByteArrayOutputStream#MAX_ARRAY_SIZE} or many other similar constants in other classes. |
| * It's not safe to allocate more then this number of elements in byte array, because it can throw |
| * java.lang.OutOfMemoryError: Requested array size exceeds VM limit |
| */ |
| private static final int MAX_BYTE_ARRAY_SIZE = Integer.MAX_VALUE - 8; |
| |
| /** */ |
| private static final long CHECK_FREQ = Long.getLong(IGNITE_MARSHAL_BUFFERS_RECHECK, DFLT_MARSHAL_BUFFERS_RECHECK); |
| |
| /** Length of char buffer (for writing strings). */ |
| private static final int CHAR_BUF_SIZE = 256; |
| |
| /** Char buffer for fast string writes. */ |
| private final char[] cbuf = new char[CHAR_BUF_SIZE]; |
| |
| /** Bytes. */ |
| private byte[] bytes; |
| |
| /** Offset. */ |
| private int off; |
| |
| /** Underlying output stream. */ |
| private OutputStream out; |
| |
| /** Maximum message size. */ |
| private int maxOff; |
| |
| /** Last length check timestamp. */ |
| private long lastCheck = U.currentTimeMillis(); |
| |
| /** |
| * |
| */ |
| public GridUnsafeDataOutput() { |
| // No-op. |
| } |
| |
| /** |
| * @param size Size |
| */ |
| public GridUnsafeDataOutput(int size) { |
| bytes = new byte[size]; |
| } |
| |
| /** |
| * @param bytes Bytes. |
| * @param off Offset. |
| */ |
| public void bytes(byte[] bytes, int off) { |
| this.bytes = bytes; |
| this.off = off; |
| } |
| |
| /** |
| * @param out Underlying output stream. |
| */ |
| @Override public void outputStream(OutputStream out) { |
| this.out = out; |
| |
| off = 0; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public byte[] array() { |
| byte[] bytes0 = new byte[off]; |
| |
| System.arraycopy(bytes, 0, bytes0, 0, off); |
| |
| return bytes0; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public byte[] internalArray() { |
| return bytes; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int offset() { |
| return off; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void offset(int off) { |
| this.off = off; |
| } |
| |
| /** |
| * @param size Size. |
| */ |
| private void requestFreeSize(int size) throws IOException { |
| if (!canBeAllocated(off + size)) |
| throw new IOException("Failed to allocate required memory (byte array size overflow detected) " + |
| "[length=" + size + ", offset=" + off + ']'); |
| |
| size = off + size; |
| |
| maxOff = Math.max(maxOff, size); |
| |
| long now = U.currentTimeMillis(); |
| |
| if (size > bytes.length) { |
| int newSize = size << 1; |
| |
| if (!canBeAllocated(newSize)) |
| newSize = MAX_BYTE_ARRAY_SIZE; |
| |
| bytes = Arrays.copyOf(bytes, newSize); // Grow. |
| } |
| else if (now - lastCheck > CHECK_FREQ) { |
| int halfSize = bytes.length >> 1; |
| |
| if (maxOff < halfSize) |
| bytes = Arrays.copyOf(bytes, halfSize); // Shrink. |
| |
| maxOff = 0; |
| lastCheck = now; |
| } |
| } |
| |
| /** |
| * @param size Size of potential byte array to check. |
| * @return true if {@code new byte[size]} won't throw {@link OutOfMemoryError} given enough heap space. |
| * @see GridUnsafeDataOutput#MAX_BYTE_ARRAY_SIZE |
| */ |
| private boolean canBeAllocated(long size) { |
| return 0 <= size && size <= MAX_BYTE_ARRAY_SIZE; |
| } |
| |
| /** |
| * @param size Size. |
| * @throws IOException In case of error. |
| */ |
| private void onWrite(int size) throws IOException { |
| if (out != null) |
| out.write(bytes, 0, size); |
| else |
| off += size; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void write(byte[] b) throws IOException { |
| requestFreeSize(b.length); |
| |
| System.arraycopy(b, 0, bytes, off, b.length); |
| |
| onWrite(b.length); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void write(byte[] b, int off, int len) throws IOException { |
| requestFreeSize(len); |
| |
| System.arraycopy(b, off, bytes, this.off, len); |
| |
| onWrite(len); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeDoubleArray(double[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| checkArrayAllocationOverflow(8, arr.length, "double"); |
| |
| int bytesToCp = arr.length << 3; |
| |
| requestFreeSize(bytesToCp); |
| |
| if (BIG_ENDIAN) { |
| long off = BYTE_ARR_OFF + this.off; |
| |
| for (double val : arr) { |
| GridUnsafe.putDoubleLE(bytes, off, val); |
| |
| off += 8; |
| } |
| } |
| else |
| GridUnsafe.copyMemory(arr, DOUBLE_ARR_OFF, bytes, BYTE_ARR_OFF + off, bytesToCp); |
| |
| onWrite(bytesToCp); |
| } |
| |
| /** {@inheritDoc} */ |
| @SuppressWarnings("ForLoopReplaceableByForEach") |
| @Override public void writeBooleanArray(boolean[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| for (int i = 0; i < arr.length; i++) |
| writeBoolean(arr[i]); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeCharArray(char[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| checkArrayAllocationOverflow(2, arr.length, "char"); |
| |
| int bytesToCp = arr.length << 1; |
| |
| requestFreeSize(bytesToCp); |
| |
| if (BIG_ENDIAN) { |
| long off = BYTE_ARR_OFF + this.off; |
| |
| for (char val : arr) { |
| GridUnsafe.putCharLE(bytes, off, val); |
| |
| off += 2; |
| } |
| } |
| else |
| GridUnsafe.copyMemory(arr, CHAR_ARR_OFF, bytes, BYTE_ARR_OFF + off, bytesToCp); |
| |
| onWrite(bytesToCp); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeLongArray(long[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| checkArrayAllocationOverflow(8, arr.length, "long"); |
| |
| int bytesToCp = arr.length << 3; |
| |
| requestFreeSize(bytesToCp); |
| |
| if (BIG_ENDIAN) { |
| long off = BYTE_ARR_OFF + this.off; |
| |
| for (long val : arr) { |
| GridUnsafe.putLongLE(bytes, off, val); |
| |
| off += 8; |
| } |
| } |
| else |
| GridUnsafe.copyMemory(arr, LONG_ARR_OFF, bytes, BYTE_ARR_OFF + off, bytesToCp); |
| |
| onWrite(bytesToCp); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeFloatArray(float[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| checkArrayAllocationOverflow(4, arr.length, "float"); |
| |
| int bytesToCp = arr.length << 2; |
| |
| requestFreeSize(bytesToCp); |
| |
| if (BIG_ENDIAN) { |
| long off = BYTE_ARR_OFF + this.off; |
| |
| for (float val : arr) { |
| GridUnsafe.putFloatLE(bytes, off, val); |
| |
| off += 4; |
| } |
| } |
| else |
| GridUnsafe.copyMemory(arr, FLOAT_ARR_OFF, bytes, BYTE_ARR_OFF + off, bytesToCp); |
| |
| onWrite(bytesToCp); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void reset() { |
| off = 0; |
| |
| out = null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeByteArray(byte[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| requestFreeSize(arr.length); |
| |
| System.arraycopy(arr, 0, bytes, off, arr.length); |
| |
| onWrite(arr.length); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeShortArray(short[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| checkArrayAllocationOverflow(2, arr.length, "short"); |
| |
| int bytesToCp = arr.length << 1; |
| |
| requestFreeSize(bytesToCp); |
| |
| if (BIG_ENDIAN) { |
| long off = BYTE_ARR_OFF + this.off; |
| |
| for (short val : arr) { |
| GridUnsafe.putShortLE(bytes, off, val); |
| |
| off += 2; |
| } |
| } |
| else |
| GridUnsafe.copyMemory(arr, SHORT_ARR_OFF, bytes, BYTE_ARR_OFF + off, bytesToCp); |
| |
| onWrite(bytesToCp); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeIntArray(int[] arr) throws IOException { |
| writeInt(arr.length); |
| |
| checkArrayAllocationOverflow(4, arr.length, "int"); |
| |
| int bytesToCp = arr.length << 2; |
| |
| requestFreeSize(bytesToCp); |
| |
| if (BIG_ENDIAN) { |
| long off = BYTE_ARR_OFF + this.off; |
| |
| for (int val : arr) { |
| GridUnsafe.putIntLE(bytes, off, val); |
| |
| off += 4; |
| } |
| } |
| else |
| GridUnsafe.copyMemory(arr, INT_ARR_OFF, bytes, BYTE_ARR_OFF + off, bytesToCp); |
| |
| onWrite(bytesToCp); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void close() throws IOException { |
| reset(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeBoolean(boolean v) throws IOException { |
| requestFreeSize(1); |
| |
| GridUnsafe.putBoolean(bytes, BYTE_ARR_OFF + off, v); |
| |
| onWrite(1); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeByte(int v) throws IOException { |
| requestFreeSize(1); |
| |
| GridUnsafe.putByte(bytes, BYTE_ARR_OFF + off, (byte)v); |
| |
| onWrite(1); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeShort(int v) throws IOException { |
| requestFreeSize(2); |
| |
| short val = (short)v; |
| |
| long off = BYTE_ARR_OFF + this.off; |
| |
| if (BIG_ENDIAN) |
| GridUnsafe.putShortLE(bytes, off, val); |
| else |
| GridUnsafe.putShort(bytes, off, val); |
| |
| onWrite(2); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeChar(int v) throws IOException { |
| requestFreeSize(2); |
| |
| char val = (char)v; |
| |
| long off = BYTE_ARR_OFF + this.off; |
| |
| if (BIG_ENDIAN) |
| GridUnsafe.putCharLE(bytes, off, val); |
| else |
| GridUnsafe.putChar(bytes, off, val); |
| |
| onWrite(2); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeInt(int v) throws IOException { |
| requestFreeSize(4); |
| |
| long off = BYTE_ARR_OFF + this.off; |
| |
| if (BIG_ENDIAN) |
| GridUnsafe.putIntLE(bytes, off, v); |
| else |
| GridUnsafe.putInt(bytes, off, v); |
| |
| onWrite(4); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeLong(long v) throws IOException { |
| requestFreeSize(8); |
| |
| long off = BYTE_ARR_OFF + this.off; |
| |
| if (BIG_ENDIAN) |
| GridUnsafe.putLongLE(bytes, off, v); |
| else |
| GridUnsafe.putLong(bytes, off, v); |
| |
| onWrite(8); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeFloat(float v) throws IOException { |
| int val = Float.floatToIntBits(v); |
| |
| writeInt(val); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeDouble(double v) throws IOException { |
| long val = Double.doubleToLongBits(v); |
| |
| writeLong(val); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void write(int b) throws IOException { |
| writeByte(b); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeBytes(String s) throws IOException { |
| int len = s.length(); |
| |
| writeInt(len); |
| |
| for (int i = 0; i < len; i++) |
| writeByte(s.charAt(i)); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeChars(String s) throws IOException { |
| int len = s.length(); |
| |
| writeInt(len); |
| |
| for (int i = 0; i < len; i++) |
| writeChar(s.charAt(i)); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void writeUTF(String s) throws IOException { |
| writeUTF(s, utfLength(s)); |
| } |
| |
| /** |
| * Check for possible arithmetic overflow when trying to serialize a humongous array. |
| * |
| * @param bytes Number of bytes in a single array element. |
| * @param arrLen Array length. |
| * @param type Type of an array. |
| * @throws IOException If oveflow presents and data corruption can occur. |
| */ |
| private void checkArrayAllocationOverflow(int bytes, int arrLen, String type) throws IOException { |
| long bytesToAlloc = (long)arrLen * bytes; |
| |
| if (!canBeAllocated(bytesToAlloc)) |
| throw new IOException("Failed to allocate required memory for " + type + " array " + |
| "(byte array size overflow detected) [length=" + arrLen + ']'); |
| } |
| |
| /** |
| * |
| * Returns the length in bytes of the UTF encoding of the given string. |
| * |
| * @param s String. |
| * @return UTF encoding length. |
| */ |
| private int utfLength(String s) { |
| int len = s.length(); |
| int utfLen = 0; |
| |
| for (int off = 0; off < len; ) { |
| int size = Math.min(len - off, CHAR_BUF_SIZE); |
| |
| s.getChars(off, off + size, cbuf, 0); |
| |
| for (int pos = 0; pos < size; pos++) { |
| char c = cbuf[pos]; |
| |
| if (c >= 0x0001 && c <= 0x007F) |
| utfLen++; |
| else |
| utfLen += c > 0x07FF ? 3 : 2; |
| } |
| |
| off += size; |
| } |
| |
| return utfLen; |
| } |
| |
| /** |
| * Writes the given string in UTF format. This method is used in |
| * situations where the UTF encoding length of the string is already |
| * known; specifying it explicitly avoids a prescan of the string to |
| * determine its UTF length. |
| * |
| * @param s String. |
| * @param utfLen UTF length encoding. |
| * @throws IOException In case of error. |
| */ |
| private void writeUTF(String s, int utfLen) throws IOException { |
| if (utfLen == s.length()) |
| writeBytes(s); |
| else { |
| writeInt(utfLen); |
| |
| writeUTFBody(s); |
| } |
| } |
| |
| /** |
| * Writes the "body" (i.e., the UTF representation minus the 2-byte or |
| * 8-byte length header) of the UTF encoding for the given string. |
| * |
| * @param s String. |
| * @throws IOException In case of error. |
| */ |
| private void writeUTFBody(String s) throws IOException { |
| int len = s.length(); |
| |
| for (int off = 0; off < len; ) { |
| int csize = Math.min(len - off, CHAR_BUF_SIZE); |
| |
| s.getChars(off, off + csize, cbuf, 0); |
| |
| for (int cpos = 0; cpos < csize; cpos++) { |
| char c = cbuf[cpos]; |
| |
| if (c <= 0x007F && c != 0) |
| write(c); |
| else if (c > 0x07FF) { |
| write(0xE0 | ((c >> 12) & 0x0F)); |
| write(0x80 | ((c >> 6) & 0x3F)); |
| write(0x80 | ((c) & 0x3F)); |
| } |
| else { |
| write(0xC0 | ((c >> 6) & 0x1F)); |
| write(0x80 | ((c) & 0x3F)); |
| } |
| } |
| |
| off += csize; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return S.toString(GridUnsafeDataOutput.class, this); |
| } |
| } |