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