| /** |
| * 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.orc.impl; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.nio.ByteBuffer; |
| |
| import org.apache.hadoop.io.Text; |
| |
| /** |
| * A class that is a growable array of bytes. Growth is managed in terms of |
| * chunks that are allocated when needed. |
| */ |
| public final class DynamicByteArray { |
| static final int DEFAULT_CHUNKSIZE = 32 * 1024; |
| static final int DEFAULT_NUM_CHUNKS = 128; |
| |
| private final int chunkSize; // our allocation sizes |
| private byte[][] data; // the real data |
| private int length; // max set element index +1 |
| private int initializedChunks = 0; // the number of chunks created |
| |
| public DynamicByteArray() { |
| this(DEFAULT_NUM_CHUNKS, DEFAULT_CHUNKSIZE); |
| } |
| |
| public DynamicByteArray(int numChunks, int chunkSize) { |
| if (chunkSize == 0) { |
| throw new IllegalArgumentException("bad chunksize"); |
| } |
| this.chunkSize = chunkSize; |
| data = new byte[numChunks][]; |
| } |
| |
| /** |
| * Ensure that the given index is valid. |
| */ |
| private void grow(int chunkIndex) { |
| if (chunkIndex >= initializedChunks) { |
| if (chunkIndex >= data.length) { |
| int newSize = Math.max(chunkIndex + 1, 2 * data.length); |
| byte[][] newChunk = new byte[newSize][]; |
| System.arraycopy(data, 0, newChunk, 0, data.length); |
| data = newChunk; |
| } |
| for(int i=initializedChunks; i <= chunkIndex; ++i) { |
| data[i] = new byte[chunkSize]; |
| } |
| initializedChunks = chunkIndex + 1; |
| } |
| } |
| |
| public byte get(int index) { |
| if (index >= length) { |
| throw new IndexOutOfBoundsException("Index " + index + |
| " is outside of 0.." + |
| (length - 1)); |
| } |
| int i = index / chunkSize; |
| int j = index % chunkSize; |
| return data[i][j]; |
| } |
| |
| public void set(int index, byte value) { |
| int i = index / chunkSize; |
| int j = index % chunkSize; |
| grow(i); |
| if (index >= length) { |
| length = index + 1; |
| } |
| data[i][j] = value; |
| } |
| |
| public int add(byte value) { |
| int i = length / chunkSize; |
| int j = length % chunkSize; |
| grow(i); |
| data[i][j] = value; |
| int result = length; |
| length += 1; |
| return result; |
| } |
| |
| /** |
| * Copy a slice of a byte array into our buffer. |
| * @param value the array to copy from |
| * @param valueOffset the first location to copy from value |
| * @param valueLength the number of bytes to copy from value |
| * @return the offset of the start of the value |
| */ |
| public int add(byte[] value, int valueOffset, int valueLength) { |
| int i = length / chunkSize; |
| int j = length % chunkSize; |
| grow((length + valueLength) / chunkSize); |
| int remaining = valueLength; |
| while (remaining > 0) { |
| int size = Math.min(remaining, chunkSize - j); |
| System.arraycopy(value, valueOffset, data[i], j, size); |
| remaining -= size; |
| valueOffset += size; |
| i += 1; |
| j = 0; |
| } |
| int result = length; |
| length += valueLength; |
| return result; |
| } |
| |
| /** |
| * Read the entire stream into this array. |
| * @param in the stream to read from |
| * @throws IOException |
| */ |
| public void readAll(InputStream in) throws IOException { |
| int currentChunk = length / chunkSize; |
| int currentOffset = length % chunkSize; |
| grow(currentChunk); |
| int currentLength = in.read(data[currentChunk], currentOffset, |
| chunkSize - currentOffset); |
| while (currentLength > 0) { |
| length += currentLength; |
| currentOffset = length % chunkSize; |
| if (currentOffset == 0) { |
| currentChunk = length / chunkSize; |
| grow(currentChunk); |
| } |
| currentLength = in.read(data[currentChunk], currentOffset, |
| chunkSize - currentOffset); |
| } |
| } |
| |
| /** |
| * Byte compare a set of bytes against the bytes in this dynamic array. |
| * @param other source of the other bytes |
| * @param otherOffset start offset in the other array |
| * @param otherLength number of bytes in the other array |
| * @param ourOffset the offset in our array |
| * @param ourLength the number of bytes in our array |
| * @return negative for less, 0 for equal, positive for greater |
| */ |
| public int compare(byte[] other, int otherOffset, int otherLength, |
| int ourOffset, int ourLength) { |
| int currentChunk = ourOffset / chunkSize; |
| int currentOffset = ourOffset % chunkSize; |
| int maxLength = Math.min(otherLength, ourLength); |
| while (maxLength > 0 && |
| other[otherOffset] == data[currentChunk][currentOffset]) { |
| otherOffset += 1; |
| currentOffset += 1; |
| if (currentOffset == chunkSize) { |
| currentChunk += 1; |
| currentOffset = 0; |
| } |
| maxLength -= 1; |
| } |
| if (maxLength == 0) { |
| return otherLength - ourLength; |
| } |
| int otherByte = 0xff & other[otherOffset]; |
| int ourByte = 0xff & data[currentChunk][currentOffset]; |
| return otherByte > ourByte ? 1 : -1; |
| } |
| |
| /** |
| * Get the size of the array. |
| * @return the number of bytes in the array |
| */ |
| public int size() { |
| return length; |
| } |
| |
| /** |
| * Clear the array to its original pristine state. |
| */ |
| public void clear() { |
| length = 0; |
| for(int i=0; i < data.length; ++i) { |
| data[i] = null; |
| } |
| initializedChunks = 0; |
| } |
| |
| /** |
| * Set a text value from the bytes in this dynamic array. |
| * @param result the value to set |
| * @param offset the start of the bytes to copy |
| * @param length the number of bytes to copy |
| */ |
| public void setText(Text result, int offset, int length) { |
| result.clear(); |
| int currentChunk = offset / chunkSize; |
| int currentOffset = offset % chunkSize; |
| int currentLength = Math.min(length, chunkSize - currentOffset); |
| while (length > 0) { |
| result.append(data[currentChunk], currentOffset, currentLength); |
| length -= currentLength; |
| currentChunk += 1; |
| currentOffset = 0; |
| currentLength = Math.min(length, chunkSize - currentOffset); |
| } |
| } |
| |
| /** |
| * Write out a range of this dynamic array to an output stream. |
| * @param out the stream to write to |
| * @param offset the first offset to write |
| * @param length the number of bytes to write |
| * @throws IOException |
| */ |
| public void write(OutputStream out, int offset, |
| int length) throws IOException { |
| int currentChunk = offset / chunkSize; |
| int currentOffset = offset % chunkSize; |
| while (length > 0) { |
| int currentLength = Math.min(length, chunkSize - currentOffset); |
| out.write(data[currentChunk], currentOffset, currentLength); |
| length -= currentLength; |
| currentChunk += 1; |
| currentOffset = 0; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| int i; |
| StringBuilder sb = new StringBuilder(length * 3); |
| |
| sb.append('{'); |
| int l = length - 1; |
| for (i=0; i<l; i++) { |
| sb.append(Integer.toHexString(get(i))); |
| sb.append(','); |
| } |
| sb.append(get(i)); |
| sb.append('}'); |
| |
| return sb.toString(); |
| } |
| |
| public void setByteBuffer(ByteBuffer result, int offset, int length) { |
| result.clear(); |
| int currentChunk = offset / chunkSize; |
| int currentOffset = offset % chunkSize; |
| int currentLength = Math.min(length, chunkSize - currentOffset); |
| while (length > 0) { |
| result.put(data[currentChunk], currentOffset, currentLength); |
| length -= currentLength; |
| currentChunk += 1; |
| currentOffset = 0; |
| currentLength = Math.min(length, chunkSize - currentOffset); |
| } |
| } |
| |
| /** |
| * Gets all the bytes of the array. |
| * |
| * @return Bytes of the array |
| */ |
| public byte[] get() { |
| byte[] result = null; |
| if (length > 0) { |
| int currentChunk = 0; |
| int currentOffset = 0; |
| int currentLength = Math.min(length, chunkSize); |
| int destOffset = 0; |
| result = new byte[length]; |
| int totalLength = length; |
| while (totalLength > 0) { |
| System.arraycopy(data[currentChunk], currentOffset, result, destOffset, currentLength); |
| destOffset += currentLength; |
| totalLength -= currentLength; |
| currentChunk += 1; |
| currentOffset = 0; |
| currentLength = Math.min(totalLength, chunkSize - currentOffset); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Get the size of the buffers. |
| */ |
| public long getSizeInBytes() { |
| return (long) initializedChunks * chunkSize; |
| } |
| } |