summaryrefslogtreecommitdiffstats
path: root/luni/src/main/java/java/io/ObjectInputStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'luni/src/main/java/java/io/ObjectInputStream.java')
-rw-r--r--luni/src/main/java/java/io/ObjectInputStream.java2970
1 files changed, 2970 insertions, 0 deletions
diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java
new file mode 100644
index 0000000..9483ab0
--- /dev/null
+++ b/luni/src/main/java/java/io/ObjectInputStream.java
@@ -0,0 +1,2970 @@
+/*
+ * 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 java.io;
+
+import java.io.EmulatedFields.ObjectSlot;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+
+// BEGIN android-added
+import dalvik.system.VMStack;
+// END android-added
+
+import org.apache.harmony.luni.util.Msg;
+import org.apache.harmony.luni.util.PriviAction;
+
+/**
+ * A specialized {@link InputStream} that is able to read (deserialize) Java
+ * objects as well as primitive data types (int, byte, char etc.). The data has
+ * typically been saved using an ObjectOutputStream.
+ *
+ * @see ObjectOutputStream
+ * @see ObjectInput
+ * @see Serializable
+ * @see Externalizable
+ *
+ * @since Android 1.0
+ */
+public class ObjectInputStream extends InputStream implements ObjectInput,
+ ObjectStreamConstants {
+
+ private static InputStream emptyStream = new ByteArrayInputStream(
+ new byte[0]);
+
+ // To put into objectsRead when reading unsharedObject
+ private static final Object UNSHARED_OBJ = new Object(); // $NON-LOCK-1$
+
+ // If the receiver has already read & not consumed a TC code
+ private boolean hasPushbackTC;
+
+ // Push back TC code if the variable above is true
+ private byte pushbackTC;
+
+ // How many nested levels to readObject. When we reach 0 we have to validate
+ // the graph then reset it
+ private int nestedLevels;
+
+ // All objects are assigned an ID (integer handle)
+ private int currentHandle;
+
+ // Where we read from
+ private DataInputStream input;
+
+ // Where we read primitive types from
+ private DataInputStream primitiveTypes;
+
+ // Where we keep primitive type data
+ private InputStream primitiveData = emptyStream;
+
+ // Resolve object is a mechanism for replacement
+ private boolean enableResolve;
+
+ // Table mapping Integer (handle) -> Object
+ private Hashtable<Integer, Object> objectsRead;
+
+ // Used by defaultReadObject
+ private Object currentObject;
+
+ // Used by defaultReadObject
+ private ObjectStreamClass currentClass;
+
+ // All validations to be executed when the complete graph is read. See inner
+ // type below.
+ private InputValidationDesc[] validations;
+
+ // Allows the receiver to decide if it needs to call readObjectOverride
+ private boolean subclassOverridingImplementation;
+
+ // Original caller's class loader, used to perform class lookups
+ private ClassLoader callerClassLoader;
+
+ // false when reading missing fields
+ private boolean mustResolve = true;
+
+ // Handle for the current class descriptor
+ private Integer descriptorHandle;
+
+ // cache for readResolve methods
+ private IdentityHashMap<Class<?>, Object> readResolveCache;
+
+ private static final Hashtable<String, Class<?>> PRIMITIVE_CLASSES = new Hashtable<String, Class<?>>();
+
+ static {
+ PRIMITIVE_CLASSES.put("byte", byte.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("short", short.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("int", int.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("long", long.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("boolean", boolean.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("char", char.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("float", float.class); //$NON-NLS-1$
+ PRIMITIVE_CLASSES.put("double", double.class); //$NON-NLS-1$
+ }
+
+ // Internal type used to keep track of validators & corresponding priority
+ static class InputValidationDesc {
+ ObjectInputValidation validator;
+
+ int priority;
+ }
+
+ /**
+ * GetField is an inner class that provides access to the persistent fields
+ * read from the source stream.
+ *
+ * @since Android 1.0
+ */
+ public abstract static class GetField {
+ /**
+ * Gets the ObjectStreamClass that describes a field.
+ *
+ * @return the descriptor class for a serialized field.
+ * @since Android 1.0
+ */
+ public abstract ObjectStreamClass getObjectStreamClass();
+
+ /**
+ * Indicates if the field identified by {@code name} is defaulted. This
+ * means that it has no value in this stream.
+ *
+ * @param name
+ * the name of the field to check.
+ * @return {@code true} if the field is defaulted, {@code false}
+ * otherwise.
+ * @throws IllegalArgumentException
+ * if {@code name} does not identify a serializable field.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @since Android 1.0
+ */
+ public abstract boolean defaulted(String name) throws IOException,
+ IllegalArgumentException;
+
+ /**
+ * Gets the value of the boolean field identified by {@code name} from
+ * the persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code boolean}.
+ * @since Android 1.0
+ */
+ public abstract boolean get(String name, boolean defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the character field identified by {@code name} from
+ * the persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code char}.
+ * @since Android 1.0
+ */
+ public abstract char get(String name, char defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the byte field identified by {@code name} from the
+ * persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code byte}.
+ * @since Android 1.0
+ */
+ public abstract byte get(String name, byte defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the short field identified by {@code name} from the
+ * persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code short}.
+ * @since Android 1.0
+ */
+ public abstract short get(String name, short defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the integer field identified by {@code name} from
+ * the persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code int}.
+ * @since Android 1.0
+ */
+ public abstract int get(String name, int defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the long field identified by {@code name} from the
+ * persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code long}.
+ * @since Android 1.0
+ */
+ public abstract long get(String name, long defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the float field identified by {@code name} from the
+ * persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code float} is
+ * not {@code char}.
+ * @since Android 1.0
+ */
+ public abstract float get(String name, float defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the double field identified by {@code name} from
+ * the persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code double}.
+ * @since Android 1.0
+ */
+ public abstract double get(String name, double defaultValue)
+ throws IOException, IllegalArgumentException;
+
+ /**
+ * Gets the value of the object field identified by {@code name} from
+ * the persistent field.
+ *
+ * @param name
+ * the name of the field to get.
+ * @param defaultValue
+ * the default value that is used if the field does not have
+ * a value when read from the source stream.
+ * @return the value of the field identified by {@code name}.
+ * @throws IOException
+ * if an error occurs while reading from the source input
+ * stream.
+ * @throws IllegalArgumentException
+ * if the type of the field identified by {@code name} is
+ * not {@code Object}.
+ * @since Android 1.0
+ */
+ public abstract Object get(String name, Object defaultValue)
+ throws IOException, IllegalArgumentException;
+ }
+
+ /**
+ * Constructs a new ObjectInputStream. This default constructor can be used
+ * by subclasses that do not want to use the public constructor if it
+ * allocates unneeded data.
+ *
+ * @throws IOException
+ * if an error occurs when creating this stream.
+ * @throws SecurityException
+ * if a security manager is installed and it denies subclassing
+ * this class.
+ * @see SecurityManager#checkPermission(java.security.Permission)
+ * @since Android 1.0
+ */
+ protected ObjectInputStream() throws IOException, SecurityException {
+ super();
+ SecurityManager currentManager = System.getSecurityManager();
+ if (currentManager != null) {
+ currentManager.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
+ }
+ // WARNING - we should throw IOException if not called from a subclass
+ // according to the JavaDoc. Add the test.
+ this.subclassOverridingImplementation = true;
+ }
+
+ /**
+ * Constructs a new ObjectInputStream that reads from the InputStream
+ * {@code input}.
+ *
+ * @param input
+ * the non-null source InputStream to filter reads on.
+ * @throws IOException
+ * if an error occurs while reading the stream header.
+ * @throws StreamCorruptedException
+ * if the source stream does not contain serialized objects that
+ * can be read.
+ * @throws SecurityException
+ * if a security manager is installed and it denies subclassing
+ * this class.
+ * @since Android 1.0
+ */
+ public ObjectInputStream(InputStream input)
+ throws StreamCorruptedException, IOException {
+ final Class<?> implementationClass = getClass();
+ final Class<?> thisClass = ObjectInputStream.class;
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null && implementationClass != thisClass) {
+ boolean mustCheck = (AccessController
+ .doPrivileged(new PrivilegedAction<Boolean>() {
+ public Boolean run() {
+ try {
+ Method method = implementationClass
+ .getMethod(
+ "readFields", //$NON-NLS-1$
+ ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES);
+ if (method.getDeclaringClass() != thisClass) {
+ return Boolean.TRUE;
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ try {
+ Method method = implementationClass
+ .getMethod(
+ "readUnshared", //$NON-NLS-1$
+ ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES);
+ if (method.getDeclaringClass() != thisClass) {
+ return Boolean.TRUE;
+ }
+ } catch (NoSuchMethodException e) {
+ }
+ return Boolean.FALSE;
+ }
+ })).booleanValue();
+ if (mustCheck) {
+ sm
+ .checkPermission(ObjectStreamConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
+ }
+ }
+ this.input = (input instanceof DataInputStream) ? (DataInputStream) input
+ : new DataInputStream(input);
+ primitiveTypes = new DataInputStream(this);
+ enableResolve = false;
+ this.subclassOverridingImplementation = false;
+ this.readResolveCache = new IdentityHashMap<Class<?>, Object>();
+ resetState();
+ nestedLevels = 0;
+ // So read...() methods can be used by
+ // subclasses during readStreamHeader()
+ primitiveData = this.input;
+ // Has to be done here according to the specification
+ readStreamHeader();
+ primitiveData = emptyStream;
+ }
+
+ /**
+ * Returns the number of bytes of primitive data that can be read from this
+ * stream without blocking. This method should not be used at any arbitrary
+ * position; just when reading primitive data types (int, char etc).
+ *
+ * @return the number of available primitive data bytes.
+ * @throws IOException
+ * if any I/O problem occurs while computing the available
+ * bytes.
+ * @since Android 1.0
+ */
+ @Override
+ public int available() throws IOException {
+ // returns 0 if next data is an object, or N if reading primitive types
+ checkReadPrimitiveTypes();
+ return primitiveData.available();
+ }
+
+ /**
+ * Checks to if it is ok to read primitive types from this stream at
+ * this point. One is not supposed to read primitive types when about to
+ * read an object, for example, so an exception has to be thrown.
+ *
+ * @throws IOException
+ * If any IO problem occurred when trying to read primitive type
+ * or if it is illegal to read primitive types
+ */
+ private void checkReadPrimitiveTypes() throws IOException {
+ // If we still have primitive data, it is ok to read primitive data
+ if (primitiveData == input || primitiveData.available() > 0) {
+ return;
+ }
+
+ // If we got here either we had no Stream previously created or
+ // we no longer have data in that one, so get more bytes
+ do {
+ int next = 0;
+ if (hasPushbackTC) {
+ hasPushbackTC = false;
+ } else {
+ next = input.read();
+ pushbackTC = (byte) next;
+ }
+ switch (pushbackTC) {
+ case TC_BLOCKDATA:
+ primitiveData = new ByteArrayInputStream(readBlockData());
+ return;
+ case TC_BLOCKDATALONG:
+ primitiveData = new ByteArrayInputStream(
+ readBlockDataLong());
+ return;
+ case TC_RESET:
+ resetState();
+ break;
+ default:
+ if (next != -1) {
+ pushbackTC();
+ }
+ return;
+ }
+ // Only TC_RESET falls through
+ } while (true);
+ }
+
+ /**
+ * Closes this stream. This implementation closes the source stream.
+ *
+ * @throws IOException
+ * if an error occurs while closing this stream.
+ * @since Android 1.0
+ */
+ @Override
+ public void close() throws IOException {
+ input.close();
+ }
+
+ /**
+ * Default method to read objects from this stream. Serializable fields
+ * defined in the object's class and superclasses are read from the source
+ * stream.
+ *
+ * @throws ClassNotFoundException
+ * if the object's class cannot be found.
+ * @throws IOException
+ * if an I/O error occurs while reading the object data.
+ * @throws NotActiveException
+ * if this method is not called from {@code readObject()}.
+ * @see ObjectOutputStream#defaultWriteObject
+ * @since Android 1.0
+ */
+ public void defaultReadObject() throws IOException, ClassNotFoundException,
+ NotActiveException {
+ // We can't be called from just anywhere. There are rules.
+ if (currentObject != null || !mustResolve) {
+ readFieldValues(currentObject, currentClass);
+ } else {
+ throw new NotActiveException();
+ }
+ }
+
+ /**
+ * Enables object replacement for this stream. By default this is not
+ * enabled. Only trusted subclasses (loaded with system class loader) are
+ * allowed to change this status.
+ *
+ * @param enable
+ * {@code true} to enable object replacement; {@code false} to
+ * disable it.
+ * @return the previous setting.
+ * @throws SecurityException
+ * if a security manager is installed and it denies enabling
+ * object replacement for this stream.
+ * @see #resolveObject
+ * @see ObjectOutputStream#enableReplaceObject
+ * @since Android 1.0
+ */
+ protected boolean enableResolveObject(boolean enable)
+ throws SecurityException {
+ if (enable) {
+ // The Stream has to be trusted for this feature to be enabled.
+ // trusted means the stream's classloader has to be null
+ SecurityManager currentManager = System.getSecurityManager();
+ if (currentManager != null) {
+ currentManager.checkPermission(SUBSTITUTION_PERMISSION);
+ }
+ }
+ boolean originalValue = enableResolve;
+ enableResolve = enable;
+ return originalValue;
+ }
+
+ /**
+ * Checks if two classes belong to the same package.
+ *
+ * @param c1
+ * one of the classes to test.
+ * @param c2
+ * the other class to test.
+ * @return {@code true} if the two classes belong to the same package,
+ * {@code false} otherwise.
+ */
+ private boolean inSamePackage(Class<?> c1, Class<?> c2) {
+ String nameC1 = c1.getName();
+ String nameC2 = c2.getName();
+ int indexDotC1 = nameC1.lastIndexOf('.');
+ int indexDotC2 = nameC2.lastIndexOf('.');
+ // BEGIN android-changed
+ // copied from newer version of harmony
+ if (indexDotC1 != indexDotC2) {
+ return false; // cannot be in the same package if indices are not
+ }
+ // END android-changed
+ // the same
+ if (indexDotC1 < 0) {
+ return true; // both of them are in default package
+ }
+ return nameC1.substring(0, indexDotC1).equals(
+ nameC2.substring(0, indexDotC2));
+ }
+
+ /**
+ * Create and return a new instance of class {@code instantiationClass}
+ * but running the constructor defined in class
+ * {@code constructorClass} (same as {@code instantiationClass}
+ * or a superclass).
+ *
+ * Has to be native to avoid visibility rules and to be able to have
+ * {@code instantiationClass} not the same as
+ * {@code constructorClass} (no such API in java.lang.reflect).
+ *
+ * @param instantiationClass
+ * The new object will be an instance of this class
+ * @param constructorClass
+ * The empty constructor to run will be in this class
+ * @return the object created from {@code instantiationClass}
+ */
+ private static native Object newInstance(Class<?> instantiationClass,
+ Class<?> constructorClass);
+
+ /**
+ * Return the next {@code int} handle to be used to indicate cyclic
+ * references being loaded from the stream.
+ *
+ * @return the next handle to represent the next cyclic reference
+ */
+ private int nextHandle() {
+ return this.currentHandle++;
+ }
+
+ /**
+ * Return the next token code (TC) from the receiver, which indicates what
+ * kind of object follows
+ *
+ * @return the next TC from the receiver
+ *
+ * @throws IOException
+ * If an IO error occurs
+ *
+ * @see ObjectStreamConstants
+ */
+ private byte nextTC() throws IOException {
+ if (hasPushbackTC) {
+ hasPushbackTC = false; // We are consuming it
+ } else {
+ // Just in case a later call decides to really push it back,
+ // we don't require the caller to pass it as parameter
+ pushbackTC = input.readByte();
+ }
+ return pushbackTC;
+ }
+
+ /**
+ * Pushes back the last TC code read
+ */
+ private void pushbackTC() {
+ hasPushbackTC = true;
+ }
+
+ /**
+ * Reads a single byte from the source stream and returns it as an integer
+ * in the range from 0 to 255. Returns -1 if the end of the source stream
+ * has been reached. Blocks if no input is available.
+ *
+ * @return the byte read or -1 if the end of the source stream has been
+ * reached.
+ * @throws IOException
+ * if an error occurs while reading from this stream.
+ * @since Android 1.0
+ */
+ @Override
+ public int read() throws IOException {
+ checkReadPrimitiveTypes();
+ return primitiveData.read();
+ }
+
+ /**
+ * Reads at most {@code length} bytes from the source stream and stores them
+ * in byte array {@code buffer} starting at offset {@code count}. Blocks
+ * until {@code count} bytes have been read, the end of the source stream is
+ * detected or an exception is thrown.
+ *
+ * @param buffer
+ * the array in which to store the bytes read.
+ * @param offset
+ * the initial position in {@code buffer} to store the bytes
+ * read from the source stream.
+ * @param length
+ * the maximum number of bytes to store in {@code buffer}.
+ * @return the number of bytes read or -1 if the end of the source input
+ * stream has been reached.
+ * @throws IndexOutOfBoundsException
+ * if {@code offset < 0} or {@code length < 0}, or if
+ * {@code offset + length} is greater than the length of
+ * {@code buffer}.
+ * @throws IOException
+ * if an error occurs while reading from this stream.
+ * @throws NullPointerException
+ * if {@code buffer} is {@code null}.
+ * @since Android 1.0
+ */
+ @Override
+ public int read(byte[] buffer, int offset, int length) throws IOException {
+ // BEGIN android-changed
+ if (buffer == null) {
+ throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$
+ }
+ // avoid int overflow
+ // Exception priorities (in case of multiple errors) differ from
+ // RI, but are spec-compliant.
+ // removed redundant check, used (offset | length) < 0 instead of
+ // (offset < 0) || (length < 0) to safe one operation
+ if ((offset | length) < 0 || length > buffer.length - offset) {
+ throw new ArrayIndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$
+ }
+ // END android-changed
+ if (length == 0) {
+ return 0;
+ }
+ checkReadPrimitiveTypes();
+ return primitiveData.read(buffer, offset, length);
+ }
+
+ /**
+ * Reads and returns an array of raw bytes with primitive data. The array
+ * will have up to 255 bytes. The primitive data will be in the format
+ * described by {@code DataOutputStream}.
+ *
+ * @return The primitive data read, as raw bytes
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the primitive data.
+ */
+ private byte[] readBlockData() throws IOException {
+ byte[] result = new byte[input.readByte() & 0xff];
+ input.readFully(result);
+ return result;
+ }
+
+ /**
+ * Reads and returns an array of raw bytes with primitive data. The array
+ * will have more than 255 bytes. The primitive data will be in the format
+ * described by {@code DataOutputStream}.
+ *
+ * @return The primitive data read, as raw bytes
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the primitive data.
+ */
+ private byte[] readBlockDataLong() throws IOException {
+ byte[] result = new byte[input.readInt()];
+ input.readFully(result);
+ return result;
+ }
+
+ /**
+ * Reads a boolean from the source stream.
+ *
+ * @return the boolean value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public boolean readBoolean() throws IOException {
+ return primitiveTypes.readBoolean();
+ }
+
+ /**
+ * Reads a byte (8 bit) from the source stream.
+ *
+ * @return the byte value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public byte readByte() throws IOException {
+ return primitiveTypes.readByte();
+ }
+
+ /**
+ * Reads a character (16 bit) from the source stream.
+ *
+ * @return the char value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public char readChar() throws IOException {
+ return primitiveTypes.readChar();
+ }
+
+ /**
+ * Reads and discards block data and objects until TC_ENDBLOCKDATA is found.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the optional class
+ * annotation.
+ * @throws ClassNotFoundException
+ * If the class corresponding to the class descriptor could not
+ * be found.
+ */
+ private void discardData() throws ClassNotFoundException, IOException {
+ primitiveData = emptyStream;
+ boolean resolve = mustResolve;
+ mustResolve = false;
+ do {
+ byte tc = nextTC();
+ if (tc == TC_ENDBLOCKDATA) {
+ mustResolve = resolve;
+ return; // End of annotation
+ }
+ readContent(tc);
+ } while (true);
+ }
+
+ /**
+ * Reads a class descriptor (an {@code ObjectStreamClass}) from the
+ * stream.
+ *
+ * @return the class descriptor read from the stream
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class
+ * descriptor.
+ * @throws ClassNotFoundException
+ * If the class corresponding to the class descriptor could not
+ * be found.
+ */
+ private ObjectStreamClass readClassDesc() throws ClassNotFoundException,
+ IOException {
+ byte tc = nextTC();
+ switch (tc) {
+ case TC_CLASSDESC:
+ return readNewClassDesc(false);
+ case TC_PROXYCLASSDESC:
+ Class<?> proxyClass = readNewProxyClassDesc();
+ ObjectStreamClass streamClass = ObjectStreamClass
+ .lookup(proxyClass);
+ streamClass.setLoadFields(new ObjectStreamField[0]);
+ registerObjectRead(streamClass, Integer.valueOf(nextHandle()),
+ false);
+ checkedSetSuperClassDesc(streamClass, readClassDesc());
+ return streamClass;
+ case TC_REFERENCE:
+ return (ObjectStreamClass) readCyclicReference();
+ case TC_NULL:
+ return null;
+ default:
+ throw new StreamCorruptedException(Msg.getString(
+ "K00d2", Integer.toHexString(tc & 0xff))); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Reads the content of the receiver based on the previously read token
+ * {@code tc}.
+ *
+ * @param tc
+ * The token code for the next item in the stream
+ * @return the object read from the stream
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class
+ * descriptor.
+ * @throws ClassNotFoundException
+ * If the class corresponding to the object being read could not
+ * be found.
+ */
+ private Object readContent(byte tc) throws ClassNotFoundException,
+ IOException {
+ switch (tc) {
+ case TC_BLOCKDATA:
+ return readBlockData();
+ case TC_BLOCKDATALONG:
+ return readBlockDataLong();
+ case TC_CLASS:
+ return readNewClass(false);
+ case TC_CLASSDESC:
+ return readNewClassDesc(false);
+ case TC_ARRAY:
+ return readNewArray(false);
+ case TC_OBJECT:
+ return readNewObject(false);
+ case TC_STRING:
+ return readNewString(false);
+ case TC_LONGSTRING:
+ return readNewLongString(false);
+ case TC_REFERENCE:
+ return readCyclicReference();
+ case TC_NULL:
+ return null;
+ case TC_EXCEPTION:
+ Exception exc = readException();
+ throw new WriteAbortedException(Msg.getString("K00d3"), exc); //$NON-NLS-1$
+ case TC_RESET:
+ resetState();
+ return null;
+ default:
+ throw new StreamCorruptedException(Msg.getString(
+ "K00d2", Integer.toHexString(tc & 0xff))); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Reads the content of the receiver based on the previously read token
+ * {@code tc}. Primitive data content is considered an error.
+ *
+ * @param unshared
+ * read the object unshared
+ * @return the object read from the stream
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class
+ * descriptor.
+ * @throws ClassNotFoundException
+ * If the class corresponding to the object being read could not
+ * be found.
+ */
+ private Object readNonPrimitiveContent(boolean unshared)
+ throws ClassNotFoundException, IOException {
+ checkReadPrimitiveTypes();
+ if (primitiveData.available() > 0) {
+ OptionalDataException e = new OptionalDataException();
+ e.length = primitiveData.available();
+ throw e;
+ }
+
+ do {
+ byte tc = nextTC();
+ switch (tc) {
+ case TC_CLASS:
+ return readNewClass(unshared);
+ case TC_CLASSDESC:
+ return readNewClassDesc(unshared);
+ case TC_ARRAY:
+ return readNewArray(unshared);
+ case TC_OBJECT:
+ return readNewObject(unshared);
+ case TC_STRING:
+ return readNewString(unshared);
+ case TC_LONGSTRING:
+ return readNewLongString(unshared);
+ case TC_ENUM:
+ return readEnum(unshared);
+ case TC_REFERENCE:
+ if (unshared) {
+ readNewHandle();
+ throw new InvalidObjectException(Msg.getString("KA002")); //$NON-NLS-1$
+ }
+ return readCyclicReference();
+ case TC_NULL:
+ return null;
+ case TC_EXCEPTION:
+ Exception exc = readException();
+ throw new WriteAbortedException(Msg.getString("K00d3"), exc); //$NON-NLS-1$
+ case TC_RESET:
+ resetState();
+ break;
+ case TC_ENDBLOCKDATA: // Can occur reading class annotation
+ pushbackTC();
+ OptionalDataException e = new OptionalDataException();
+ e.eof = true;
+ throw e;
+ default:
+ throw new StreamCorruptedException(Msg.getString(
+ "K00d2", Integer.toHexString(tc & 0xff))); //$NON-NLS-1$
+ }
+ // Only TC_RESET falls through
+ } while (true);
+ }
+
+ /**
+ * Reads the next item from the stream assuming it is a cyclic reference to
+ * an object previously read. Return the actual object previously read.
+ *
+ * @return the object previously read from the stream
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class
+ * descriptor.
+ * @throws InvalidObjectException
+ * If the cyclic reference is not valid.
+ */
+ private Object readCyclicReference() throws InvalidObjectException,
+ IOException {
+ return registeredObjectRead(readNewHandle());
+ }
+
+ /**
+ * Reads a double (64 bit) from the source stream.
+ *
+ * @return the double value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public double readDouble() throws IOException {
+ return primitiveTypes.readDouble();
+ }
+
+ /**
+ * Read the next item assuming it is an exception. The exception is not a
+ * regular instance in the object graph, but the exception instance that
+ * happened (if any) when dumping the original object graph. The set of seen
+ * objects will be reset just before and just after loading this exception
+ * object.
+ * <p>
+ * When exceptions are found normally in the object graph, they are loaded
+ * as a regular object, and not by this method. In that case, the set of
+ * "known objects" is not reset.
+ *
+ * @return the exception read
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the exception
+ * object.
+ * @throws ClassNotFoundException
+ * If a class could not be found when reading the object graph
+ * for the exception
+ * @throws OptionalDataException
+ * If optional data could not be found when reading the
+ * exception graph
+ * @throws WriteAbortedException
+ * If another exception was caused when dumping this exception
+ */
+ private Exception readException() throws WriteAbortedException,
+ OptionalDataException, ClassNotFoundException, IOException {
+
+ resetSeenObjects();
+
+ // Now we read the Throwable object that was saved
+ // WARNING - the grammar says it is a Throwable, but the
+ // WriteAbortedException constructor takes an Exception. So, we read an
+ // Exception from the stream
+ Exception exc = (Exception) readObject();
+
+ // We reset the receiver's state (the grammar has "reset" in normal
+ // font)
+ resetSeenObjects();
+ return exc;
+ }
+
+ /**
+ * Reads a collection of field descriptors (name, type name, etc) for the
+ * class descriptor {@code cDesc} (an {@code ObjectStreamClass})
+ *
+ * @param cDesc
+ * The class descriptor (an {@code ObjectStreamClass})
+ * for which to write field information
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the field
+ * descriptors.
+ * @throws ClassNotFoundException
+ * If a class for one of the field types could not be found
+ *
+ * @see #readObject()
+ */
+ private void readFieldDescriptors(ObjectStreamClass cDesc)
+ throws ClassNotFoundException, IOException {
+ short numFields = input.readShort();
+ ObjectStreamField[] fields = new ObjectStreamField[numFields];
+
+ // We set it now, but each element will be inserted in the array further
+ // down
+ cDesc.setLoadFields(fields);
+
+ // Check ObjectOutputStream.writeFieldDescriptors
+ for (short i = 0; i < numFields; i++) {
+ char typecode = (char) input.readByte();
+ String fieldName = input.readUTF();
+ boolean isPrimType = ObjectStreamClass.isPrimitiveType(typecode);
+ String classSig;
+ if (isPrimType) {
+ classSig = String.valueOf(typecode);
+ } else {
+ // The spec says it is a UTF, but experience shows they dump
+ // this String using writeObject (unlike the field name, which
+ // is saved with writeUTF).
+ // And if resolveObject is enabled, the classSig may be modified
+ // so that the original class descriptor cannot be read
+ // properly, so it is disabled.
+ boolean old = enableResolve;
+ try {
+ enableResolve = false;
+ classSig = (String) readObject();
+ } finally {
+ enableResolve = old;
+ }
+ }
+ ObjectStreamField f = new ObjectStreamField(classSig, fieldName);
+ fields[i] = f;
+ }
+ }
+
+ /**
+ * Reads the persistent fields of the object that is currently being read
+ * from the source stream. The values read are stored in a GetField object
+ * that provides access to the persistent fields. This GetField object is
+ * then returned.
+ *
+ * @return the GetField object from which persistent fields can be accessed
+ * by name.
+ * @throws ClassNotFoundException
+ * if the class of an object being deserialized can not be
+ * found.
+ * @throws IOException
+ * if an error occurs while reading from this stream.
+ * @throws NotActiveException
+ * if this stream is currently not reading an object.
+ * @since Android 1.0
+ */
+ public GetField readFields() throws IOException, ClassNotFoundException,
+ NotActiveException {
+ // We can't be called from just anywhere. There are rules.
+ if (currentObject == null) {
+ throw new NotActiveException();
+ }
+ EmulatedFieldsForLoading result = new EmulatedFieldsForLoading(
+ currentClass);
+ readFieldValues(result);
+ return result;
+ }
+
+ /**
+ * Reads a collection of field values for the emulated fields
+ * {@code emulatedFields}
+ *
+ * @param emulatedFields
+ * an {@code EmulatedFieldsForLoading}, concrete subclass
+ * of {@code GetField}
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the field values.
+ * @throws InvalidClassException
+ * If an incompatible type is being assigned to an emulated
+ * field.
+ * @throws OptionalDataException
+ * If optional data could not be found when reading the
+ * exception graph
+ *
+ * @see #readFields
+ * @see #readObject()
+ */
+ private void readFieldValues(EmulatedFieldsForLoading emulatedFields)
+ throws OptionalDataException, InvalidClassException, IOException {
+ EmulatedFields.ObjectSlot[] slots = emulatedFields.emulatedFields()
+ .slots();
+ for (ObjectSlot element : slots) {
+ element.defaulted = false;
+ Class<?> type = element.field.getType();
+ if (type == Integer.TYPE) {
+ element.fieldValue = Integer.valueOf(input.readInt());
+ } else if (type == Byte.TYPE) {
+ element.fieldValue = Byte.valueOf(input.readByte());
+ } else if (type == Character.TYPE) {
+ element.fieldValue = Character.valueOf(input.readChar());
+ } else if (type == Short.TYPE) {
+ element.fieldValue = Short.valueOf(input.readShort());
+ } else if (type == Boolean.TYPE) {
+ element.fieldValue = Boolean.valueOf(input.readBoolean());
+ } else if (type == Long.TYPE) {
+ element.fieldValue = Long.valueOf(input.readLong());
+ } else if (type == Float.TYPE) {
+ element.fieldValue = Float.valueOf(input.readFloat());
+ } else if (type == Double.TYPE) {
+ element.fieldValue = Double.valueOf(input.readDouble());
+ } else {
+ // Either array or Object
+ try {
+ element.fieldValue = readObject();
+ } catch (ClassNotFoundException cnf) {
+ // WARNING- Not sure this is the right thing to do. Write
+ // test case.
+ throw new InvalidClassException(cnf.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads a collection of field values for the class descriptor
+ * {@code classDesc} (an {@code ObjectStreamClass}). The
+ * values will be used to set instance fields in object {@code obj}.
+ * This is the default mechanism, when emulated fields (an
+ * {@code GetField}) are not used. Actual values to load are stored
+ * directly into the object {@code obj}.
+ *
+ * @param obj
+ * Instance in which the fields will be set.
+ * @param classDesc
+ * A class descriptor (an {@code ObjectStreamClass})
+ * defining which fields should be loaded.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the field values.
+ * @throws InvalidClassException
+ * If an incompatible type is being assigned to an emulated
+ * field.
+ * @throws OptionalDataException
+ * If optional data could not be found when reading the
+ * exception graph
+ * @throws ClassNotFoundException
+ * If a class of an object being de-serialized can not be found
+ *
+ * @see #readFields
+ * @see #readObject()
+ */
+ private void readFieldValues(Object obj, ObjectStreamClass classDesc)
+ throws OptionalDataException, ClassNotFoundException, IOException {
+ // Now we must read all fields and assign them to the receiver
+ ObjectStreamField[] fields = classDesc.getLoadFields();
+ fields = (null == fields ? new ObjectStreamField[] {} : fields);
+ Class<?> declaringClass = classDesc.forClass();
+ if (declaringClass == null && mustResolve) {
+ throw new ClassNotFoundException(classDesc.getName());
+ }
+
+ for (ObjectStreamField fieldDesc : fields) {
+ // Code duplication starts, just because Java is typed
+ if (fieldDesc.isPrimitive()) {
+ try {
+ switch (fieldDesc.getTypeCode()) {
+ case 'B':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readByte());
+ break;
+ case 'C':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readChar());
+ break;
+ case 'D':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readDouble());
+ break;
+ case 'F':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readFloat());
+ break;
+ case 'I':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readInt());
+ break;
+ case 'J':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readLong());
+ break;
+ case 'S':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readShort());
+ break;
+ case 'Z':
+ setField(obj, declaringClass, fieldDesc.getName(),
+ input.readBoolean());
+ break;
+ default:
+ throw new StreamCorruptedException(Msg.getString(
+ "K00d5", fieldDesc.getTypeCode())); //$NON-NLS-1$
+ }
+ } catch (NoSuchFieldError err) {
+ }
+ } else {
+ // Object type (array included).
+ String fieldName = fieldDesc.getName();
+ boolean setBack = false;
+ ObjectStreamField field = classDesc.getField(fieldName);
+ if (mustResolve && field == null) {
+ setBack = true;
+ mustResolve = false;
+ }
+ Object toSet;
+ if (field != null && field.isUnshared()) {
+ toSet = readUnshared();
+ } else {
+ toSet = readObject();
+ }
+ if (setBack) {
+ mustResolve = true;
+ }
+ if (field != null) {
+ if (toSet != null) {
+ // BEGIN android-removed
+ // Class<?> fieldType = field.getType();
+ // END android-removed
+ // BEGIN android-added
+ // Originally getTypeInternal() was called getType().
+ // After the semantics of getType() changed inside
+ // Harmony, the check below wasn't adjusted and didn't
+ // work anymore.
+ Class<?> fieldType = field.getTypeInternal();
+ // END android-added
+ Class<?> valueType = toSet.getClass();
+ if (!fieldType.isAssignableFrom(valueType)) {
+ throw new ClassCastException(Msg.getString(
+ "K00d4", new String[] { //$NON-NLS-1$
+ fieldType.toString(), valueType.toString(),
+ classDesc.getName() + "." //$NON-NLS-1$
+ + fieldName }));
+ }
+ try {
+ objSetField(obj, declaringClass, fieldName, field
+ .getTypeString(), toSet);
+ } catch (NoSuchFieldError e) {
+ // Ignored
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Reads a float (32 bit) from the source stream.
+ *
+ * @return the float value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public float readFloat() throws IOException {
+ return primitiveTypes.readFloat();
+ }
+
+ /**
+ * Reads bytes from the source stream into the byte array {@code buffer}.
+ * This method will block until {@code buffer.length} bytes have been read.
+ *
+ * @param buffer
+ * the array in which to store the bytes read.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public void readFully(byte[] buffer) throws IOException {
+ primitiveTypes.readFully(buffer);
+ }
+
+ /**
+ * Reads bytes from the source stream into the byte array {@code buffer}.
+ * This method will block until {@code length} number of bytes have been
+ * read.
+ *
+ * @param buffer
+ * the byte array in which to store the bytes read.
+ * @param offset
+ * the initial position in {@code buffer} to store the bytes
+ * read from the source stream.
+ * @param length
+ * the maximum number of bytes to store in {@code buffer}.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public void readFully(byte[] buffer, int offset, int length)
+ throws IOException {
+ primitiveTypes.readFully(buffer, offset, length);
+ }
+
+ /**
+ * Walks the hierarchy of classes described by class descriptor
+ * {@code classDesc} and reads the field values corresponding to
+ * fields declared by the corresponding class descriptor. The instance to
+ * store field values into is {@code object}. If the class
+ * (corresponding to class descriptor {@code classDesc}) defines
+ * private instance method {@code readObject} it will be used to load
+ * field values.
+ *
+ * @param object
+ * Instance into which stored field values loaded.
+ * @param classDesc
+ * A class descriptor (an {@code ObjectStreamClass})
+ * defining which fields should be loaded.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the field values in
+ * the hierarchy.
+ * @throws ClassNotFoundException
+ * If a class for one of the field types could not be found
+ * @throws NotActiveException
+ * If {@code defaultReadObject} is called from the wrong
+ * context.
+ *
+ * @see #defaultReadObject
+ * @see #readObject()
+ */
+ private void readHierarchy(Object object, ObjectStreamClass classDesc)
+ throws IOException, ClassNotFoundException, NotActiveException {
+ // We can't be called from just anywhere. There are rules.
+ if (object == null && mustResolve) {
+ throw new NotActiveException();
+ }
+
+ ArrayList<ObjectStreamClass> streamClassList = new ArrayList<ObjectStreamClass>(
+ 32);
+ ObjectStreamClass nextStreamClass = classDesc;
+ while (nextStreamClass != null) {
+ streamClassList.add(0, nextStreamClass);
+ nextStreamClass = nextStreamClass.getSuperclass();
+ }
+ if (object == null) {
+ Iterator<ObjectStreamClass> streamIt = streamClassList.iterator();
+ while (streamIt.hasNext()) {
+ ObjectStreamClass streamClass = streamIt.next();
+ readObjectForClass(null, streamClass);
+ }
+ } else {
+ ArrayList<Class<?>> classList = new ArrayList<Class<?>>(32);
+ Class<?> nextClass = object.getClass();
+ while (nextClass != null) {
+ Class<?> testClass = nextClass.getSuperclass();
+ if (testClass != null) {
+ classList.add(0, nextClass);
+ }
+ nextClass = testClass;
+ }
+ int lastIndex = 0;
+ for (int i = 0; i < classList.size(); i++) {
+ Class<?> superclass = classList.get(i);
+ int index = findStreamSuperclass(superclass, streamClassList,
+ lastIndex);
+ if (index == -1) {
+ readObjectNoData(object, superclass);
+ } else {
+ for (int j = lastIndex; j <= index; j++) {
+ readObjectForClass(object, streamClassList.get(j));
+ }
+ lastIndex = index + 1;
+ }
+ }
+ }
+ }
+
+ private int findStreamSuperclass(Class<?> cl,
+ ArrayList<ObjectStreamClass> classList, int lastIndex) {
+ ObjectStreamClass objCl;
+ String forName;
+
+ for (int i = lastIndex; i < classList.size(); i++) {
+ objCl = classList.get(i);
+ forName = objCl.forClass().getName();
+
+ if (objCl.getName().equals(forName)) {
+ if (cl.getName().equals(objCl.getName())) {
+ return i;
+ }
+ } else {
+ // there was a class replacement
+ if (cl.getName().equals(forName)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ private void readObjectNoData(Object object, Class<?> cl)
+ throws ObjectStreamException {
+ if (!ObjectStreamClass.isSerializable(cl)) {
+ return;
+ }
+
+ final Method readMethod = ObjectStreamClass
+ .getPrivateReadObjectNoDataMethod(cl);
+ if (readMethod != null) {
+ AccessController.doPrivileged(new PriviAction<Object>(readMethod));
+ try {
+ readMethod.invoke(object, new Object[0]);
+ } catch (InvocationTargetException e) {
+ Throwable ex = e.getTargetException();
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ } else if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ throw (ObjectStreamException) ex;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ }
+
+ private void readObjectForClass(Object object, ObjectStreamClass classDesc)
+ throws IOException, ClassNotFoundException, NotActiveException {
+ // Have to do this before calling defaultReadObject or anything that
+ // calls defaultReadObject
+ currentObject = object;
+ currentClass = classDesc;
+
+ boolean hadWriteMethod = (classDesc.getFlags() & SC_WRITE_METHOD) > 0;
+ Class<?> targetClass = classDesc.forClass();
+ final Method readMethod;
+ if (targetClass == null || !mustResolve) {
+ readMethod = null;
+ } else {
+ readMethod = ObjectStreamClass
+ .getPrivateReadObjectMethod(targetClass);
+ }
+ try {
+ if (readMethod != null) {
+ // We have to be able to fetch its value, even if it is private
+ AccessController.doPrivileged(new PriviAction<Object>(
+ readMethod));
+ try {
+ readMethod.invoke(object, new Object[] { this });
+ } catch (InvocationTargetException e) {
+ Throwable ex = e.getTargetException();
+ if (ex instanceof ClassNotFoundException) {
+ throw (ClassNotFoundException) ex;
+ } else if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ } else if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ throw (IOException) ex;
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e.toString());
+ }
+ } else {
+ defaultReadObject();
+ }
+ if (hadWriteMethod) {
+ discardData();
+ }
+ } finally {
+ // Cleanup, needs to run always so that we can later detect invalid
+ // calls to defaultReadObject
+ currentObject = null; // We did not set this, so we do not need to
+ // clean it
+ currentClass = null;
+ }
+ }
+
+ /**
+ * Reads an integer (32 bit) from the source stream.
+ *
+ * @return the integer value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public int readInt() throws IOException {
+ return primitiveTypes.readInt();
+ }
+
+ /**
+ * Reads the next line from the source stream. Lines are terminated by
+ * {@code '\r'}, {@code '\n'}, {@code "\r\n"} or an {@code EOF}.
+ *
+ * @return the string read from the source stream.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @deprecated Use {@link BufferedReader}
+ * @since Android 1.0
+ */
+ @SuppressWarnings("deprecation")
+ @Deprecated
+ public String readLine() throws IOException {
+ return primitiveTypes.readLine();
+ }
+
+ /**
+ * Reads a long (64 bit) from the source stream.
+ *
+ * @return the long value read from the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public long readLong() throws IOException {
+ return primitiveTypes.readLong();
+ }
+
+ /**
+ * Read a new array from the receiver. It is assumed the array has not been
+ * read yet (not a cyclic reference). Return the array read.
+ *
+ * @param unshared
+ * read the object unshared
+ * @return the array read
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the array.
+ * @throws ClassNotFoundException
+ * If a class for one of the objects could not be found
+ * @throws OptionalDataException
+ * If optional data could not be found when reading the array.
+ */
+ private Object readNewArray(boolean unshared) throws OptionalDataException,
+ ClassNotFoundException, IOException {
+ ObjectStreamClass classDesc = readClassDesc();
+
+ if (classDesc == null) {
+ throw new InvalidClassException(Msg.getString("K00d1")); //$NON-NLS-1$
+ }
+
+ Integer newHandle = Integer.valueOf(nextHandle());
+
+ // Array size
+ int size = input.readInt();
+ Class<?> arrayClass = classDesc.forClass();
+ Class<?> componentType = arrayClass.getComponentType();
+ Object result = Array.newInstance(componentType, size);
+
+ registerObjectRead(result, newHandle, unshared);
+
+ // Now we have code duplication just because Java is typed. We have to
+ // read N elements and assign to array positions, but we must typecast
+ // the array first, and also call different methods depending on the
+ // elements.
+ if (componentType.isPrimitive()) {
+ if (componentType == Integer.TYPE) {
+ int[] intArray = (int[]) result;
+ for (int i = 0; i < size; i++) {
+ intArray[i] = input.readInt();
+ }
+ } else if (componentType == Byte.TYPE) {
+ byte[] byteArray = (byte[]) result;
+ input.readFully(byteArray, 0, size);
+ } else if (componentType == Character.TYPE) {
+ char[] charArray = (char[]) result;
+ for (int i = 0; i < size; i++) {
+ charArray[i] = input.readChar();
+ }
+ } else if (componentType == Short.TYPE) {
+ short[] shortArray = (short[]) result;
+ for (int i = 0; i < size; i++) {
+ shortArray[i] = input.readShort();
+ }
+ } else if (componentType == Boolean.TYPE) {
+ boolean[] booleanArray = (boolean[]) result;
+ for (int i = 0; i < size; i++) {
+ booleanArray[i] = input.readBoolean();
+ }
+ } else if (componentType == Long.TYPE) {
+ long[] longArray = (long[]) result;
+ for (int i = 0; i < size; i++) {
+ longArray[i] = input.readLong();
+ }
+ } else if (componentType == Float.TYPE) {
+ float[] floatArray = (float[]) result;
+ for (int i = 0; i < size; i++) {
+ floatArray[i] = input.readFloat();
+ }
+ } else if (componentType == Double.TYPE) {
+ double[] doubleArray = (double[]) result;
+ for (int i = 0; i < size; i++) {
+ doubleArray[i] = input.readDouble();
+ }
+ } else {
+ throw new ClassNotFoundException(Msg.getString(
+ "K00d7", classDesc.getName())); //$NON-NLS-1$
+ }
+ } else {
+ // Array of Objects
+ Object[] objectArray = (Object[]) result;
+ for (int i = 0; i < size; i++) {
+ objectArray[i] = readObject();
+ }
+ }
+ if (enableResolve) {
+ result = resolveObject(result);
+ registerObjectRead(result, newHandle, false);
+ }
+ return result;
+ }
+
+ /**
+ * Reads a new class from the receiver. It is assumed the class has not been
+ * read yet (not a cyclic reference). Return the class read.
+ *
+ * @param unshared
+ * read the object unshared
+ * @return The {@code java.lang.Class} read from the stream.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class.
+ * @throws ClassNotFoundException
+ * If a class for one of the objects could not be found
+ */
+ private Class<?> readNewClass(boolean unshared)
+ throws ClassNotFoundException, IOException {
+ ObjectStreamClass classDesc = readClassDesc();
+
+ if (classDesc != null) {
+ Integer newHandle = Integer.valueOf(nextHandle());
+ Class<?> localClass = classDesc.forClass();
+ if (localClass != null) {
+ registerObjectRead(localClass, newHandle, unshared);
+ }
+ return localClass;
+ }
+ throw new InvalidClassException(Msg.getString("K00d1")); //$NON-NLS-1$
+ }
+
+ /*
+ * read class type for Enum, note there's difference between enum and normal
+ * classes
+ */
+ private ObjectStreamClass readEnumDesc() throws IOException,
+ ClassNotFoundException {
+ byte tc = nextTC();
+ switch (tc) {
+ case TC_CLASSDESC:
+ return readEnumDescInternal();
+ case TC_REFERENCE:
+ return (ObjectStreamClass) readCyclicReference();
+ case TC_NULL:
+ return null;
+ default:
+ throw new StreamCorruptedException(Msg.getString(
+ "K00d2", Integer.toHexString(tc & 0xff))); //$NON-NLS-1$
+ }
+ }
+
+ private ObjectStreamClass readEnumDescInternal() throws IOException,
+ ClassNotFoundException {
+ ObjectStreamClass classDesc;
+ primitiveData = input;
+ Integer oldHandle = descriptorHandle;
+ descriptorHandle = Integer.valueOf(nextHandle());
+ classDesc = readClassDescriptor();
+ if (descriptorHandle != null) {
+ registerObjectRead(classDesc, descriptorHandle, false);
+ }
+ descriptorHandle = oldHandle;
+ primitiveData = emptyStream;
+ classDesc.setClass(resolveClass(classDesc));
+ // Consume unread class annotation data and TC_ENDBLOCKDATA
+ discardData();
+ ObjectStreamClass superClass = readClassDesc();
+ checkedSetSuperClassDesc(classDesc, superClass);
+ // Check SUIDs, note all SUID for Enum is 0L
+ if (0L != classDesc.getSerialVersionUID()
+ || 0L != superClass.getSerialVersionUID()) {
+ throw new InvalidClassException(superClass.getName(), Msg
+ .getString("K00da", superClass, //$NON-NLS-1$
+ superClass));
+ }
+ byte tc = nextTC();
+ // discard TC_ENDBLOCKDATA after classDesc if any
+ if (tc == TC_ENDBLOCKDATA) {
+ // read next parent class. For enum, it may be null
+ superClass.setSuperclass(readClassDesc());
+ } else {
+ // not TC_ENDBLOCKDATA, push back for next read
+ pushbackTC();
+ }
+ return classDesc;
+ }
+
+ @SuppressWarnings("unchecked")// For the Enum.valueOf call
+ private Object readEnum(boolean unshared) throws OptionalDataException,
+ ClassNotFoundException, IOException {
+ // read classdesc for Enum first
+ ObjectStreamClass classDesc = readEnumDesc();
+ Integer newHandle = Integer.valueOf(nextHandle());
+ // read name after class desc
+ String name;
+ byte tc = nextTC();
+ switch (tc) {
+ case TC_REFERENCE:
+ if (unshared) {
+ readNewHandle();
+ throw new InvalidObjectException(Msg.getString("KA002")); //$NON-NLS-1$
+ }
+ name = (String) readCyclicReference();
+ break;
+ case TC_STRING:
+ name = (String) readNewString(unshared);
+ break;
+ default:
+ throw new StreamCorruptedException(Msg.getString("K00d2"));//$NON-NLS-1$
+ }
+
+ Enum<?> result = Enum.valueOf((Class) classDesc.forClass(), name);
+ registerObjectRead(result, newHandle, unshared);
+
+ return result;
+ }
+
+ /**
+ * Reads a new class descriptor from the receiver. It is assumed the class
+ * descriptor has not been read yet (not a cyclic reference). Return the
+ * class descriptor read.
+ *
+ * @param unshared
+ * read the object unshared
+ * @return The {@code ObjectStreamClass} read from the stream.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class
+ * descriptor.
+ * @throws ClassNotFoundException
+ * If a class for one of the objects could not be found
+ */
+ private ObjectStreamClass readNewClassDesc(boolean unshared)
+ throws ClassNotFoundException, IOException {
+ // So read...() methods can be used by
+ // subclasses during readClassDescriptor()
+ primitiveData = input;
+ Integer oldHandle = descriptorHandle;
+ descriptorHandle = Integer.valueOf(nextHandle());
+ ObjectStreamClass newClassDesc = readClassDescriptor();
+ if (descriptorHandle != null) {
+ registerObjectRead(newClassDesc, descriptorHandle, unshared);
+ }
+ descriptorHandle = oldHandle;
+ primitiveData = emptyStream;
+
+ // We need to map classDesc to class.
+ try {
+ newClassDesc.setClass(resolveClass(newClassDesc));
+ // Check SUIDs
+ verifySUID(newClassDesc);
+ // Check base name of the class
+ verifyBaseName(newClassDesc);
+ } catch (ClassNotFoundException e) {
+ if (mustResolve) {
+ throw e;
+ // Just continue, the class may not be required
+ }
+ }
+
+ // Resolve the field signatures using the class loader of the
+ // resolved class
+ ObjectStreamField[] fields = newClassDesc.getLoadFields();
+ fields = (null == fields ? new ObjectStreamField[] {} : fields);
+ ClassLoader loader = newClassDesc.forClass() == null ? callerClassLoader
+ : newClassDesc.forClass().getClassLoader();
+ for (ObjectStreamField element : fields) {
+ element.resolve(loader);
+ }
+
+ // Consume unread class annotation data and TC_ENDBLOCKDATA
+ discardData();
+ checkedSetSuperClassDesc(newClassDesc, readClassDesc());
+ return newClassDesc;
+ }
+
+ /**
+ * Reads a new proxy class descriptor from the receiver. It is assumed the
+ * proxy class descriptor has not been read yet (not a cyclic reference).
+ * Return the proxy class descriptor read.
+ *
+ * @return The {@code Class} read from the stream.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the class
+ * descriptor.
+ * @throws ClassNotFoundException
+ * If a class for one of the objects could not be found
+ */
+ private Class<?> readNewProxyClassDesc() throws ClassNotFoundException,
+ IOException {
+ int count = input.readInt();
+ String[] interfaceNames = new String[count];
+ for (int i = 0; i < count; i++) {
+ interfaceNames[i] = input.readUTF();
+ }
+ Class<?> proxy = resolveProxyClass(interfaceNames);
+ // Consume unread class annotation data and TC_ENDBLOCKDATA
+ discardData();
+ return proxy;
+ }
+
+ /**
+ * Reads a class descriptor from the source stream.
+ *
+ * @return the class descriptor read from the source stream.
+ * @throws ClassNotFoundException
+ * if a class for one of the objects cannot be found.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ protected ObjectStreamClass readClassDescriptor() throws IOException,
+ ClassNotFoundException {
+
+ ObjectStreamClass newClassDesc = new ObjectStreamClass();
+ String name = input.readUTF();
+ if ("".equals(name)) {
+ throw new IOException("The stream is corrupted.");
+ }
+ newClassDesc.setName(name);
+ newClassDesc.setSerialVersionUID(input.readLong());
+ newClassDesc.setFlags(input.readByte());
+
+ // We must register the class descriptor before reading field
+ // descriptors.
+ // if called outside of readObject, the descriptorHandle might be null
+ descriptorHandle = (null == descriptorHandle ? Integer
+ .valueOf(nextHandle()) : descriptorHandle);
+ registerObjectRead(newClassDesc, descriptorHandle, false);
+ descriptorHandle = null;
+
+ readFieldDescriptors(newClassDesc);
+ return newClassDesc;
+ }
+
+ /**
+ * Creates the proxy class that implements the interfaces specified in
+ * {@code interfaceNames}.
+ *
+ * @param interfaceNames
+ * the interfaces used to create the proxy class.
+ * @return the proxy class.
+ * @throws ClassNotFoundException
+ * if the proxy class or any of the specified interfaces cannot
+ * be created.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @see ObjectOutputStream#annotateProxyClass(Class)
+ * @since Android 1.0
+ */
+ protected Class<?> resolveProxyClass(String[] interfaceNames)
+ throws IOException, ClassNotFoundException {
+ // BEGIN android-removed
+ // ClassLoader loader = VM.getNonBootstrapClassLoader();
+ // END android-removed
+ // BEGIN android-added
+ ClassLoader loader = ClassLoader.getSystemClassLoader();
+ // END android-added
+ Class<?>[] interfaces = new Class<?>[interfaceNames.length];
+ for (int i = 0; i < interfaceNames.length; i++) {
+ interfaces[i] = Class.forName(interfaceNames[i], false, loader);
+ }
+ try {
+ return Proxy.getProxyClass(loader, interfaces);
+ } catch (IllegalArgumentException e) {
+ throw new ClassNotFoundException(e.toString(), e);
+ }
+ }
+
+ /**
+ * Write a new handle describing a cyclic reference from the stream.
+ *
+ * @return the handle read
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the handle
+ */
+ private Integer readNewHandle() throws IOException {
+ return Integer.valueOf(input.readInt());
+ }
+
+ /**
+ * Read a new object from the stream. It is assumed the object has not been
+ * loaded yet (not a cyclic reference). Return the object read.
+ *
+ * If the object implements {@code Externalizable} its
+ * {@code readExternal} is called. Otherwise, all fields described by
+ * the class hierarchy are loaded. Each class can define how its declared
+ * instance fields are loaded by defining a private method
+ * {@code readObject}
+ *
+ * @param unshared
+ * read the object unshared
+ * @return the object read
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the object.
+ * @throws OptionalDataException
+ * If optional data could not be found when reading the object
+ * graph
+ * @throws ClassNotFoundException
+ * If a class for one of the objects could not be found
+ */
+ private Object readNewObject(boolean unshared)
+ throws OptionalDataException, ClassNotFoundException, IOException {
+ ObjectStreamClass classDesc = readClassDesc();
+
+ if (classDesc == null) {
+ throw new InvalidClassException(Msg.getString("K00d1")); //$NON-NLS-1$
+ }
+
+ Integer newHandle = Integer.valueOf(nextHandle());
+
+ // Note that these values come from the Stream, and in fact it could be
+ // that the classes have been changed so that the info below now
+ // conflicts with the newer class
+ boolean wasExternalizable = (classDesc.getFlags() & SC_EXTERNALIZABLE) > 0;
+ boolean wasSerializable = (classDesc.getFlags() & SC_SERIALIZABLE) > 0;
+
+ // Maybe we should cache the values above in classDesc ? It may be the
+ // case that when reading classDesc we may need to read more stuff
+ // depending on the values above
+ Class<?> objectClass = classDesc.forClass();
+
+ Object result, registeredResult = null;
+ if (objectClass != null) {
+ // The class of the instance may not be the same as the class of the
+ // constructor to run
+ // This is the constructor to run if Externalizable
+ Class<?> constructorClass = objectClass;
+
+ // WARNING - What if the object is serializable and externalizable ?
+ // Is that possible ?
+ if (wasSerializable) {
+ // Now we must run the constructor of the class just above the
+ // one that implements Serializable so that slots that were not
+ // dumped can be initialized properly
+ while (constructorClass != null
+ && ObjectStreamClass.isSerializable(constructorClass)) {
+ constructorClass = constructorClass.getSuperclass();
+ }
+ }
+
+ // Fetch the empty constructor, or null if none.
+ Constructor<?> constructor = null;
+ if (constructorClass != null) {
+ try {
+ constructor = constructorClass
+ .getDeclaredConstructor(ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES);
+ } catch (NoSuchMethodException nsmEx) {
+ // Ignored
+ }
+ }
+
+ // Has to have an empty constructor
+ if (constructor == null) {
+ throw new InvalidClassException(constructorClass.getName(), Msg
+ .getString("K00dc")); //$NON-NLS-1$
+ }
+
+ int constructorModifiers = constructor.getModifiers();
+
+ // Now we must check if the empty constructor is visible to the
+ // instantiation class
+ if (Modifier.isPrivate(constructorModifiers)
+ || (wasExternalizable && !Modifier
+ .isPublic(constructorModifiers))) {
+ throw new InvalidClassException(constructorClass.getName(), Msg
+ .getString("K00dc")); //$NON-NLS-1$
+ }
+
+ // We know we are testing from a subclass, so the only other case
+ // where the visibility is not allowed is when the constructor has
+ // default visibility and the instantiation class is in a different
+ // package than the constructor class
+ if (!Modifier.isPublic(constructorModifiers)
+ && !Modifier.isProtected(constructorModifiers)) {
+ // Not public, not private and not protected...means default
+ // visibility. Check if same package
+ if (!inSamePackage(constructorClass, objectClass)) {
+ throw new InvalidClassException(constructorClass.getName(),
+ Msg.getString("K00dc")); //$NON-NLS-1$
+ }
+ }
+
+ // Now we know which class to instantiate and which constructor to
+ // run. We are allowed to run the constructor.
+ result = newInstance(objectClass, constructorClass);
+ registerObjectRead(result, newHandle, unshared);
+
+ registeredResult = result;
+ } else {
+ result = null;
+ }
+
+ try {
+ // This is how we know what to do in defaultReadObject. And it is
+ // also used by defaultReadObject to check if it was called from an
+ // invalid place. It also allows readExternal to call
+ // defaultReadObject and have it work.
+ currentObject = result;
+ currentClass = classDesc;
+
+ // If Externalizable, just let the object read itself
+ if (wasExternalizable) {
+ boolean blockData = (classDesc.getFlags() & SC_BLOCK_DATA) > 0;
+ if (!blockData) {
+ primitiveData = input;
+ }
+ if (mustResolve) {
+ Externalizable extern = (Externalizable) result;
+ extern.readExternal(this);
+ }
+ if (blockData) {
+ // Similar to readHierarchy. Anything not read by
+ // readExternal has to be consumed here
+ discardData();
+ } else {
+ primitiveData = emptyStream;
+ }
+ } else {
+ // If we got here, it is Serializable but not Externalizable.
+ // Walk the hierarchy reading each class' slots
+ readHierarchy(result, classDesc);
+ }
+ } finally {
+ // Cleanup, needs to run always so that we can later detect invalid
+ // calls to defaultReadObject
+ currentObject = null;
+ currentClass = null;
+ }
+
+ if (objectClass != null) {
+ Object readResolveMethod = readResolveCache.get(objectClass);
+ if (readResolveMethod != this) {
+ if (readResolveMethod == null) {
+ final Method readResolve = ObjectStreamClass
+ .methodReadResolve(objectClass);
+ if (readResolve == null) {
+ readResolveCache.put(objectClass, this);
+ readResolveMethod = null;
+ } else {
+ // Has replacement method
+ AccessController.doPrivileged(new PriviAction<Object>(
+ readResolve));
+ readResolveCache.put(objectClass, readResolve);
+ readResolveMethod = readResolve;
+ }
+ }
+ if (readResolveMethod != null) {
+ try {
+ result = ((Method) readResolveMethod).invoke(result,
+ (Object[]) null);
+ } catch (IllegalAccessException iae) {
+ } catch (InvocationTargetException ite) {
+ Throwable target = ite.getTargetException();
+ if (target instanceof ObjectStreamException) {
+ throw (ObjectStreamException) target;
+ } else if (target instanceof Error) {
+ throw (Error) target;
+ } else {
+ throw (RuntimeException) target;
+ }
+ }
+ }
+ }
+ }
+ // We get here either if class-based replacement was not needed or if it
+ // was needed but produced the same object or if it could not be
+ // computed.
+
+ // The object to return is the one we instantiated or a replacement for
+ // it
+ if (result != null && enableResolve) {
+ result = resolveObject(result);
+ }
+ if (registeredResult != result) {
+ registerObjectRead(result, newHandle, unshared);
+ }
+ return result;
+ }
+
+ /**
+ * Read a string encoded in {@link DataInput modified UTF-8} from the
+ * receiver. Return the string read.
+ *
+ * @param unshared
+ * read the object unshared
+ * @return the string just read.
+ * @throws IOException
+ * If an IO exception happened when reading the String.
+ */
+ private Object readNewString(boolean unshared) throws IOException {
+ Object result = input.readUTF();
+ if (enableResolve) {
+ result = resolveObject(result);
+ }
+ int newHandle = nextHandle();
+ registerObjectRead(result, Integer.valueOf(newHandle), unshared);
+
+ return result;
+ }
+
+ /**
+ * Read a new String in UTF format from the receiver. Return the string
+ * read.
+ *
+ * @param unshared
+ * read the object unshared
+ * @return the string just read.
+ *
+ * @throws IOException
+ * If an IO exception happened when reading the String.
+ */
+ private Object readNewLongString(boolean unshared) throws IOException {
+ long length = input.readLong();
+ Object result = input.decodeUTF((int) length);
+ if (enableResolve) {
+ result = resolveObject(result);
+ }
+ int newHandle = nextHandle();
+ registerObjectRead(result, Integer.valueOf(newHandle), unshared);
+
+ return result;
+ }
+
+ /**
+ * Reads the next object from the source stream.
+ *
+ * @return the object read from the source stream.
+ * @throws ClassNotFoundException
+ * if the class of one of the objects in the object graph cannot
+ * be found.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @throws OptionalDataException
+ * if primitive data types were found instead of an object.
+ * @see ObjectOutputStream#writeObject(Object)
+ * @since Android 1.0
+ */
+ public final Object readObject() throws OptionalDataException,
+ ClassNotFoundException, IOException {
+ return readObject(false);
+ }
+
+ /**
+ * Reads the next unshared object from the source stream.
+ *
+ * @return the new object read.
+ * @throws ClassNotFoundException
+ * if the class of one of the objects in the object graph cannot
+ * be found.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @see ObjectOutputStream#writeUnshared
+ * @since Android 1.0
+ */
+ public Object readUnshared() throws IOException, ClassNotFoundException {
+ return readObject(true);
+ }
+
+ private Object readObject(boolean unshared) throws OptionalDataException,
+ ClassNotFoundException, IOException {
+ boolean restoreInput = (primitiveData == input);
+ if (restoreInput) {
+ primitiveData = emptyStream;
+ }
+
+ // This is the spec'ed behavior in JDK 1.2. Very bizarre way to allow
+ // behavior overriding.
+ if (subclassOverridingImplementation && !unshared) {
+ return readObjectOverride();
+ }
+
+ // If we still had primitive types to read, should we discard them
+ // (reset the primitiveTypes stream) or leave as is, so that attempts to
+ // read primitive types won't read 'past data' ???
+ Object result;
+ try {
+ // We need this so we can tell when we are returning to the
+ // original/outside caller
+ if (++nestedLevels == 1) {
+ // Remember the caller's class loader
+ // BEGIN android-changed
+ callerClassLoader = getClosestUserClassLoader();
+ // END android-changed
+ }
+
+ result = readNonPrimitiveContent(unshared);
+ if (restoreInput) {
+ primitiveData = input;
+ }
+ } finally {
+ // We need this so we can tell when we are returning to the
+ // original/outside caller
+ if (--nestedLevels == 0) {
+ // We are going to return to the original caller, perform
+ // cleanups.
+ // No more need to remember the caller's class loader
+ callerClassLoader = null;
+ }
+ }
+
+ // Done reading this object. Is it time to return to the original
+ // caller? If so we need to perform validations first.
+ if (nestedLevels == 0 && validations != null) {
+ // We are going to return to the original caller. If validation is
+ // enabled we need to run them now and then cleanup the validation
+ // collection
+ try {
+ for (InputValidationDesc element : validations) {
+ element.validator.validateObject();
+ }
+ } finally {
+ // Validations have to be renewed, since they are only called
+ // from readObject
+ validations = null;
+ }
+ }
+ return result;
+ }
+
+ // BEGIN android-added
+ private static final ClassLoader bootstrapLoader
+ = Object.class.getClassLoader();
+ private static final ClassLoader systemLoader
+ = ClassLoader.getSystemClassLoader();
+
+ /**
+ * Searches up the call stack to find the closest user-defined class loader.
+ *
+ * @return a user-defined class loader or null if one isn't found
+ */
+ private static ClassLoader getClosestUserClassLoader() {
+ Class<?>[] stackClasses = VMStack.getClasses(-1, false);
+ for (Class<?> stackClass : stackClasses) {
+ ClassLoader loader = stackClass.getClassLoader();
+ if (loader != null && loader != bootstrapLoader
+ && loader != systemLoader) {
+ return loader;
+ }
+ }
+ return null;
+ }
+ // END android-added
+
+ /**
+ * Method to be overriden by subclasses to read the next object from the
+ * source stream.
+ *
+ * @return the object read from the source stream.
+ * @throws ClassNotFoundException
+ * if the class of one of the objects in the object graph cannot
+ * be found.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @throws OptionalDataException
+ * if primitive data types were found instead of an object.
+ * @see ObjectOutputStream#writeObjectOverride
+ * @since Android 1.0
+ */
+ protected Object readObjectOverride() throws OptionalDataException,
+ ClassNotFoundException, IOException {
+ if (input == null) {
+ return null;
+ }
+ // Subclasses must override.
+ throw new IOException();
+ }
+
+ /**
+ * Reads a short (16 bit) from the source stream.
+ *
+ * @return the short value read from the source stream.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public short readShort() throws IOException {
+ return primitiveTypes.readShort();
+ }
+
+ /**
+ * Reads and validates the ObjectInputStream header from the source stream.
+ *
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @throws StreamCorruptedException
+ * if the source stream does not contain readable serialized
+ * objects.
+ * @since Android 1.0
+ */
+ protected void readStreamHeader() throws IOException,
+ StreamCorruptedException {
+ if (input.readShort() == STREAM_MAGIC
+ && input.readShort() == STREAM_VERSION) {
+ return;
+ }
+ throw new StreamCorruptedException();
+ }
+
+ /**
+ * Reads an unsigned byte (8 bit) from the source stream.
+ *
+ * @return the unsigned byte value read from the source stream packaged in
+ * an integer.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public int readUnsignedByte() throws IOException {
+ return primitiveTypes.readUnsignedByte();
+ }
+
+ /**
+ * Reads an unsigned short (16 bit) from the source stream.
+ *
+ * @return the unsigned short value read from the source stream packaged in
+ * an integer.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public int readUnsignedShort() throws IOException {
+ return primitiveTypes.readUnsignedShort();
+ }
+
+ /**
+ * Reads a string encoded in {@link DataInput modified UTF-8} from the
+ * source stream.
+ *
+ * @return the string encoded in {@link DataInput modified UTF-8} read from
+ * the source stream.
+ * @throws EOFException
+ * if the end of the input is reached before the read
+ * request can be satisfied.
+ * @throws IOException
+ * if an error occurs while reading from the source stream.
+ * @since Android 1.0
+ */
+ public String readUTF() throws IOException {
+ return primitiveTypes.readUTF();
+ }
+
+ /**
+ * Return the object previously read tagged with handle {@code handle}.
+ *
+ * @param handle
+ * The handle that this object was assigned when it was read.
+ * @return the object previously read.
+ *
+ * @throws InvalidObjectException
+ * If there is no previously read object with this handle
+ */
+ private Object registeredObjectRead(Integer handle)
+ throws InvalidObjectException {
+ Object res = objectsRead.get(handle);
+
+ if (res == UNSHARED_OBJ) {
+ throw new InvalidObjectException(Msg.getString("KA010")); //$NON-NLS-1$
+ }
+
+ return res;
+ }
+
+ /**
+ * Assume object {@code obj} has been read, and assign a handle to
+ * it, {@code handle}.
+ *
+ * @param obj
+ * Non-null object being loaded.
+ * @param handle
+ * An Integer, the handle to this object
+ * @param unshared
+ * Boolean, indicates that caller is reading in unshared mode
+ *
+ * @see #nextHandle
+ */
+ private void registerObjectRead(Object obj, Integer handle, boolean unshared) {
+ objectsRead.put(handle, unshared ? UNSHARED_OBJ : obj);
+ }
+
+ /**
+ * Registers a callback for post-deserialization validation of objects. It
+ * allows to perform additional consistency checks before the {@code
+ * readObject()} method of this class returns its result to the caller. This
+ * method can only be called from within the {@code readObject()} method of
+ * a class that implements "special" deserialization rules. It can be called
+ * multiple times. Validation callbacks are then done in order of decreasing
+ * priority, defined by {@code priority}.
+ *
+ * @param object
+ * an object that can validate itself by receiving a callback.
+ * @param priority
+ * the validator's priority.
+ * @throws InvalidObjectException
+ * if {@code object} is {@code null}.
+ * @throws NotActiveException
+ * if this stream is currently not reading objects. In that
+ * case, calling this method is not allowed.
+ * @see ObjectInputValidation#validateObject()
+ */
+ public synchronized void registerValidation(ObjectInputValidation object,
+ int priority) throws NotActiveException, InvalidObjectException {
+ // Validation can only be registered when inside readObject calls
+ Object instanceBeingRead = this.currentObject;
+
+ // We can't be called from just anywhere. There are rules.
+ if (instanceBeingRead == null) {
+ throw new NotActiveException();
+ }
+ if (object == null) {
+ throw new InvalidObjectException(Msg.getString("K00d9")); //$NON-NLS-1$
+ }
+ // From now on it is just insertion in a SortedCollection. Since
+ // the Java class libraries don't provide that, we have to
+ // implement it from scratch here.
+ InputValidationDesc desc = new InputValidationDesc();
+ desc.validator = object;
+ desc.priority = priority;
+ // No need for this, validateObject does not take a parameter
+ // desc.toValidate = instanceBeingRead;
+ if (validations == null) {
+ validations = new InputValidationDesc[1];
+ validations[0] = desc;
+ } else {
+ int i = 0;
+ for (; i < validations.length; i++) {
+ InputValidationDesc validation = validations[i];
+ // Sorted, higher priority first.
+ if (priority >= validation.priority) {
+ break; // Found the index where to insert
+ }
+ }
+ InputValidationDesc[] oldValidations = validations;
+ int currentSize = oldValidations.length;
+ validations = new InputValidationDesc[currentSize + 1];
+ System.arraycopy(oldValidations, 0, validations, 0, i);
+ System.arraycopy(oldValidations, i, validations, i + 1, currentSize
+ - i);
+ validations[i] = desc;
+ }
+ }
+
+ /**
+ * Reset the collection of objects already loaded by the receiver.
+ */
+ private void resetSeenObjects() {
+ objectsRead = new Hashtable<Integer, Object>();
+ currentHandle = baseWireHandle;
+ primitiveData = emptyStream;
+ }
+
+ /**
+ * Reset the receiver. The collection of objects already read by the
+ * receiver is reset, and internal structures are also reset so that the
+ * receiver knows it is in a fresh clean state.
+ */
+ private void resetState() {
+ resetSeenObjects();
+ hasPushbackTC = false;
+ pushbackTC = 0;
+ // nestedLevels = 0;
+ }
+
+ /**
+ * Loads the Java class corresponding to the class descriptor {@code
+ * osClass} that has just been read from the source stream.
+ *
+ * @param osClass
+ * an ObjectStreamClass read from the source stream.
+ * @return a Class corresponding to the descriptor {@code osClass}.
+ * @throws ClassNotFoundException
+ * if the class for an object cannot be found.
+ * @throws IOException
+ * if an I/O error occurs while creating the class.
+ * @see ObjectOutputStream#annotateClass(Class)
+ * @since Android 1.0
+ */
+ protected Class<?> resolveClass(ObjectStreamClass osClass)
+ throws IOException, ClassNotFoundException {
+ String className = osClass.getName();
+ // if it is primitive class, for example, long.class
+ Class<?> cls = PRIMITIVE_CLASSES.get(className);
+ if (null == cls) {
+ // not primitive class
+ // Use the first non-null ClassLoader on the stack. If null, use the
+ // system class loader
+ return Class.forName(className, true, callerClassLoader);
+ }
+ return cls;
+ }
+
+ /**
+ * Allows trusted subclasses to substitute the specified original {@code
+ * object} with a new object. Object substitution has to be activated first
+ * with calling {@code enableResolveObject(true)}. This implementation just
+ * returns {@code object}.
+ *
+ * @param object
+ * the original object for which a replacement may be defined.
+ * @return the replacement object for {@code object}.
+ * @throws IOException
+ * if any I/O error occurs while creating the replacement
+ * object.
+ * @see #enableResolveObject
+ * @see ObjectOutputStream#enableReplaceObject
+ * @see ObjectOutputStream#replaceObject
+ * @since Android 1.0
+ */
+ protected Object resolveObject(Object object) throws IOException {
+ // By default no object replacement. Subclasses can override
+ return object;
+ }
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code byte} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, byte value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code char} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, char value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code double} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, double value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code float} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, float value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code int} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, int value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code long} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, long value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new value {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * Class which declares the field
+ * @param fieldName
+ * Name of the field to set
+ * @param fieldTypeName
+ * Name of the class defining the type of the field
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void objSetField(Object instance,
+ Class<?> declaringClass, String fieldName, String fieldTypeName,
+ Object value) throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code short} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, short value)
+ throws NoSuchFieldError;
+
+ /**
+ * Set a given declared field named {@code fieldName} of
+ * {@code instance} to the new {@code boolean} value
+ * {@code value}.
+ *
+ * This method could be implemented non-natively on top of java.lang.reflect
+ * implementations that support the {@code setAccessible} API, at the
+ * expense of extra object creation (java.lang.reflect.Field). Otherwise
+ * Serialization could not set private fields, except by the use of a native
+ * method like this one.
+ *
+ * @param instance
+ * Object whose field to set
+ * @param declaringClass
+ * {@code instance}'s declaring class
+ * @param fieldName
+ * Name of the field to set
+ * @param value
+ * New value for the field
+ *
+ * @throws NoSuchFieldError
+ * If the field does not exist.
+ */
+ private static native void setField(Object instance,
+ Class<?> declaringClass, String fieldName, boolean value)
+ throws NoSuchFieldError;
+
+ /**
+ * Skips {@code length} bytes on the source stream. This method should not
+ * be used to skip bytes at any arbitrary position, just when reading
+ * primitive data types (int, char etc).
+ *
+ * @param length
+ * the number of bytes to skip.
+ * @return the number of bytes actually skipped.
+ * @throws IOException
+ * if an error occurs while skipping bytes on the source stream.
+ * @throws NullPointerException
+ * if the source stream is {@code null}.
+ * @since Android 1.0
+ */
+ public int skipBytes(int length) throws IOException {
+ // To be used with available. Ok to call if reading primitive buffer
+ if (input == null) {
+ throw new NullPointerException();
+ }
+
+ int offset = 0;
+ while (offset < length) {
+ checkReadPrimitiveTypes();
+ long skipped = primitiveData.skip(length - offset);
+ if (skipped == 0) {
+ return offset;
+ }
+ offset += (int) skipped;
+ }
+ return length;
+ }
+
+ /**
+ * Verify if the SUID for descriptor {@code loadedStreamClass}matches
+ * the SUID of the corresponding loaded class.
+ *
+ * @param loadedStreamClass
+ * An ObjectStreamClass that was loaded from the stream.
+ *
+ * @throws InvalidClassException
+ * If the SUID of the stream class does not match the VM class
+ */
+ private void verifySUID(ObjectStreamClass loadedStreamClass)
+ throws InvalidClassException {
+ Class<?> localClass = loadedStreamClass.forClass();
+ // Instances of java.lang.Class are always Serializable, even if their
+ // instances aren't (e.g. java.lang.Object.class). We cannot call lookup
+ // because it returns null if the parameter represents instances that
+ // cannot be serialized, and that is not what we want. If we are loading
+ // an instance of java.lang.Class, we better have the corresponding
+ // ObjectStreamClass.
+ ObjectStreamClass localStreamClass = ObjectStreamClass
+ .lookupStreamClass(localClass);
+ if (loadedStreamClass.getSerialVersionUID() != localStreamClass
+ .getSerialVersionUID()) {
+ throw new InvalidClassException(loadedStreamClass.getName(), Msg
+ .getString("K00da", loadedStreamClass, //$NON-NLS-1$
+ localStreamClass));
+ }
+ }
+
+ /**
+ * Verify if the base name for descriptor {@code loadedStreamClass}
+ * matches the base name of the corresponding loaded class.
+ *
+ * @param loadedStreamClass
+ * An ObjectStreamClass that was loaded from the stream.
+ *
+ * @throws InvalidClassException
+ * If the base name of the stream class does not match the VM
+ * class
+ */
+ private void verifyBaseName(ObjectStreamClass loadedStreamClass)
+ throws InvalidClassException {
+ Class<?> localClass = loadedStreamClass.forClass();
+ ObjectStreamClass localStreamClass = ObjectStreamClass
+ .lookupStreamClass(localClass);
+ String loadedClassBaseName = getBaseName(loadedStreamClass.getName());
+ String localClassBaseName = getBaseName(localStreamClass.getName());
+
+ if (!loadedClassBaseName.equals(localClassBaseName)) {
+ throw new InvalidClassException(loadedStreamClass.getName(), Msg
+ .getString("KA015", loadedClassBaseName, //$NON-NLS-1$
+ localClassBaseName));
+ }
+ }
+
+ private static String getBaseName(String fullName) {
+ int k = fullName.lastIndexOf("."); //$NON-NLS-1$
+
+ if (k == -1 || k == (fullName.length() - 1)) {
+ return fullName;
+ }
+ return fullName.substring(k + 1);
+ }
+
+ // Avoid recursive defining.
+ private static void checkedSetSuperClassDesc(ObjectStreamClass desc,
+ ObjectStreamClass superDesc) throws StreamCorruptedException {
+ if (desc.equals(superDesc)) {
+ throw new StreamCorruptedException();
+ }
+ desc.setSuperclass(superDesc);
+ }
+}