blob: e6762bed7e4ad0d5a5ac513af16b52ade366c1b4 [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 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);
}
}
}