blob: 7f41bb6f3769d6b76b8f9828130d235ca4c11e23 [file] [log] [blame]
Austin Schuh272c6132020-11-14 16:37:52 -08001/*
2 * Copyright 2014 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.flatbuffers;
18
19import java.math.BigInteger;
20import java.nio.ByteBuffer;
21import java.nio.ByteOrder;
22import java.nio.charset.StandardCharsets;
23import java.util.ArrayList;
24import java.util.Collections;
25import java.util.Comparator;
26import java.util.HashMap;
27
28import static com.google.flatbuffers.FlexBuffers.*;
29import static com.google.flatbuffers.FlexBuffers.Unsigned.byteToUnsignedInt;
30import static com.google.flatbuffers.FlexBuffers.Unsigned.intToUnsignedLong;
31import static com.google.flatbuffers.FlexBuffers.Unsigned.shortToUnsignedInt;
32
33/// @file
34/// @addtogroup flatbuffers_java_api
35/// @{
36
37/**
38 * Helper class that builds FlexBuffers
39 * <p> This class presents all necessary APIs to create FlexBuffers. A `ByteBuffer` will be used to store the
40 * data. It can be created internally, or passed down in the constructor.</p>
41 *
42 * <p>There are some limitations when compared to original implementation in C++. Most notably:
43 * <ul>
44 * <li><p> No support for mutations (might change in the future).</p></li>
45 * <li><p> Buffer size limited to {@link Integer#MAX_VALUE}</p></li>
46 * <li><p> Since Java does not support unsigned type, all unsigned operations accepts an immediate higher representation
47 * of similar type.</p></li>
48 * </ul>
49 * </p>
50 */
51public class FlexBuffersBuilder {
52
53 /**
54 * No keys or strings will be shared
55 */
56 public static final int BUILDER_FLAG_NONE = 0;
57 /**
58 * Keys will be shared between elements. Identical keys will only be serialized once, thus possibly saving space.
59 * But serialization performance might be slower and consumes more memory.
60 */
61 public static final int BUILDER_FLAG_SHARE_KEYS = 1;
62 /**
63 * Strings will be shared between elements. Identical strings will only be serialized once, thus possibly saving space.
64 * But serialization performance might be slower and consumes more memory. This is ideal if you expect many repeated
65 * strings on the message.
66 */
67 public static final int BUILDER_FLAG_SHARE_STRINGS = 2;
68 /**
69 * Strings and keys will be shared between elements.
70 */
71 public static final int BUILDER_FLAG_SHARE_KEYS_AND_STRINGS = 3;
72 /**
73 * Reserved for the future.
74 */
75 public static final int BUILDER_FLAG_SHARE_KEY_VECTORS = 4;
76 /**
77 * Reserved for the future.
78 */
79 public static final int BUILDER_FLAG_SHARE_ALL = 7;
80
81 /// @cond FLATBUFFERS_INTERNAL
82 private static final int WIDTH_8 = 0;
83 private static final int WIDTH_16 = 1;
84 private static final int WIDTH_32 = 2;
85 private static final int WIDTH_64 = 3;
86 private final ReadWriteBuf bb;
87 private final ArrayList<Value> stack = new ArrayList<>();
88 private final HashMap<String, Integer> keyPool = new HashMap<>();
89 private final HashMap<String, Integer> stringPool = new HashMap<>();
90 private final int flags;
91 private boolean finished = false;
92
93 // A lambda to sort map keys
94 private Comparator<Value> keyComparator = new Comparator<Value>() {
95 @Override
96 public int compare(Value o1, Value o2) {
97 int ia = o1.key;
98 int io = o2.key;
99 byte c1, c2;
100 do {
101 c1 = bb.get(ia);
102 c2 = bb.get(io);
103 if (c1 == 0)
104 return c1 - c2;
105 ia++;
106 io++;
107 }
108 while (c1 == c2);
109 return c1 - c2;
110 }
111 };
112 /// @endcond
113
114 /**
115 * Constructs a newly allocated {@code FlexBuffersBuilder} with {@link #BUILDER_FLAG_SHARE_KEYS} set.
116 * @param bufSize size of buffer in bytes.
117 */
118 public FlexBuffersBuilder(int bufSize) {
119 this(new ArrayReadWriteBuf(bufSize), BUILDER_FLAG_SHARE_KEYS);
120 }
121
122 /**
123 * Constructs a newly allocated {@code FlexBuffersBuilder} with {@link #BUILDER_FLAG_SHARE_KEYS} set.
124 */
125 public FlexBuffersBuilder() {
126 this(256);
127 }
128
129 /**
130 * Constructs a newly allocated {@code FlexBuffersBuilder}.
131 *
132 * @param bb `ByteBuffer` that will hold the message
133 * @param flags Share flags
134 */
135 @Deprecated
136 public FlexBuffersBuilder(ByteBuffer bb, int flags) {
137 this(new ArrayReadWriteBuf(bb.array()), flags);
138 }
139
140 public FlexBuffersBuilder(ReadWriteBuf bb, int flags) {
141 this.bb = bb;
142 this.flags = flags;
143 }
144
145 /**
146 * Constructs a newly allocated {@code FlexBuffersBuilder}.
147 * By default same keys will be serialized only once
148 * @param bb `ByteBuffer` that will hold the message
149 */
150 public FlexBuffersBuilder(ByteBuffer bb) {
151 this(bb, BUILDER_FLAG_SHARE_KEYS);
152 }
153
154 /**
155 * Reset the FlexBuffersBuilder by purging all data that it holds.
156 */
157 public void clear(){
158 bb.clear();
159 stack.clear();
160 keyPool.clear();
161 stringPool.clear();
162 finished = false;
163 }
164
165 /**
166 * Return `ByteBuffer` containing FlexBuffer message. {@code #finish()} must be called before calling this
167 * function otherwise an assert will trigger.
168 *
169 * @return `ByteBuffer` with finished message
170 */
171 public ReadWriteBuf getBuffer() {
172 assert (finished);
173 return bb;
174 }
175
176 /**
177 * Insert a single boolean into the buffer
178 * @param val true or false
179 */
180 public void putBoolean(boolean val) {
181 putBoolean(null, val);
182 }
183
184 /**
185 * Insert a single boolean into the buffer
186 * @param key key used to store element in map
187 * @param val true or false
188 */
189 public void putBoolean(String key, boolean val) {
190 stack.add(Value.bool(putKey(key), val));
191 }
192
193 private int putKey(String key) {
194 if (key == null) {
195 return -1;
196 }
197 int pos = bb.writePosition();
198 if ((flags & BUILDER_FLAG_SHARE_KEYS) != 0) {
199 Integer keyFromPool = keyPool.get(key);
200 if (keyFromPool == null) {
201 byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
202 bb.put(keyBytes, 0, keyBytes.length);
203 bb.put((byte) 0);
204 keyPool.put(key, pos);
205 } else {
206 pos = keyFromPool;
207 }
208 } else {
209 byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
210 bb.put(keyBytes, 0, keyBytes.length);
211 bb.put((byte) 0);
212 keyPool.put(key, pos);
213 }
214 return pos;
215 }
216
217 /**
218 * Adds a integer into the buff
219 * @param val integer
220 */
221 public void putInt(int val) {
222 putInt(null, val);
223 }
224
225 /**
226 * Adds a integer into the buff
227 * @param key key used to store element in map
228 * @param val integer
229 */
230 public void putInt(String key, int val) {
231 putInt(key, (long) val);
232 }
233
234 /**
235 * Adds a integer into the buff
236 * @param key key used to store element in map
237 * @param val 64-bit integer
238 */
239 public void putInt(String key, long val) {
240 int iKey = putKey(key);
241 if (Byte.MIN_VALUE <= val && val <= Byte.MAX_VALUE) {
242 stack.add(Value.int8(iKey, (int) val));
243 } else if (Short.MIN_VALUE <= val && val <= Short.MAX_VALUE) {
244 stack.add(Value.int16(iKey, (int) val));
245 } else if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) {
246 stack.add(Value.int32(iKey, (int) val));
247 } else {
248 stack.add(Value.int64(iKey, val));
249 }
250 }
251
252 /**
253 * Adds a 64-bit integer into the buff
254 * @param value integer
255 */
256 public void putInt(long value) {
257 putInt(null, value);
258 }
259
260 /**
261 * Adds a unsigned integer into the buff.
262 * @param value integer representing unsigned value
263 */
264 public void putUInt(int value) {
265 putUInt(null, (long) value);
266 }
267
268 /**
269 * Adds a unsigned integer (stored in a signed 64-bit integer) into the buff.
270 * @param value integer representing unsigned value
271 */
272 public void putUInt(long value) {
273 putUInt(null, value);
274 }
275
276 /**
277 * Adds a 64-bit unsigned integer (stored as {@link BigInteger}) into the buff.
278 * Warning: This operation might be very slow.
279 * @param value integer representing unsigned value
280 */
281 public void putUInt64(BigInteger value) {
282 putUInt64(null, value.longValue());
283 }
284
285 private void putUInt64(String key, long value) {
286 stack.add(Value.uInt64(putKey(key), value));
287 }
288
289 private void putUInt(String key, long value) {
290 int iKey = putKey(key);
291 Value vVal;
292
293 int width = widthUInBits(value);
294
295 if (width == WIDTH_8) {
296 vVal = Value.uInt8(iKey, (int)value);
297 } else if (width == WIDTH_16) {
298 vVal = Value.uInt16(iKey, (int)value);
299 } else if (width == WIDTH_32) {
300 vVal = Value.uInt32(iKey, (int)value);
301 } else {
302 vVal = Value.uInt64(iKey, value);
303 }
304 stack.add(vVal);
305 }
306
307 /**
308 * Adds a 32-bit float into the buff.
309 * @param value float representing value
310 */
311 public void putFloat(float value) {
312 putFloat(null, value);
313 }
314
315 /**
316 * Adds a 32-bit float into the buff.
317 * @param key key used to store element in map
318 * @param value float representing value
319 */
320 public void putFloat(String key, float val) {
321 stack.add(Value.float32(putKey(key), val));
322 }
323
324 /**
325 * Adds a 64-bit float into the buff.
326 * @param value float representing value
327 */
328 public void putFloat(double value) {
329 putFloat(null, value);
330 }
331
332 /**
333 * Adds a 64-bit float into the buff.
334 * @param key key used to store element in map
335 * @param value float representing value
336 */
337 public void putFloat(String key, double val) {
338 stack.add(Value.float64(putKey(key), val));
339 }
340
341 /**
342 * Adds a String into the buffer
343 * @param value string
344 * @return start position of string in the buffer
345 */
346 public int putString(String value) {
347 return putString(null, value);
348 }
349
350 /**
351 * Adds a String into the buffer
352 * @param key key used to store element in map
353 * @param value string
354 * @return start position of string in the buffer
355 */
356 public int putString(String key, String val) {
357 int iKey = putKey(key);
358 if ((flags & FlexBuffersBuilder.BUILDER_FLAG_SHARE_STRINGS) != 0) {
359 Integer i = stringPool.get(val);
360 if (i == null) {
361 Value value = writeString(iKey, val);
362 stringPool.put(val, (int) value.iValue);
363 stack.add(value);
364 return (int) value.iValue;
365 } else {
366 int bitWidth = widthUInBits(val.length());
367 stack.add(Value.blob(iKey, i, FBT_STRING, bitWidth));
368 return i;
369 }
370 } else {
371 Value value = writeString(iKey, val);
372 stack.add(value);
373 return (int) value.iValue;
374 }
375 }
376
377 private Value writeString(int key, String s) {
378 return writeBlob(key, s.getBytes(StandardCharsets.UTF_8), FBT_STRING, true);
379 }
380
381 // in bits to fit a unsigned int
382 static int widthUInBits(long len) {
383 if (len <= byteToUnsignedInt((byte)0xff)) return WIDTH_8;
384 if (len <= shortToUnsignedInt((short)0xffff)) return WIDTH_16;
385 if (len <= intToUnsignedLong(0xffff_ffff)) return WIDTH_32;
386 return WIDTH_64;
387 }
388
389 private Value writeBlob(int key, byte[] blob, int type, boolean trailing) {
390 int bitWidth = widthUInBits(blob.length);
391 int byteWidth = align(bitWidth);
392 writeInt(blob.length, byteWidth);
393 int sloc = bb.writePosition();
394 bb.put(blob, 0, blob.length);
395 if (trailing) {
396 bb.put((byte) 0);
397 }
398 return Value.blob(key, sloc, type, bitWidth);
399 }
400
401 // Align to prepare for writing a scalar with a certain size.
402 private int align(int alignment) {
403 int byteWidth = 1 << alignment;
404 int padBytes = Value.paddingBytes(bb.writePosition(), byteWidth);
405 while (padBytes-- != 0) {
406 bb.put((byte) 0);
407 }
408 return byteWidth;
409 }
410
411 private void writeInt(long value, int byteWidth) {
412 switch (byteWidth) {
413 case 1: bb.put((byte) value); break;
414 case 2: bb.putShort((short) value); break;
415 case 4: bb.putInt((int) value); break;
416 case 8: bb.putLong(value); break;
417 }
418 }
419
420 /**
421 * Adds a byte array into the message
422 * @param value byte array
423 * @return position in buffer as the start of byte array
424 */
425 public int putBlob(byte[] value) {
426 return putBlob(null, value);
427 }
428
429 /**
430 * Adds a byte array into the message
431 * @param key key used to store element in map
432 * @param value byte array
433 * @return position in buffer as the start of byte array
434 */
435 public int putBlob(String key, byte[] val) {
436 int iKey = putKey(key);
437 Value value = writeBlob(iKey, val, FBT_BLOB, false);
438 stack.add(value);
439 return (int) value.iValue;
440 }
441
442 /**
443 * Start a new vector in the buffer.
444 * @return a reference indicating position of the vector in buffer. This
445 * reference must be passed along when the vector is finished using endVector()
446 */
447 public int startVector() {
448 return stack.size();
449 }
450
451 /**
452 * Finishes a vector, but writing the information in the buffer
453 * @param key key used to store element in map
454 * @param start reference for begining of the vector. Returned by {@link startVector()}
James Kuszmaul8e62b022022-03-22 09:33:25 -0700455 * @param typed boolean indicating whether vector is typed
456 * @param fixed boolean indicating whether vector is fixed
Austin Schuh272c6132020-11-14 16:37:52 -0800457 * @return Reference to the vector
458 */
459 public int endVector(String key, int start, boolean typed, boolean fixed) {
460 int iKey = putKey(key);
461 Value vec = createVector(iKey, start, stack.size() - start, typed, fixed, null);
462 // Remove temp elements and return vector.
463 while (stack.size() > start) {
464 stack.remove(stack.size() - 1);
465 }
466 stack.add(vec);
467 return (int) vec.iValue;
468 }
469
470 /**
471 * Finish writing the message into the buffer. After that no other element must
472 * be inserted into the buffer. Also, you must call this function before start using the
473 * FlexBuffer message
474 * @return `ByteBuffer` containing the FlexBuffer message
475 */
476 public ByteBuffer finish() {
477 // If you hit this assert, you likely have objects that were never included
478 // in a parent. You need to have exactly one root to finish a buffer.
479 // Check your Start/End calls are matched, and all objects are inside
480 // some other object.
481 assert (stack.size() == 1);
482 // Write root value.
483 int byteWidth = align(stack.get(0).elemWidth(bb.writePosition(), 0));
484 writeAny(stack.get(0), byteWidth);
485 // Write root type.
486 bb.put(stack.get(0).storedPackedType());
487 // Write root size. Normally determined by parent, but root has no parent :)
488 bb.put((byte) byteWidth);
489 this.finished = true;
490 return ByteBuffer.wrap(bb.data(), 0, bb.writePosition());
491 }
492
493 /*
494 * Create a vector based on the elements stored in the stack
495 *
496 * @param key reference to its key
497 * @param start element in the stack
498 * @param length size of the vector
499 * @param typed whether is TypedVector or not
500 * @param fixed whether is Fixed vector or not
501 * @param keys Value representing key vector
502 * @return Value representing the created vector
503 */
504 private Value createVector(int key, int start, int length, boolean typed, boolean fixed, Value keys) {
505 assert (!fixed || typed); // typed=false, fixed=true combination is not supported.
506 // Figure out smallest bit width we can store this vector with.
507 int bitWidth = Math.max(WIDTH_8, widthUInBits(length));
508 int prefixElems = 1;
509 if (keys != null) {
510 // If this vector is part of a map, we will pre-fix an offset to the keys
511 // to this vector.
512 bitWidth = Math.max(bitWidth, keys.elemWidth(bb.writePosition(), 0));
513 prefixElems += 2;
514 }
515 int vectorType = FBT_KEY;
516 // Check bit widths and types for all elements.
517 for (int i = start; i < stack.size(); i++) {
518 int elemWidth = stack.get(i).elemWidth(bb.writePosition(), i + prefixElems);
519 bitWidth = Math.max(bitWidth, elemWidth);
520 if (typed) {
521 if (i == start) {
522 vectorType = stack.get(i).type;
523 if (!FlexBuffers.isTypedVectorElementType(vectorType)) {
524 throw new FlexBufferException("TypedVector does not support this element type");
525 }
526 } else {
527 // If you get this assert, you are writing a typed vector with
528 // elements that are not all the same type.
529 assert (vectorType == stack.get(i).type);
530 }
531 }
532 }
533 // If you get this assert, your fixed types are not one of:
534 // Int / UInt / Float / Key.
535 assert (!fixed || FlexBuffers.isTypedVectorElementType(vectorType));
536
537 int byteWidth = align(bitWidth);
538 // Write vector. First the keys width/offset if available, and size.
539 if (keys != null) {
540 writeOffset(keys.iValue, byteWidth);
541 writeInt(1L << keys.minBitWidth, byteWidth);
542 }
543 if (!fixed) {
544 writeInt(length, byteWidth);
545 }
546 // Then the actual data.
547 int vloc = bb.writePosition();
548 for (int i = start; i < stack.size(); i++) {
549 writeAny(stack.get(i), byteWidth);
550 }
551 // Then the types.
552 if (!typed) {
553 for (int i = start; i < stack.size(); i++) {
554 bb.put(stack.get(i).storedPackedType(bitWidth));
555 }
556 }
557 return new Value(key, keys != null ? FBT_MAP
558 : (typed ? FlexBuffers.toTypedVector(vectorType, fixed ? length : 0)
559 : FBT_VECTOR), bitWidth, vloc);
560 }
561
562 private void writeOffset(long val, int byteWidth) {
563 int reloff = (int) (bb.writePosition() - val);
564 assert (byteWidth == 8 || reloff < 1L << (byteWidth * 8));
565 writeInt(reloff, byteWidth);
566 }
567
568 private void writeAny(final Value val, int byteWidth) {
569 switch (val.type) {
570 case FBT_NULL:
571 case FBT_BOOL:
572 case FBT_INT:
573 case FBT_UINT:
574 writeInt(val.iValue, byteWidth);
575 break;
576 case FBT_FLOAT:
577 writeDouble(val.dValue, byteWidth);
578 break;
579 default:
580 writeOffset(val.iValue, byteWidth);
581 break;
582 }
583 }
584
585 private void writeDouble(double val, int byteWidth) {
586 if (byteWidth == 4) {
587 bb.putFloat((float) val);
588 } else if (byteWidth == 8) {
589 bb.putDouble(val);
590 }
591 }
592
593 /**
594 * Start a new map in the buffer.
595 * @return a reference indicating position of the map in buffer. This
596 * reference must be passed along when the map is finished using endMap()
597 */
598 public int startMap() {
599 return stack.size();
600 }
601
602 /**
603 * Finishes a map, but writing the information in the buffer
604 * @param key key used to store element in map
605 * @param start reference for begining of the map. Returned by {@link startMap()}
606 * @return Reference to the map
607 */
608 public int endMap(String key, int start) {
609 int iKey = putKey(key);
610
611 Collections.sort(stack.subList(start, stack.size()), keyComparator);
612
613 Value keys = createKeyVector(start, stack.size() - start);
614 Value vec = createVector(iKey, start, stack.size() - start, false, false, keys);
615 // Remove temp elements and return map.
616 while (stack.size() > start) {
617 stack.remove(stack.size() - 1);
618 }
619 stack.add(vec);
620 return (int) vec.iValue;
621 }
622
623 private Value createKeyVector(int start, int length) {
624 // Figure out smallest bit width we can store this vector with.
625 int bitWidth = Math.max(WIDTH_8, widthUInBits(length));
626 int prefixElems = 1;
627 // Check bit widths and types for all elements.
628 for (int i = start; i < stack.size(); i++) {
629 int elemWidth = Value.elemWidth(FBT_KEY, WIDTH_8, stack.get(i).key, bb.writePosition(), i + prefixElems);
630 bitWidth = Math.max(bitWidth, elemWidth);
631 }
632
633 int byteWidth = align(bitWidth);
634 // Write vector. First the keys width/offset if available, and size.
635 writeInt(length, byteWidth);
636 // Then the actual data.
637 int vloc = bb.writePosition();
638 for (int i = start; i < stack.size(); i++) {
639 int pos = stack.get(i).key;
640 assert(pos != -1);
641 writeOffset(stack.get(i).key, byteWidth);
642 }
643 // Then the types.
644 return new Value(-1, FlexBuffers.toTypedVector(FBT_KEY,0), bitWidth, vloc);
645 }
646
647 private static class Value {
648 final int type;
649 // for scalars, represents scalar size in bytes
650 // for vectors, represents the size
651 // for string, length
652 final int minBitWidth;
653 // float value
654 final double dValue;
655 // integer value
656 long iValue;
657 // position of the key associated with this value in buffer
658 int key;
659
660 Value(int key, int type, int bitWidth, long iValue) {
661 this.key = key;
662 this.type = type;
663 this.minBitWidth = bitWidth;
664 this.iValue = iValue;
665 this.dValue = Double.MIN_VALUE;
666 }
667
668 Value(int key, int type, int bitWidth, double dValue) {
669 this.key = key;
670 this.type = type;
671 this.minBitWidth = bitWidth;
672 this.dValue = dValue;
673 this.iValue = Long.MIN_VALUE;
674 }
675
676 static Value bool(int key, boolean b) {
677 return new Value(key, FBT_BOOL, WIDTH_8, b ? 1 : 0);
678 }
679
680 static Value blob(int key, int position, int type, int bitWidth) {
681 return new Value(key, type, bitWidth, position);
682 }
683
684 static Value int8(int key, int value) {
685 return new Value(key, FBT_INT, WIDTH_8, value);
686 }
687
688 static Value int16(int key, int value) {
689 return new Value(key, FBT_INT, WIDTH_16, value);
690 }
691
692 static Value int32(int key, int value) {
693 return new Value(key, FBT_INT, WIDTH_32, value);
694 }
695
696 static Value int64(int key, long value) {
697 return new Value(key, FBT_INT, WIDTH_64, value);
698 }
699
700 static Value uInt8(int key, int value) {
701 return new Value(key, FBT_UINT, WIDTH_8, value);
702 }
703
704 static Value uInt16(int key, int value) {
705 return new Value(key, FBT_UINT, WIDTH_16, value);
706 }
707
708 static Value uInt32(int key, int value) {
709 return new Value(key, FBT_UINT, WIDTH_32, value);
710 }
711
712 static Value uInt64(int key, long value) {
713 return new Value(key, FBT_UINT, WIDTH_64, value);
714 }
715
716 static Value float32(int key, float value) {
717 return new Value(key, FBT_FLOAT, WIDTH_32, value);
718 }
719
720 static Value float64(int key, double value) {
721 return new Value(key, FBT_FLOAT, WIDTH_64, value);
722 }
723
724 private byte storedPackedType() {
725 return storedPackedType(WIDTH_8);
726 }
727
728 private byte storedPackedType(int parentBitWidth) {
729 return packedType(storedWidth(parentBitWidth), type);
730 }
731
732 private static byte packedType(int bitWidth, int type) {
733 return (byte) (bitWidth | (type << 2));
734 }
735
736 private int storedWidth(int parentBitWidth) {
737 if (FlexBuffers.isTypeInline(type)) {
738 return Math.max(minBitWidth, parentBitWidth);
739 } else {
740 return minBitWidth;
741 }
742 }
743
744 private int elemWidth(int bufSize, int elemIndex) {
745 return elemWidth(type, minBitWidth, iValue, bufSize, elemIndex);
746 }
747
748 private static int elemWidth(int type, int minBitWidth, long iValue, int bufSize, int elemIndex) {
749 if (FlexBuffers.isTypeInline(type)) {
750 return minBitWidth;
751 } else {
752 // We have an absolute offset, but want to store a relative offset
753 // elem_index elements beyond the current buffer end. Since whether
754 // the relative offset fits in a certain byte_width depends on
755 // the size of the elements before it (and their alignment), we have
756 // to test for each size in turn.
757
758 // Original implementation checks for largest scalar
759 // which is long unsigned int
760 for (int byteWidth = 1; byteWidth <= 32; byteWidth *= 2) {
761 // Where are we going to write this offset?
762 int offsetLoc = bufSize + paddingBytes(bufSize, byteWidth) + (elemIndex * byteWidth);
763 // Compute relative offset.
764 long offset = offsetLoc - iValue;
765 // Does it fit?
James Kuszmaul8e62b022022-03-22 09:33:25 -0700766 int bitWidth = widthUInBits(offset);
Austin Schuh272c6132020-11-14 16:37:52 -0800767 if (((1L) << bitWidth) == byteWidth)
768 return bitWidth;
769 }
770 assert (false); // Must match one of the sizes above.
771 return WIDTH_64;
772 }
773 }
774
775 private static int paddingBytes(int bufSize, int scalarSize) {
776 return ((~bufSize) + 1) & (scalarSize - 1);
777 }
778 }
779}
780
781/// @}