| /* |
| * 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; |
| |
| // BEGIN android-note |
| // Harmony uses ObjectAccessors to access fields through JNI. Android has not |
| // yet migrated that API. As a consequence, there's a lot of changes here... |
| // END android-note |
| |
| import dalvik.system.VMStack; |
| 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.HashMap; |
| import java.util.Iterator; |
| 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 |
| */ |
| public class ObjectInputStream extends InputStream implements ObjectInput, |
| ObjectStreamConstants { |
| |
| // BEGIN android-note |
| // this is non-static to avoid sync contention. Would static be faster? |
| // END android-note |
| private 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 HashMap<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; |
| |
| private static final HashMap<String, Class<?>> PRIMITIVE_CLASSES = |
| new HashMap<String, Class<?>>(); |
| |
| static { |
| PRIMITIVE_CLASSES.put("byte", byte.class); |
| PRIMITIVE_CLASSES.put("short", short.class); |
| PRIMITIVE_CLASSES.put("int", int.class); |
| PRIMITIVE_CLASSES.put("long", long.class); |
| PRIMITIVE_CLASSES.put("boolean", boolean.class); |
| PRIMITIVE_CLASSES.put("char", char.class); |
| PRIMITIVE_CLASSES.put("float", float.class); |
| PRIMITIVE_CLASSES.put("double", double.class); |
| } |
| |
| // BEGIN android-removed |
| // private ObjectAccessor accessor = AccessorFactory.getObjectAccessor(); |
| // END android-removed |
| |
| // 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. |
| */ |
| public abstract static class GetField { |
| /** |
| * Gets the ObjectStreamClass that describes a field. |
| * |
| * @return the descriptor class for a serialized field. |
| */ |
| 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. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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}. |
| */ |
| 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) |
| */ |
| 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. |
| */ |
| 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", |
| ObjectStreamClass.EMPTY_CONSTRUCTOR_PARAM_TYPES); |
| if (method.getDeclaringClass() != thisClass) { |
| return Boolean.TRUE; |
| } |
| } catch (NoSuchMethodException e) { |
| } |
| try { |
| Method method = implementationClass |
| .getMethod( |
| "readUnshared", |
| 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; |
| 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; |
| } |
| |
| @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. |
| */ |
| @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 |
| */ |
| 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 |
| */ |
| 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('.'); |
| if (indexDotC1 != indexDotC2) { |
| return false; // cannot be in the same package if indices are not |
| } |
| // the same |
| if (indexDotC1 < 0) { |
| return true; // both of them are in default package |
| } |
| return nameC1.substring(0, indexDotC1).equals( |
| nameC2.substring(0, indexDotC2)); |
| } |
| |
| // BEGIN android-added |
| /** |
| * 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); |
| // END android-added |
| |
| /** |
| * 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 Integer nextHandle() { |
| return Integer.valueOf(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. |
| */ |
| @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}. |
| */ |
| @Override |
| public int read(byte[] buffer, int offset, int length) throws IOException { |
| // Force buffer null check first! |
| if (offset > buffer.length || offset < 0) { |
| throw new ArrayIndexOutOfBoundsException("Offset out of bounds: " + offset); |
| } |
| if (length < 0 || length > buffer.length - offset) { |
| throw new ArrayIndexOutOfBoundsException("Length out of bounds: " + length); |
| } |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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, nextHandle(), false); |
| checkedSetSuperClassDesc(streamClass, readClassDesc()); |
| return streamClass; |
| case TC_REFERENCE: |
| return (ObjectStreamClass) readCyclicReference(); |
| case TC_NULL: |
| return null; |
| default: |
| throw corruptStream(tc); |
| } |
| } |
| |
| private StreamCorruptedException corruptStream(byte tc) throws StreamCorruptedException { |
| throw new StreamCorruptedException("Wrong format: " + Integer.toHexString(tc & 0xff)); |
| } |
| |
| /** |
| * 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("Read an exception", exc); |
| case TC_RESET: |
| resetState(); |
| return null; |
| default: |
| throw corruptStream(tc); |
| } |
| } |
| |
| /** |
| * 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("Unshared read of back reference"); |
| } |
| return readCyclicReference(); |
| case TC_NULL: |
| return null; |
| case TC_EXCEPTION: |
| Exception exc = readException(); |
| throw new WriteAbortedException("Read an exception", exc); |
| 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 corruptStream(tc); |
| } |
| // 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. |
| */ |
| 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; |
| } |
| } |
| |
| classSig = formatClassSig(classSig); |
| ObjectStreamField f = new ObjectStreamField(classSig, fieldName); |
| fields[i] = f; |
| } |
| } |
| |
| /* |
| * Format the class signature for ObjectStreamField, for example, |
| * "[L[Ljava.lang.String;;" is converted to "[Ljava.lang.String;" |
| */ |
| private static String formatClassSig(String classSig) { |
| int start = 0; |
| int end = classSig.length(); |
| |
| if (end <= 0) { |
| return classSig; |
| } |
| |
| while (classSig.startsWith("[L", start) |
| && classSig.charAt(end - 1) == ';') { |
| start += 2; |
| end--; |
| } |
| |
| if (start > 0) { |
| start -= 2; |
| end++; |
| return classSig.substring(start, end); |
| } |
| return classSig; |
| } |
| |
| /** |
| * 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. |
| */ |
| 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) { |
| |
| // BEGIN android-removed |
| // // get associated Field |
| // long fieldID = fieldDesc.getFieldID(accessor, declaringClass); |
| // END android-removed |
| |
| // Code duplication starts, just because Java is typed |
| if (fieldDesc.isPrimitive()) { |
| try { |
| // BEGIN android-changed |
| switch (fieldDesc.getTypeCode()) { |
| case 'B': |
| setFieldByte(obj, declaringClass, fieldDesc.getName(), |
| input.readByte()); |
| break; |
| case 'C': |
| setFieldChar(obj, declaringClass, fieldDesc.getName(), |
| input.readChar()); |
| break; |
| case 'D': |
| setFieldDouble(obj, declaringClass, fieldDesc.getName(), |
| input.readDouble()); |
| break; |
| case 'F': |
| setFieldFloat(obj, declaringClass, fieldDesc.getName(), |
| input.readFloat()); |
| break; |
| case 'I': |
| setFieldInt(obj, declaringClass, fieldDesc.getName(), |
| input.readInt()); |
| break; |
| case 'J': |
| setFieldLong(obj, declaringClass, fieldDesc.getName(), |
| input.readLong()); |
| break; |
| case 'S': |
| setFieldShort(obj, declaringClass, fieldDesc.getName(), |
| input.readShort()); |
| break; |
| case 'Z': |
| setFieldBool(obj, declaringClass, fieldDesc.getName(), |
| input.readBoolean()); |
| break; |
| default: |
| throw new StreamCorruptedException("Invalid typecode: " + |
| fieldDesc.getTypeCode()); |
| } |
| // END android-changed |
| } catch (NoSuchFieldError err) { |
| } |
| } else { |
| // Object type (array included). |
| String fieldName = fieldDesc.getName(); |
| boolean setBack = false; |
| // BEGIN android-added |
| ObjectStreamField field = classDesc.getField(fieldName); |
| // END android-added |
| if (mustResolve && fieldDesc == null) { |
| setBack = true; |
| mustResolve = false; |
| } |
| Object toSet; |
| if (fieldDesc != null && fieldDesc.isUnshared()) { |
| toSet = readUnshared(); |
| } else { |
| toSet = readObject(); |
| } |
| if (setBack) { |
| mustResolve = true; |
| } |
| if (fieldDesc != null) { |
| if (toSet != null) { |
| // BEGIN android-changed |
| // Get the field type from the local field rather than |
| // from the stream's supplied data. That's the field |
| // we'll be setting, so that's the one that needs to be |
| // validated. |
| Class<?> fieldType = field.getTypeInternal(); |
| // END android-added |
| Class<?> valueType = toSet.getClass(); |
| if (!fieldType.isAssignableFrom(valueType)) { |
| throw new ClassCastException(classDesc.getName() + "." + fieldName + |
| " - " + fieldType + " not compatible with " + valueType); |
| } |
| try { |
| // BEGIN android-changed |
| setFieldObject(obj, declaringClass, fieldName, field.getTypeString(), |
| toSet); |
| // END android-changed |
| } 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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, ObjectStreamClass.lookupStreamClass(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, ObjectStreamClass classDesc) |
| throws ObjectStreamException { |
| if (!classDesc.isSerializable()) { |
| return; |
| } |
| if (classDesc.hasMethodReadObjectNoData()){ |
| final Method readMethod = classDesc.getMethodReadObjectNoData(); |
| 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 = classDesc.getMethodReadObject(); |
| } |
| 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. |
| */ |
| 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} |
| */ |
| @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. |
| */ |
| 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) { |
| missingClassDescriptor(); |
| } |
| |
| Integer newHandle = 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("Wrong base type in " + classDesc.getName()); |
| } |
| } else { |
| // Array of Objects |
| Object[] objectArray = (Object[]) result; |
| for (int i = 0; i < size; i++) { |
| // TODO: This place is the opportunity for enhancement |
| // We can implement writing elements through fast-path, |
| // without setting up the context (see readObject()) for |
| // each element with public API |
| 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) { |
| missingClassDescriptor(); |
| } |
| Class<?> localClass = classDesc.forClass(); |
| if (localClass != null) { |
| registerObjectRead(localClass, nextHandle(), unshared); |
| } |
| return localClass; |
| } |
| |
| /* |
| * 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 corruptStream(tc); |
| } |
| } |
| |
| private ObjectStreamClass readEnumDescInternal() throws IOException, |
| ClassNotFoundException { |
| ObjectStreamClass classDesc; |
| primitiveData = input; |
| Integer oldHandle = descriptorHandle; |
| descriptorHandle = nextHandle(); |
| classDesc = readClassDescriptor(); |
| 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(), |
| "Incompatible class (SUID): " + superClass + " but expected " + 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 = nextHandle(); |
| // read name after class desc |
| String name; |
| byte tc = nextTC(); |
| switch (tc) { |
| case TC_REFERENCE: |
| if (unshared) { |
| readNewHandle(); |
| throw new InvalidObjectException("Unshared read of back reference"); |
| } |
| name = (String) readCyclicReference(); |
| break; |
| case TC_STRING: |
| name = (String) readNewString(unshared); |
| break; |
| default: |
| throw corruptStream(tc); |
| } |
| |
| 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 = nextHandle(); |
| ObjectStreamClass newClassDesc = readClassDescriptor(); |
| registerObjectRead(newClassDesc, descriptorHandle, unshared); |
| descriptorHandle = oldHandle; |
| primitiveData = emptyStream; |
| |
| // We need to map classDesc to class. |
| try { |
| newClassDesc.setClass(resolveClass(newClassDesc)); |
| // Check SUIDs & base name of the class |
| verifyAndInit(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. |
| */ |
| protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { |
| ObjectStreamClass newClassDesc = new ObjectStreamClass(); |
| String name = input.readUTF(); |
| if (name.length() == 0) { |
| 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 ? nextHandle() : descriptorHandle); |
| registerObjectRead(newClassDesc, descriptorHandle, false); |
| |
| 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) |
| */ |
| protected Class<?> resolveProxyClass(String[] interfaceNames) |
| throws IOException, ClassNotFoundException { |
| // TODO: This method is opportunity for performance enhancement |
| // We can cache the classloader and recently used interfaces. |
| // BEGIN android-changed |
| // ClassLoader loader = VM.getNonBootstrapClassLoader(); |
| ClassLoader loader = ClassLoader.getSystemClassLoader(); |
| // END android-changed |
| 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 int readNewHandle() throws IOException { |
| return input.readInt(); |
| } |
| |
| private Class<?> resolveConstructorClass(Class<?> objectClass, boolean wasSerializable, boolean wasExternalizable) |
| throws OptionalDataException, ClassNotFoundException, IOException { |
| |
| // 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(), |
| "IllegalAccessException"); |
| } |
| |
| 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(), |
| "IllegalAccessException"); |
| } |
| |
| // 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(), |
| "IllegalAccessException"); |
| } |
| } |
| |
| return constructorClass; |
| } |
| |
| /** |
| * 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</code> its |
| * <code>readExternal</code> 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</code> |
| * |
| * @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 missingClassDescriptor(); |
| } |
| |
| Integer newHandle = 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) { |
| |
| // BEGIN android-changed |
| // long constructor = classDesc.getConstructor(); |
| // if (constructor == ObjectStreamClass.CONSTRUCTOR_IS_NOT_RESOLVED) { |
| // constructor = accessor.getMethodID(resolveConstructorClass(objectClass, wasSerializable, wasExternalizable), null, new Class[0]); |
| // classDesc.setConstructor(constructor); |
| // } |
| Class constructorClass = resolveConstructorClass(objectClass, wasSerializable, wasExternalizable); |
| // END android-changed |
| |
| // Now we know which class to instantiate and which constructor to |
| // run. We are allowed to run the constructor. |
| // BEGIN android-changed |
| // result = accessor.newInstance(objectClass, constructor, null); |
| result = newInstance(objectClass, constructorClass); |
| // END android-changed |
| 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) { |
| |
| if (classDesc.hasMethodReadResolve()){ |
| Method methodReadResolve = classDesc.getMethodReadResolve(); |
| try { |
| result = methodReadResolve.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; |
| } |
| |
| private InvalidClassException missingClassDescriptor() throws InvalidClassException { |
| throw new InvalidClassException("Read null attempting to read class descriptor for object"); |
| } |
| |
| /** |
| * 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); |
| } |
| registerObjectRead(result, nextHandle(), 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); |
| } |
| registerObjectRead(result, nextHandle(), 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) |
| */ |
| 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 |
| */ |
| 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 |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| 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("Cannot read back reference to unshared object"); |
| } |
| 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 && nestedLevels == 0) { |
| throw new NotActiveException(); |
| } |
| if (object == null) { |
| throw new InvalidObjectException("Callback object cannot be null"); |
| } |
| // 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 HashMap<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) |
| */ |
| protected Class<?> resolveClass(ObjectStreamClass osClass) |
| throws IOException, ClassNotFoundException { |
| // fastpath: obtain cached value |
| Class<?> cls = osClass.forClass(); |
| if (null == cls) { |
| // slowpath: resolve the class |
| String className = osClass.getName(); |
| |
| // if it is primitive class, for example, long.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 |
| cls = 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 |
| */ |
| protected Object resolveObject(Object object) throws IOException { |
| // By default no object replacement. Subclasses can override |
| return object; |
| } |
| |
| // BEGIN android-added |
| |
| /* |
| * These methods set the value of a field named fieldName of instance. The |
| * field is declared by declaringClass. The field is the same type as the |
| * value parameter. |
| * |
| * these methods could be implemented non-natively on top of |
| * java.lang.reflect at the expense of extra object creation |
| * (java.lang.reflect.Field). Otherwise Serialization could not fetch |
| * private fields, except by the use of a native method like this one. |
| * |
| * @throws NoSuchFieldError If the field does not exist. |
| */ |
| private static native void setFieldByte(Object instance, |
| Class<?> declaringClass, String fieldName, byte value) |
| throws NoSuchFieldError; |
| |
| |
| private static native void setFieldChar(Object instance, |
| Class<?> declaringClass, String fieldName, char value) |
| throws NoSuchFieldError; |
| |
| |
| private static native void setFieldDouble(Object instance, |
| Class<?> declaringClass, String fieldName, double value) |
| throws NoSuchFieldError; |
| |
| private static native void setFieldFloat(Object instance, |
| Class<?> declaringClass, String fieldName, float value) |
| throws NoSuchFieldError; |
| |
| private static native void setFieldInt(Object instance, |
| Class<?> declaringClass, String fieldName, int value) |
| throws NoSuchFieldError; |
| |
| private static native void setFieldLong(Object instance, |
| Class<?> declaringClass, String fieldName, long value) |
| throws NoSuchFieldError; |
| |
| private static native void setFieldObject(Object instance, |
| Class<?> declaringClass, String fieldName, String fieldTypeName, |
| Object value) throws NoSuchFieldError; |
| |
| private static native void setFieldShort(Object instance, |
| Class<?> declaringClass, String fieldName, short value) |
| throws NoSuchFieldError; |
| |
| private static native void setFieldBool(Object instance, |
| Class<?> declaringClass, String fieldName, boolean value) |
| throws NoSuchFieldError; |
| |
| // END android-added |
| |
| /** |
| * 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}. |
| */ |
| 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 & the base name for descriptor |
| * <code>loadedStreamClass</code>matches |
| * the SUID & the base name of the corresponding loaded class and |
| * init private fields. |
| * |
| * @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 verifyAndInit(ObjectStreamClass loadedStreamClass) |
| throws InvalidClassException { |
| |
| Class<?> localClass = loadedStreamClass.forClass(); |
| ObjectStreamClass localStreamClass = ObjectStreamClass |
| .lookupStreamClass(localClass); |
| |
| if (loadedStreamClass.getSerialVersionUID() != localStreamClass |
| .getSerialVersionUID()) { |
| throw new InvalidClassException(loadedStreamClass.getName(), |
| "Incompatible class (SUID): " + loadedStreamClass + |
| " but expected " + localStreamClass); |
| } |
| |
| String loadedClassBaseName = getBaseName(loadedStreamClass.getName()); |
| String localClassBaseName = getBaseName(localStreamClass.getName()); |
| |
| if (!loadedClassBaseName.equals(localClassBaseName)) { |
| throw new InvalidClassException(loadedStreamClass.getName(), |
| String.format("Incompatible class (base name): %s but expected %s", |
| loadedClassBaseName, localClassBaseName)); |
| } |
| |
| loadedStreamClass.initPrivateFields(localStreamClass); |
| } |
| |
| private static String getBaseName(String fullName) { |
| int k = fullName.lastIndexOf('.'); |
| |
| 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); |
| } |
| } |