// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package com.google.protobuf;
import java.nio.ByteBuffer;
import java.util.List;
/** Container for the field metadata of a single proto2 schema. */
final class Proto2Manifest {
static final int INT_LENGTH = 4;
static final int LONG_LENGTH = INT_LENGTH * 2;
static final int LONGS_PER_FIELD = 2;
/**
* Note that field length is always a power of two so that we can use bit shifting (rather than
* division) to find the location of a field when parsing.
*/
static final int FIELD_LENGTH = LONGS_PER_FIELD * LONG_LENGTH;
static final int FIELD_SHIFT = 4 /* 2^4 = 16 */;
static final int OFFSET_BITS = 20;
static final int OFFSET_MASK = 0XFFFFF;
static final long EMPTY_LONG = 0xFFFFFFFFFFFFFFFFL;
/**
* Holds all information for accessing the message fields. The layout is as follows (field
* positions are relative to the offset of the start of the field in the buffer):
*
* <p>
*
* <pre>
* [ 0 - 3] unused
* [ 4 - 31] field number
* [32 - 37] unused
* [38 - 43] field type
* [44 - 63] field offset
* [64 - 69] unused
* [70 - 75] field presence mask shift
* [76 - 95] presence field offset
* [96 - 127] unused
* </pre>
*/
final ByteBuffer buffer;
final long address;
final long limit;
final int numFields;
final int minFieldNumber;
final int maxFieldNumber;
private Proto2Manifest(
ByteBuffer buffer,
long address,
long limit,
int numFields,
int minFieldNumber,
int maxFieldNumber) {
this.buffer = buffer;
this.address = address;
this.limit = limit;
this.numFields = numFields;
this.minFieldNumber = minFieldNumber;
this.maxFieldNumber = maxFieldNumber;
}
boolean isFieldInRange(int fieldNumber) {
return fieldNumber >= minFieldNumber && fieldNumber <= maxFieldNumber;
}
long tablePositionForFieldNumber(int fieldNumber) {
if (fieldNumber < minFieldNumber || fieldNumber > maxFieldNumber) {
return -1;
}
return indexToAddress(fieldNumber - minFieldNumber);
}
<T> boolean isFieldPresent(T message, long pos) {
int maskShiftAndOffset = UnsafeUtil.getInt(pos + LONG_LENGTH);
long offset = maskShiftAndOffset & OFFSET_MASK;
int mask = 1 << (maskShiftAndOffset >>> OFFSET_BITS);
return (UnsafeUtil.getInt(message, offset) & mask) != 0;
}
<T> void setFieldPresent(T message, long pos) {
int maskShiftAndOffset = UnsafeUtil.getInt(pos + LONG_LENGTH);
long offset = maskShiftAndOffset & OFFSET_MASK;
int mask = 1 << (maskShiftAndOffset >>> OFFSET_BITS);
UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(message, offset) | mask);
}
long lookupPositionForFieldNumber(int fieldNumber) {
int min = 0;
int max = numFields - 1;
while (min <= max) {
// Find the midpoint address.
int mid = (max + min) >>> 1;
long midAddress = indexToAddress(mid);
int midFieldNumber = numberAt(midAddress);
if (fieldNumber == midFieldNumber) {
// Found the field.
return midAddress;
}
if (fieldNumber < midFieldNumber) {
// Search the lower half.
max = mid - 1;
} else {
// Search the upper half.
min = mid + 1;
}
}
return -1;
}
int numberAt(long pos) {
return UnsafeUtil.getInt(pos);
}
int typeAndOffsetAt(long pos) {
return UnsafeUtil.getInt(pos + INT_LENGTH);
}
private long indexToAddress(int index) {
return address + (index << FIELD_SHIFT);
}
static byte type(int value) {
return (byte) (value >>> OFFSET_BITS);
}
static long offset(int value) {
return value & OFFSET_MASK;
}
static Proto2Manifest newTableManfiest(MessageInfo descriptor) {
List<FieldInfo> fds = descriptor.getFields();
if (fds.isEmpty()) {
throw new IllegalArgumentException("Table-based schema requires at least one field");
}
// Set up the buffer for direct indexing by field number.
final int minFieldNumber = fds.get(0).getFieldNumber();
final int maxFieldNumber = fds.get(fds.size() - 1).getFieldNumber();
final int numEntries = (maxFieldNumber - minFieldNumber) + 1;
int bufferLength = numEntries * FIELD_LENGTH;
ByteBuffer buffer = ByteBuffer.allocateDirect(bufferLength + LONG_LENGTH);
long tempAddress = UnsafeUtil.addressOffset(buffer);
if ((tempAddress & 7L) != 0) {
// Make sure that the memory address is 8-byte aligned.
tempAddress = (tempAddress & ~7L) + LONG_LENGTH;
}
final long address = tempAddress;
final long limit = address + bufferLength;
// Fill in the manifest data from the descriptors.
int fieldIndex = 0;
FieldInfo fd = fds.get(fieldIndex++);
for (int bufferIndex = 0; bufferIndex < bufferLength; bufferIndex += FIELD_LENGTH) {
final int fieldNumber = fd.getFieldNumber();
if (bufferIndex < ((fieldNumber - minFieldNumber) << FIELD_SHIFT)) {
// Mark this entry as "empty".
long skipLimit = address + bufferIndex + FIELD_LENGTH;
for (long skipPos = address + bufferIndex; skipPos < skipLimit; skipPos += LONG_LENGTH) {
UnsafeUtil.putLong(skipPos, EMPTY_LONG);
}
continue;
}
// We found the entry for the next field. Store the entry in the manifest for
// this field and increment the field index.
FieldType type = fd.getType();
long pos = address + bufferIndex;
UnsafeUtil.putInt(pos, fieldNumber);
UnsafeUtil.putInt(
pos + INT_LENGTH,
(type.id() << OFFSET_BITS) | (int) UnsafeUtil.objectFieldOffset(fd.getField()));
if (!type.isList()) {
int presenceOffset = (int) UnsafeUtil.objectFieldOffset(fd.getPresenceField());
int maskShift = Integer.numberOfTrailingZeros(fd.getPresenceMask());
UnsafeUtil.putInt(pos + LONG_LENGTH, maskShift << OFFSET_BITS | presenceOffset);
}
// Advance to the next field, unless we're at the end.
if (fieldIndex < fds.size()) {
fd = fds.get(fieldIndex++);
}
}
return new Proto2Manifest(buffer, address, limit, fds.size(), minFieldNumber, maxFieldNumber);
}
static Proto2Manifest newLookupManifest(MessageInfo descriptor) {
List<FieldInfo> fds = descriptor.getFields();
final int numFields = fds.size();
int bufferLength = numFields * FIELD_LENGTH;
final ByteBuffer buffer = ByteBuffer.allocateDirect(bufferLength + LONG_LENGTH);
long tempAddress = UnsafeUtil.addressOffset(buffer);
if ((tempAddress & 7L) != 0) {
// Make sure that the memory address is 8-byte aligned.
tempAddress = (tempAddress & ~7L) + LONG_LENGTH;
}
final long address = tempAddress;
final long limit = address + bufferLength;
// Allocate and populate the data buffer.
long pos = address;
for (int i = 0; i < fds.size(); ++i, pos += FIELD_LENGTH) {
FieldInfo fd = fds.get(i);
UnsafeUtil.putInt(pos, fd.getFieldNumber());
UnsafeUtil.putInt(
pos + INT_LENGTH,
(fd.getType().id() << OFFSET_BITS) | (int) UnsafeUtil.objectFieldOffset(fd.getField()));
if (!fd.getType().isList()) {
int presenceOffset = (int) UnsafeUtil.objectFieldOffset(fd.getPresenceField());
int maskShift = Integer.numberOfTrailingZeros(fd.getPresenceMask());
UnsafeUtil.putInt(pos + LONG_LENGTH, maskShift << OFFSET_BITS | presenceOffset);
}
}
if (numFields > 0) {
return new Proto2Manifest(
buffer,
address,
limit,
numFields,
fds.get(0).getFieldNumber(),
fds.get(numFields - 1).getFieldNumber());
}
return new Proto2Manifest(buffer, address, limit, numFields, -1, -1);
}
}