diff options
Diffstat (limited to 'luni/src/main/java/java/io/ObjectInputStream.java')
-rw-r--r-- | luni/src/main/java/java/io/ObjectInputStream.java | 2970 |
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); + } +} |