4
4
import io .objectbox .flatbuffers .FlexBuffers ;
5
5
import io .objectbox .flatbuffers .FlexBuffersBuilder ;
6
6
7
+ import java .lang .reflect .Field ;
7
8
import java .nio .ByteBuffer ;
8
9
import java .util .ArrayList ;
9
10
import java .util .HashMap ;
13
14
14
15
/**
15
16
* Converts between {@link Map} properties and byte arrays using FlexBuffers.
16
- *
17
+ * <p>
17
18
* All keys must have the same type (see {@link #convertToKey(String)}),
18
19
* 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.
19
23
*/
20
24
public abstract class FlexMapConverter implements PropertyConverter <Map <Object , Object >, byte []> {
21
25
@@ -57,12 +61,11 @@ private void addMap(FlexBuffersBuilder builder, String mapKey, Map<Object, Objec
57
61
builder .putString (key , (String ) value );
58
62
} else if (value instanceof Boolean ) {
59
63
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 );
63
66
} else if (value instanceof Long ) {
64
67
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.
66
69
// } else if (value instanceof Float) {
67
70
// builder.putFloat(key, (Float) value);
68
71
} else if (value instanceof Double ) {
@@ -92,12 +95,11 @@ private void addVector(FlexBuffersBuilder builder, String vectorKey, List<Object
92
95
builder .putString ((String ) item );
93
96
} else if (item instanceof Boolean ) {
94
97
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 );
98
100
} else if (item instanceof Long ) {
99
101
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.
101
103
// } else if (item instanceof Float) {
102
104
// builder.putFloat((Float) item);
103
105
} else if (item instanceof Double ) {
@@ -124,11 +126,28 @@ public Map<Object, Object> convertToEntityProperty(byte[] databaseValue) {
124
126
125
127
/**
126
128
* Converts a FlexBuffers string map key to the Java map key (e.g. String to Integer).
127
- *
129
+ * <p>
128
130
* This required conversion restricts all keys (root and embedded maps) to the same type.
129
131
*/
130
132
abstract Object convertToKey (String keyValue );
131
133
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
+
132
151
private Map <Object , Object > buildMap (FlexBuffers .Map map ) {
133
152
// As recommended by docs, iterate keys and values vectors in parallel to avoid binary search of key vector.
134
153
int entryCount = map .size ();
@@ -148,9 +167,12 @@ private Map<Object, Object> buildMap(FlexBuffers.Map map) {
148
167
} else if (value .isBoolean ()) {
149
168
resultMap .put (key , value .asBoolean ());
150
169
} 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
+ }
154
176
} else if (value .isFloat ()) {
155
177
// FIXME Float or Double? Reading Float as Double is destructive.
156
178
resultMap .put (key , value .asFloat ());
@@ -169,6 +191,9 @@ private List<Object> buildList(FlexBuffers.Vector vector) {
169
191
int itemCount = vector .size ();
170
192
List <Object > list = new ArrayList <>(itemCount );
171
193
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
+
172
197
for (int i = 0 ; i < itemCount ; i ++) {
173
198
FlexBuffers .Reference item = vector .get (i );
174
199
if (item .isMap ()) {
@@ -180,9 +205,14 @@ private List<Object> buildList(FlexBuffers.Vector vector) {
180
205
} else if (item .isBoolean ()) {
181
206
list .add (item .asBoolean ());
182
207
} 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
+ }
186
216
} else if (item .isFloat ()) {
187
217
// FIXME Float or Double? Reading Float as Double is destructive.
188
218
list .add (item .asFloat ());
0 commit comments