blob: 749eb29b2d6ae2dacf7c2bf2ba0e8a649c54afac [file] [log] [blame]
/*
* 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);
}
}