| // Copyright 2015 The Bazel Authors. All rights reserved. |
| // |
| // 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.google.devtools.build.android.ziputils; |
| |
| import static java.nio.ByteOrder.LITTLE_ENDIAN; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import java.nio.ByteBuffer; |
| |
| /** |
| * A {@code View} represents a range of a larger sequence of bytes (e.g. part of a file). |
| * It consist of an internal byte buffer providing access to the data range, and an |
| * offset of the first byte in the buffer within the larger sequence. |
| * Subclasses will typically assign a specific interpretations of the data in a view |
| * (e.g. records of a specific type). |
| * |
| * <p>Instances of subclasses may generally be "allocated", or created "of" or "over" a |
| * byte buffer provided at creation time. |
| * |
| * <p>An "allocated" view gets a new heap allocated byte buffer. Allocation methods |
| * typically defines parameters for variable sized part of the object they create a |
| * view of. Once allocated, the variable size parts (e.g. filename) cannot be changed. |
| * |
| * <p>A view created "of" an existing byte buffer, expects the buffer to contain an |
| * appropriate record at its current position. The buffers limit is set at the |
| * end of the object being viewed. |
| * |
| * <p>A view created "over" an existing byte buffer, reserves space in the buffer for the |
| * object being viewed. The variable sized parts of the object are initialized, but |
| * otherwise the existing buffer data are left as-is, and can be manipulated through the |
| * view. |
| * |
| * <p> An view can also be copied into an existing byte buffer. This is like creating a |
| * view "over" the buffer, but initializing the new view as an exact copy of the one |
| * being copied. |
| * |
| * @param <SUB> to be type-safe, a subclass {@code S} must extend {@code View<S>}. It must not |
| * extend {@code View<S2>}, where {@code S2} is another subclass, which is not also a superclass |
| * of {@code S}. To maintain this guarantee, this class is declared abstract and package private. |
| * Unchecked warnings are suppressed as per this specification constraint. |
| */ |
| abstract class View<SUB extends View<?>> { |
| |
| /** Zero length byte array */ |
| protected static final byte[] EMPTY = {}; |
| |
| /** {@code ByteBuffer} backing this view. */ |
| protected final ByteBuffer buffer; |
| |
| /** |
| * Offset of first byte covered by this view. For input views, this is the file offset where the |
| * item occur. For output, it's the offset at which we expect to write the item (may be -1, for |
| * unknown). |
| */ |
| protected long fileOffset; |
| |
| /** |
| * Creates a view backed by the given {@code ByteBuffer}. Sets the buffer's byte order to |
| * little endian, and sets the file offset to -1 (unknown). |
| * |
| * @param buffer backing byte buffer. |
| */ |
| protected View(ByteBuffer buffer) { |
| buffer.order(LITTLE_ENDIAN); |
| this.buffer = buffer; |
| this.fileOffset = -1; |
| } |
| |
| /** |
| * Sets the file offset of the data item covered by this view, |
| * |
| * @param fileOffset |
| * @return this object. |
| */ |
| @SuppressWarnings("unchecked") // safe by specification |
| public SUB at(long fileOffset) { |
| this.fileOffset = fileOffset; |
| return (SUB) this; |
| } |
| |
| /** |
| * Gets the fileOffset of this view. |
| * |
| * @return the location of the viewed object within the underlying file. |
| */ |
| public long fileOffset() { |
| return fileOffset; |
| } |
| |
| /** |
| * Returns an array with data copied from the backing byte buffer, at the given offset, relative |
| * to the beginning of this view. This method does not perform boundary checks of offset or |
| * length. This method may temporarily changes the position of the backing buffer. However, after |
| * the call, the position will be unchanged. |
| * |
| * @param off offset relative to this view. |
| * @param len number of bytes to return. |
| * @return Newly allocated array with copy of requested data. |
| * @throws IndexOutOfBoundsException in case of illegal arguments. |
| */ |
| protected byte[] getBytes(int off, int len) { |
| if (len == 0) { |
| return EMPTY; |
| } |
| byte[] bytes; |
| try { |
| bytes = new byte[len]; |
| int currPos = buffer.position(); |
| buffer.position(off); |
| buffer.get(bytes); |
| buffer.position(currPos); |
| } catch (Exception ex) { |
| throw new IndexOutOfBoundsException(); |
| } |
| return bytes; |
| } |
| |
| /** |
| * Returns a String representation of {@code len} bytes starting at offset {@code off} in this |
| * view. This method may temporarily changes the position of the backing buffer. However, after |
| * the call, the position will be unchanged. |
| * |
| * @param off offset relative to backing buffer. |
| * @param len number of bytes to return. This method may throw an |
| * @return Newly allocated String created by interpreting the specified bytes as UTF-8 data. |
| * @throws IndexOutOfBoundsException in case of illegal arguments. |
| */ |
| protected String getString(int off, int len) { |
| if (len == 0) { |
| return ""; |
| } |
| if (buffer.hasArray()) { |
| return new String(buffer.array(), buffer.arrayOffset() + off, len, UTF_8); |
| } else { |
| return new String(getBytes(off, len), UTF_8); |
| } |
| } |
| |
| /** |
| * Gets the value of an identified integer field. |
| * |
| * @param id field identifier |
| * @return the value of the field identified by {@code id}. |
| */ |
| public int get(IntFieldId<? extends SUB> id) { |
| return buffer.getInt(id.address()); |
| } |
| |
| /** |
| * Gets the value of an identified short field. |
| * |
| * @param id field identifier |
| * @return the value of the field identified by {@code id}. |
| */ |
| public short get(ShortFieldId<? extends SUB> id) { |
| return buffer.getShort(id.address()); |
| } |
| |
| /** |
| * Sets the value of an identified integer field. |
| * |
| * @param id field identifier |
| * @param value value to set for the field identified by {@code id}. |
| * @return this object. |
| */ |
| @SuppressWarnings("unchecked") // safe by specification |
| public SUB set(IntFieldId<? extends SUB> id, int value) { |
| buffer.putInt(id.address(), value); |
| return (SUB) this; |
| } |
| |
| /** |
| * Sets the value of an identified short field. |
| * |
| * @param id field identifier |
| * @param value value to set for the field identified by {@code id}. |
| * @return this object. |
| */ |
| @SuppressWarnings("unchecked") // safe by specification |
| public SUB set(ShortFieldId<? extends SUB> id, short value) { |
| buffer.putShort(id.address(), value); |
| return (SUB) this; |
| } |
| |
| /** |
| * Copies the values of one or more identified fields from another view to this view. |
| * |
| * @param from The view from which to copy field values. |
| * @param ids field identifiers for fields to copy. |
| * @return this object. |
| */ |
| @SuppressWarnings("unchecked") // safe by specification |
| public SUB copy(View<SUB> from, FieldId<? extends SUB, ?>... ids) { |
| for (FieldId<? extends SUB, ?> id : ids) { |
| int address = id.address; |
| buffer.put(address, from.buffer.get(address++)); |
| buffer.put(address, from.buffer.get(address++)); |
| if (id.type() == Integer.TYPE) { |
| buffer.put(address, from.buffer.get(address++)); |
| buffer.put(address, from.buffer.get(address)); |
| } |
| } |
| return (SUB) this; |
| } |
| |
| /** |
| * Base class for data field descriptors. Describes a data field's type and address in a view. |
| * This base class allows the |
| * {@link #copy(View, com.google.devtools.build.android.ziputils.View.FieldId[])} method |
| * to operate of fields of mixed types. |
| * |
| * @param <T> {@code Integer.TYPE} or {@code Short.TYPE}. |
| * @param <V> subclass of {@code View} for which a field id is defined. |
| */ |
| protected abstract static class FieldId<V extends View<?>, T> { |
| private final int address; |
| private final Class<T> type; |
| |
| protected FieldId(int address, Class<T> type) { |
| this.address = address; |
| this.type = type; |
| } |
| |
| /** |
| * Returns the class of the field type, {@code Class<T>}. |
| */ |
| public Class<T> type() { |
| return type; |
| } |
| |
| /** |
| * Returns the field address, within a record of type {@code V} |
| */ |
| public int address() { |
| return address; |
| } |
| } |
| |
| /** |
| * Describes an integer fields for a view. |
| * |
| * @param <V> subclass of {@code View} for which a field id is defined. |
| */ |
| protected static class IntFieldId<V extends View<?>> extends FieldId<V, Integer> { |
| protected IntFieldId(int address) { |
| super(address, Integer.TYPE); |
| } |
| } |
| |
| /** |
| * Describes a short field for a view. |
| * |
| * @param <V> subclass of {@code View} for which a field id is defined. |
| */ |
| protected static class ShortFieldId<V extends View<?>> extends FieldId<V, Short> { |
| protected ShortFieldId(int address) { |
| super(address, Short.TYPE); |
| } |
| } |
| } |