/* * 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); } }