1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.work;
18 
19 import android.arch.persistence.room.TypeConverter;
20 import android.support.annotation.NonNull;
21 import android.support.annotation.VisibleForTesting;
22 
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Map;
31 
32 /**
33  * Persistable set of key/value pairs which are passed as inputs and outputs for {@link Worker}s.
34  * This is a lightweight container, and should not be considered your data store.  As such, there is
35  * an enforced {@link #MAX_DATA_BYTES} limit on the serialized (byte array) size of the payloads.
36  * This class will throw {@link IllegalStateException}s if you try to serialize or deserialize past
37  * this limit.
38  */
39 
40 public final class Data {
41 
42     public static final Data EMPTY = new Data.Builder().build();
43     public static final int MAX_DATA_BYTES = 10 * 1024;    // 10KB
44 
45     private static final String TAG = "Data";
46 
47     private Map<String, Object> mValues;
48 
Data()49     Data() {    // stub required for room
50     }
51 
Data(Map<String, ?> values)52     Data(Map<String, ?> values) {
53         mValues = new HashMap<>(values);
54     }
55 
56     /**
57      * Get the boolean value for the given key.
58      *
59      * @param key The key for the argument
60      * @param defaultValue The default value to return if the key is not found
61      * @return The value specified by the key if it exists; the default value otherwise
62      */
getBoolean(String key, boolean defaultValue)63     public boolean getBoolean(String key, boolean defaultValue) {
64         Object value = mValues.get(key);
65         if (value instanceof Boolean) {
66             return (boolean) value;
67         } else {
68             return defaultValue;
69         }
70     }
71 
72     /**
73      * Get the boolean array value for the given key.
74      *
75      * @param key The key for the argument
76      * @return The value specified by the key if it exists; {@code null} otherwise
77      */
getBooleanArray(String key)78     public boolean[] getBooleanArray(String key) {
79         Object value = mValues.get(key);
80         if (value instanceof Boolean[]) {
81             Boolean[] array = (Boolean[]) value;
82             boolean[] returnArray = new boolean[array.length];
83             for (int i = 0; i < array.length; ++i) {
84                 returnArray[i] = array[i];
85             }
86             return returnArray;
87         } else {
88             return null;
89         }
90     }
91 
92 
93     /**
94      * Get the integer value for the given key.
95      *
96      * @param key The key for the argument
97      * @param defaultValue The default value to return if the key is not found
98      * @return The value specified by the key if it exists; the default value otherwise
99      */
getInt(String key, int defaultValue)100     public int getInt(String key, int defaultValue) {
101         Object value = mValues.get(key);
102         if (value instanceof Integer) {
103             return (int) value;
104         } else {
105             return defaultValue;
106         }
107     }
108 
109     /**
110      * Get the integer array value for the given key.
111      *
112      * @param key The key for the argument
113      * @return The value specified by the key if it exists; {@code null} otherwise
114      */
getIntArray(String key)115     public int[] getIntArray(String key) {
116         Object value = mValues.get(key);
117         if (value instanceof Integer[]) {
118             Integer[] array = (Integer[]) value;
119             int[] returnArray = new int[array.length];
120             for (int i = 0; i < array.length; ++i) {
121                 returnArray[i] = array[i];
122             }
123             return returnArray;
124         } else {
125             return null;
126         }
127     }
128 
129     /**
130      * Get the long value for the given key.
131      *
132      * @param key The key for the argument
133      * @param defaultValue The default value to return if the key is not found
134      * @return The value specified by the key if it exists; the default value otherwise
135      */
getLong(String key, long defaultValue)136     public long getLong(String key, long defaultValue) {
137         Object value = mValues.get(key);
138         if (value instanceof Long) {
139             return (long) value;
140         } else {
141             return defaultValue;
142         }
143     }
144 
145     /**
146      * Get the long array value for the given key.
147      *
148      * @param key The key for the argument
149      * @return The value specified by the key if it exists; {@code null} otherwise
150      */
getLongArray(String key)151     public long[] getLongArray(String key) {
152         Object value = mValues.get(key);
153         if (value instanceof Long[]) {
154             Long[] array = (Long[]) value;
155             long[] returnArray = new long[array.length];
156             for (int i = 0; i < array.length; ++i) {
157                 returnArray[i] = array[i];
158             }
159             return returnArray;
160         } else {
161             return null;
162         }
163     }
164 
165     /**
166      * Get the float value for the given key.
167      *
168      * @param key The key for the argument
169      * @param defaultValue The default value to return if the key is not found
170      * @return The value specified by the key if it exists; the default value otherwise
171      */
getFloat(String key, float defaultValue)172     public float getFloat(String key, float defaultValue) {
173         Object value = mValues.get(key);
174         if (value instanceof Float) {
175             return (float) value;
176         } else {
177             return defaultValue;
178         }
179     }
180 
181     /**
182      * Get the float array value for the given key.
183      *
184      * @param key The key for the argument
185      * @return The value specified by the key if it exists; {@code null} otherwise
186      */
getFloatArray(String key)187     public float[] getFloatArray(String key) {
188         Object value = mValues.get(key);
189         if (value instanceof Float[]) {
190             Float[] array = (Float[]) value;
191             float[] returnArray = new float[array.length];
192             for (int i = 0; i < array.length; ++i) {
193                 returnArray[i] = array[i];
194             }
195             return returnArray;
196         } else {
197             return null;
198         }
199     }
200 
201     /**
202      * Get the double value for the given key.
203      *
204      * @param key The key for the argument
205      * @param defaultValue The default value to return if the key is not found
206      * @return The value specified by the key if it exists; the default value otherwise
207      */
getDouble(String key, double defaultValue)208     public double getDouble(String key, double defaultValue) {
209         Object value = mValues.get(key);
210         if (value instanceof Double) {
211             return (double) value;
212         } else {
213             return defaultValue;
214         }
215     }
216 
217     /**
218      * Get the double array value for the given key.
219      *
220      * @param key The key for the argument
221      * @return The value specified by the key if it exists; {@code null} otherwise
222      */
getDoubleArray(String key)223     public double[] getDoubleArray(String key) {
224         Object value = mValues.get(key);
225         if (value instanceof Double[]) {
226             Double[] array = (Double[]) value;
227             double[] returnArray = new double[array.length];
228             for (int i = 0; i < array.length; ++i) {
229                 returnArray[i] = array[i];
230             }
231             return returnArray;
232         } else {
233             return null;
234         }
235     }
236 
237     /**
238      * Get the String value for the given key.
239      *
240      * @param key The key for the argument
241      * @param defaultValue The default value to return if the key is not found
242      * @return The value specified by the key if it exists; the default value otherwise
243      */
getString(String key, String defaultValue)244     public String getString(String key, String defaultValue) {
245         Object value = mValues.get(key);
246         if (value instanceof String) {
247             return (String) value;
248         } else {
249             return defaultValue;
250         }
251     }
252 
253     /**
254      * Get the String array value for the given key.
255      *
256      * @param key The key for the argument
257      * @return The value specified by the key if it exists; {@code null} otherwise
258      */
getStringArray(String key)259     public String[] getStringArray(String key) {
260         Object value = mValues.get(key);
261         if (value instanceof String[]) {
262             return (String[]) value;
263         } else {
264             return null;
265         }
266     }
267 
268     /**
269      * Gets all the values in this Data object.
270      *
271      * @return A {@link Map} of key-value pairs for this object; this Map is unmodifiable and should
272      * be used for reads only.
273      */
getKeyValueMap()274     public Map<String, Object> getKeyValueMap() {
275         return Collections.unmodifiableMap(mValues);
276     }
277 
278     /**
279      * @return The number of arguments
280      */
281     @VisibleForTesting
size()282     public int size() {
283         return mValues.size();
284     }
285 
286     /**
287      * Converts {@link Data} to a byte array for persistent storage.
288      *
289      * @param data The {@link Data} object to convert
290      * @return The byte array representation of the input
291      * @throws IllegalStateException if the serialized payload is bigger than
292      *         {@link #MAX_DATA_BYTES}
293      */
294     @TypeConverter
toByteArray(Data data)295     public static byte[] toByteArray(Data data) throws IllegalStateException {
296         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
297         ObjectOutputStream objectOutputStream = null;
298         try {
299             objectOutputStream = new ObjectOutputStream(outputStream);
300             objectOutputStream.writeInt(data.size());
301             for (Map.Entry<String, Object> entry : data.mValues.entrySet()) {
302                 objectOutputStream.writeUTF(entry.getKey());
303                 objectOutputStream.writeObject(entry.getValue());
304             }
305         } catch (IOException e) {
306             e.printStackTrace();
307         } finally {
308             if (objectOutputStream != null) {
309                 try {
310                     objectOutputStream.close();
311                 } catch (IOException e) {
312                     e.printStackTrace();
313                 }
314             }
315             try {
316                 outputStream.close();
317             } catch (IOException e) {
318                 e.printStackTrace();
319             }
320         }
321 
322         if (outputStream.size() > MAX_DATA_BYTES) {
323             throw new IllegalStateException(
324                     "Data cannot occupy more than " + MAX_DATA_BYTES + "KB when serialized");
325         }
326         return outputStream.toByteArray();
327     }
328 
329     /**
330      * Converts a byte array to {@link Data}.
331      *
332      * @param bytes The byte array representation to convert
333      * @return An {@link Data} object built from the input
334      * @throws IllegalStateException if bytes is bigger than {@link #MAX_DATA_BYTES}
335      */
336     @TypeConverter
fromByteArray(byte[] bytes)337     public static Data fromByteArray(byte[] bytes) throws IllegalStateException {
338         if (bytes.length > MAX_DATA_BYTES) {
339             throw new IllegalStateException(
340                     "Data cannot occupy more than " + MAX_DATA_BYTES + "KB when serialized");
341         }
342 
343         Map<String, Object> map = new HashMap<>();
344         ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
345         ObjectInputStream objectInputStream = null;
346         try {
347             objectInputStream = new ObjectInputStream(inputStream);
348             for (int i = objectInputStream.readInt(); i > 0; i--) {
349                 map.put(objectInputStream.readUTF(), objectInputStream.readObject());
350             }
351         } catch (IOException | ClassNotFoundException e) {
352             e.printStackTrace();
353         } finally {
354             if (objectInputStream != null) {
355                 try {
356                     objectInputStream.close();
357                 } catch (IOException e) {
358                     e.printStackTrace();
359                 }
360             }
361             try {
362                 inputStream.close();
363             } catch (IOException e) {
364                 e.printStackTrace();
365             }
366         }
367         return new Data(map);
368     }
369 
370     @Override
equals(Object o)371     public boolean equals(Object o) {
372         if (this == o) {
373             return true;
374         }
375         if (o == null || getClass() != o.getClass()) {
376             return false;
377         }
378         Data other = (Data) o;
379         return mValues.equals(other.mValues);
380     }
381 
382     @Override
hashCode()383     public int hashCode() {
384         return 31 * mValues.hashCode();
385     }
386 
convertPrimitiveBooleanArray(boolean[] value)387     private static Boolean[] convertPrimitiveBooleanArray(boolean[] value) {
388         Boolean[] returnValue = new Boolean[value.length];
389         for (int i = 0; i < value.length; ++i) {
390             returnValue[i] = value[i];
391         }
392         return returnValue;
393     }
394 
convertPrimitiveIntArray(int[] value)395     private static Integer[] convertPrimitiveIntArray(int[] value) {
396         Integer[] returnValue = new Integer[value.length];
397         for (int i = 0; i < value.length; ++i) {
398             returnValue[i] = value[i];
399         }
400         return returnValue;
401     }
402 
convertPrimitiveLongArray(long[] value)403     private static Long[] convertPrimitiveLongArray(long[] value) {
404         Long[] returnValue = new Long[value.length];
405         for (int i = 0; i < value.length; ++i) {
406             returnValue[i] = value[i];
407         }
408         return returnValue;
409     }
410 
convertPrimitiveFloatArray(float[] value)411     private static Float[] convertPrimitiveFloatArray(float[] value) {
412         Float[] returnValue = new Float[value.length];
413         for (int i = 0; i < value.length; ++i) {
414             returnValue[i] = value[i];
415         }
416         return returnValue;
417     }
418 
convertPrimitiveDoubleArray(double[] value)419     private static Double[] convertPrimitiveDoubleArray(double[] value) {
420         Double[] returnValue = new Double[value.length];
421         for (int i = 0; i < value.length; ++i) {
422             returnValue[i] = value[i];
423         }
424         return returnValue;
425     }
426 
427     /**
428      * A builder for {@link Data}.
429      */
430     public static final class Builder {
431 
432         private Map<String, Object> mValues = new HashMap<>();
433 
434         /**
435          * Puts a boolean into the arguments.
436          *
437          * @param key The key for this argument
438          * @param value The value for this argument
439          * @return The {@link Builder}
440          */
putBoolean(String key, boolean value)441         public Builder putBoolean(String key, boolean value) {
442             mValues.put(key, value);
443             return this;
444         }
445 
446         /**
447          * Puts a boolean array into the arguments.
448          *
449          * @param key The key for this argument
450          * @param value The value for this argument
451          * @return The {@link Builder}
452          */
putBooleanArray(String key, boolean[] value)453         public Builder putBooleanArray(String key, boolean[] value) {
454             mValues.put(key, convertPrimitiveBooleanArray(value));
455             return this;
456         }
457 
458         /**
459          * Puts an integer into the arguments.
460          *
461          * @param key The key for this argument
462          * @param value The value for this argument
463          * @return The {@link Builder}
464          */
putInt(String key, int value)465         public Builder putInt(String key, int value) {
466             mValues.put(key, value);
467             return this;
468         }
469 
470         /**
471          * Puts an integer array into the arguments.
472          *
473          * @param key The key for this argument
474          * @param value The value for this argument
475          * @return The {@link Builder}
476          */
putIntArray(String key, int[] value)477         public Builder putIntArray(String key, int[] value) {
478             mValues.put(key, convertPrimitiveIntArray(value));
479             return this;
480         }
481 
482         /**
483          * Puts a long into the arguments.
484          *
485          * @param key The key for this argument
486          * @param value The value for this argument
487          * @return The {@link Builder}
488          */
putLong(String key, long value)489         public Builder putLong(String key, long value) {
490             mValues.put(key, value);
491             return this;
492         }
493 
494         /**
495          * Puts a long array into the arguments.
496          *
497          * @param key The key for this argument
498          * @param value The value for this argument
499          * @return The {@link Builder}
500          */
putLongArray(String key, long[] value)501         public Builder putLongArray(String key, long[] value) {
502             mValues.put(key, convertPrimitiveLongArray(value));
503             return this;
504         }
505 
506         /**
507          * Puts a float into the arguments.
508          *
509          * @param key The key for this argument
510          * @param value The value for this argument
511          * @return The {@link Builder}
512          */
putFloat(String key, float value)513         public Builder putFloat(String key, float value) {
514             mValues.put(key, value);
515             return this;
516         }
517 
518         /**
519          * Puts a float array into the arguments.
520          *
521          * @param key The key for this argument
522          * @param value The value for this argument
523          * @return The {@link Builder}
524          */
putFloatArray(String key, float[] value)525         public Builder putFloatArray(String key, float[] value) {
526             mValues.put(key, convertPrimitiveFloatArray(value));
527             return this;
528         }
529 
530         /**
531          * Puts a double into the arguments.
532          *
533          * @param key The key for this argument
534          * @param value The value for this argument
535          * @return The {@link Builder}
536          */
putDouble(String key, double value)537         public Builder putDouble(String key, double value) {
538             mValues.put(key, value);
539             return this;
540         }
541 
542         /**
543          * Puts a double array into the arguments.
544          *
545          * @param key The key for this argument
546          * @param value The value for this argument
547          * @return The {@link Builder}
548          */
putDoubleArray(String key, double[] value)549         public Builder putDoubleArray(String key, double[] value) {
550             mValues.put(key, convertPrimitiveDoubleArray(value));
551             return this;
552         }
553 
554         /**
555          * Puts a String into the arguments.
556          *
557          * @param key The key for this argument
558          * @param value The value for this argument
559          * @return The {@link Builder}
560          */
putString(String key, String value)561         public Builder putString(String key, String value) {
562             mValues.put(key, value);
563             return this;
564         }
565 
566         /**
567          * Puts a String array into the arguments.
568          *
569          * @param key The key for this argument
570          * @param value The value for this argument
571          * @return The {@link Builder}
572          */
putStringArray(String key, String[] value)573         public Builder putStringArray(String key, String[] value) {
574             mValues.put(key, value);
575             return this;
576         }
577 
578         /**
579          * Puts all input key-value pairs from the {@link Data} into the Builder.
580          * Any non-valid types will be logged and ignored.  Valid types are: Boolean, Integer,
581          * Long, Double, String, and array versions of each of those types.
582          * Any {@code null} values will also be ignored.
583          *
584          * @param data {@link Data} containing key-value pairs to add
585          * @return The {@link Builder}
586          */
putAll(@onNull Data data)587         public Builder putAll(@NonNull Data data) {
588             putAll(data.mValues);
589             return this;
590         }
591 
592         /**
593          * Puts all input key-value pairs into the Builder. Valid types are: Boolean, Integer,
594          * Long, Float, Double, String, and array versions of each of those types.
595          * Invalid types throw an {@link IllegalArgumentException}.
596          *
597          * @param values A {@link Map} of key-value pairs to add
598          * @return The {@link Builder}
599          */
putAll(Map<String, Object> values)600         public Builder putAll(Map<String, Object> values) {
601             for (Map.Entry<String, Object> entry : values.entrySet()) {
602                 String key = entry.getKey();
603                 Object value = entry.getValue();
604                 if (value == null) {
605                     mValues.put(key, null);
606                     continue;
607                 }
608                 Class valueType = value.getClass();
609                 if (valueType == Boolean.class
610                         || valueType == Integer.class
611                         || valueType == Long.class
612                         || valueType == Float.class
613                         || valueType == Double.class
614                         || valueType == String.class
615                         || valueType == Boolean[].class
616                         || valueType == Integer[].class
617                         || valueType == Long[].class
618                         || valueType == Float[].class
619                         || valueType == Double[].class
620                         || valueType == String[].class) {
621                     mValues.put(key, value);
622                 } else if (valueType == boolean[].class) {
623                     mValues.put(key, convertPrimitiveBooleanArray((boolean[]) value));
624                 } else if (valueType == int[].class) {
625                     mValues.put(key, convertPrimitiveIntArray((int[]) value));
626                 } else if (valueType == long[].class) {
627                     mValues.put(key, convertPrimitiveLongArray((long[]) value));
628                 } else if (valueType == float[].class) {
629                     mValues.put(key, convertPrimitiveFloatArray((float[]) value));
630                 } else if (valueType == double[].class) {
631                     mValues.put(key, convertPrimitiveDoubleArray((double[]) value));
632                 } else {
633                     throw new IllegalArgumentException(
634                             String.format("Key %s has invalid type %s", key, valueType));
635                 }
636             }
637             return this;
638         }
639 
640         /**
641          * Builds an {@link Data} object.
642          *
643          * @return The {@link Data} object containing all key-value pairs specified by this
644          *         {@link Builder}.
645          */
build()646         public Data build() {
647             return new Data(mValues);
648         }
649     }
650 }
651