Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.util;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.collection.SimpleArrayMap;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.annotation.RobocopTarget;
import org.mozilla.gecko.annotation.WrapForJNI;
/**
* A lighter-weight version of Bundle that adds support for type coercion (e.g. int to double) in
* order to better cooperate with JS objects.
*/
@RobocopTarget
public final class GeckoBundle implements Parcelable {
private static final String LOGTAG = "GeckoBundle";
private static final boolean DEBUG = false;
@WrapForJNI(calledFrom = "gecko")
private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final int[] EMPTY_INT_ARRAY = new int[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final GeckoBundle[] EMPTY_BUNDLE_ARRAY = new GeckoBundle[0];
private SimpleArrayMap<String, Object> mMap;
/** Construct an empty GeckoBundle. */
public GeckoBundle() {
mMap = new SimpleArrayMap<>();
}
/**
* Construct an empty GeckoBundle with specific capacity.
*
* @param capacity Initial capacity.
*/
public GeckoBundle(final int capacity) {
mMap = new SimpleArrayMap<>(capacity);
}
/**
* Construct a copy of another GeckoBundle.
*
* @param bundle GeckoBundle to copy from.
*/
public GeckoBundle(final GeckoBundle bundle) {
mMap = new SimpleArrayMap<>(bundle.mMap);
}
@WrapForJNI(calledFrom = "gecko")
private GeckoBundle(final String[] keys, final Object[] values) {
final int len = keys.length;
mMap = new SimpleArrayMap<>(len);
for (int i = 0; i < len; i++) {
mMap.put(keys[i], values[i]);
}
}
/** Clear all mappings. */
public void clear() {
mMap.clear();
}
/**
* Returns whether a mapping exists. Null String, Bundle, or arrays are treated as nonexistent.
*
* @param key Key to look for.
* @return True if the specified key exists and the value is not null.
*/
public boolean containsKey(final String key) {
return mMap.get(key) != null;
}
/**
* Returns the value associated with a mapping as an Object.
*
* @param key Key to look for.
* @return Mapping value or null if the mapping does not exist.
*/
public Object get(final String key) {
return mMap.get(key);
}
/**
* Returns the value associated with a boolean mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Boolean value
*/
public boolean getBoolean(final String key, final boolean defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : (Boolean) value;
}
/**
* Returns the value associated with a boolean mapping, or false if the mapping does not exist.
*
* @param key Key to look for.
* @return Boolean value
*/
public boolean getBoolean(final String key) {
return getBoolean(key, false);
}
/**
* Returns the value associated with a Boolean mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Boolean value
*/
public Boolean getBooleanObject(final String key, final Boolean defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : (Boolean) value;
}
/**
* Returns the value associated with a Boolean mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Boolean value
*/
public Boolean getBooleanObject(final String key) {
return getBooleanObject(key, null);
}
/**
* Returns the value associated with a boolean array mapping, or null if the mapping does not
* exist.
*
* @param key Key to look for.
* @return Boolean array value
*/
public boolean[] getBooleanArray(final String key) {
final Object value = mMap.get(key);
return value == null
? null
: Array.getLength(value) == 0 ? EMPTY_BOOLEAN_ARRAY : (boolean[]) value;
}
/**
* Returns the value associated with a double mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Double value
*/
public double getDouble(final String key, final double defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : ((Number) value).doubleValue();
}
/**
* Returns the value associated with a double mapping, or 0.0 if the mapping does not exist.
*
* @param key Key to look for.
* @return Double value
*/
public double getDouble(final String key) {
return getDouble(key, 0.0);
}
private static double[] getDoubleArray(final int[] array) {
final int len = array.length;
final double[] ret = new double[len];
for (int i = 0; i < len; i++) {
ret[i] = (double) array[i];
}
return ret;
}
/**
* Returns the value associated with a double array mapping, or null if the mapping does not
* exist.
*
* @param key Key to look for.
* @return Double array value
*/
public double[] getDoubleArray(final String key) {
final Object value = mMap.get(key);
return value == null
? null
: Array.getLength(value) == 0
? EMPTY_DOUBLE_ARRAY
: value instanceof int[] ? getDoubleArray((int[]) value) : (double[]) value;
}
/**
* Returns the value associated with a Double mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Double value
*/
public Double getDoubleObject(final String key) {
return getDoubleObject(key, null);
}
/**
* Returns the value associated with a Double mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @return Double value
*/
public Double getDoubleObject(final String key, final Double defaultValue) {
final Object value = mMap.get(key);
if (value == null) {
return defaultValue;
}
return ((Number) value).doubleValue();
}
/**
* Returns the value associated with an int mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Int value
*/
public int getInt(final String key, final int defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : ((Number) value).intValue();
}
/**
* Returns the value associated with an int mapping, or 0 if the mapping does not exist.
*
* @param key Key to look for.
* @return Int value
*/
public int getInt(final String key) {
return getInt(key, 0);
}
/**
* Returns the value associated with an Integer mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Int value
*/
public Integer getInteger(final String key, final Integer defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : ((Integer) value);
}
/**
* Returns the value associated with an Integer mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Int value
*/
public Integer getInteger(final String key) {
return getInteger(key, null);
}
private static int[] getIntArray(final double[] array) {
final int len = array.length;
final int[] ret = new int[len];
for (int i = 0; i < len; i++) {
ret[i] = (int) array[i];
}
return ret;
}
/**
* Returns the value associated with an int array mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Int array value
*/
public int[] getIntArray(final String key) {
final Object value = mMap.get(key);
return value == null
? null
: Array.getLength(value) == 0
? EMPTY_INT_ARRAY
: value instanceof double[] ? getIntArray((double[]) value) : (int[]) value;
}
/**
* Returns the value associated with an byte array mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Byte array value
*/
public byte[] getByteArray(final String key) {
final Object value = mMap.get(key);
return value == null ? null : Array.getLength(value) == 0 ? EMPTY_BYTE_ARRAY : (byte[]) value;
}
/**
* Returns the value associated with an int/double mapping as a long value, or defaultValue if the
* mapping does not exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping does not exist.
* @return Long value
*/
public long getLong(final String key, final long defaultValue) {
final Object value = mMap.get(key);
return value == null ? defaultValue : ((Number) value).longValue();
}
/**
* Returns the value associated with an int/double mapping as a long value, or 0 if the mapping
* does not exist.
*
* @param key Key to look for.
* @return Long value
*/
public long getLong(final String key) {
return getLong(key, 0L);
}
private static long[] getLongArray(final Object array) {
final int len = Array.getLength(array);
final long[] ret = new long[len];
for (int i = 0; i < len; i++) {
ret[i] = ((Number) Array.get(array, i)).longValue();
}
return ret;
}
/**
* Returns the value associated with an int/double array mapping as a long array, or null if the
* mapping does not exist.
*
* @param key Key to look for.
* @return Long array value
*/
public long[] getLongArray(final String key) {
final Object value = mMap.get(key);
return value == null
? null
: Array.getLength(value) == 0 ? EMPTY_LONG_ARRAY : getLongArray(value);
}
/**
* Returns the value associated with a String mapping, or defaultValue if the mapping does not
* exist.
*
* @param key Key to look for.
* @param defaultValue Value to return if mapping value is null or mapping does not exist.
* @return String value
*/
public String getString(final String key, final String defaultValue) {
// If the key maps to null, technically we should return null because the mapping
// exists and null is a valid string value. However, people expect the default
// value to be returned instead, so we make an exception to return the default value.
final Object value = mMap.get(key);
return value == null ? defaultValue : (String) value;
}
/**
* Returns the value associated with a String mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return String value
*/
public String getString(final String key) {
return getString(key, null);
}
// The only case where we convert String[] to/from GeckoBundle[] is if every element
// is null.
private static int getNullArrayLength(final Object array) {
final int len = Array.getLength(array);
for (int i = 0; i < len; i++) {
if (Array.get(array, i) != null) {
throw new ClassCastException("Cannot cast array type");
}
}
return len;
}
/**
* Returns the value associated with a String array mapping, or null if the mapping does not
* exist.
*
* @param key Key to look for.
* @return String array value
*/
public String[] getStringArray(final String key) {
final Object value = mMap.get(key);
return value == null
? null
: Array.getLength(value) == 0
? EMPTY_STRING_ARRAY
: !(value instanceof String[])
? new String[getNullArrayLength(value)]
: (String[]) value;
}
/*
* Returns the value associated with a RectF mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return RectF value
*/
public RectF getRectF(final String key) {
final GeckoBundle rectBundle = getBundle(key);
if (rectBundle == null) {
return null;
}
return new RectF(
(float) rectBundle.getDouble("left"),
(float) rectBundle.getDouble("top"),
(float) rectBundle.getDouble("right"),
(float) rectBundle.getDouble("bottom"));
}
/**
* Returns the value associated with a Point mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Point value
*/
public Point getPoint(final String key) {
final GeckoBundle ptBundle = getBundle(key);
if (ptBundle == null) {
return null;
}
return new Point(ptBundle.getInt("x"), ptBundle.getInt("y"));
}
/**
* Returns the value associated with a PointF mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return Point value
*/
public PointF getPointF(final String key) {
final GeckoBundle ptBundle = getBundle(key);
if (ptBundle == null) {
return null;
}
return new PointF((float) ptBundle.getDouble("x"), (float) ptBundle.getDouble("y"));
}
/**
* Returns the value associated with a GeckoBundle mapping, or null if the mapping does not exist.
*
* @param key Key to look for.
* @return GeckoBundle value
*/
public GeckoBundle getBundle(final String key) {
return (GeckoBundle) mMap.get(key);
}
/**
* Returns the value associated with a GeckoBundle array mapping, or null if the mapping does not
* exist.
*
* @param key Key to look for.
* @return GeckoBundle array value
*/
public GeckoBundle[] getBundleArray(final String key) {
final Object value = mMap.get(key);
return value == null
? null
: Array.getLength(value) == 0
? EMPTY_BUNDLE_ARRAY
: !(value instanceof GeckoBundle[])
? new GeckoBundle[getNullArrayLength(value)]
: (GeckoBundle[]) value;
}
/**
* Returns whether this GeckoBundle has no mappings.
*
* @return True if no mapping exists.
*/
public boolean isEmpty() {
return mMap.isEmpty();
}
/**
* Returns an array of all mapped keys.
*
* @return String array containing all mapped keys.
*/
@WrapForJNI(calledFrom = "gecko")
public String[] keys() {
final int len = mMap.size();
final String[] ret = new String[len];
for (int i = 0; i < len; i++) {
ret[i] = mMap.keyAt(i);
}
return ret;
}
@WrapForJNI(calledFrom = "gecko")
private Object[] values() {
final int len = mMap.size();
final Object[] ret = new Object[len];
for (int i = 0; i < len; i++) {
ret[i] = mMap.valueAt(i);
}
return ret;
}
private void put(final String key, final Object value) {
// We intentionally disallow a generic put() method for type safety and sanity. For
// example, we assume elsewhere in the code that a value belongs to a small list of
// predefined types, and cannot be any arbitrary object. If you want to put an
// Object in the bundle, check the type of the Object first and call the
// corresponding put methods. For example,
//
// if (obj instanceof Integer) {
// bundle.putInt(key, (Integer) key);
// } else if (obj instanceof String) {
// bundle.putString(key, (String) obj);
// } else {
// throw new IllegalArgumentException("unexpected type");
// }
throw new UnsupportedOperationException();
}
/**
* Map a key to a boolean value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBoolean(final String key, final boolean value) {
mMap.put(key, value);
}
/**
* Map a key to a boolean array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBooleanArray(final String key, final boolean[] value) {
mMap.put(key, value);
}
/**
* Map a key to a boolean array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBooleanArray(final String key, final Boolean[] value) {
if (value == null) {
mMap.put(key, null);
return;
}
final boolean[] array = new boolean[value.length];
for (int i = 0; i < value.length; i++) {
array[i] = value[i];
}
mMap.put(key, array);
}
/**
* Map a key to a boolean array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBooleanArray(final String key, final Collection<Boolean> value) {
if (value == null) {
mMap.put(key, null);
return;
}
final boolean[] array = new boolean[value.size()];
int i = 0;
for (final Boolean element : value) {
array[i++] = element;
}
mMap.put(key, array);
}
/**
* Map a key to a double value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putDouble(final String key, final double value) {
mMap.put(key, value);
}
/**
* Map a key to a double array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putDoubleArray(final String key, final double[] value) {
mMap.put(key, value);
}
/**
* Map a key to a double array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putDoubleArray(final String key, final Double[] value) {
putDoubleArray(key, Arrays.asList(value));
}
/**
* Map a key to a double array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putDoubleArray(final String key, final Collection<Double> value) {
if (value == null) {
mMap.put(key, null);
return;
}
final double[] array = new double[value.size()];
int i = 0;
for (final Double element : value) {
array[i++] = element;
}
mMap.put(key, array);
}
/**
* Map a key to an int value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putInt(final String key, final int value) {
mMap.put(key, value);
}
/**
* Map a key to an int array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putIntArray(final String key, final int[] value) {
mMap.put(key, value);
}
/**
* Map a key to a int array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putIntArray(final String key, final Integer[] value) {
putIntArray(key, Arrays.asList(value));
}
/**
* Map a key to a int array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putIntArray(final String key, final Collection<Integer> value) {
if (value == null) {
mMap.put(key, null);
return;
}
final int[] array = new int[value.size()];
int i = 0;
for (final Integer element : value) {
array[i++] = element;
}
mMap.put(key, array);
}
/**
* Map a key to a long value stored as a double value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putLong(final String key, final long value) {
mMap.put(key, (double) value);
}
/**
* Map a key to a long array value stored as a double array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putLongArray(final String key, final long[] value) {
if (value == null) {
mMap.put(key, null);
return;
}
final double[] array = new double[value.length];
for (int i = 0; i < value.length; i++) {
array[i] = (double) value[i];
}
mMap.put(key, array);
}
/**
* Map a key to a long array value stored as a double array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putLongArray(final String key, final Long[] value) {
putLongArray(key, Arrays.asList(value));
}
/**
* Map a key to a long array value stored as a double array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putLongArray(final String key, final Collection<Long> value) {
if (value == null) {
mMap.put(key, null);
return;
}
final double[] array = new double[value.size()];
int i = 0;
for (final Long element : value) {
array[i++] = (double) element;
}
mMap.put(key, array);
}
/**
* Map a key to a String value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putString(final String key, final String value) {
mMap.put(key, value);
}
/**
* Map a key to a String array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putStringArray(final String key, final String[] value) {
mMap.put(key, value);
}
/**
* Map a key to a String array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putStringArray(final String key, final Collection<String> value) {
if (value == null) {
mMap.put(key, null);
return;
}
final String[] array = new String[value.size()];
int i = 0;
for (final String element : value) {
array[i++] = element;
}
mMap.put(key, array);
}
/**
* Map a key to a GeckoBundle value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBundle(final String key, final GeckoBundle value) {
mMap.put(key, value);
}
/**
* Map a key to a GeckoBundle array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBundleArray(final String key, final GeckoBundle[] value) {
mMap.put(key, value);
}
/**
* Map a key to a GeckoBundle array value.
*
* @param key Key to map.
* @param value Value to map to.
*/
public void putBundleArray(final String key, final Collection<GeckoBundle> value) {
if (value == null) {
mMap.put(key, null);
return;
}
final GeckoBundle[] array = new GeckoBundle[value.size()];
int i = 0;
for (final GeckoBundle element : value) {
array[i++] = element;
}
mMap.put(key, array);
}
/**
* Remove a mapping.
*
* @param key Key to remove.
*/
public void remove(final String key) {
mMap.remove(key);
}
/**
* Returns number of mappings in this GeckoBundle.
*
* @return Number of mappings.
*/
public int size() {
return mMap.size();
}
private static Object normalizeValue(final Object value) {
if (value instanceof Integer) {
// We treat int and double as the same type.
return ((Integer) value).doubleValue();
} else if (value instanceof int[]) {
// We treat int[] and double[] as the same type.
final int[] array = (int[]) value;
return array.length == 0 ? EMPTY_STRING_ARRAY : getDoubleArray(array);
} else if (value != null && value.getClass().isArray()) {
// We treat arrays of all nulls as the same type, including empty arrays.
final int len = Array.getLength(value);
for (int i = 0; i < len; i++) {
if (Array.get(value, i) != null) {
return value;
}
}
return len == 0 ? EMPTY_STRING_ARRAY : new String[len];
}
return value;
}
@Override // Object
public boolean equals(final Object other) {
if (!(other instanceof GeckoBundle)) {
return false;
}
// Support library's SimpleArrayMap.equals is buggy, so roll our own version.
final SimpleArrayMap<String, Object> otherMap = ((GeckoBundle) other).mMap;
if (mMap == otherMap) {
return true;
}
if (mMap.size() != otherMap.size()) {
return false;
}
for (int i = 0; i < mMap.size(); i++) {
final String thisKey = mMap.keyAt(i);
final int otherKey = otherMap.indexOfKey(thisKey);
if (otherKey < 0) {
return false;
}
final Object thisValue = normalizeValue(mMap.valueAt(i));
final Object otherValue = normalizeValue(otherMap.valueAt(otherKey));
if (thisValue == otherValue) {
continue;
} else if (thisValue == null || otherValue == null) {
return false;
}
final Class<?> thisClass = thisValue.getClass();
final Class<?> otherClass = otherValue.getClass();
if (thisClass != otherClass && !thisClass.equals(otherClass)) {
return false;
} else if (!thisClass.isArray()) {
if (!thisValue.equals(otherValue)) {
return false;
}
continue;
}
// Work with both primitive arrays and Object arrays, unlike Arrays.equals().
final int thisLen = Array.getLength(thisValue);
final int otherLen = Array.getLength(otherValue);
if (thisLen != otherLen) {
return false;
}
for (int j = 0; j < thisLen; j++) {
final Object thisElem = Array.get(thisValue, j);
final Object otherElem = Array.get(otherValue, j);
if (thisElem != otherElem
&& (thisElem == null || otherElem == null || !thisElem.equals(otherElem))) {
return false;
}
}
}
return true;
}
@Override // Object
public int hashCode() {
return mMap.hashCode();
}
@Override // Object
public String toString() {
return mMap.toString();
}
public JSONObject toJSONObject() throws JSONException {
final JSONObject out = new JSONObject();
for (int i = 0; i < mMap.size(); i++) {
final Object value = mMap.valueAt(i);
final Object jsonValue;
if (value instanceof GeckoBundle) {
jsonValue = ((GeckoBundle) value).toJSONObject();
} else if (value instanceof GeckoBundle[]) {
final GeckoBundle[] array = (GeckoBundle[]) value;
final JSONArray jsonArray = new JSONArray();
for (final GeckoBundle element : array) {
jsonArray.put(element == null ? JSONObject.NULL : element.toJSONObject());
}
jsonValue = jsonArray;
} else if (Build.VERSION.SDK_INT >= 19) {
// gradle task (testWithGeckoBinariesDebugUnitTest) won't use this since that unit test
// runs on build task.
final Object wrapped = JSONObject.wrap(value);
jsonValue = wrapped != null ? wrapped : value.toString();
} else if (value == null) {
// This is used by UnitTest only
jsonValue = JSONObject.NULL;
} else if (value.getClass().isArray()) {
// This is used by UnitTest only
final JSONArray jsonArray = new JSONArray();
for (int j = 0; j < Array.getLength(value); j++) {
jsonArray.put(Array.get(value, j));
}
jsonValue = jsonArray;
} else {
// This is used by UnitTest only
jsonValue = value;
}
out.put(mMap.keyAt(i), jsonValue);
}
return out;
}
public Bundle toBundle() {
final Bundle out = new Bundle(mMap.size());
for (int i = 0; i < mMap.size(); i++) {
final String key = mMap.keyAt(i);
final Object val = mMap.valueAt(i);
if (val == null) {
out.putString(key, null);
} else if (val instanceof GeckoBundle) {
out.putBundle(key, ((GeckoBundle) val).toBundle());
} else if (val instanceof GeckoBundle[]) {
final GeckoBundle[] array = (GeckoBundle[]) val;
final Parcelable[] parcelables = new Parcelable[array.length];
for (int j = 0; j < array.length; j++) {
if (array[j] != null) {
parcelables[j] = array[j].toBundle();
}
}
out.putParcelableArray(key, parcelables);
} else if (val instanceof Boolean) {
out.putBoolean(key, (Boolean) val);
} else if (val instanceof boolean[]) {
out.putBooleanArray(key, (boolean[]) val);
} else if (val instanceof Byte || val instanceof Short || val instanceof Integer) {
out.putInt(key, ((Number) val).intValue());
} else if (val instanceof int[]) {
out.putIntArray(key, (int[]) val);
} else if (val instanceof Float || val instanceof Double || val instanceof Long) {
out.putDouble(key, ((Number) val).doubleValue());
} else if (val instanceof double[]) {
out.putDoubleArray(key, (double[]) val);
} else if (val instanceof CharSequence || val instanceof Character) {
out.putString(key, val.toString());
} else if (val instanceof String[]) {
out.putStringArray(key, (String[]) val);
} else {
throw new UnsupportedOperationException();
}
}
return out;
}
public static GeckoBundle fromBundle(final Bundle bundle) {
if (bundle == null) {
return null;
}
final String[] keys = new String[bundle.size()];
final Object[] values = new Object[bundle.size()];
int i = 0;
for (final String key : bundle.keySet()) {
final Object value = bundle.get(key);
keys[i] = key;
if (value instanceof Bundle || value == null) {
values[i] = fromBundle((Bundle) value);
} else if (value instanceof Parcelable[]) {
final Parcelable[] array = (Parcelable[]) value;
final GeckoBundle[] out = new GeckoBundle[array.length];
for (int j = 0; j < array.length; j++) {
out[j] = fromBundle((Bundle) array[j]);
}
values[i] = out;
} else if (value instanceof Boolean
|| value instanceof Integer
|| value instanceof Double
|| value instanceof String
|| value instanceof boolean[]
|| value instanceof int[]
|| value instanceof double[]
|| value instanceof String[]) {
values[i] = value;
} else if (value instanceof Byte || value instanceof Short) {
values[i] = ((Number) value).intValue();
} else if (value instanceof Float || value instanceof Long) {
values[i] = ((Number) value).doubleValue();
} else if (value instanceof CharSequence || value instanceof Character) {
values[i] = value.toString();
} else {
throw new UnsupportedOperationException();
}
i++;
}
return new GeckoBundle(keys, values);
}
private static Object fromJSONValue(final Object value) throws JSONException {
if (value == null || value == JSONObject.NULL) {
return null;
} else if (value instanceof JSONObject) {
return fromJSONObject((JSONObject) value);
}
if (value instanceof JSONArray) {
final JSONArray array = (JSONArray) value;
final int len = array.length();
if (len == 0) {
return EMPTY_BOOLEAN_ARRAY;
}
Object out = null;
for (int i = 0; i < len; i++) {
final Object element = fromJSONValue(array.opt(i));
if (element == null) {
continue;
}
if (out == null) {
Class<?> type = element.getClass();
if (type == Boolean.class) {
type = boolean.class;
} else if (type == Integer.class) {
type = int.class;
} else if (type == Double.class) {
type = double.class;
}
out = Array.newInstance(type, len);
}
Array.set(out, i, element);
}
if (out == null) {
// Treat all-null arrays as String arrays.
return new String[len];
}
return out;
}
if (value instanceof Boolean
|| value instanceof Integer
|| value instanceof Double
|| value instanceof String) {
return value;
}
if (value instanceof Byte || value instanceof Short) {
return ((Number) value).intValue();
}
if (value instanceof Float || value instanceof Long) {
return ((Number) value).doubleValue();
}
return value.toString();
}
public static GeckoBundle fromJSONObject(final JSONObject obj) throws JSONException {
if (obj == null || obj == JSONObject.NULL) {
return null;
}
final String[] keys = new String[obj.length()];
final Object[] values = new Object[obj.length()];
final Iterator<String> iter = obj.keys();
for (int i = 0; iter.hasNext(); i++) {
final String key = iter.next();
keys[i] = key;
values[i] = fromJSONValue(obj.opt(key));
}
return new GeckoBundle(keys, values);
}
@Override // Parcelable
public int describeContents() {
return 0;
}
@Override // Parcelable
public void writeToParcel(final Parcel dest, final int flags) {
final int len = mMap.size();
dest.writeInt(len);
for (int i = 0; i < len; i++) {
dest.writeString(mMap.keyAt(i));
dest.writeValue(mMap.valueAt(i));
}
}
// AIDL code may call readFromParcel even though it's not part of Parcelable.
public void readFromParcel(final Parcel source) {
final ClassLoader loader = getClass().getClassLoader();
final int len = source.readInt();
mMap.clear();
mMap.ensureCapacity(len);
for (int i = 0; i < len; i++) {
final String key = source.readString();
Object val = source.readValue(loader);
if (val instanceof Parcelable[]) {
final Parcelable[] array = (Parcelable[]) val;
val = Arrays.copyOf(array, array.length, GeckoBundle[].class);
}
mMap.put(key, val);
}
}
public static final Parcelable.Creator<GeckoBundle> CREATOR =
new Parcelable.Creator<GeckoBundle>() {
@Override
public GeckoBundle createFromParcel(final Parcel source) {
final GeckoBundle bundle = new GeckoBundle(0);
bundle.readFromParcel(source);
return bundle;
}
@Override
public GeckoBundle[] newArray(final int size) {
return new GeckoBundle[size];
}
};
}