blob: 21d04573c3310f3291a64850ff502154765346a3 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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.
*/
package com.android.dx.util;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Wrapper for a {@code byte[]}, which provides read-only access and
* can "reveal" a partial slice of the underlying array.
*
* <b>Note:</b> Multibyte accessors all use big-endian order.
*/
public final class ByteArray {
/** {@code non-null;} underlying array */
private final byte[] bytes;
/** {@code >= 0}; start index of the slice (inclusive) */
private final int start;
/** {@code >= 0, <= bytes.length}; size computed as
* {@code end - start} (in the constructor) */
private final int size;
/**
* Constructs an instance.
*
* @param bytes {@code non-null;} the underlying array
* @param start {@code >= 0;} start index of the slice (inclusive)
* @param end {@code >= start, <= bytes.length;} end index of
* the slice (exclusive)
*/
public ByteArray(byte[] bytes, int start, int end) {
if (bytes == null) {
throw new NullPointerException("bytes == null");
}
if (start < 0) {
throw new IllegalArgumentException("start < 0");
}
if (end < start) {
throw new IllegalArgumentException("end < start");
}
if (end > bytes.length) {
throw new IllegalArgumentException("end > bytes.length");
}
this.bytes = bytes;
this.start = start;
this.size = end - start;
}
/**
* Constructs an instance from an entire {@code byte[]}.
*
* @param bytes {@code non-null;} the underlying array
*/
public ByteArray(byte[] bytes) {
this(bytes, 0, bytes.length);
}
/**
* Gets the size of the array, in bytes.
*
* @return {@code >= 0;} the size
*/
public int size() {
return size;
}
/**
* Returns a slice (that is, a sub-array) of this instance.
*
* @param start {@code >= 0;} start index of the slice (inclusive)
* @param end {@code >= start, <= size();} end index of
* the slice (exclusive)
* @return {@code non-null;} the slice
*/
public ByteArray slice(int start, int end) {
checkOffsets(start, end);
return new ByteArray(bytes, start + this.start, end + this.start);
}
/**
* Returns the offset into the given array represented by the given
* offset into this instance.
*
* @param offset offset into this instance
* @param bytes {@code non-null;} (alleged) underlying array
* @return corresponding offset into {@code bytes}
* @throws IllegalArgumentException thrown if {@code bytes} is
* not the underlying array of this instance
*/
public int underlyingOffset(int offset, byte[] bytes) {
if (bytes != this.bytes) {
throw new IllegalArgumentException("wrong bytes");
}
return start + offset;
}
/**
* Gets the {@code signed byte} value at a particular offset.
*
* @param off {@code >= 0, < size();} offset to fetch
* @return {@code signed byte} at that offset
*/
public int getByte(int off) {
checkOffsets(off, off + 1);
return getByte0(off);
}
/**
* Gets the {@code signed short} value at a particular offset.
*
* @param off {@code >= 0, < (size() - 1);} offset to fetch
* @return {@code signed short} at that offset
*/
public int getShort(int off) {
checkOffsets(off, off + 2);
return (getByte0(off) << 8) | getUnsignedByte0(off + 1);
}
/**
* Gets the {@code signed int} value at a particular offset.
*
* @param off {@code >= 0, < (size() - 3);} offset to fetch
* @return {@code signed int} at that offset
*/
public int getInt(int off) {
checkOffsets(off, off + 4);
return (getByte0(off) << 24) |
(getUnsignedByte0(off + 1) << 16) |
(getUnsignedByte0(off + 2) << 8) |
getUnsignedByte0(off + 3);
}
/**
* Gets the {@code signed long} value at a particular offset.
*
* @param off {@code >= 0, < (size() - 7);} offset to fetch
* @return {@code signed int} at that offset
*/
public long getLong(int off) {
checkOffsets(off, off + 8);
int part1 = (getByte0(off) << 24) |
(getUnsignedByte0(off + 1) << 16) |
(getUnsignedByte0(off + 2) << 8) |
getUnsignedByte0(off + 3);
int part2 = (getByte0(off + 4) << 24) |
(getUnsignedByte0(off + 5) << 16) |
(getUnsignedByte0(off + 6) << 8) |
getUnsignedByte0(off + 7);
return (part2 & 0xffffffffL) | ((long) part1) << 32;
}
/**
* Gets the {@code unsigned byte} value at a particular offset.
*
* @param off {@code >= 0, < size();} offset to fetch
* @return {@code unsigned byte} at that offset
*/
public int getUnsignedByte(int off) {
checkOffsets(off, off + 1);
return getUnsignedByte0(off);
}
/**
* Gets the {@code unsigned short} value at a particular offset.
*
* @param off {@code >= 0, < (size() - 1);} offset to fetch
* @return {@code unsigned short} at that offset
*/
public int getUnsignedShort(int off) {
checkOffsets(off, off + 2);
return (getUnsignedByte0(off) << 8) | getUnsignedByte0(off + 1);
}
/**
* Copies the contents of this instance into the given raw
* {@code byte[]} at the given offset. The given array must be
* large enough.
*
* @param out {@code non-null;} array to hold the output
* @param offset {@code non-null;} index into {@code out} for the first
* byte of output
*/
public void getBytes(byte[] out, int offset) {
if ((out.length - offset) < size) {
throw new IndexOutOfBoundsException("(out.length - offset) < " +
"size()");
}
System.arraycopy(bytes, start, out, offset, size);
}
/**
* Checks a range of offsets for validity, throwing if invalid.
*
* @param s start offset (inclusive)
* @param e end offset (exclusive)
*/
private void checkOffsets(int s, int e) {
if ((s < 0) || (e < s) || (e > size)) {
throw new IllegalArgumentException("bad range: " + s + ".." + e +
"; actual size " + size);
}
}
/**
* Gets the {@code signed byte} value at the given offset,
* without doing any argument checking.
*
* @param off offset to fetch
* @return byte at that offset
*/
private int getByte0(int off) {
return bytes[start + off];
}
/**
* Gets the {@code unsigned byte} value at the given offset,
* without doing any argument checking.
*
* @param off offset to fetch
* @return byte at that offset
*/
private int getUnsignedByte0(int off) {
return bytes[start + off] & 0xff;
}
/**
* Gets a {@code DataInputStream} that reads from this instance,
* with the cursor starting at the beginning of this instance's data.
* <b>Note:</b> The returned instance may be cast to {@link #GetCursor}
* if needed.
*
* @return {@code non-null;} an appropriately-constructed
* {@code DataInputStream} instance
*/
public MyDataInputStream makeDataInputStream() {
return new MyDataInputStream(makeInputStream());
}
/**
* Gets a {@code InputStream} that reads from this instance,
* with the cursor starting at the beginning of this instance's data.
* <b>Note:</b> The returned instance may be cast to {@link #GetCursor}
* if needed.
*
* @return {@code non-null;} an appropriately-constructed
* {@code InputStream} instancex
*/
public MyInputStream makeInputStream() {
return new MyInputStream();
}
/**
* Helper interface that allows one to get the cursor (of a stream).
*/
public interface GetCursor {
/**
* Gets the current cursor.
*
* @return {@code 0..size();} the cursor
*/
public int getCursor();
}
/**
* Helper class for {@link #makeInputStream}, which implements the
* stream functionality.
*/
public class MyInputStream extends InputStream {
/** 0..size; the cursor */
private int cursor;
/** 0..size; the mark */
private int mark;
public MyInputStream() {
cursor = 0;
mark = 0;
}
public int read() throws IOException {
if (cursor >= size) {
return -1;
}
int result = getUnsignedByte0(cursor);
cursor++;
return result;
}
public int read(byte[] arr, int offset, int length) {
if ((offset + length) > arr.length) {
length = arr.length - offset;
}
int maxLength = size - cursor;
if (length > maxLength) {
length = maxLength;
}
System.arraycopy(bytes, cursor + start, arr, offset, length);
cursor += length;
return length;
}
public int available() {
return size - cursor;
}
public void mark(int reserve) {
mark = cursor;
}
public void reset() {
cursor = mark;
}
public boolean markSupported() {
return true;
}
}
/**
* Helper class for {@link #makeDataInputStream}. This is used
* simply so that the cursor of a wrapped {@link #MyInputStream}
* instance may be easily determined.
*/
public static class MyDataInputStream extends DataInputStream {
/** {@code non-null;} the underlying {@link #MyInputStream} */
private final MyInputStream wrapped;
public MyDataInputStream(MyInputStream wrapped) {
super(wrapped);
this.wrapped = wrapped;
}
}
}