Skip to content

Commit cfdf381

Browse files
Merge branch '125-flexmap' into dev
2 parents 342b96a + e0aa2b3 commit cfdf381

16 files changed

+374
-62
lines changed

objectbox-java/src/main/java/io/objectbox/Property.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ private void checkNotStringArray() {
443443
}
444444

445445
/**
446-
* For a String array or String-key map property, matches if at least one element equals the given value
446+
* For a String array, list or String-key map property, matches if at least one element equals the given value
447447
* using {@link StringOrder#CASE_SENSITIVE StringOrder#CASE_SENSITIVE}.
448448
*
449449
* @see #containsElement(String, StringOrder)

objectbox-java/src/main/java/io/objectbox/converter/FlexMapConverter.java renamed to objectbox-java/src/main/java/io/objectbox/converter/FlexObjectConverter.java

Lines changed: 106 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,31 @@
1010
import java.util.HashMap;
1111
import java.util.List;
1212
import java.util.Map;
13-
import java.util.Map.Entry;
1413
import java.util.concurrent.atomic.AtomicReference;
1514

1615
/**
17-
* Converts between {@link Map} properties and byte arrays using FlexBuffers.
16+
* Converts between {@link Object} properties and byte arrays using FlexBuffers.
1817
* <p>
19-
* All keys must have the same type (see {@link #convertToKey(String)}),
20-
* value types are limited to those supported by FlexBuffers.
18+
* Types are limited to those supported by FlexBuffers, including that map keys must be {@link String}.
19+
* (There are subclasses available that auto-convert {@link Integer} and {@link Long} key maps,
20+
* see {@link #convertToKey}.)
2121
* <p>
2222
* If any item requires 64 bits for storage in the FlexBuffers Map/Vector (a large Long, a Double)
23-
* all integers are restored as Long, otherwise Integer.
23+
* all integers are restored as {@link Long}, otherwise {@link Integer}.
24+
* So e.g. when storing only a {@link Long} value of {@code 1L}, the value restored from the
25+
* database will be of type {@link Integer}.
26+
* (There are subclasses available that always restore as {@link Long}, see {@link #shouldRestoreAsLong}.)
27+
* <p>
28+
* Values of type {@link Float} are always restored as {@link Double}.
29+
* Cast to {@link Float} to obtain the original value.
2430
*/
25-
public abstract class FlexMapConverter implements PropertyConverter<Map<Object, Object>, byte[]> {
31+
public class FlexObjectConverter implements PropertyConverter<Object, byte[]> {
2632

2733
private static final AtomicReference<FlexBuffersBuilder> cachedBuilder = new AtomicReference<>();
2834

2935
@Override
30-
public byte[] convertToDatabaseValue(Map<Object, Object> map) {
31-
if (map == null) return null;
36+
public byte[] convertToDatabaseValue(Object value) {
37+
if (value == null) return null;
3238

3339
FlexBuffersBuilder builder = cachedBuilder.getAndSet(null);
3440
if (builder == null) {
@@ -40,7 +46,7 @@ public byte[] convertToDatabaseValue(Map<Object, Object> map) {
4046
);
4147
}
4248

43-
addMap(builder, null, map);
49+
addValue(builder, value);
4450

4551
ByteBuffer buffer = builder.finish();
4652

@@ -56,16 +62,59 @@ public byte[] convertToDatabaseValue(Map<Object, Object> map) {
5662
return out;
5763
}
5864

65+
private void addValue(FlexBuffersBuilder builder, Object value) {
66+
if (value instanceof Map) {
67+
//noinspection unchecked
68+
addMap(builder, null, (Map<Object, Object>) value);
69+
} else if (value instanceof List) {
70+
//noinspection unchecked
71+
addVector(builder, null, (List<Object>) value);
72+
} else if (value instanceof String) {
73+
builder.putString((String) value);
74+
} else if (value instanceof Boolean) {
75+
builder.putBoolean((Boolean) value);
76+
} else if (value instanceof Byte) {
77+
// Will always be restored as Integer.
78+
builder.putInt(((Byte) value).intValue());
79+
} else if (value instanceof Short) {
80+
// Will always be restored as Integer.
81+
builder.putInt(((Short) value).intValue());
82+
} else if (value instanceof Integer) {
83+
builder.putInt((Integer) value);
84+
} else if (value instanceof Long) {
85+
builder.putInt((Long) value);
86+
} else if (value instanceof Float) {
87+
builder.putFloat((Float) value);
88+
} else if (value instanceof Double) {
89+
builder.putFloat((Double) value);
90+
} else if (value instanceof byte[]) {
91+
builder.putBlob((byte[]) value);
92+
} else {
93+
throw new IllegalArgumentException(
94+
"Values of this type are not supported: " + value.getClass().getSimpleName());
95+
}
96+
}
97+
98+
/**
99+
* Checks Java map key is of the expected type, otherwise throws.
100+
*/
101+
protected void checkMapKeyType(Object rawKey) {
102+
if (!(rawKey instanceof String)) {
103+
throw new IllegalArgumentException("Map keys must be String");
104+
}
105+
}
106+
59107
private void addMap(FlexBuffersBuilder builder, String mapKey, Map<Object, Object> map) {
60108
int mapStart = builder.startMap();
61109

62-
for (Entry<Object, Object> entry : map.entrySet()) {
110+
for (Map.Entry<Object, Object> entry : map.entrySet()) {
111+
Object rawKey = entry.getKey();
63112
Object value = entry.getValue();
64-
if (entry.getKey() == null || value == null) {
113+
if (rawKey == null || value == null) {
65114
throw new IllegalArgumentException("Map keys or values must not be null");
66115
}
67-
68-
String key = entry.getKey().toString();
116+
checkMapKeyType(rawKey);
117+
String key = rawKey.toString();
69118
if (value instanceof Map) {
70119
//noinspection unchecked
71120
addMap(builder, key, (Map<Object, Object>) value);
@@ -76,6 +125,12 @@ private void addMap(FlexBuffersBuilder builder, String mapKey, Map<Object, Objec
76125
builder.putString(key, (String) value);
77126
} else if (value instanceof Boolean) {
78127
builder.putBoolean(key, (Boolean) value);
128+
} else if (value instanceof Byte) {
129+
// Will always be restored as Integer.
130+
builder.putInt(key, ((Byte) value).intValue());
131+
} else if (value instanceof Short) {
132+
// Will always be restored as Integer.
133+
builder.putInt(key, ((Short) value).intValue());
79134
} else if (value instanceof Integer) {
80135
builder.putInt(key, (Integer) value);
81136
} else if (value instanceof Long) {
@@ -99,6 +154,9 @@ private void addVector(FlexBuffersBuilder builder, String vectorKey, List<Object
99154
int vectorStart = builder.startVector();
100155

101156
for (Object item : list) {
157+
if (item == null) {
158+
throw new IllegalArgumentException("List elements must not be null");
159+
}
102160
if (item instanceof Map) {
103161
//noinspection unchecked
104162
addMap(builder, null, (Map<Object, Object>) item);
@@ -109,6 +167,12 @@ private void addVector(FlexBuffersBuilder builder, String vectorKey, List<Object
109167
builder.putString((String) item);
110168
} else if (item instanceof Boolean) {
111169
builder.putBoolean((Boolean) item);
170+
} else if (item instanceof Byte) {
171+
// Will always be restored as Integer.
172+
builder.putInt(((Byte) item).intValue());
173+
} else if (item instanceof Short) {
174+
// Will always be restored as Integer.
175+
builder.putInt(((Short) item).intValue());
112176
} else if (item instanceof Integer) {
113177
builder.putInt((Integer) item);
114178
} else if (item instanceof Long) {
@@ -129,20 +193,42 @@ private void addVector(FlexBuffersBuilder builder, String vectorKey, List<Object
129193
}
130194

131195
@Override
132-
public Map<Object, Object> convertToEntityProperty(byte[] databaseValue) {
196+
public Object convertToEntityProperty(byte[] databaseValue) {
133197
if (databaseValue == null) return null;
134198

135-
FlexBuffers.Map map = FlexBuffers.getRoot(new ArrayReadWriteBuf(databaseValue, databaseValue.length)).asMap();
136-
137-
return buildMap(map);
199+
FlexBuffers.Reference value = FlexBuffers.getRoot(new ArrayReadWriteBuf(databaseValue, databaseValue.length));
200+
if (value.isMap()) {
201+
return buildMap(value.asMap());
202+
} else if (value.isVector()) {
203+
return buildList(value.asVector());
204+
} else if (value.isString()) {
205+
return value.asString();
206+
} else if (value.isBoolean()) {
207+
return value.asBoolean();
208+
} else if (value.isInt()) {
209+
if (shouldRestoreAsLong(value)) {
210+
return value.asLong();
211+
} else {
212+
return value.asInt();
213+
}
214+
} else if (value.isFloat()) {
215+
// Always return as double; if original was float consumer can cast to obtain original value.
216+
return value.asFloat();
217+
} else if (value.isBlob()) {
218+
return value.asBlob().getBytes();
219+
} else {
220+
throw new IllegalArgumentException("FlexBuffers type is not supported: " + value.getType());
221+
}
138222
}
139223

140224
/**
141225
* Converts a FlexBuffers string map key to the Java map key (e.g. String to Integer).
142226
* <p>
143227
* This required conversion restricts all keys (root and embedded maps) to the same type.
144228
*/
145-
abstract Object convertToKey(String keyValue);
229+
Object convertToKey(String keyValue) {
230+
return keyValue;
231+
}
146232

147233
/**
148234
* Returns true if the width in bytes stored in the private parentWidth field of FlexBuffers.Reference is 8.
@@ -190,7 +276,7 @@ private Map<Object, Object> buildMap(FlexBuffers.Map map) {
190276
resultMap.put(key, value.asInt());
191277
}
192278
} else if (value.isFloat()) {
193-
// Always return as double; if original was float casting will give original value.
279+
// Always return as double; if original was float consumer can cast to obtain original value.
194280
resultMap.put(key, value.asFloat());
195281
} else if (value.isBlob()) {
196282
resultMap.put(key, value.asBlob().getBytes());
@@ -230,7 +316,7 @@ private List<Object> buildList(FlexBuffers.Vector vector) {
230316
list.add(item.asInt());
231317
}
232318
} else if (item.isFloat()) {
233-
// Always return as double; if original was float casting will give original value.
319+
// Always return as double; if original was float consumer can cast to obtain original value.
234320
list.add(item.asFloat());
235321
} else if (item.isBlob()) {
236322
list.add(item.asBlob().getBytes());
@@ -242,5 +328,4 @@ private List<Object> buildList(FlexBuffers.Vector vector) {
242328

243329
return list;
244330
}
245-
246331
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package io.objectbox.converter;
22

33
/**
4-
* Used to automatically convert {@code Map<Integer, V>}.
4+
* Used to automatically convert {@code Map&lt;Integer, V&gt;}.
55
*/
6-
public class IntegerFlexMapConverter extends FlexMapConverter {
6+
public class IntegerFlexMapConverter extends FlexObjectConverter {
7+
8+
@Override
9+
protected void checkMapKeyType(Object rawKey) {
10+
if (!(rawKey instanceof Integer)) {
11+
throw new IllegalArgumentException("Map keys must be Integer");
12+
}
13+
}
14+
715
@Override
816
Integer convertToKey(String keyValue) {
917
return Integer.valueOf(keyValue);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import io.objectbox.flatbuffers.FlexBuffers;
44

55
/**
6-
* Used to automatically convert {@code Map<Integer, Long>}.
6+
* Used to automatically convert {@code Map&lt;Integer, Long&gt;}.
7+
* <p>
8+
* Unlike {@link FlexObjectConverter} always restores integer map values as {@link Long}.
79
*/
810
public class IntegerLongMapConverter extends IntegerFlexMapConverter {
911
@Override

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33
/**
44
* Used to automatically convert {@code Map<Long, V>}.
55
*/
6-
public class LongFlexMapConverter extends FlexMapConverter {
6+
public class LongFlexMapConverter extends FlexObjectConverter {
7+
8+
@Override
9+
protected void checkMapKeyType(Object rawKey) {
10+
if (!(rawKey instanceof Long)) {
11+
throw new IllegalArgumentException("Map keys must be Long");
12+
}
13+
}
14+
715
@Override
816
Object convertToKey(String keyValue) {
917
return Long.valueOf(keyValue);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import io.objectbox.flatbuffers.FlexBuffers;
44

55
/**
6-
* Used to automatically convert {@code Map<Long, Long>}.
6+
* Used to automatically convert {@code Map&lt;Long, Long&gt;}.
7+
* <p>
8+
* Unlike {@link FlexObjectConverter} always restores integer map values as {@link Long}.
79
*/
810
public class LongLongMapConverter extends LongFlexMapConverter {
911
@Override
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
package io.objectbox.converter;
22

33
/**
4-
* Used to automatically convert {@code Map<String, V>}.
4+
* Used to automatically convert {@code Map&lt;String, V&gt;}.
55
*/
6-
public class StringFlexMapConverter extends FlexMapConverter {
7-
@Override
8-
Object convertToKey(String keyValue) {
9-
return keyValue;
10-
}
6+
public class StringFlexMapConverter extends FlexObjectConverter {
117
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import io.objectbox.flatbuffers.FlexBuffers;
44

55
/**
6-
* Used to automatically convert {@code Map<String, Long>}.
6+
* Used to automatically convert {@code Map&lt;String, Long&gt;}.
77
*/
88
public class StringLongMapConverter extends StringFlexMapConverter {
99
@Override

objectbox-java/src/main/java/io/objectbox/query/QueryBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ public QueryBuilder<T> contains(Property<T> property, String value, StringOrder
771771
}
772772

773773
/**
774-
* For a String array or String-key map property, matches if at least one element equals the given value.
774+
* For a String array, list or String-key map property, matches if at least one element equals the given value.
775775
*/
776776
public QueryBuilder<T> containsElement(Property<T> property, String value, StringOrder order) {
777777
verifyHandle();

tests/objectbox-java-test/src/main/java/io/objectbox/TestEntity.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class TestEntity {
5252
/** In "real" entity would be annotated with @Unsigned. */
5353
private long simpleLongU;
5454
private Map<String, Object> stringObjectMap;
55+
private Object flexProperty;
5556

5657
transient boolean noArgsConstructorCalled;
5758

@@ -66,7 +67,8 @@ public TestEntity(long id) {
6667
public TestEntity(long id, boolean simpleBoolean, byte simpleByte, short simpleShort, int simpleInt,
6768
long simpleLong, float simpleFloat, double simpleDouble, String simpleString,
6869
byte[] simpleByteArray, String[] simpleStringArray, List<String> simpleStringList,
69-
short simpleShortU, int simpleIntU, long simpleLongU, Map<String, Object> stringObjectMap) {
70+
short simpleShortU, int simpleIntU, long simpleLongU, Map<String, Object> stringObjectMap,
71+
Object flexProperty) {
7072
this.id = id;
7173
this.simpleBoolean = simpleBoolean;
7274
this.simpleByte = simpleByte;
@@ -83,6 +85,7 @@ public TestEntity(long id, boolean simpleBoolean, byte simpleByte, short simpleS
8385
this.simpleIntU = simpleIntU;
8486
this.simpleLongU = simpleLongU;
8587
this.stringObjectMap = stringObjectMap;
88+
this.flexProperty = flexProperty;
8689
if (STRING_VALUE_THROW_IN_CONSTRUCTOR.equals(simpleString)) {
8790
throw new RuntimeException(EXCEPTION_IN_CONSTRUCTOR_MESSAGE);
8891
}
@@ -226,6 +229,16 @@ public TestEntity setStringObjectMap(Map<String, Object> stringObjectMap) {
226229
return this;
227230
}
228231

232+
@Nullable
233+
public Object getFlexProperty() {
234+
return flexProperty;
235+
}
236+
237+
public TestEntity setFlexProperty(@Nullable Object flexProperty) {
238+
this.flexProperty = flexProperty;
239+
return this;
240+
}
241+
229242
@Override
230243
public String toString() {
231244
return "TestEntity{" +
@@ -245,6 +258,7 @@ public String toString() {
245258
", simpleIntU=" + simpleIntU +
246259
", simpleLongU=" + simpleLongU +
247260
", stringObjectMap=" + stringObjectMap +
261+
", flexProperty=" + flexProperty +
248262
", noArgsConstructorCalled=" + noArgsConstructorCalled +
249263
'}';
250264
}

0 commit comments

Comments
 (0)