44import io .objectbox .flatbuffers .FlexBuffers ;
55import io .objectbox .flatbuffers .FlexBuffersBuilder ;
66
7+ import java .lang .reflect .Field ;
78import java .nio .ByteBuffer ;
89import java .util .ArrayList ;
910import java .util .HashMap ;
1314
1415/**
1516 * Converts between {@link Map} properties and byte arrays using FlexBuffers.
16- *
17+ * <p>
1718 * All keys must have the same type (see {@link #convertToKey(String)}),
1819 * value types are limited to those supported by FlexBuffers.
20+ * <p>
21+ * If any item requires 64 bits for storage in the FlexBuffers Map/Vector (a large Long, a Double)
22+ * all integers are restored as Long, otherwise Integer.
1923 */
2024public abstract class FlexMapConverter implements PropertyConverter <Map <Object , Object >, byte []> {
2125
@@ -57,12 +61,11 @@ private void addMap(FlexBuffersBuilder builder, String mapKey, Map<Object, Objec
5761 builder .putString (key , (String ) value );
5862 } else if (value instanceof Boolean ) {
5963 builder .putBoolean (key , (Boolean ) value );
60- // FIXME When restoring, can't know if Integer or Long.
61- // } else if (value instanceof Integer) {
62- // builder.putInt(key, (Integer) value);
64+ } else if (value instanceof Integer ) {
65+ builder .putInt (key , (Integer ) value );
6366 } else if (value instanceof Long ) {
6467 builder .putInt (key , (Long ) value );
65- // FIXME When restoring, can't know if Float or Double.
68+ // FIXME When restoring, can't know if Float or Double.
6669// } else if (value instanceof Float) {
6770// builder.putFloat(key, (Float) value);
6871 } else if (value instanceof Double ) {
@@ -92,12 +95,11 @@ private void addVector(FlexBuffersBuilder builder, String vectorKey, List<Object
9295 builder .putString ((String ) item );
9396 } else if (item instanceof Boolean ) {
9497 builder .putBoolean ((Boolean ) item );
95- // FIXME When restoring, can't know if Integer or Long.
96- // } else if (item instanceof Integer) {
97- // builder.putInt((Integer) item);
98+ } else if (item instanceof Integer ) {
99+ builder .putInt ((Integer ) item );
98100 } else if (item instanceof Long ) {
99101 builder .putInt ((Long ) item );
100- // FIXME When restoring, can't know if Float or Double.
102+ // FIXME When restoring, can't know if Float or Double.
101103// } else if (item instanceof Float) {
102104// builder.putFloat((Float) item);
103105 } else if (item instanceof Double ) {
@@ -124,11 +126,28 @@ public Map<Object, Object> convertToEntityProperty(byte[] databaseValue) {
124126
125127 /**
126128 * Converts a FlexBuffers string map key to the Java map key (e.g. String to Integer).
127- *
129+ * <p>
128130 * This required conversion restricts all keys (root and embedded maps) to the same type.
129131 */
130132 abstract Object convertToKey (String keyValue );
131133
134+ /**
135+ * Returns the width in bytes stored in the private parentWidth field of FlexBuffers.Reference.
136+ * Note: FlexBuffers stores all items in a map/vector using the size of the widest item. However,
137+ * an item's size is only as wide as needed, e.g. a 64-bit integer (Java Long, 8 bytes) will be
138+ * reduced to 1 byte if it does not exceed its value range.
139+ */
140+ private int getParentWidthInBytesOf (FlexBuffers .Reference reference ) {
141+ try {
142+ Field parentWidthF = reference .getClass ().getDeclaredField ("parentWidth" );
143+ parentWidthF .setAccessible (true );
144+ return (int ) parentWidthF .get (reference );
145+ } catch (Exception e ) {
146+ // If thrown, it is likely the FlexBuffers API has changed and the above should be updated.
147+ throw new RuntimeException ("FlexMapConverter could not determine FlexBuffers integer bit width." , e );
148+ }
149+ }
150+
132151 private Map <Object , Object > buildMap (FlexBuffers .Map map ) {
133152 // As recommended by docs, iterate keys and values vectors in parallel to avoid binary search of key vector.
134153 int entryCount = map .size ();
@@ -148,9 +167,12 @@ private Map<Object, Object> buildMap(FlexBuffers.Map map) {
148167 } else if (value .isBoolean ()) {
149168 resultMap .put (key , value .asBoolean ());
150169 } else if (value .isInt ()) {
151- // FIXME Integer or Long?
152- // resultMap.put(key, value.asInt());
153- resultMap .put (key , value .asLong ());
170+ int parentWidthInBytes = getParentWidthInBytesOf (value );
171+ if (parentWidthInBytes == 8 ) {
172+ resultMap .put (key , value .asLong ());
173+ } else {
174+ resultMap .put (key , value .asInt ());
175+ }
154176 } else if (value .isFloat ()) {
155177 // FIXME Float or Double? Reading Float as Double is destructive.
156178 resultMap .put (key , value .asFloat ());
@@ -169,6 +191,9 @@ private List<Object> buildList(FlexBuffers.Vector vector) {
169191 int itemCount = vector .size ();
170192 List <Object > list = new ArrayList <>(itemCount );
171193
194+ // FlexBuffers uses the byte width of the biggest item to size all items, so only need to check the first.
195+ Integer parentWidthInBytes = null ;
196+
172197 for (int i = 0 ; i < itemCount ; i ++) {
173198 FlexBuffers .Reference item = vector .get (i );
174199 if (item .isMap ()) {
@@ -180,9 +205,14 @@ private List<Object> buildList(FlexBuffers.Vector vector) {
180205 } else if (item .isBoolean ()) {
181206 list .add (item .asBoolean ());
182207 } else if (item .isInt ()) {
183- // FIXME Integer or Long?
184- // list.add(item.asInt());
185- list .add (item .asLong ());
208+ if (parentWidthInBytes == null ) {
209+ parentWidthInBytes = getParentWidthInBytesOf (item );
210+ }
211+ if (parentWidthInBytes == 8 ) {
212+ list .add (item .asLong ());
213+ } else {
214+ list .add (item .asInt ());
215+ }
186216 } else if (item .isFloat ()) {
187217 // FIXME Float or Double? Reading Float as Double is destructive.
188218 list .add (item .asFloat ());
0 commit comments