aboutsummaryrefslogtreecommitdiff
path: root/java/core/src/main/java/com/google/protobuf/Proto2Manifest.java
blob: b6eed904aa52d9e04764edae9c07e06b77f150a8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// 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);
  }
}