Skip to content

Commit 388a56e

Browse files
Use FlexBuffers.Reference.parentWidth to restore Integer or Long.
1 parent c4fcb1d commit 388a56e

File tree

2 files changed

+91
-20
lines changed

2 files changed

+91
-20
lines changed

objectbox-java/src/main/java/io/objectbox/converter/FlexMapConverter.java

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.objectbox.flatbuffers.FlexBuffers;
55
import io.objectbox.flatbuffers.FlexBuffersBuilder;
66

7+
import java.lang.reflect.Field;
78
import java.nio.ByteBuffer;
89
import java.util.ArrayList;
910
import java.util.HashMap;
@@ -13,9 +14,12 @@
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
*/
2024
public 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());

tests/objectbox-java-test/src/test/java/io/objectbox/converter/FlexMapConverterTest.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import static org.junit.Assert.assertArrayEquals;
1515
import static org.junit.Assert.assertEquals;
1616
import static org.junit.Assert.assertThrows;
17+
import static org.junit.Assert.assertTrue;
1718

1819
public class FlexMapConverterTest {
1920

@@ -42,13 +43,55 @@ public void keysString_valsSupportedTypes_works() {
4243

4344
map.put("string", "Grüezi");
4445
map.put("boolean", true);
45-
// map.put("integer", 1);
46-
map.put("long", -2L);
46+
map.put("long", 1L);
4747
// map.put("float", 1.3f);
4848
map.put("double", -1.4d);
4949
convertAndBackThenAssert(map, converter);
5050
}
5151

52+
/**
53+
* If no item is wider than 32 bits, all integers are restored as Integer.
54+
*/
55+
@SuppressWarnings("unchecked")
56+
@Test
57+
public void keysString_valsIntegersBiggest32Bit_works() {
58+
FlexMapConverter converter = new StringFlexMapConverter();
59+
Map<String, Object> expected = new HashMap<>();
60+
61+
expected.put("integer-8bit", -1);
62+
expected.put("integer-32bit", Integer.MAX_VALUE);
63+
expected.put("long-8bit", -2L);
64+
expected.put("long-32bit", (long) Integer.MIN_VALUE);
65+
66+
Map<String, Object> actual = convertAndBack(expected, converter);
67+
68+
assertEquals(expected.size(), actual.size());
69+
for (Object value : actual.values()) {
70+
assertTrue(value instanceof Integer);
71+
}
72+
}
73+
74+
/**
75+
* If at least one item is 64 bit wide, all integers are restored as Long.
76+
*/
77+
@SuppressWarnings("unchecked")
78+
@Test
79+
public void keysString_valsIntegersBiggest64Bit_works() {
80+
FlexMapConverter converter = new StringFlexMapConverter();
81+
Map<String, Object> expected = new HashMap<>();
82+
83+
expected.put("integer-8bit", -1);
84+
expected.put("integer-32bit", Integer.MAX_VALUE);
85+
expected.put("long-64bit", Integer.MAX_VALUE + 1L);
86+
87+
Map<String, Object> actual = convertAndBack(expected, converter);
88+
89+
assertEquals(expected.size(), actual.size());
90+
for (Object value : actual.values()) {
91+
assertTrue(value instanceof Long);
92+
}
93+
}
94+
5295
// Note: can't use assertEquals(map, map) as byte[] does not implement equals(obj).
5396
@SuppressWarnings("unchecked")
5497
@Test
@@ -124,15 +167,13 @@ public void nestedList_works() {
124167
List<Object> embeddedList1 = new LinkedList<>();
125168
embeddedList1.add("Grüezi");
126169
embeddedList1.add(true);
127-
// embeddedList1.add(1);
128170
embeddedList1.add(-2L);
129171
// embeddedList1.add(1.3f);
130172
embeddedList1.add(-1.4d);
131173
map.put("Hello", embeddedList1);
132174
List<Object> embeddedList2 = new LinkedList<>();
133175
embeddedList2.add("Grüezi");
134176
embeddedList2.add(true);
135-
// embeddedList2.add(21);
136177
embeddedList2.add(-22L);
137178
// embeddedList2.add(2.3f);
138179
embeddedList2.add(-2.4d);

0 commit comments

Comments
 (0)