android.os.Parcel Maven / Gradle / Ivy
Show all versions of android-util Show documentation
/*
* Copyright (c) 2016, Stein Eldar johnsen
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package android.os;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import static java.lang.Math.min;
import static java.lang.System.arraycopy;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.fill;
/**
* Non-Android port of android serialization utility class.
*
* Base post of the
* android.os.Parcel
* class. This is mimicking the interface and base functionality of the parcel,
* but not the IPC callable functionality. As opposed to the Android version
* this will serialize everything immediately. There are also a couple of
* methods *not* implemented as they refer to android-internal utility classes
* or android system functionality.
*
* E.g. methods referencing android.util.* classes are omitted (SparseArray,
* Size, SizeF, IBinder).
*
* The main reason for porting this is to be able to extensively test parcelable
* classes without needing the whole android system to do the testing.
*
* Parcel: http://developer.android.com/reference/android/os/Parcel.html
* Parcelable: http://developer.android.com/reference/android/os/Parcelable.html
*
* Also see {@link android.os.Parcelable}.
*/
@SuppressWarnings("unused")
public final class Parcel {
/**
* Retrieve a new Parcel object from the pool.
*
* @return The obtained parcel.
*/
public static Parcel obtain() {
synchronized (pool) {
if (pool.size() > 0) {
return pool.poll();
}
}
return new Parcel();
}
/**
* Put a Parcel object back into the pool.
*/
public void recycle() {
size = 0;
position = 0;
// clear data so it won't leak.
fill(buffer, 0, buffer.length, (byte) 0);
synchronized (pool) {
// If the pool is already full, cycle back in new parcels to enable
// refreshing memory.
if (pool.size() == kMaxPoolSize) {
pool.poll();
}
pool.offer(this);
}
}
public void appendFrom(Parcel parcel, int offset, int length) {
append(parcel.buffer, offset, length);
}
public int dataAvail() {
return size - position;
}
public int dataCapacity() {
return buffer.length;
}
public int dataPosition() {
return position;
}
public int dataSize() {
return size;
}
public void setDataCapacity(int capacity) {
if (capacity < size) {
throw new IllegalArgumentException(
"New capacity " + capacity + " is smaller than current data size: " + size);
}
byte[] tmp = new byte[capacity];
arraycopy(buffer, 0, tmp, 0, size);
buffer = tmp;
}
public void setDataPosition(int pos) {
if (pos < 0) {
throw new IllegalArgumentException("Negative position " + pos);
}
if (pos > size) {
throw new IllegalArgumentException(
"New position " + pos + " is after last known byte: " + size);
}
position = pos;
}
public void setDataSize(int newSize) {
if (newSize == size)
return;
if (newSize < size) {
trim(newSize);
} else {
grow(newSize);
}
}
public byte[] marshall() {
byte[] result = new byte[size];
arraycopy(buffer, 0, result, 0, size);
return result;
}
public void unmarshall(byte[] data, int offset, int length) {
position = size = 0;
append(data, offset, length);
}
// --- Data readers and writers.
public void writeByte(byte b) {
ensureCapacity(size + 1);
size += encode(b, buffer, size);
}
public byte readByte() {
ensureAvailable(1);
byte tmp = buffer[position];
++position;
return tmp;
}
public void writeDouble(double d) {
ensureCapacity(size + 8);
long value = Double.doubleToLongBits(d);
size += encode(value, buffer, size);
}
public double readDouble() {
ensureAvailable(8);
long value = decode(buffer, position, 8);
position += 8;
return Double.longBitsToDouble(value);
}
public void writeFloat(float f) {
ensureCapacity(size + 4);
int value = Float.floatToIntBits(f);
size += encode(value, buffer, size);
}
public float readFloat() {
ensureAvailable(4);
int value = (int) decode(buffer, position, 4);
position += 4;
return Float.intBitsToFloat(value);
}
public void writeInt(int i) {
ensureCapacity(size + 4);
size += encode(i, buffer, size);
}
public int readInt() {
ensureAvailable(4);
int value = (int) decode(buffer, position, 4);
position += 4;
return value;
}
public void writeLong(long l) {
ensureCapacity(size + 8);
size += encode(l, buffer, size);
}
public long readLong() {
ensureAvailable(8);
long value = decode(buffer, position, 8);
position += 8;
return value;
}
public void writeString(String s) {
if (s != null) {
byte[] bytes = s.getBytes(UTF_8);
ensureCapacity(size + 4 + bytes.length);
size += encode(bytes.length, buffer, size);
arraycopy(bytes, 0, buffer, size, bytes.length);
size += bytes.length;
} else {
writeInt(-1);
}
}
public String readString() {
final int len = readInt();
if (len < 0)
return null;
ensureAvailable(len);
String out = new String(buffer, position, len, UTF_8);
position += len;
return out;
}
public void writeBooleanArray(boolean[] arr) {
byte[] bytes = packBits(arr);
ensureCapacity(4 + bytes.length);
encode(arr.length, buffer, size);
size += 4;
arraycopy(bytes, 0, buffer, size, bytes.length);
size += bytes.length;
}
public void readBooleanArray(boolean[] dest) {
boolean[] arr = createBooleanArray();
arraycopy(arr, 0, dest, 0, min(dest.length, arr.length));
}
public boolean[] createBooleanArray() {
final int len = readInt();
byte[] tmp = new byte[booleanArraySizeToBytes(len)];
arraycopy(buffer, position, tmp, 0, tmp.length);
position += tmp.length;
return unpackBits(tmp, len);
}
public void writeByteArray(byte[] arr) {
writeByteArray(arr, 0, arr.length);
}
public void writeByteArray(byte[] arr, int offset, int len) {
ensureCapacity(size + 4 + len);
size += encode(len, buffer, size);
arraycopy(arr, offset, buffer, size, len);
size += len;
}
public void readByteArray(byte[] dest) {
final int len = readInt();
final int copyLen = min(dest.length, len);
ensureAvailable(len);
arraycopy(buffer, position, dest, 0, copyLen);
position += len;
}
public byte[] createByteArray() {
final int len = readInt();
ensureAvailable(len);
byte[] out = new byte[len];
arraycopy(buffer, position, out, 0, len);
position += len;
return out;
}
public void writeCharArray(char[] arr) {
writeByteArray(chars2bytes(arr));
}
public void readCharArray(char[] dest) {
char[] out = createCharArray();
arraycopy(out, 0, dest, 0, min(out.length, dest.length));
}
public char[] createCharArray() {
return bytes2chars(createByteArray());
}
public void writeDoubleArray(double[] arr) {
ensureCapacity(4 + (8 * arr.length));
writeInt(arr.length);
for (double d : arr) {
writeDouble(d);
}
}
public void readDoubleArray(double[] dest) {
double[] out = createDoubleArray();
arraycopy(out, 0, dest, 0, min(dest.length, out.length));
}
public double[] createDoubleArray() {
final int len = readInt();
final double[] out = new double[len];
for (int i = 0; i < len; ++i) {
out[i] = readDouble();
}
return out;
}
public void writeFloatArray(float[] arr) {
ensureCapacity(4 + (4 * arr.length));
writeInt(arr.length);
for (float f : arr) {
writeFloat(f);
}
}
public void readFloatArray(float[] dest) {
float[] out = createFloatArray();
arraycopy(out, 0, dest, 0, min(dest.length, out.length));
}
public float[] createFloatArray() {
final int len = readInt();
final float[] out = new float[len];
for (int i = 0; i < len; ++i) {
out[i] = readFloat();
}
return out;
}
public void writeIntArray(int[] arr) {
ensureCapacity(4 + (4 * arr.length));
writeInt(arr.length);
for (int i : arr) {
writeInt(i);
}
}
public void readIntArray(int[] dest) {
int[] out = createIntArray();
arraycopy(out, 0, dest, 0, min(dest.length, out.length));
}
public int[] createIntArray() {
final int len = readInt();
final int[] out = new int[len];
for (int i = 0; i < len; ++i) {
out[i] = readInt();
}
return out;
}
public void writeLongArray(long[] arr) {
ensureCapacity(4 + (8 * arr.length));
writeInt(arr.length);
for (long l : arr) {
writeLong(l);
}
}
public void readLongArray(long[] dest) {
long[] out = createLongArray();
arraycopy(out, 0, dest, 0, min(dest.length, out.length));
}
public long[] createLongArray() {
final int len = readInt();
final long[] out = new long[len];
for (int i = 0; i < len; ++i) {
out[i] = readLong();
}
return out;
}
public void writeStringArray(String[] arr) {
writeInt(arr.length);
for (String s : arr) {
writeString(s);
}
}
public void readStringArray(String[] dest) {
final int len = readInt();
for (int i = 0; i < len; i++) {
String tmp = readString();
if (i < dest.length) {
dest[i] = tmp;
}
}
}
public String[] createStringArray() {
final int len = readInt();
String[] out = new String[len];
for (int i = 0; i < len; ++i) {
out[i] = readString();
}
return out;
}
public void writeParcelable(Parcelable p, int flags) {
writeCreator(p.getClass());
p.writeToParcel(this, flags);
}
public T readParcelable(ClassLoader loader) {
Parcelable.Creator creator = readCreator(loader);
return creator.createFromParcel(this);
}
public void writeParcelableArray(T[] arr, int flags) {
Class> klass = Null.class;
for (T t : arr) {
if (t == null) {
throw new IllegalArgumentException("null item in parcelable array");
}
klass = t.getClass();
}
writeCreator(klass);
writeTypedArray(arr, flags);
}
public T[] readParcelableArray(ClassLoader loader) {
Parcelable.Creator creator = readCreator(loader);
return createTypedArray(creator);
}
public void writeTypedObject(T source, int flags) {
source.writeToParcel(this, flags);
}
public T readTypedObject(Parcelable.Creator creator) {
return creator.createFromParcel(this);
}
public void writeTypedArray(T[] arr, int flags) {
writeInt(arr.length);
for (T t : arr) {
writeTypedObject(t, flags);
}
}
public T[] createTypedArray(Parcelable.Creator creator) {
final int len = readInt();
final T[] out = creator.newArray(len);
for (int i = 0; i < len; ++i) {
out[i] = readTypedObject(creator);
}
return out;
}
public void writeTypedList(List list) {
writeInt(list.size());
for (T value : list) {
writeTypedObject(value, 0);
}
}
public ArrayList createTypedArrayList(Parcelable.Creator creator) {
final int size = readInt();
ArrayList out = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
out.add(readTypedObject(creator));
}
return out;
}
public static final Parcelable.Creator STRING_CREATOR = new Parcelable.Creator() {
@Override
public String createFromParcel(Parcel source) {
return source.readString();
}
@Override
public String[] newArray(int size) {
return new String[size];
}
};
// --- PRIVATE AFTER HERE ---
private static class Null implements Parcelable {
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// nothing
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public Null createFromParcel(Parcel source) {
return null;
}
@Override
public Null[] newArray(int size) {
return new Null[size];
}
};
}
private byte[] buffer;
private int size;
private int position;
private static final int kCapacityStep = 1 << 10; // 1k per capacity step.
static final int kMaxPoolSize = 10;
private static final Queue pool;
static {
pool = new ArrayDeque<>(kMaxPoolSize);
}
// Only used in tests.
@SuppressWarnings("unused")
protected static void clearPool() {
synchronized (pool) {
pool.clear();
}
}
private Parcel() {
buffer = new byte[kCapacityStep];
size = 0;
position = 0;
}
private void ensureCapacity(int capacity) {
if (buffer.length < capacity) {
setDataCapacity(capacity);
}
}
private void ensureAvailable(int avail) {
if ((position + avail) > size) {
throw new ParcelFormatException(
"Unable to read " + avail + " bytes at position " + position +
" only " + (size - position) + " bytes available");
}
}
@SuppressWarnings("unchecked")
private Parcelable.Creator getCreator(Class klass) {
if (klass.equals(String.class)) {
return (Parcelable.Creator) STRING_CREATOR;
} else {
try {
Field field = klass.getDeclaredField("CREATOR");
boolean accessible = field.isAccessible();
field.setAccessible(true);
Parcelable.Creator creator = (Parcelable.Creator) field.get(null);
field.setAccessible(accessible);
return creator;
} catch (NoSuchFieldException e) {
throw new BadParcelableException("No creator for parcelable class " + klass.getSimpleName());
} catch (IllegalAccessException e) {
throw new BadParcelableException("Unable to access creator for class " + klass.getSimpleName());
}
}
}
@SuppressWarnings("unchecked")
private Parcelable.Creator readCreator(ClassLoader loader) {
if (loader == null) {
loader = ClassLoader.getSystemClassLoader();
}
String name = readString();
try {
return getCreator((Class) loader.loadClass(name));
} catch (ClassNotFoundException cfe) {
throw new BadParcelableException(cfe);
}
}
private void writeCreator(Class> klass) {
writeString(klass.getName());
}
private void trim(int newSize) {
if (newSize < size) {
size = newSize;
}
}
private void grow(int newSize) {
if (newSize > size) {
ensureCapacity(newSize);
fill(buffer, size, newSize, (byte) 0);
size = newSize;
}
}
private void append(byte[] array, int offset, int length) {
ensureCapacity(size + length);
arraycopy(array, offset, buffer, size, length);
size += length;
}
private static int encode(byte value, byte[] into, int offset) {
into[offset] = value;
return 1;
}
private static int encode(int value, byte[] into, int offset) {
into[offset + 3] = (byte) ((value & 0xff000000) >>> 24);
into[offset + 2] = (byte) ((value & 0x00ff0000) >>> 16);
into[offset + 1] = (byte) ((value & 0x0000ff00) >>> 8);
into[offset] = (byte) (value & 0x000000ff);
return 4;
}
private static int encode(long value, byte[] into, int offset) {
into[offset + 7] = (byte) ((value & 0xff00000000000000L) >>> 56);
into[offset + 6] = (byte) ((value & 0x00ff000000000000L) >>> 48);
into[offset + 5] = (byte) ((value & 0x0000ff0000000000L) >>> 40);
into[offset + 4] = (byte) ((value & 0x000000ff00000000L) >>> 32);
into[offset + 3] = (byte) ((value & 0x00000000ff000000L) >>> 24);
into[offset + 2] = (byte) ((value & 0x0000000000ff0000L) >>> 16);
into[offset + 1] = (byte) ((value & 0x000000000000ff00L) >>> 8);
into[offset] = (byte) (value & 0x00000000000000ffL);
return 8;
}
private static long decode(byte[] from, int offset, int bytes) {
long result = 0;
switch (bytes) {
case 8:
result = valueOf(from[offset + 7]) << 56
| valueOf(from[offset + 6]) << 48
| valueOf(from[offset + 5]) << 40
| valueOf(from[offset + 4]) << 32
| valueOf(from[offset + 3]) << 24
| valueOf(from[offset + 2]) << 16
| valueOf(from[offset + 1]) << 8
| valueOf(from[offset]);
break;
case 4:
result = result
| valueOf(from[offset + 3]) << 24
| valueOf(from[offset + 2]) << 16
| valueOf(from[offset + 1]) << 8
| valueOf(from[offset]);
break;
default:
throw new IllegalArgumentException();
}
return result;
}
private static int booleanArraySizeToBytes(int size) {
return (size + 7) / 8;
}
private static boolean isTrue(byte b, int pos) {
return ((b & (0x01 << pos)) != 0);
}
private static boolean isTrue(byte[] arr, int pos) {
return isTrue(arr[pos / 8], pos % 8);
}
private static int setBit(int b, boolean value, int pos) {
int bit = (value ? (0x01 << pos) : 0);
return b | bit;
}
private static byte[] packBits(boolean[] arr) {
final int len = booleanArraySizeToBytes(arr.length);
final byte[] dest = new byte[len];
int pos = 0;
for (int i = 0; i < len; ++i) {
int b = 0;
for (int j = 0; j < 8; ++j, ++pos) {
if (pos < arr.length) {
b = setBit(b, arr[pos], j);
}
}
dest[i] = (byte) b;
}
return dest;
}
private static boolean[] unpackBits(byte[] arr, int len) {
final boolean[] out = new boolean[len];
for (int i = 0; i < len; ++i) {
out[i] = isTrue(arr, i);
}
return out;
}
private static long valueOf(byte b) {
if (b < 0)
return 0x100L + b;
return b;
}
private byte[] chars2bytes(char[] chars) {
byte[] out = new byte[chars.length * 2];
for (int i = 0; i < chars.length; ++i) {
int op = i * 2;
out[op] = (byte) (chars[i] % 0x00ff);
out[op + 1] = (byte) ((chars[i] % 0xff00) >>> 8);
}
return out;
}
private char[] bytes2chars(byte[] bytes) {
char[] out = new char[bytes.length / 2];
for (int i = 0; i < out.length; ++i) {
int bp = i * 2;
int a = bytes[bp + 1];
if (a < 0) a = 0x100 + a;
int b = bytes[bp];
if (b < 0) b = 0x100 + b;
out[i] = (char) (a << 8 | b);
}
return out;
}
}