/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.io; import dalvik.system.VMStack; import java.io.EmulatedFields.ObjectSlot; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import libcore.util.EmptyArray; /** * 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 { // TODO: this is non-static to avoid sync contention. Would static be faster? private InputStream emptyStream = new ByteArrayInputStream(EmptyArray.BYTE); // 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 nextHandle; // 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; /** * All the objects we've read, indexed by their serialization handle (minus the base offset). */ private ArrayList<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 int descriptorHandle = -1; private static final HashMap<String, Class<?>> PRIMITIVE_CLASSES = new HashMap<String, Class<?>>(); static { PRIMITIVE_CLASSES.put("boolean", boolean.class); PRIMITIVE_CLASSES.put("byte", byte.class); PRIMITIVE_CLASSES.put("char", char.class); PRIMITIVE_CLASSES.put("double", double.class); PRIMITIVE_CLASSES.put("float", float.class); PRIMITIVE_CLASSES.put("int", int.class); PRIMITIVE_CLASSES.put("long", long.class); PRIMITIVE_CLASSES.put("short", short.class); PRIMITIVE_CLASSES.put("void", void.class); } // 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. */ protected ObjectInputStream() throws IOException { // 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. */ public ObjectInputStream(InputStream input) throws StreamCorruptedException, IOException { 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 { 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. * @see #resolveObject * @see ObjectOutputStream#enableReplaceObject */ protected boolean enableResolveObject(boolean enable) { boolean originalValue = enableResolve; enableResolve = enable; return originalValue; } /** * Return the next {@code int} handle to be used to indicate cyclic * references being loaded from the stream. * * @return the next handle to represent the next cyclic reference */ private int nextHandle() { return nextHandle++; } /** * 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(); } @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException { Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount); if (byteCount == 0) { return 0; } checkReadPrimitiveTypes(); return primitiveData.read(buffer, byteOffset, byteCount); } /** * 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(ObjectStreamClass.NO_FIELDS); 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 { 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 == int.class) { element.fieldValue = input.readInt(); } else if (type == byte.class) { element.fieldValue = input.readByte(); } else if (type == char.class) { element.fieldValue = input.readChar(); } else if (type == short.class) { element.fieldValue = input.readShort(); } else if (type == boolean.class) { element.fieldValue = input.readBoolean(); } else if (type == long.class) { element.fieldValue = input.readLong(); } else if (type == float.class) { element.fieldValue = input.readFloat(); } else if (type == double.class) { element.fieldValue = 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 = (fields == null) ? ObjectStreamClass.NO_FIELDS : fields; Class<?> declaringClass = classDesc.forClass(); if (declaringClass == null && mustResolve) { throw new ClassNotFoundException(classDesc.getName()); } for (ObjectStreamField fieldDesc : fields) { // checkAndGetReflectionField() can return null if it was not able to find the field or // if it is transient or static. We still need to read the data and do the other // checking... Field field = classDesc.checkAndGetReflectionField(fieldDesc); try { Class<?> type = fieldDesc.getTypeInternal(); if (type == byte.class) { byte b = input.readByte(); if (field != null) { field.setByte(obj, b); } } else if (type == char.class) { char c = input.readChar(); if (field != null) { field.setChar(obj, c); } } else if (type == double.class) { double d = input.readDouble(); if (field != null) { field.setDouble(obj, d); } } else if (type == float.class) { float f = input.readFloat(); if (field != null) { field.setFloat(obj, f); } } else if (type == int.class) { int i = input.readInt(); if (field != null) { field.setInt(obj, i); } } else if (type == long.class) { long j = input.readLong(); if (field != null) { field.setLong(obj, j); } } else if (type == short.class) { short s = input.readShort(); if (field != null) { field.setShort(obj, s); } } else if (type == boolean.class) { boolean z = input.readBoolean(); if (field != null) { field.setBoolean(obj, z); } } else { Object toSet = fieldDesc.isUnshared() ? readUnshared() : readObject(); if (toSet != null) { // 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. String fieldName = fieldDesc.getName(); ObjectStreamField localFieldDesc = classDesc.getField(fieldName); Class<?> fieldType = localFieldDesc.getTypeInternal(); Class<?> valueType = toSet.getClass(); if (!fieldType.isAssignableFrom(valueType)) { throw new ClassCastException(classDesc.getName() + "." + fieldName + " - " + fieldType + " not compatible with " + valueType); } if (field != null) { field.set(obj, toSet); } } } } catch (IllegalAccessException iae) { // ObjectStreamField should have called setAccessible(true). throw new AssertionError(iae); } catch (NoSuchFieldError 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 dst}. * This method will block until {@code dst.length} bytes have been read. * * @param dst * 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[] dst) throws IOException { primitiveTypes.readFully(dst); } /** * Reads {@code byteCount} bytes from the source stream into the byte array {@code dst}. * * @param dst * the byte array in which to store the bytes read. * @param offset * the initial position in {@code dst} to store the bytes * read from the source stream. * @param byteCount * the number of bytes to 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[] dst, int offset, int byteCount) throws IOException { primitiveTypes.readFully(dst, offset, byteCount); } /** * 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 { if (object == null && mustResolve) { throw new NotActiveException(); } List<ObjectStreamClass> streamClassList = classDesc.getHierarchy(); if (object == null) { for (ObjectStreamClass objectStreamClass : streamClassList) { readObjectForClass(null, objectStreamClass); } } else { List<Class<?>> superclasses = cachedSuperclasses.get(object.getClass()); if (superclasses == null) { superclasses = cacheSuperclassesFor(object.getClass()); } int lastIndex = 0; for (int i = 0, end = superclasses.size(); i < end; ++i) { Class<?> superclass = superclasses.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 HashMap<Class<?>, List<Class<?>>> cachedSuperclasses = new HashMap<Class<?>, List<Class<?>>>(); private List<Class<?>> cacheSuperclassesFor(Class<?> c) { ArrayList<Class<?>> result = new ArrayList<Class<?>>(); Class<?> nextClass = c; while (nextClass != null) { Class<?> testClass = nextClass.getSuperclass(); if (testClass != null) { result.add(0, nextClass); } nextClass = testClass; } cachedSuperclasses.put(c, result); return result; } private int findStreamSuperclass(Class<?> cl, List<ObjectStreamClass> classList, int lastIndex) { for (int i = lastIndex, end = classList.size(); i < end; i++) { ObjectStreamClass objCl = classList.get(i); String 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); } 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 readMethod.setAccessible(true); try { readMethod.invoke(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} instead. */ @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) { throw missingClassDescriptor(); } int 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 == int.class) { int[] intArray = (int[]) result; for (int i = 0; i < size; i++) { intArray[i] = input.readInt(); } } else if (componentType == byte.class) { byte[] byteArray = (byte[]) result; input.readFully(byteArray, 0, size); } else if (componentType == char.class) { char[] charArray = (char[]) result; for (int i = 0; i < size; i++) { charArray[i] = input.readChar(); } } else if (componentType == short.class) { short[] shortArray = (short[]) result; for (int i = 0; i < size; i++) { shortArray[i] = input.readShort(); } } else if (componentType == boolean.class) { boolean[] booleanArray = (boolean[]) result; for (int i = 0; i < size; i++) { booleanArray[i] = input.readBoolean(); } } else if (componentType == long.class) { long[] longArray = (long[]) result; for (int i = 0; i < size; i++) { longArray[i] = input.readLong(); } } else if (componentType == float.class) { float[] floatArray = (float[]) result; for (int i = 0; i < size; i++) { floatArray[i] = input.readFloat(); } } else if (componentType == double.class) { 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) { throw 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; int 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(); Class enumType = classDesc.checkAndGetTcEnumClass(); int 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; try { result = Enum.valueOf(enumType, name); } catch (IllegalArgumentException e) { InvalidObjectException ioe = new InvalidObjectException(e.getMessage()); ioe.initCause(e); throw ioe; } 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; int 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 = (fields == null) ? ObjectStreamClass.NO_FIELDS : 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 unset. */ if (descriptorHandle == -1) { descriptorHandle = nextHandle(); } 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 { ClassLoader loader = callerClassLoader; 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); } } private int readNewHandle() throws IOException { return input.readInt(); } /** * Read a new object from the stream. It is assumed the object has not been * loaded yet (not a cyclic reference). Return the object read. * * If the object implements <code>Externalizable</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(); } Class<?> objectClass = classDesc.checkAndGetTcObjectClass(); int newHandle = nextHandle(); Object result; Object registeredResult = null; if (objectClass != null) { // Now we know which class to instantiate and which constructor to // run. We are allowed to run the constructor. result = classDesc.newInstance(objectClass); 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 // Note that this value comes 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; 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 ignored) { } 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 callerClassLoader = VMStack.getClosestUserClassLoader(); } 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; } /** * Method to be overridden 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(); } /** * Returns the previously-read object corresponding to the given serialization handle. * @throws InvalidObjectException * If there is no previously-read object with this handle */ private Object registeredObjectRead(int handle) throws InvalidObjectException { Object res = objectsRead.get(handle - ObjectStreamConstants.baseWireHandle); if (res == UNSHARED_OBJ) { throw new InvalidObjectException("Cannot read back reference to unshared object"); } return res; } /** * Associates a read object with the its serialization handle. */ private void registerObjectRead(Object obj, int handle, boolean unshared) throws IOException { if (unshared) { obj = UNSHARED_OBJ; } int index = handle - ObjectStreamConstants.baseWireHandle; int size = objectsRead.size(); // ObjectOutputStream sometimes wastes a handle. I've compared hex dumps of the RI // and it seems like that's a 'feature'. Look for calls to objectsWritten.put that // are guarded by !unshared tests. while (index > size) { objectsRead.add(null); ++size; } if (index == size) { objectsRead.add(obj); } else { objectsRead.set(index, 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; 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 ArrayList<Object>(); nextHandle = 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 (cls == null) { // slowpath: resolve the class String className = osClass.getName(); // if it is primitive class, for example, long.class cls = PRIMITIVE_CLASSES.get(className); if (cls == null) { // not primitive class cls = Class.forName(className, false, 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; } /** * 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("source stream is null"); } 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); } }