Home | History | Annotate | Download | only in work
      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 
     49     Data() {    // stub required for room
     50     }
     51 
     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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    274     public Map<String, Object> getKeyValueMap() {
    275         return Collections.unmodifiableMap(mValues);
    276     }
    277 
    278     /**
    279      * @return The number of arguments
    280      */
    281     @VisibleForTesting
    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
    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
    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
    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
    383     public int hashCode() {
    384         return 31 * mValues.hashCode();
    385     }
    386 
    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 
    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 
    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 
    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 
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    646         public Data build() {
    647             return new Data(mValues);
    648         }
    649     }
    650 }
    651