1010import java .util .HashMap ;
1111import java .util .List ;
1212import java .util .Map ;
13- import java .util .Map .Entry ;
1413import 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}
0 commit comments