Skip to content

Commit f2aadab

Browse files
Merge branch '60-string-map-to-flexbuffer-converter' into dev
2 parents ccee90c + c2f7bb4 commit f2aadab

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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.HashMap;
9+
import java.util.Map;
10+
import java.util.Map.Entry;
11+
import java.util.concurrent.atomic.AtomicReference;
12+
13+
/**
14+
* Converts a String map entity property to a byte array database value using FlexBuffers.
15+
*/
16+
public class StringMapConverter implements PropertyConverter<Map<String, String>, byte[]> {
17+
18+
private static final AtomicReference<FlexBuffersBuilder> cachedBuilder = new AtomicReference<>();
19+
20+
@Override
21+
public byte[] convertToDatabaseValue(Map<String, String> map) {
22+
if (map == null) return null;
23+
24+
FlexBuffersBuilder builder = cachedBuilder.getAndSet(null);
25+
if (builder == null) {
26+
// Note: BUILDER_FLAG_SHARE_KEYS_AND_STRINGS is as fast as no flags for small maps/strings
27+
// and faster for larger maps/strings. BUILDER_FLAG_SHARE_STRINGS is always slower.
28+
builder = new FlexBuffersBuilder(
29+
new ArrayReadWriteBuf(512),
30+
FlexBuffersBuilder.BUILDER_FLAG_SHARE_KEYS_AND_STRINGS
31+
);
32+
}
33+
int mapStart = builder.startMap();
34+
35+
for (Entry<String, String> entry : map.entrySet()) {
36+
if (entry.getKey() == null || entry.getValue() == null) {
37+
throw new IllegalArgumentException("Map keys or values must not be null");
38+
}
39+
builder.putString(entry.getKey(), entry.getValue());
40+
}
41+
42+
builder.endMap(null, mapStart);
43+
ByteBuffer buffer = builder.finish();
44+
45+
byte[] out = new byte[buffer.limit()];
46+
buffer.get(out);
47+
48+
// Cache if builder does not consume too much memory
49+
if (buffer.limit() <= 256 * 1024) {
50+
builder.clear();
51+
cachedBuilder.getAndSet(builder);
52+
}
53+
54+
return out;
55+
}
56+
57+
@Override
58+
public Map<String, String> convertToEntityProperty(byte[] databaseValue) {
59+
if (databaseValue == null) return null;
60+
61+
FlexBuffers.Map map = FlexBuffers.getRoot(new ArrayReadWriteBuf(databaseValue, databaseValue.length)).asMap();
62+
63+
// As recommended by docs, iterate keys and values vectors in parallel to avoid binary search of key vector.
64+
int entryCount = map.size();
65+
FlexBuffers.KeyVector keys = map.keys();
66+
FlexBuffers.Vector values = map.values();
67+
// Note: avoid HashMap re-hashing by choosing large enough initial capacity.
68+
// From docs: If the initial capacity is greater than the maximum number of entries divided by the load factor,
69+
// no rehash operations will ever occur.
70+
// So set initial capacity based on default load factor 0.75 accordingly.
71+
Map<String, String> resultMap = new HashMap<>((int) (entryCount / 0.75 + 1));
72+
for (int i = 0; i < entryCount; i++) {
73+
String key = keys.get(i).toString();
74+
String value = values.get(i).asString();
75+
resultMap.put(key, value);
76+
}
77+
78+
return resultMap;
79+
}
80+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.objectbox.converter;
2+
3+
import org.junit.Test;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import javax.annotation.Nullable;
9+
10+
11+
import static org.junit.Assert.assertEquals;
12+
13+
public class StringMapConverterTest {
14+
15+
@Test
16+
public void works() {
17+
convertAndBackThenAssert(null);
18+
19+
convertAndBackThenAssert(new HashMap<>());
20+
21+
Map<String, String> mapWithValues = new HashMap<>();
22+
mapWithValues.put("Hello", "Grüezi");
23+
mapWithValues.put("💡", "Idea");
24+
mapWithValues.put("", "Empty String Key");
25+
convertAndBackThenAssert(mapWithValues);
26+
}
27+
28+
private void convertAndBackThenAssert(@Nullable Map<String, String> expected) {
29+
StringMapConverter converter = new StringMapConverter();
30+
byte[] converted = converter.convertToDatabaseValue(expected);
31+
32+
Map<String, String> actual = converter.convertToEntityProperty(converted);
33+
assertEquals(expected, actual);
34+
}
35+
}

0 commit comments

Comments
 (0)