| /* |
| * 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.cassandra.io.util; |
| |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.io.UTFDataFormatException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.WritableByteChannel; |
| |
| import org.apache.cassandra.utils.memory.MemoryUtil; |
| |
| import com.google.common.base.Function; |
| |
| /** |
| * Base class for DataOutput implementations that does not have an optimized implementations of Plus methods |
| * and does no buffering. |
| * <p> |
| * Unlike BufferedDataOutputStreamPlus this is capable of operating as an unbuffered output stream. |
| * Currently necessary because SequentialWriter implements its own buffering along with mark/reset/truncate. |
| * </p> |
| */ |
| public abstract class UnbufferedDataOutputStreamPlus extends DataOutputStreamPlus |
| { |
| private static final byte[] zeroBytes = new byte[2]; |
| |
| protected UnbufferedDataOutputStreamPlus() |
| { |
| super(); |
| } |
| |
| protected UnbufferedDataOutputStreamPlus(WritableByteChannel channel) |
| { |
| super(channel); |
| } |
| |
| /* |
| !! DataOutput methods below are copied from the implementation in Apache Harmony RandomAccessFile. |
| */ |
| |
| /** |
| * Writes the entire contents of the byte array <code>buffer</code> to |
| * this RandomAccessFile starting at the current file pointer. |
| * |
| * @param buffer the buffer to be written. |
| * @throws IOException If an error occurs trying to write to this RandomAccessFile. |
| */ |
| public void write(byte[] buffer) throws IOException |
| { |
| write(buffer, 0, buffer.length); |
| } |
| |
| /** |
| * Writes <code>count</code> bytes from the byte array <code>buffer</code> |
| * starting at <code>offset</code> to this RandomAccessFile starting at |
| * the current file pointer.. |
| * |
| * @param buffer the bytes to be written |
| * @param offset offset in buffer to get bytes |
| * @param count number of bytes in buffer to write |
| * @throws IOException If an error occurs attempting to write to this |
| * RandomAccessFile. |
| * @throws IndexOutOfBoundsException If offset or count are outside of bounds. |
| */ |
| public abstract void write(byte[] buffer, int offset, int count) throws IOException; |
| |
| /** |
| * Writes the specified byte <code>oneByte</code> to this RandomAccessFile |
| * starting at the current file pointer. Only the low order byte of |
| * <code>oneByte</code> is written. |
| * |
| * @param oneByte the byte to be written |
| * @throws IOException If an error occurs attempting to write to this |
| * RandomAccessFile. |
| */ |
| public abstract void write(int oneByte) throws IOException; |
| |
| /** |
| * Writes a boolean to this output stream. |
| * |
| * @param val the boolean value to write to the OutputStream |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeBoolean(boolean val) throws IOException |
| { |
| write(val ? 1 : 0); |
| } |
| |
| /** |
| * Writes a 8-bit byte to this output stream. |
| * |
| * @param val the byte value to write to the OutputStream |
| * @throws java.io.IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeByte(int val) throws IOException |
| { |
| write(val & 0xFF); |
| } |
| |
| /** |
| * Writes the low order 8-bit bytes from a String to this output stream. |
| * |
| * @param str the String containing the bytes to write to the OutputStream |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeBytes(String str) throws IOException |
| { |
| byte bytes[] = new byte[str.length()]; |
| for (int index = 0; index < str.length(); index++) |
| { |
| bytes[index] = (byte) (str.charAt(index) & 0xFF); |
| } |
| write(bytes); |
| } |
| |
| /** |
| * Writes the specified 16-bit character to the OutputStream. Only the lower |
| * 2 bytes are written with the higher of the 2 bytes written first. This |
| * represents the Unicode value of val. |
| * |
| * @param val the character to be written |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeChar(int val) throws IOException |
| { |
| write((val >>> 8) & 0xFF); |
| write((val >>> 0) & 0xFF); |
| } |
| |
| /** |
| * Writes the specified 16-bit characters contained in str to the |
| * OutputStream. Only the lower 2 bytes of each character are written with |
| * the higher of the 2 bytes written first. This represents the Unicode |
| * value of each character in str. |
| * |
| * @param str the String whose characters are to be written. |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeChars(String str) throws IOException |
| { |
| byte newBytes[] = new byte[str.length() * 2]; |
| for (int index = 0; index < str.length(); index++) |
| { |
| int newIndex = index == 0 ? index : index * 2; |
| newBytes[newIndex] = (byte) ((str.charAt(index) >> 8) & 0xFF); |
| newBytes[newIndex + 1] = (byte) (str.charAt(index) & 0xFF); |
| } |
| write(newBytes); |
| } |
| |
| /** |
| * Writes a 64-bit double to this output stream. The resulting output is the |
| * 8 bytes resulting from calling Double.doubleToLongBits(). |
| * |
| * @param val the double to be written. |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeDouble(double val) throws IOException |
| { |
| writeLong(Double.doubleToLongBits(val)); |
| } |
| |
| /** |
| * Writes a 32-bit float to this output stream. The resulting output is the |
| * 4 bytes resulting from calling Float.floatToIntBits(). |
| * |
| * @param val the float to be written. |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeFloat(float val) throws IOException |
| { |
| writeInt(Float.floatToIntBits(val)); |
| } |
| |
| /** |
| * Writes a 32-bit int to this output stream. The resulting output is the 4 |
| * bytes, highest order first, of val. |
| * |
| * @param val the int to be written. |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public void writeInt(int val) throws IOException |
| { |
| write((val >>> 24) & 0xFF); |
| write((val >>> 16) & 0xFF); |
| write((val >>> 8) & 0xFF); |
| write((val >>> 0) & 0xFF); |
| } |
| |
| /** |
| * Writes a 64-bit long to this output stream. The resulting output is the 8 |
| * bytes, highest order first, of val. |
| * |
| * @param val the long to be written. |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public void writeLong(long val) throws IOException |
| { |
| write((int) (val >>> 56) & 0xFF); |
| write((int) (val >>> 48) & 0xFF); |
| write((int) (val >>> 40) & 0xFF); |
| write((int) (val >>> 32) & 0xFF); |
| write((int) (val >>> 24) & 0xFF); |
| write((int) (val >>> 16) & 0xFF); |
| write((int) (val >>> 8) & 0xFF); |
| write((int) (val >>> 0) & 0xFF); |
| } |
| |
| /** |
| * Writes the specified 16-bit short to the OutputStream. Only the lower 2 |
| * bytes are written with the higher of the 2 bytes written first. |
| * |
| * @param val the short to be written |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public void writeShort(int val) throws IOException |
| { |
| writeChar(val); |
| } |
| |
| /** |
| * Writes the specified String out in UTF format to the provided DataOutput |
| * |
| * @param str the String to be written in UTF format. |
| * @param out the DataOutput to write the UTF encoded string to |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public static void writeUTF(String str, DataOutput out) throws IOException |
| { |
| int length = str.length(); |
| if (length == 0) |
| { |
| out.write(zeroBytes); |
| return; |
| } |
| |
| int utfCount = 0; |
| int maxSize = 2; |
| for (int i = 0 ; i < length ; i++) |
| { |
| int ch = str.charAt(i); |
| if ((ch > 0) & (ch <= 127)) |
| utfCount += 1; |
| else if (ch <= 2047) |
| utfCount += 2; |
| else |
| utfCount += maxSize = 3; |
| } |
| |
| if (utfCount > 65535) |
| throw new UTFDataFormatException(); //$NON-NLS-1$ |
| |
| byte[] utfBytes = retrieveTemporaryBuffer(utfCount + 2); |
| |
| int bufferLength = utfBytes.length; |
| if (utfCount == length) |
| { |
| utfBytes[0] = (byte) (utfCount >> 8); |
| utfBytes[1] = (byte) utfCount; |
| int firstIndex = 2; |
| for (int offset = 0 ; offset < length ; offset += bufferLength) |
| { |
| int runLength = Math.min(bufferLength - firstIndex, length - offset) + firstIndex; |
| offset -= firstIndex; |
| for (int i = firstIndex ; i < runLength; i++) |
| utfBytes[i] = (byte) str.charAt(offset + i); |
| out.write(utfBytes, 0, runLength); |
| firstIndex = 0; |
| } |
| } |
| else |
| { |
| int utfIndex = 2; |
| int offset = 0; |
| utfBytes[0] = (byte) (utfCount >> 8); |
| utfBytes[1] = (byte) utfCount; |
| |
| while (length > 0) |
| { |
| int charRunLength = (utfBytes.length - utfIndex) / maxSize; |
| if (charRunLength < 128 && charRunLength < length) |
| { |
| out.write(utfBytes, 0, utfIndex); |
| utfIndex = 0; |
| } |
| if (charRunLength > length) |
| charRunLength = length; |
| |
| for (int i = 0 ; i < charRunLength ; i++) |
| { |
| char ch = str.charAt(offset + i); |
| if ((ch > 0) && (ch <= 127)) |
| { |
| utfBytes[utfIndex++] = (byte) ch; |
| } |
| else if (ch <= 2047) |
| { |
| utfBytes[utfIndex++] = (byte) (0xc0 | (0x1f & (ch >> 6))); |
| utfBytes[utfIndex++] = (byte) (0x80 | (0x3f & ch)); |
| } |
| else |
| { |
| utfBytes[utfIndex++] = (byte) (0xe0 | (0x0f & (ch >> 12))); |
| utfBytes[utfIndex++] = (byte) (0x80 | (0x3f & (ch >> 6))); |
| utfBytes[utfIndex++] = (byte) (0x80 | (0x3f & ch)); |
| } |
| } |
| |
| offset += charRunLength; |
| length -= charRunLength; |
| } |
| |
| out.write(utfBytes, 0, utfIndex); |
| } |
| } |
| |
| /** |
| * Writes the specified String out in UTF format. |
| * |
| * @param str the String to be written in UTF format. |
| * @throws IOException If an error occurs attempting to write to this |
| * DataOutputStream. |
| */ |
| public final void writeUTF(String str) throws IOException |
| { |
| writeUTF(str, this); |
| } |
| |
| // ByteBuffer to use for defensive copies |
| private final ByteBuffer hollowBufferD = MemoryUtil.getHollowDirectByteBuffer(); |
| |
| @Override |
| public void write(ByteBuffer buf) throws IOException |
| { |
| if (buf.hasArray()) |
| { |
| write(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); |
| } |
| else |
| { |
| assert buf.isDirect(); |
| MemoryUtil.duplicateDirectByteBuffer(buf, hollowBufferD); |
| while (hollowBufferD.hasRemaining()) |
| channel.write(hollowBufferD); |
| } |
| } |
| |
| public void write(Memory memory, long offset, long length) throws IOException |
| { |
| for (ByteBuffer buffer : memory.asByteBuffers(offset, length)) |
| write(buffer); |
| } |
| |
| @Override |
| public <R> R applyToChannel(Function<WritableByteChannel, R> f) throws IOException |
| { |
| return f.apply(channel); |
| } |
| } |