Skip to content

Commit c4fcb1d

Browse files
Add generic FlexMapConverter.
1 parent 8687cc0 commit c4fcb1d

File tree

5 files changed

+426
-0
lines changed

5 files changed

+426
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package io.objectbox.converter;
2+
3+
import io.objectbox.flatbuffers.ArrayReadWriteBuf;
4+
import io.objectbox.flatbuffers.FlexBuffers;
5+
import io.objectbox.flatbuffers.FlexBuffersBuilder;
6+
7+
import java.nio.ByteBuffer;
8+
import java.util.ArrayList;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Map.Entry;
13+
14+
/**
15+
* Converts between {@link Map} properties and byte arrays using FlexBuffers.
16+
*
17+
* All keys must have the same type (see {@link #convertToKey(String)}),
18+
* value types are limited to those supported by FlexBuffers.
19+
*/
20+
public abstract class FlexMapConverter implements PropertyConverter<Map<Object, Object>, byte[]> {
21+
22+
@Override
23+
public byte[] convertToDatabaseValue(Map<Object, Object> map) {
24+
if (map == null) return null;
25+
26+
FlexBuffersBuilder builder = new FlexBuffersBuilder(
27+
new ArrayReadWriteBuf(512),
28+
FlexBuffersBuilder.BUILDER_FLAG_SHARE_KEYS_AND_STRINGS
29+
);
30+
31+
addMap(builder, null, map);
32+
33+
ByteBuffer buffer = builder.finish();
34+
35+
byte[] out = new byte[buffer.limit()];
36+
buffer.get(out);
37+
return out;
38+
}
39+
40+
private void addMap(FlexBuffersBuilder builder, String mapKey, Map<Object, Object> map) {
41+
int mapStart = builder.startMap();
42+
43+
for (Entry<Object, Object> entry : map.entrySet()) {
44+
Object value = entry.getValue();
45+
if (entry.getKey() == null || value == null) {
46+
throw new IllegalArgumentException("Map keys or values must not be null");
47+
}
48+
49+
String key = entry.getKey().toString();
50+
if (value instanceof Map) {
51+
//noinspection unchecked
52+
addMap(builder, key, (Map<Object, Object>) value);
53+
} else if (value instanceof List) {
54+
//noinspection unchecked
55+
addVector(builder, key, (List<Object>) value);
56+
} else if (value instanceof String) {
57+
builder.putString(key, (String) value);
58+
} else if (value instanceof Boolean) {
59+
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);
63+
} else if (value instanceof Long) {
64+
builder.putInt(key, (Long) value);
65+
// FIXME When restoring, can't know if Float or Double.
66+
// } else if (value instanceof Float) {
67+
// builder.putFloat(key, (Float) value);
68+
} else if (value instanceof Double) {
69+
builder.putFloat(key, (Double) value);
70+
} else if (value instanceof byte[]) {
71+
builder.putBlob(key, (byte[]) value);
72+
} else {
73+
throw new IllegalArgumentException(
74+
"Map values of this type are not supported: " + value.getClass().getSimpleName());
75+
}
76+
}
77+
78+
builder.endMap(mapKey, mapStart);
79+
}
80+
81+
private void addVector(FlexBuffersBuilder builder, String vectorKey, List<Object> list) {
82+
int vectorStart = builder.startVector();
83+
84+
for (Object item : list) {
85+
if (item instanceof Map) {
86+
//noinspection unchecked
87+
addMap(builder, null, (Map<Object, Object>) item);
88+
} else if (item instanceof List) {
89+
//noinspection unchecked
90+
addVector(builder, null, (List<Object>) item);
91+
} else if (item instanceof String) {
92+
builder.putString((String) item);
93+
} else if (item instanceof Boolean) {
94+
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 Long) {
99+
builder.putInt((Long) item);
100+
// FIXME When restoring, can't know if Float or Double.
101+
// } else if (item instanceof Float) {
102+
// builder.putFloat((Float) item);
103+
} else if (item instanceof Double) {
104+
builder.putFloat((Double) item);
105+
} else if (item instanceof byte[]) {
106+
builder.putBlob((byte[]) item);
107+
} else {
108+
throw new IllegalArgumentException(
109+
"List values of this type are not supported: " + item.getClass().getSimpleName());
110+
}
111+
}
112+
113+
builder.endVector(vectorKey, vectorStart, false, false);
114+
}
115+
116+
@Override
117+
public Map<Object, Object> convertToEntityProperty(byte[] databaseValue) {
118+
if (databaseValue == null) return null;
119+
120+
FlexBuffers.Map map = FlexBuffers.getRoot(new ArrayReadWriteBuf(databaseValue, databaseValue.length)).asMap();
121+
122+
return buildMap(map);
123+
}
124+
125+
/**
126+
* Converts a FlexBuffers string map key to the Java map key (e.g. String to Integer).
127+
*
128+
* This required conversion restricts all keys (root and embedded maps) to the same type.
129+
*/
130+
abstract Object convertToKey(String keyValue);
131+
132+
private Map<Object, Object> buildMap(FlexBuffers.Map map) {
133+
// As recommended by docs, iterate keys and values vectors in parallel to avoid binary search of key vector.
134+
int entryCount = map.size();
135+
FlexBuffers.KeyVector keys = map.keys();
136+
FlexBuffers.Vector values = map.values();
137+
Map<Object, Object> resultMap = new HashMap<>(entryCount);
138+
for (int i = 0; i < entryCount; i++) {
139+
String rawKey = keys.get(i).toString();
140+
Object key = convertToKey(rawKey);
141+
FlexBuffers.Reference value = values.get(i);
142+
if (value.isMap()) {
143+
resultMap.put(key, buildMap(value.asMap()));
144+
} else if (value.isVector()) {
145+
resultMap.put(key, buildList(value.asVector()));
146+
} else if (value.isString()) {
147+
resultMap.put(key, value.asString());
148+
} else if (value.isBoolean()) {
149+
resultMap.put(key, value.asBoolean());
150+
} else if (value.isInt()) {
151+
// FIXME Integer or Long?
152+
// resultMap.put(key, value.asInt());
153+
resultMap.put(key, value.asLong());
154+
} else if (value.isFloat()) {
155+
// FIXME Float or Double? Reading Float as Double is destructive.
156+
resultMap.put(key, value.asFloat());
157+
} else if (value.isBlob()) {
158+
resultMap.put(key, value.asBlob().getBytes());
159+
} else {
160+
throw new IllegalArgumentException(
161+
"Map values of this type are not supported: " + value.getClass().getSimpleName());
162+
}
163+
}
164+
165+
return resultMap;
166+
}
167+
168+
private List<Object> buildList(FlexBuffers.Vector vector) {
169+
int itemCount = vector.size();
170+
List<Object> list = new ArrayList<>(itemCount);
171+
172+
for (int i = 0; i < itemCount; i++) {
173+
FlexBuffers.Reference item = vector.get(i);
174+
if (item.isMap()) {
175+
list.add(buildMap(item.asMap()));
176+
} else if (item.isVector()) {
177+
list.add(buildList(item.asVector()));
178+
} else if (item.isString()) {
179+
list.add(item.asString());
180+
} else if (item.isBoolean()) {
181+
list.add(item.asBoolean());
182+
} else if (item.isInt()) {
183+
// FIXME Integer or Long?
184+
// list.add(item.asInt());
185+
list.add(item.asLong());
186+
} else if (item.isFloat()) {
187+
// FIXME Float or Double? Reading Float as Double is destructive.
188+
list.add(item.asFloat());
189+
} else if (item.isBlob()) {
190+
list.add(item.asBlob().getBytes());
191+
} else {
192+
throw new IllegalArgumentException(
193+
"List values of this type are not supported: " + item.getClass().getSimpleName());
194+
}
195+
}
196+
197+
return list;
198+
}
199+
200+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.objectbox.converter;
2+
3+
public class IntegerFlexMapConverter extends FlexMapConverter {
4+
@Override
5+
Integer convertToKey(String keyValue) {
6+
return Integer.valueOf(keyValue);
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.objectbox.converter;
2+
3+
public class LongFlexMapConverter extends FlexMapConverter {
4+
@Override
5+
Object convertToKey(String keyValue) {
6+
return Long.valueOf(keyValue);
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.objectbox.converter;
2+
3+
public class StringFlexMapConverter extends FlexMapConverter {
4+
@Override
5+
Object convertToKey(String keyValue) {
6+
return keyValue;
7+
}
8+
}

0 commit comments

Comments
 (0)