|
| 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 | +} |
0 commit comments