1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 package org.mozilla.gecko.util; 7 8 import org.mozilla.gecko.annotation.RobocopTarget; 9 import org.mozilla.gecko.annotation.WrapForJNI; 10 11 import org.json.JSONArray; 12 import org.json.JSONException; 13 import org.json.JSONObject; 14 15 import android.os.Build; 16 import android.os.Bundle; 17 import android.os.Parcel; 18 import android.os.Parcelable; 19 import android.support.v4.util.SimpleArrayMap; 20 21 import java.lang.reflect.Array; 22 import java.util.Arrays; 23 import java.util.Collection; 24 import java.util.Iterator; 25 26 /** 27 * A lighter-weight version of Bundle that adds support for type coercion (e.g. 28 * int to double) in order to better cooperate with JS objects. 29 */ 30 @RobocopTarget 31 public final class GeckoBundle implements Parcelable { 32 private static final String LOGTAG = "GeckoBundle"; 33 private static final boolean DEBUG = false; 34 35 @WrapForJNI(calledFrom = "gecko") 36 private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0]; 37 private static final int[] EMPTY_INT_ARRAY = new int[0]; 38 private static final long[] EMPTY_LONG_ARRAY = new long[0]; 39 private static final double[] EMPTY_DOUBLE_ARRAY = new double[0]; 40 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 41 private static final GeckoBundle[] EMPTY_BUNDLE_ARRAY = new GeckoBundle[0]; 42 43 private SimpleArrayMap<String, Object> mMap; 44 45 /** 46 * Construct an empty GeckoBundle. 47 */ GeckoBundle()48 public GeckoBundle() { 49 mMap = new SimpleArrayMap<>(); 50 } 51 52 /** 53 * Construct an empty GeckoBundle with specific capacity. 54 * 55 * @param capacity Initial capacity. 56 */ GeckoBundle(final int capacity)57 public GeckoBundle(final int capacity) { 58 mMap = new SimpleArrayMap<>(capacity); 59 } 60 61 /** 62 * Construct a copy of another GeckoBundle. 63 * 64 * @param bundle GeckoBundle to copy from. 65 */ GeckoBundle(final GeckoBundle bundle)66 public GeckoBundle(final GeckoBundle bundle) { 67 mMap = new SimpleArrayMap<>(bundle.mMap); 68 } 69 70 @WrapForJNI(calledFrom = "gecko") GeckoBundle(final String[] keys, final Object[] values)71 private GeckoBundle(final String[] keys, final Object[] values) { 72 final int len = keys.length; 73 mMap = new SimpleArrayMap<>(len); 74 for (int i = 0; i < len; i++) { 75 mMap.put(keys[i], values[i]); 76 } 77 } 78 79 /** 80 * Clear all mappings. 81 */ clear()82 public void clear() { 83 mMap.clear(); 84 } 85 86 /** 87 * Returns whether a mapping exists. Null String, Bundle, or arrays are treated as 88 * nonexistent. 89 * 90 * @param key Key to look for. 91 * @return True if the specified key exists and the value is not null. 92 */ containsKey(final String key)93 public boolean containsKey(final String key) { 94 return mMap.get(key) != null; 95 } 96 97 /** 98 * Returns the value associated with a mapping as an Object. 99 * 100 * @param key Key to look for. 101 * @return Mapping value or null if the mapping does not exist. 102 */ get(final String key)103 public Object get(final String key) { 104 return mMap.get(key); 105 } 106 107 /** 108 * Returns the value associated with a boolean mapping, or defaultValue if the mapping 109 * does not exist. 110 * 111 * @param key Key to look for. 112 * @param defaultValue Value to return if mapping does not exist. 113 * @return Boolean value 114 */ getBoolean(final String key, final boolean defaultValue)115 public boolean getBoolean(final String key, final boolean defaultValue) { 116 final Object value = mMap.get(key); 117 return value == null ? defaultValue : (Boolean) value; 118 } 119 120 /** 121 * Returns the value associated with a boolean mapping, or false if the mapping does 122 * not exist. 123 * 124 * @param key Key to look for. 125 * @return Boolean value 126 */ getBoolean(final String key)127 public boolean getBoolean(final String key) { 128 return getBoolean(key, false); 129 } 130 131 /** 132 * Returns the value associated with a Boolean mapping, or defaultValue if the mapping 133 * does not exist. 134 * 135 * @param key Key to look for. 136 * @param defaultValue Value to return if mapping does not exist. 137 * @return Boolean value 138 */ getBooleanObject(final String key, final Boolean defaultValue)139 public Boolean getBooleanObject(final String key, final Boolean defaultValue) { 140 final Object value = mMap.get(key); 141 return value == null ? defaultValue : (Boolean) value; 142 } 143 144 /** 145 * Returns the value associated with a Boolean mapping, or null if the mapping does 146 * not exist. 147 * 148 * @param key Key to look for. 149 * @return Boolean value 150 */ getBooleanObject(final String key)151 public Boolean getBooleanObject(final String key) { 152 return getBooleanObject(key, null); 153 } 154 155 /** 156 * Returns the value associated with a boolean array mapping, or null if the mapping 157 * does not exist. 158 * 159 * @param key Key to look for. 160 * @return Boolean array value 161 */ getBooleanArray(final String key)162 public boolean[] getBooleanArray(final String key) { 163 final Object value = mMap.get(key); 164 return value == null ? null : 165 Array.getLength(value) == 0 ? EMPTY_BOOLEAN_ARRAY : (boolean[]) value; 166 } 167 168 /** 169 * Returns the value associated with a double mapping, or defaultValue if the mapping 170 * does not exist. 171 * 172 * @param key Key to look for. 173 * @param defaultValue Value to return if mapping does not exist. 174 * @return Double value 175 */ getDouble(final String key, final double defaultValue)176 public double getDouble(final String key, final double defaultValue) { 177 final Object value = mMap.get(key); 178 return value == null ? defaultValue : ((Number) value).doubleValue(); 179 } 180 181 /** 182 * Returns the value associated with a double mapping, or 0.0 if the mapping does not 183 * exist. 184 * 185 * @param key Key to look for. 186 * @return Double value 187 */ getDouble(final String key)188 public double getDouble(final String key) { 189 return getDouble(key, 0.0); 190 } 191 getDoubleArray(final int[] array)192 private static double[] getDoubleArray(final int[] array) { 193 final int len = array.length; 194 final double[] ret = new double[len]; 195 for (int i = 0; i < len; i++) { 196 ret[i] = (double) array[i]; 197 } 198 return ret; 199 } 200 201 /** 202 * Returns the value associated with a double array mapping, or null if the mapping 203 * does not exist. 204 * 205 * @param key Key to look for. 206 * @return Double array value 207 */ getDoubleArray(final String key)208 public double[] getDoubleArray(final String key) { 209 final Object value = mMap.get(key); 210 return value == null ? null : Array.getLength(value) == 0 ? EMPTY_DOUBLE_ARRAY : 211 value instanceof int[] ? getDoubleArray((int[]) value) : (double[]) value; 212 } 213 214 /** 215 * Returns the value associated with an int mapping, or defaultValue if the mapping 216 * does not exist. 217 * 218 * @param key Key to look for. 219 * @param defaultValue Value to return if mapping does not exist. 220 * @return Int value 221 */ getInt(final String key, final int defaultValue)222 public int getInt(final String key, final int defaultValue) { 223 final Object value = mMap.get(key); 224 return value == null ? defaultValue : ((Number) value).intValue(); 225 } 226 227 /** 228 * Returns the value associated with an int mapping, or 0 if the mapping does not 229 * exist. 230 * 231 * @param key Key to look for. 232 * @return Int value 233 */ getInt(final String key)234 public int getInt(final String key) { 235 return getInt(key, 0); 236 } 237 238 /** 239 * Returns the value associated with an Integer mapping, or defaultValue if the mapping 240 * does not exist. 241 * 242 * @param key Key to look for. 243 * @param defaultValue Value to return if mapping does not exist. 244 * @return Int value 245 */ getInteger(final String key, final Integer defaultValue)246 public Integer getInteger(final String key, final Integer defaultValue) { 247 final Object value = mMap.get(key); 248 return value == null ? defaultValue : ((Integer) value); 249 } 250 251 /** 252 * Returns the value associated with an Integer mapping, or null if the mapping does not 253 * exist. 254 * 255 * @param key Key to look for. 256 * @return Int value 257 */ getInteger(final String key)258 public Integer getInteger(final String key) { 259 return getInteger(key, null); 260 } 261 getIntArray(final double[] array)262 private static int[] getIntArray(final double[] array) { 263 final int len = array.length; 264 final int[] ret = new int[len]; 265 for (int i = 0; i < len; i++) { 266 ret[i] = (int) array[i]; 267 } 268 return ret; 269 } 270 271 /** 272 * Returns the value associated with an int array mapping, or null if the mapping does 273 * not exist. 274 * 275 * @param key Key to look for. 276 * @return Int array value 277 */ getIntArray(final String key)278 public int[] getIntArray(final String key) { 279 final Object value = mMap.get(key); 280 return value == null ? null : Array.getLength(value) == 0 ? EMPTY_INT_ARRAY : 281 value instanceof double[] ? getIntArray((double[]) value) : (int[]) value; 282 } 283 284 /** 285 * Returns the value associated with an int/double mapping as a long value, or 286 * defaultValue if the mapping does not exist. 287 * 288 * @param key Key to look for. 289 * @param defaultValue Value to return if mapping does not exist. 290 * @return Long value 291 */ getLong(final String key, final long defaultValue)292 public long getLong(final String key, final long defaultValue) { 293 final Object value = mMap.get(key); 294 return value == null ? defaultValue : ((Number) value).longValue(); 295 } 296 297 /** 298 * Returns the value associated with an int/double mapping as a long value, or 299 * 0 if the mapping does not exist. 300 * 301 * @param key Key to look for. 302 * @return Long value 303 */ getLong(final String key)304 public long getLong(final String key) { 305 return getLong(key, 0L); 306 } 307 getLongArray(final Object array)308 private static long[] getLongArray(final Object array) { 309 final int len = Array.getLength(array); 310 final long[] ret = new long[len]; 311 for (int i = 0; i < len; i++) { 312 ret[i] = ((Number) Array.get(array, i)).longValue(); 313 } 314 return ret; 315 } 316 317 /** 318 * Returns the value associated with an int/double array mapping as a long array, or 319 * null if the mapping does not exist. 320 * 321 * @param key Key to look for. 322 * @return Long array value 323 */ getLongArray(final String key)324 public long[] getLongArray(final String key) { 325 final Object value = mMap.get(key); 326 return value == null ? null : 327 Array.getLength(value) == 0 ? EMPTY_LONG_ARRAY : getLongArray(value); 328 } 329 330 /** 331 * Returns the value associated with a String mapping, or defaultValue if the mapping 332 * does not exist. 333 * 334 * @param key Key to look for. 335 * @param defaultValue Value to return if mapping value is null or mapping does not exist. 336 * @return String value 337 */ getString(final String key, final String defaultValue)338 public String getString(final String key, final String defaultValue) { 339 // If the key maps to null, technically we should return null because the mapping 340 // exists and null is a valid string value. However, people expect the default 341 // value to be returned instead, so we make an exception to return the default value. 342 final Object value = mMap.get(key); 343 return value == null ? defaultValue : (String) value; 344 } 345 346 /** 347 * Returns the value associated with a String mapping, or null if the mapping does not 348 * exist. 349 * 350 * @param key Key to look for. 351 * @return String value 352 */ getString(final String key)353 public String getString(final String key) { 354 return getString(key, null); 355 } 356 357 // The only case where we convert String[] to/from GeckoBundle[] is if every element 358 // is null. getNullArrayLength(final Object array)359 private static int getNullArrayLength(final Object array) { 360 final int len = Array.getLength(array); 361 for (int i = 0; i < len; i++) { 362 if (Array.get(array, i) != null) { 363 throw new ClassCastException("Cannot cast array type"); 364 } 365 } 366 return len; 367 } 368 369 /** 370 * Returns the value associated with a String array mapping, or null if the mapping 371 * does not exist. 372 * 373 * @param key Key to look for. 374 * @return String array value 375 */ getStringArray(final String key)376 public String[] getStringArray(final String key) { 377 final Object value = mMap.get(key); 378 return value == null ? null : Array.getLength(value) == 0 ? EMPTY_STRING_ARRAY : 379 !(value instanceof String[]) ? new String[getNullArrayLength(value)] : 380 (String[]) value; 381 } 382 383 /** 384 * Returns the value associated with a GeckoBundle mapping, or null if the mapping 385 * does not exist. 386 * 387 * @param key Key to look for. 388 * @return GeckoBundle value 389 */ getBundle(final String key)390 public GeckoBundle getBundle(final String key) { 391 return (GeckoBundle) mMap.get(key); 392 } 393 394 /** 395 * Returns the value associated with a GeckoBundle array mapping, or null if the 396 * mapping does not exist. 397 * 398 * @param key Key to look for. 399 * @return GeckoBundle array value 400 */ getBundleArray(final String key)401 public GeckoBundle[] getBundleArray(final String key) { 402 final Object value = mMap.get(key); 403 return value == null ? null : Array.getLength(value) == 0 ? EMPTY_BUNDLE_ARRAY : 404 !(value instanceof GeckoBundle[]) ? new GeckoBundle[getNullArrayLength(value)] : 405 (GeckoBundle[]) value; 406 } 407 408 /** 409 * Returns whether this GeckoBundle has no mappings. 410 * 411 * @return True if no mapping exists. 412 */ isEmpty()413 public boolean isEmpty() { 414 return mMap.isEmpty(); 415 } 416 417 /** 418 * Returns an array of all mapped keys. 419 * 420 * @return String array containing all mapped keys. 421 */ 422 @WrapForJNI(calledFrom = "gecko") keys()423 public String[] keys() { 424 final int len = mMap.size(); 425 final String[] ret = new String[len]; 426 for (int i = 0; i < len; i++) { 427 ret[i] = mMap.keyAt(i); 428 } 429 return ret; 430 } 431 432 @WrapForJNI(calledFrom = "gecko") values()433 private Object[] values() { 434 final int len = mMap.size(); 435 final Object[] ret = new Object[len]; 436 for (int i = 0; i < len; i++) { 437 ret[i] = mMap.valueAt(i); 438 } 439 return ret; 440 } 441 put(final String key, final Object value)442 private void put(final String key, final Object value) { 443 // We intentionally disallow a generic put() method for type safety and sanity. For 444 // example, we assume elsewhere in the code that a value belongs to a small list of 445 // predefined types, and cannot be any arbitrary object. If you want to put an 446 // Object in the bundle, check the type of the Object first and call the 447 // corresponding put methods. For example, 448 // 449 // if (obj instanceof Integer) { 450 // bundle.putInt(key, (Integer) key); 451 // } else if (obj instanceof String) { 452 // bundle.putString(key, (String) obj); 453 // } else { 454 // throw new IllegalArgumentException("unexpected type"); 455 // } 456 throw new UnsupportedOperationException(); 457 } 458 459 /** 460 * Map a key to a boolean value. 461 * 462 * @param key Key to map. 463 * @param value Value to map to. 464 */ putBoolean(final String key, final boolean value)465 public void putBoolean(final String key, final boolean value) { 466 mMap.put(key, value); 467 } 468 469 /** 470 * Map a key to a boolean array value. 471 * 472 * @param key Key to map. 473 * @param value Value to map to. 474 */ putBooleanArray(final String key, final boolean[] value)475 public void putBooleanArray(final String key, final boolean[] value) { 476 mMap.put(key, value); 477 } 478 479 /** 480 * Map a key to a boolean array value. 481 * 482 * @param key Key to map. 483 * @param value Value to map to. 484 */ putBooleanArray(final String key, final Boolean[] value)485 public void putBooleanArray(final String key, final Boolean[] value) { 486 if (value == null) { 487 mMap.put(key, null); 488 return; 489 } 490 final boolean[] array = new boolean[value.length]; 491 for (int i = 0; i < value.length; i++) { 492 array[i] = value[i]; 493 } 494 mMap.put(key, array); 495 } 496 497 /** 498 * Map a key to a boolean array value. 499 * 500 * @param key Key to map. 501 * @param value Value to map to. 502 */ putBooleanArray(final String key, final Collection<Boolean> value)503 public void putBooleanArray(final String key, final Collection<Boolean> value) { 504 if (value == null) { 505 mMap.put(key, null); 506 return; 507 } 508 final boolean[] array = new boolean[value.size()]; 509 int i = 0; 510 for (final Boolean element : value) { 511 array[i++] = element; 512 } 513 mMap.put(key, array); 514 } 515 516 /** 517 * Map a key to a double value. 518 * 519 * @param key Key to map. 520 * @param value Value to map to. 521 */ putDouble(final String key, final double value)522 public void putDouble(final String key, final double value) { 523 mMap.put(key, value); 524 } 525 526 /** 527 * Map a key to a double array value. 528 * 529 * @param key Key to map. 530 * @param value Value to map to. 531 */ putDoubleArray(final String key, final double[] value)532 public void putDoubleArray(final String key, final double[] value) { 533 mMap.put(key, value); 534 } 535 536 /** 537 * Map a key to a double array value. 538 * 539 * @param key Key to map. 540 * @param value Value to map to. 541 */ putDoubleArray(final String key, final Double[] value)542 public void putDoubleArray(final String key, final Double[] value) { 543 putDoubleArray(key, Arrays.asList(value)); 544 } 545 546 /** 547 * Map a key to a double array value. 548 * 549 * @param key Key to map. 550 * @param value Value to map to. 551 */ putDoubleArray(final String key, final Collection<Double> value)552 public void putDoubleArray(final String key, final Collection<Double> value) { 553 if (value == null) { 554 mMap.put(key, null); 555 return; 556 } 557 final double[] array = new double[value.size()]; 558 int i = 0; 559 for (final Double element : value) { 560 array[i++] = element; 561 } 562 mMap.put(key, array); 563 } 564 565 /** 566 * Map a key to an int value. 567 * 568 * @param key Key to map. 569 * @param value Value to map to. 570 */ putInt(final String key, final int value)571 public void putInt(final String key, final int value) { 572 mMap.put(key, value); 573 } 574 575 /** 576 * Map a key to an int array value. 577 * 578 * @param key Key to map. 579 * @param value Value to map to. 580 */ putIntArray(final String key, final int[] value)581 public void putIntArray(final String key, final int[] value) { 582 mMap.put(key, value); 583 } 584 585 /** 586 * Map a key to a int array value. 587 * 588 * @param key Key to map. 589 * @param value Value to map to. 590 */ putIntArray(final String key, final Integer[] value)591 public void putIntArray(final String key, final Integer[] value) { 592 putIntArray(key, Arrays.asList(value)); 593 } 594 595 /** 596 * Map a key to a int array value. 597 * 598 * @param key Key to map. 599 * @param value Value to map to. 600 */ putIntArray(final String key, final Collection<Integer> value)601 public void putIntArray(final String key, final Collection<Integer> value) { 602 if (value == null) { 603 mMap.put(key, null); 604 return; 605 } 606 final int[] array = new int[value.size()]; 607 int i = 0; 608 for (final Integer element : value) { 609 array[i++] = element; 610 } 611 mMap.put(key, array); 612 } 613 614 /** 615 * Map a key to a long value stored as a double value. 616 * 617 * @param key Key to map. 618 * @param value Value to map to. 619 */ putLong(final String key, final long value)620 public void putLong(final String key, final long value) { 621 mMap.put(key, (double) value); 622 } 623 624 /** 625 * Map a key to a long array value stored as a double array value. 626 * 627 * @param key Key to map. 628 * @param value Value to map to. 629 */ putLongArray(final String key, final long[] value)630 public void putLongArray(final String key, final long[] value) { 631 if (value == null) { 632 mMap.put(key, null); 633 return; 634 } 635 final double[] array = new double[value.length]; 636 for (int i = 0; i < value.length; i++) { 637 array[i] = (double) value[i]; 638 } 639 mMap.put(key, array); 640 } 641 642 /** 643 * Map a key to a long array value stored as a double array value. 644 * 645 * @param key Key to map. 646 * @param value Value to map to. 647 */ putLongArray(final String key, final Long[] value)648 public void putLongArray(final String key, final Long[] value) { 649 putLongArray(key, Arrays.asList(value)); 650 } 651 652 /** 653 * Map a key to a long array value stored as a double array value. 654 * 655 * @param key Key to map. 656 * @param value Value to map to. 657 */ putLongArray(final String key, final Collection<Long> value)658 public void putLongArray(final String key, final Collection<Long> value) { 659 if (value == null) { 660 mMap.put(key, null); 661 return; 662 } 663 final double[] array = new double[value.size()]; 664 int i = 0; 665 for (final Long element : value) { 666 array[i++] = (double) element; 667 } 668 mMap.put(key, array); 669 } 670 671 /** 672 * Map a key to a String value. 673 * 674 * @param key Key to map. 675 * @param value Value to map to. 676 */ putString(final String key, final String value)677 public void putString(final String key, final String value) { 678 mMap.put(key, value); 679 } 680 681 /** 682 * Map a key to a String array value. 683 * 684 * @param key Key to map. 685 * @param value Value to map to. 686 */ putStringArray(final String key, final String[] value)687 public void putStringArray(final String key, final String[] value) { 688 mMap.put(key, value); 689 } 690 691 /** 692 * Map a key to a String array value. 693 * 694 * @param key Key to map. 695 * @param value Value to map to. 696 */ putStringArray(final String key, final Collection<String> value)697 public void putStringArray(final String key, final Collection<String> value) { 698 if (value == null) { 699 mMap.put(key, null); 700 return; 701 } 702 final String[] array = new String[value.size()]; 703 int i = 0; 704 for (final String element : value) { 705 array[i++] = element; 706 } 707 mMap.put(key, array); 708 } 709 710 /** 711 * Map a key to a GeckoBundle value. 712 * 713 * @param key Key to map. 714 * @param value Value to map to. 715 */ putBundle(final String key, final GeckoBundle value)716 public void putBundle(final String key, final GeckoBundle value) { 717 mMap.put(key, value); 718 } 719 720 /** 721 * Map a key to a GeckoBundle array value. 722 * 723 * @param key Key to map. 724 * @param value Value to map to. 725 */ putBundleArray(final String key, final GeckoBundle[] value)726 public void putBundleArray(final String key, final GeckoBundle[] value) { 727 mMap.put(key, value); 728 } 729 730 /** 731 * Map a key to a GeckoBundle array value. 732 * 733 * @param key Key to map. 734 * @param value Value to map to. 735 */ putBundleArray(final String key, final Collection<GeckoBundle> value)736 public void putBundleArray(final String key, final Collection<GeckoBundle> value) { 737 if (value == null) { 738 mMap.put(key, null); 739 return; 740 } 741 final GeckoBundle[] array = new GeckoBundle[value.size()]; 742 int i = 0; 743 for (final GeckoBundle element : value) { 744 array[i++] = element; 745 } 746 mMap.put(key, array); 747 } 748 749 /** 750 * Remove a mapping. 751 * 752 * @param key Key to remove. 753 */ remove(final String key)754 public void remove(final String key) { 755 mMap.remove(key); 756 } 757 758 /** 759 * Returns number of mappings in this GeckoBundle. 760 * 761 * @return Number of mappings. 762 */ size()763 public int size() { 764 return mMap.size(); 765 } 766 normalizeValue(final Object value)767 private static Object normalizeValue(final Object value) { 768 if (value instanceof Integer) { 769 // We treat int and double as the same type. 770 return ((Integer) value).doubleValue(); 771 772 } else if (value instanceof int[]) { 773 // We treat int[] and double[] as the same type. 774 final int[] array = (int[]) value; 775 return array.length == 0 ? EMPTY_STRING_ARRAY : getDoubleArray(array); 776 777 } else if (value != null && value.getClass().isArray()) { 778 // We treat arrays of all nulls as the same type, including empty arrays. 779 final int len = Array.getLength(value); 780 for (int i = 0; i < len; i++) { 781 if (Array.get(value, i) != null) { 782 return value; 783 } 784 } 785 return len == 0 ? EMPTY_STRING_ARRAY : new String[len]; 786 } 787 return value; 788 } 789 790 @Override // Object equals(final Object other)791 public boolean equals(final Object other) { 792 if (!(other instanceof GeckoBundle)) { 793 return false; 794 } 795 796 // Support library's SimpleArrayMap.equals is buggy, so roll our own version. 797 final SimpleArrayMap<String, Object> otherMap = ((GeckoBundle) other).mMap; 798 if (mMap == otherMap) { 799 return true; 800 } 801 if (mMap.size() != otherMap.size()) { 802 return false; 803 } 804 805 for (int i = 0; i < mMap.size(); i++) { 806 final String thisKey = mMap.keyAt(i); 807 final int otherKey = otherMap.indexOfKey(thisKey); 808 if (otherKey < 0) { 809 return false; 810 } 811 final Object thisValue = normalizeValue(mMap.valueAt(i)); 812 final Object otherValue = normalizeValue(otherMap.valueAt(otherKey)); 813 if (thisValue == otherValue) { 814 continue; 815 } else if (thisValue == null || otherValue == null) { 816 return false; 817 } 818 819 final Class<?> thisClass = thisValue.getClass(); 820 final Class<?> otherClass = otherValue.getClass(); 821 if (thisClass != otherClass && !thisClass.equals(otherClass)) { 822 return false; 823 } else if (!thisClass.isArray()) { 824 if (!thisValue.equals(otherValue)) { 825 return false; 826 } 827 continue; 828 } 829 830 // Work with both primitive arrays and Object arrays, unlike Arrays.equals(). 831 final int thisLen = Array.getLength(thisValue); 832 final int otherLen = Array.getLength(otherValue); 833 if (thisLen != otherLen) { 834 return false; 835 } 836 for (int j = 0; j < thisLen; j++) { 837 final Object thisElem = Array.get(thisValue, j); 838 final Object otherElem = Array.get(otherValue, j); 839 if (thisElem != otherElem && (thisElem == null || 840 otherElem == null || !thisElem.equals(otherElem))) { 841 return false; 842 } 843 } 844 } 845 return true; 846 } 847 848 @Override // Object hashCode()849 public int hashCode() { 850 return mMap.hashCode(); 851 } 852 853 @Override // Object toString()854 public String toString() { 855 return mMap.toString(); 856 } 857 toJSONObject()858 public JSONObject toJSONObject() throws JSONException { 859 final JSONObject out = new JSONObject(); 860 for (int i = 0; i < mMap.size(); i++) { 861 final Object value = mMap.valueAt(i); 862 final Object jsonValue; 863 864 if (value instanceof GeckoBundle) { 865 jsonValue = ((GeckoBundle) value).toJSONObject(); 866 } else if (value instanceof GeckoBundle[]) { 867 final GeckoBundle[] array = (GeckoBundle[]) value; 868 final JSONArray jsonArray = new JSONArray(); 869 for (final GeckoBundle element : array) { 870 jsonArray.put(element == null ? JSONObject.NULL : element.toJSONObject()); 871 } 872 jsonValue = jsonArray; 873 } else if (Build.VERSION.SDK_INT >= 19) { 874 final Object wrapped = JSONObject.wrap(value); 875 jsonValue = wrapped != null ? wrapped : value.toString(); 876 } else if (value == null) { 877 jsonValue = JSONObject.NULL; 878 } else if (value.getClass().isArray()) { 879 final JSONArray jsonArray = new JSONArray(); 880 for (int j = 0; j < Array.getLength(value); j++) { 881 jsonArray.put(Array.get(value, j)); 882 } 883 jsonValue = jsonArray; 884 } else { 885 jsonValue = value; 886 } 887 out.put(mMap.keyAt(i), jsonValue); 888 } 889 return out; 890 } 891 toBundle()892 public Bundle toBundle() { 893 final Bundle out = new Bundle(mMap.size()); 894 for (int i = 0; i < mMap.size(); i++) { 895 final String key = mMap.keyAt(i); 896 final Object val = mMap.valueAt(i); 897 898 if (val == null) { 899 out.putString(key, null); 900 } else if (val instanceof GeckoBundle) { 901 out.putBundle(key, ((GeckoBundle) val).toBundle()); 902 } else if (val instanceof GeckoBundle[]) { 903 final GeckoBundle[] array = (GeckoBundle[]) val; 904 final Parcelable[] parcelables = new Parcelable[array.length]; 905 for (int j = 0; j < array.length; j++) { 906 if (array[j] != null) { 907 parcelables[j] = array[j].toBundle(); 908 } 909 } 910 out.putParcelableArray(key, parcelables); 911 } else if (val instanceof Boolean) { 912 out.putBoolean(key, (Boolean) val); 913 } else if (val instanceof boolean[]) { 914 out.putBooleanArray(key, (boolean[]) val); 915 } else if (val instanceof Byte || val instanceof Short || val instanceof Integer) { 916 out.putInt(key, ((Number) val).intValue()); 917 } else if (val instanceof int[]) { 918 out.putIntArray(key, (int[]) val); 919 } else if (val instanceof Float || val instanceof Double || val instanceof Long) { 920 out.putDouble(key, ((Number) val).doubleValue()); 921 } else if (val instanceof double[]) { 922 out.putDoubleArray(key, (double[]) val); 923 } else if (val instanceof CharSequence || val instanceof Character) { 924 out.putString(key, val.toString()); 925 } else if (val instanceof String[]) { 926 out.putStringArray(key, (String[]) val); 927 } else { 928 throw new UnsupportedOperationException(); 929 } 930 } 931 return out; 932 } 933 fromBundle(final Bundle bundle)934 public static GeckoBundle fromBundle(final Bundle bundle) { 935 if (bundle == null) { 936 return null; 937 } 938 939 final String[] keys = new String[bundle.size()]; 940 final Object[] values = new Object[bundle.size()]; 941 int i = 0; 942 943 for (final String key : bundle.keySet()) { 944 final Object value = bundle.get(key); 945 keys[i] = key; 946 947 if (value instanceof Bundle || value == null) { 948 values[i] = fromBundle((Bundle) value); 949 } else if (value instanceof Parcelable[]) { 950 final Parcelable[] array = (Parcelable[]) value; 951 final GeckoBundle[] out = new GeckoBundle[array.length]; 952 for (int j = 0; j < array.length; j++) { 953 out[j] = fromBundle((Bundle) array[j]); 954 } 955 values[i] = out; 956 } else if (value instanceof Boolean || value instanceof Integer || 957 value instanceof Double || value instanceof String || 958 value instanceof boolean[] || value instanceof int[] || 959 value instanceof double[] || value instanceof String[]) { 960 values[i] = value; 961 } else if (value instanceof Byte || value instanceof Short) { 962 values[i] = ((Number) value).intValue(); 963 } else if (value instanceof Float || value instanceof Long) { 964 values[i] = ((Number) value).doubleValue(); 965 } else if (value instanceof CharSequence || value instanceof Character) { 966 values[i] = value.toString(); 967 } else { 968 throw new UnsupportedOperationException(); 969 } 970 971 i++; 972 } 973 return new GeckoBundle(keys, values); 974 } 975 fromJSONValue(final Object value)976 private static Object fromJSONValue(final Object value) throws JSONException { 977 if (value == null || value == JSONObject.NULL) { 978 return null; 979 } else if (value instanceof JSONObject) { 980 return fromJSONObject((JSONObject) value); 981 } 982 if (value instanceof JSONArray) { 983 final JSONArray array = (JSONArray) value; 984 final int len = array.length(); 985 if (len == 0) { 986 return EMPTY_BOOLEAN_ARRAY; 987 } 988 Object out = null; 989 for (int i = 0; i < len; i++) { 990 final Object element = fromJSONValue(array.opt(i)); 991 if (element == null) { 992 continue; 993 } 994 if (out == null) { 995 Class<?> type = element.getClass(); 996 if (type == Boolean.class) { 997 type = boolean.class; 998 } else if (type == Integer.class) { 999 type = int.class; 1000 } else if (type == Double.class) { 1001 type = double.class; 1002 } 1003 out = Array.newInstance(type, len); 1004 } 1005 Array.set(out, i, element); 1006 } 1007 if (out == null) { 1008 // Treat all-null arrays as String arrays. 1009 return new String[len]; 1010 } 1011 return out; 1012 } 1013 if (value instanceof Boolean || value instanceof Integer || 1014 value instanceof Double || value instanceof String) { 1015 return value; 1016 } 1017 if (value instanceof Byte || value instanceof Short) { 1018 return ((Number) value).intValue(); 1019 } 1020 if (value instanceof Float || value instanceof Long) { 1021 return ((Number) value).doubleValue(); 1022 } 1023 return value.toString(); 1024 } 1025 fromJSONObject(final JSONObject obj)1026 public static GeckoBundle fromJSONObject(final JSONObject obj) throws JSONException { 1027 if (obj == null || obj == JSONObject.NULL) { 1028 return null; 1029 } 1030 1031 final String[] keys = new String[obj.length()]; 1032 final Object[] values = new Object[obj.length()]; 1033 1034 final Iterator<String> iter = obj.keys(); 1035 for (int i = 0; iter.hasNext(); i++) { 1036 final String key = iter.next(); 1037 keys[i] = key; 1038 values[i] = fromJSONValue(obj.opt(key)); 1039 } 1040 return new GeckoBundle(keys, values); 1041 } 1042 1043 @Override // Parcelable describeContents()1044 public int describeContents() { 1045 return 0; 1046 } 1047 1048 @Override // Parcelable writeToParcel(final Parcel dest, final int flags)1049 public void writeToParcel(final Parcel dest, final int flags) { 1050 final int len = mMap.size(); 1051 dest.writeInt(len); 1052 1053 for (int i = 0; i < len; i++) { 1054 dest.writeString(mMap.keyAt(i)); 1055 dest.writeValue(mMap.valueAt(i)); 1056 } 1057 } 1058 1059 // AIDL code may call readFromParcel even though it's not part of Parcelable. readFromParcel(final Parcel source)1060 public void readFromParcel(final Parcel source) { 1061 final ClassLoader loader = getClass().getClassLoader(); 1062 final int len = source.readInt(); 1063 mMap.clear(); 1064 mMap.ensureCapacity(len); 1065 1066 for (int i = 0; i < len; i++) { 1067 final String key = source.readString(); 1068 Object val = source.readValue(loader); 1069 1070 if (val instanceof Parcelable[]) { 1071 final Parcelable[] array = (Parcelable[]) val; 1072 val = Arrays.copyOf(array, array.length, GeckoBundle[].class); 1073 } 1074 1075 mMap.put(key, val); 1076 } 1077 } 1078 1079 public static final Parcelable.Creator<GeckoBundle> CREATOR = 1080 new Parcelable.Creator<GeckoBundle>() { 1081 @Override 1082 public GeckoBundle createFromParcel(final Parcel source) { 1083 final GeckoBundle bundle = new GeckoBundle(0); 1084 bundle.readFromParcel(source); 1085 return bundle; 1086 } 1087 1088 @Override 1089 public GeckoBundle[] newArray(final int size) { 1090 return new GeckoBundle[size]; 1091 } 1092 }; 1093 } 1094