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