blob: 0d142bc0a4717c411500528172c2745391197c90 [file] [log] [blame]
// 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 com.google.common.primitives.UnsignedInts;
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 (unsigned) integer field.
*
* @param id field identifier
* @return the value of the field identified by {@code id}.
*/
public long get(IntFieldId<? extends SUB> id) {
return UnsignedInts.toLong(buffer.getInt(id.address()));
}
/**
* Gets the value of an identified (unsigned) short field.
*
* @param id field identifier
* @return the value of the field identified by {@code id}.
*/
public int get(ShortFieldId<? extends SUB> id) {
return UnsignedShorts.toInt(buffer.getShort(id.address()));
}
/**
* Sets the value of an identified (unsigned) 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, long value) {
buffer.putInt(id.address(), UnsignedInts.checkedCast(value));
return (SUB) this;
}
/**
* Sets the value of an identified (unsigned) 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, int value) {
buffer.putShort(id.address(), UnsignedShorts.checkedCast(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);
}
}
}