/* * 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, 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; } /** * Gets the current cursor. * * @return {@code 0..size();} the cursor */ public int getCursor() { return cursor; } } /** * 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; } /** * Gets the current cursor. * * @return {@code 0..size();} the cursor */ public int getCursor() { return wrapped.getCursor(); } } }