blob: 1bde314c7c6ef0f32ac6ca107eede0971fdc5fa7 [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;
import java.lang.ref.SoftReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.nio.ByteOrder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.WeakHashMap;
import libcore.io.Memory;
import libcore.util.EmptyArray;
/**
* Represents a descriptor for identifying a class during serialization and
* deserialization. Information contained in the descriptor includes the name
* and SUID of the class as well as field names and types. Information inherited
* from the superclasses is also taken into account.
*
* @see ObjectOutputStream
* @see ObjectInputStream
* @see java.lang.Class
*/
public class ObjectStreamClass implements Serializable {
// No need to compute the SUID for ObjectStreamClass, just use the value
// below
private static final long serialVersionUID = -6120832682080437368L;
// Name of the field that contains the SUID value (if present)
private static final String UID_FIELD_NAME = "serialVersionUID";
static final long CONSTRUCTOR_IS_NOT_RESOLVED = -1;
private static final int CLASS_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.FINAL |
Modifier.INTERFACE | Modifier.ABSTRACT;
private static final int FIELD_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE |
Modifier.TRANSIENT;
private static final int METHOD_MODIFIERS_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL | Modifier.SYNCHRONIZED |
Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT;
private static final Class<?>[] READ_PARAM_TYPES = new Class[] { ObjectInputStream.class };
private static final Class<?>[] WRITE_PARAM_TYPES = new Class[] { ObjectOutputStream.class };
/**
* Constant indicating that the class has no Serializable fields.
*/
public static final ObjectStreamField[] NO_FIELDS = new ObjectStreamField[0];
/*
* used to fetch field serialPersistentFields and checking its type
*/
static final Class<?> ARRAY_OF_FIELDS;
static {
try {
ARRAY_OF_FIELDS = Class.forName("[Ljava.io.ObjectStreamField;");
} catch (ClassNotFoundException e) {
// This should not happen
throw new AssertionError(e);
}
}
private static final String CLINIT_NAME = "<clinit>";
private static final int CLINIT_MODIFIERS = Modifier.STATIC;
private static final String CLINIT_SIGNATURE = "()V";
// Used to determine if an object is Serializable or Externalizable
private static final Class<Serializable> SERIALIZABLE = Serializable.class;
private static final Class<Externalizable> EXTERNALIZABLE = Externalizable.class;
// Used to test if the object is a String or a class.
static final Class<String> STRINGCLASS = String.class;
static final Class<?> CLASSCLASS = Class.class;
static final Class<ObjectStreamClass> OBJECTSTREAMCLASSCLASS = ObjectStreamClass.class;
private transient Method methodWriteReplace;
private transient Method methodReadResolve;
private transient Method methodWriteObject;
private transient Method methodReadObject;
private transient Method methodReadObjectNoData;
/**
* Indicates whether the class properties resolved
*
* @see #resolveProperties()
*/
private transient boolean arePropertiesResolved;
/**
* Cached class properties
*
* @see #resolveProperties()
* @see #isSerializable()
* @see #isExternalizable()
* @see #isProxy()
* @see #isEnum()
*/
private transient boolean isSerializable;
private transient boolean isExternalizable;
private transient boolean isProxy;
private transient boolean isEnum;
// ClassDesc //
// Name of the class this descriptor represents
private transient String className;
// Corresponding loaded class with the name above
private transient Class<?> resolvedClass;
private transient Class<?> resolvedConstructorClass;
private transient long resolvedConstructorMethodId;
// Serial version UID of the class the descriptor represents
private transient long svUID;
// ClassDescInfo //
// Any combination of SC_WRITE_METHOD, SC_SERIALIZABLE and SC_EXTERNALIZABLE
// (see ObjectStreamConstants)
private transient byte flags;
// Descriptor for the superclass of the class associated with this
// descriptor
private transient ObjectStreamClass superclass;
// Array of ObjectStreamField (see below) describing the fields of this
// class
private transient ObjectStreamField[] fields;
// Array of ObjectStreamField describing the serialized fields of this class
private transient ObjectStreamField[] loadFields;
// ObjectStreamField doesn't override hashCode or equals, so this is equivalent to an
// IdentityHashMap, which is fine for our purposes.
private transient HashMap<ObjectStreamField, Field> reflectionFields =
new HashMap<ObjectStreamField, Field>();
// MethodID for deserialization constructor
private transient long constructor = CONSTRUCTOR_IS_NOT_RESOLVED;
void setConstructor(long newConstructor) {
constructor = newConstructor;
}
long getConstructor() {
return constructor;
}
Field getReflectionField(ObjectStreamField osf) {
synchronized (reflectionFields) {
Field field = reflectionFields.get(osf);
if (field != null) {
return field;
}
}
try {
Class<?> declaringClass = forClass();
Field field = declaringClass.getDeclaredField(osf.getName());
field.setAccessible(true);
synchronized (reflectionFields) {
reflectionFields.put(osf, field);
}
return reflectionFields.get(osf);
} catch (NoSuchFieldException ex) {
// The caller messed up. We'll return null and won't try to resolve this again.
return null;
}
}
/*
* If an ObjectStreamClass describes an Externalizable class, it (the
* descriptor) should not have field descriptors (ObjectStreamField) at all.
* The ObjectStreamClass that gets saved should simply have no field info.
* This is a footnote in page 1511 (class Serializable) of "The Java Class
* Libraries, Second Edition, Vol. I".
*/
/**
* Constructs a new instance of this class.
*/
ObjectStreamClass() {
}
/**
* Compute class descriptor for a given class <code>cl</code>.
*
* @param cl
* a java.langClass for which to compute the corresponding
* descriptor
* @return the computer class descriptor
*/
private static ObjectStreamClass createClassDesc(Class<?> cl) {
ObjectStreamClass result = new ObjectStreamClass();
boolean isArray = cl.isArray();
boolean serializable = isSerializable(cl);
boolean externalizable = isExternalizable(cl);
result.isSerializable = serializable;
result.isExternalizable = externalizable;
// Now we fill in the values
result.setName(cl.getName());
result.setClass(cl);
Class<?> superclass = cl.getSuperclass();
if (superclass != null) {
result.setSuperclass(lookup(superclass));
}
Field[] declaredFields = null;
// Compute the SUID
if (serializable || externalizable) {
if (result.isEnum() || result.isProxy()) {
result.setSerialVersionUID(0L);
} else {
declaredFields = cl.getDeclaredFields();
result.setSerialVersionUID(computeSerialVersionUID(cl, declaredFields));
}
}
// Serializables need field descriptors
if (serializable && !isArray) {
if (declaredFields == null) {
declaredFields = cl.getDeclaredFields();
}
result.buildFieldDescriptors(declaredFields);
} else {
// Externalizables or arrays do not need FieldDesc info
result.setFields(NO_FIELDS);
}
// Copy all fields to loadFields - they should be read by default in
// ObjectInputStream.defaultReadObject() method
ObjectStreamField[] fields = result.getFields();
if (fields != null) {
ObjectStreamField[] loadFields = new ObjectStreamField[fields.length];
for (int i = 0; i < fields.length; ++i) {
loadFields[i] = new ObjectStreamField(fields[i].getName(),
fields[i].getType(), fields[i].isUnshared());
// resolve type string to init typeString field in
// ObjectStreamField
loadFields[i].getTypeString();
}
result.setLoadFields(loadFields);
}
byte flags = 0;
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
flags |= ObjectStreamConstants.SC_BLOCK_DATA; // use protocol version 2 by default
} else if (serializable) {
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
}
result.methodWriteReplace = findMethod(cl, "writeReplace");
result.methodReadResolve = findMethod(cl, "readResolve");
result.methodWriteObject = findPrivateMethod(cl, "writeObject", WRITE_PARAM_TYPES);
result.methodReadObject = findPrivateMethod(cl, "readObject", READ_PARAM_TYPES);
result.methodReadObjectNoData = findPrivateMethod(cl, "readObjectNoData", EmptyArray.CLASS);
if (result.hasMethodWriteObject()) {
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
}
result.setFlags(flags);
return result;
}
/**
* Builds the collection of field descriptors for the receiver
*
* @param declaredFields
* collection of java.lang.reflect.Field for which to compute
* field descriptors
*/
void buildFieldDescriptors(Field[] declaredFields) {
// We could find the field ourselves in the collection, but calling
// reflect is easier. Optimize if needed.
final Field f = ObjectStreamClass.fieldSerialPersistentFields(this.forClass());
// If we could not find the emulated fields, we'll have to compute
// dumpable fields from reflect fields
boolean useReflectFields = f == null; // Assume we will compute the
// fields to dump based on the
// reflect fields
ObjectStreamField[] _fields = null;
if (!useReflectFields) {
// The user declared a collection of emulated fields. Use them.
// We have to be able to fetch its value, even if it is private
f.setAccessible(true);
try {
// static field, pass null
_fields = (ObjectStreamField[]) f.get(null);
} catch (IllegalAccessException ex) {
throw new AssertionError(ex);
}
} else {
// Compute collection of dumpable fields based on reflect fields
List<ObjectStreamField> serializableFields =
new ArrayList<ObjectStreamField>(declaredFields.length);
// Filter, we are only interested in fields that are serializable
for (Field declaredField : declaredFields) {
int modifiers = declaredField.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
ObjectStreamField field = new ObjectStreamField(declaredField.getName(),
declaredField.getType());
serializableFields.add(field);
}
}
if (serializableFields.size() == 0) {
_fields = NO_FIELDS; // If no serializable fields, share the
// special value so that users can test
} else {
_fields = serializableFields.toArray(new ObjectStreamField[serializableFields.size()]);
}
}
Arrays.sort(_fields);
// assign offsets
int primOffset = 0, objectOffset = 0;
for (int i = 0; i < _fields.length; i++) {
Class<?> type = _fields[i].getType();
if (type.isPrimitive()) {
_fields[i].offset = primOffset;
primOffset += primitiveSize(type);
} else {
_fields[i].offset = objectOffset++;
}
}
fields = _fields;
}
/**
* Compute and return the Serial Version UID of the class {@code cl}.
* The value is computed based on the class name, superclass chain, field
* names, method names, modifiers, etc.
*
* @param cl
* a java.lang.Class for which to compute the SUID
* @param fields
* cl.getDeclaredFields(), pre-computed by the caller
* @return the value of SUID of this class
*/
private static long computeSerialVersionUID(Class<?> cl, Field[] fields) {
/*
* First we should try to fetch the static slot 'static final long
* serialVersionUID'. If it is defined, return it. If not defined, we
* really need to compute SUID using SHAOutputStream
*/
for (int i = 0; i < fields.length; i++) {
final Field field = fields[i];
if (field.getType() == long.class) {
int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
if (UID_FIELD_NAME.equals(field.getName())) {
/*
* We need to be able to see it even if we have no
* visibility. That is why we set accessible first (new
* API in reflect 1.2)
*/
field.setAccessible(true);
try {
// Static field, parameter is ignored
return field.getLong(null);
} catch (IllegalAccessException iae) {
throw new RuntimeException("Error fetching SUID: " + iae);
}
}
}
}
}
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA");
} catch (NoSuchAlgorithmException e) {
throw new Error(e);
}
ByteArrayOutputStream sha = new ByteArrayOutputStream();
try {
DataOutputStream output = new DataOutputStream(sha);
output.writeUTF(cl.getName());
int classModifiers = CLASS_MODIFIERS_MASK & cl.getModifiers();
/*
* Workaround for 1F9LOQO. Arrays are ABSTRACT in JDK, but that is
* not in the specification. Since we want to be compatible for
* X-loading, we have to pretend we have the same shape
*/
boolean isArray = cl.isArray();
if (isArray) {
classModifiers |= Modifier.ABSTRACT;
}
// Required for JDK UID compatibility
if (cl.isInterface() && !Modifier.isPublic(classModifiers)) {
classModifiers &= ~Modifier.ABSTRACT;
}
output.writeInt(classModifiers);
/*
* In JDK1.2 arrays implement Cloneable and Serializable but not in
* JDK 1.1.7. So, JDK 1.2 "pretends" arrays have no interfaces when
* computing SHA-1 to be compatible.
*/
if (!isArray) {
// Interface information
Class<?>[] interfaces = cl.getInterfaces();
if (interfaces.length > 1) {
// Only attempt to sort if really needed (saves object
// creation, etc)
Comparator<Class<?>> interfaceComparator = new Comparator<Class<?>>() {
public int compare(Class<?> itf1, Class<?> itf2) {
return itf1.getName().compareTo(itf2.getName());
}
};
Arrays.sort(interfaces, interfaceComparator);
}
// Dump them
for (int i = 0; i < interfaces.length; i++) {
output.writeUTF(interfaces[i].getName());
}
}
// Field information
if (fields.length > 1) {
// Only attempt to sort if really needed (saves object creation,
// etc)
Comparator<Field> fieldComparator = new Comparator<Field>() {
public int compare(Field field1, Field field2) {
return field1.getName().compareTo(field2.getName());
}
};
Arrays.sort(fields, fieldComparator);
}
// Dump them
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
int modifiers = field.getModifiers() & FIELD_MODIFIERS_MASK;
boolean skip = Modifier.isPrivate(modifiers) &&
(Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers));
if (!skip) {
// write name, modifier & "descriptor" of all but private
// static and private transient
output.writeUTF(field.getName());
output.writeInt(modifiers);
output.writeUTF(descriptorForFieldSignature(getFieldSignature(field)));
}
}
/*
* Normally constructors come before methods (because <init> <
* anyMethodName). However, <clinit> is an exception. Besides,
* reflect will not let us get to it.
*/
if (hasClinit(cl)) {
// write name, modifier & "descriptor"
output.writeUTF(CLINIT_NAME);
output.writeInt(CLINIT_MODIFIERS);
output.writeUTF(CLINIT_SIGNATURE);
}
// Constructor information
Constructor<?>[] constructors = cl.getDeclaredConstructors();
if (constructors.length > 1) {
// Only attempt to sort if really needed (saves object creation,
// etc)
Comparator<Constructor<?>> constructorComparator = new Comparator<Constructor<?>>() {
public int compare(Constructor<?> ctr1, Constructor<?> ctr2) {
// All constructors have same name, so we sort based on
// signature
return (getConstructorSignature(ctr1)
.compareTo(getConstructorSignature(ctr2)));
}
};
Arrays.sort(constructors, constructorComparator);
}
// Dump them
for (int i = 0; i < constructors.length; i++) {
Constructor<?> constructor = constructors[i];
int modifiers = constructor.getModifiers()
& METHOD_MODIFIERS_MASK;
boolean isPrivate = Modifier.isPrivate(modifiers);
if (!isPrivate) {
/*
* write name, modifier & "descriptor" of all but private
* ones
*
* constructor.getName() returns the constructor name as
* typed, not the VM name
*/
output.writeUTF("<init>");
output.writeInt(modifiers);
output.writeUTF(descriptorForSignature(
getConstructorSignature(constructor)).replace('/',
'.'));
}
}
// Method information
Method[] methods = cl.getDeclaredMethods();
if (methods.length > 1) {
Comparator<Method> methodComparator = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
int result = m1.getName().compareTo(m2.getName());
if (result == 0) {
// same name, signature will tell which one comes
// first
return getMethodSignature(m1).compareTo(
getMethodSignature(m2));
}
return result;
}
};
Arrays.sort(methods, methodComparator);
}
// Dump them
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
int modifiers = method.getModifiers() & METHOD_MODIFIERS_MASK;
boolean isPrivate = Modifier.isPrivate(modifiers);
if (!isPrivate) {
// write name, modifier & "descriptor" of all but private
// ones
output.writeUTF(method.getName());
output.writeInt(modifiers);
output.writeUTF(descriptorForSignature(
getMethodSignature(method)).replace('/', '.'));
}
}
} catch (IOException e) {
throw new RuntimeException(e + " computing SHA-1/SUID");
}
// now compute the UID based on the SHA
byte[] hash = digest.digest(sha.toByteArray());
return Memory.peekLong(hash, 0, ByteOrder.LITTLE_ENDIAN);
}
/**
* Returns what the serialization specification calls "descriptor" given a
* field signature.
*
* @param signature
* a field signature
* @return containing the descriptor
*/
private static String descriptorForFieldSignature(String signature) {
return signature.replace('.', '/');
}
/**
* Return what the serialization specification calls "descriptor" given a
* method/constructor signature.
*
* @param signature
* a method or constructor signature
* @return containing the descriptor
*/
private static String descriptorForSignature(String signature) {
return signature.substring(signature.indexOf("("));
}
/**
* Return the java.lang.reflect.Field {@code serialPersistentFields}
* if class {@code cl} implements it. Return null otherwise.
*
* @param cl
* a java.lang.Class which to test
* @return {@code java.lang.reflect.Field} if the class has
* serialPersistentFields {@code null} if the class does not
* have serialPersistentFields
*/
static Field fieldSerialPersistentFields(Class<?> cl) {
try {
Field f = cl.getDeclaredField("serialPersistentFields");
int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPrivate(modifiers)
&& Modifier.isFinal(modifiers)) {
if (f.getType() == ARRAY_OF_FIELDS) {
return f;
}
}
} catch (NoSuchFieldException nsm) {
// Ignored
}
return null;
}
/**
* Returns the class (java.lang.Class) for this descriptor.
*
* @return the class in the local VM that this descriptor represents;
* {@code null} if there is no corresponding class.
*/
public Class<?> forClass() {
return resolvedClass;
}
/**
* Create and return a new instance of class 'instantiationClass'
* using JNI to call the constructor chosen by resolveConstructorClass.
*
* The returned instance may have uninitialized fields, including final fields.
*/
Object newInstance(Class<?> instantiationClass) throws InvalidClassException {
resolveConstructorClass(instantiationClass);
return newInstance(instantiationClass, resolvedConstructorMethodId);
}
private static native Object newInstance(Class<?> instantiationClass, long methodId);
private Class<?> resolveConstructorClass(Class<?> objectClass) throws InvalidClassException {
if (resolvedConstructorClass != null) {
return resolvedConstructorClass;
}
// 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 ?
boolean wasSerializable = (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0;
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(EmptyArray.CLASS);
} catch (NoSuchMethodException ignored) {
}
}
// Has to have an empty constructor
if (constructor == null) {
String className = constructorClass != null ? constructorClass.getName() : null;
throw new InvalidClassException(className, "IllegalAccessException");
}
int constructorModifiers = constructor.getModifiers();
boolean isPublic = Modifier.isPublic(constructorModifiers);
boolean isProtected = Modifier.isProtected(constructorModifiers);
boolean isPrivate = Modifier.isPrivate(constructorModifiers);
// Now we must check if the empty constructor is visible to the
// instantiation class
boolean wasExternalizable = (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0;
if (isPrivate || (wasExternalizable && !isPublic)) {
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 (!isPublic && !isProtected) {
// Not public, not private and not protected...means default
// visibility. Check if same package
if (!inSamePackage(constructorClass, objectClass)) {
throw new InvalidClassException(constructorClass.getName(), "IllegalAccessException");
}
}
resolvedConstructorClass = constructorClass;
resolvedConstructorMethodId = getConstructorId(resolvedConstructorClass);
return constructorClass;
}
private static native long getConstructorId(Class<?> c);
/**
* 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 == -1) {
return true; // both of them are in default package
}
return nameC1.regionMatches(0, nameC2, 0, indexDotC1);
}
/**
* Return a String representing the signature for a Constructor {@code c}.
*
* @param c
* a java.lang.reflect.Constructor for which to compute the
* signature
* @return the constructor's signature
*/
static native String getConstructorSignature(Constructor<?> c);
/**
* Gets a field descriptor of the class represented by this class
* descriptor.
*
* @param name
* the name of the desired field.
* @return the field identified by {@code name} or {@code null} if there is
* no such field.
*/
public ObjectStreamField getField(String name) {
ObjectStreamField[] allFields = getFields();
for (int i = 0; i < allFields.length; i++) {
ObjectStreamField f = allFields[i];
if (f.getName().equals(name)) {
return f;
}
}
return null;
}
/**
* Returns the collection of field descriptors for the fields of the
* corresponding class
*
* @return the receiver's collection of declared fields for the class it
* represents
*/
ObjectStreamField[] fields() {
if (fields == null) {
Class<?> forCl = forClass();
if (forCl != null && isSerializable() && !forCl.isArray()) {
buildFieldDescriptors(forCl.getDeclaredFields());
} else {
// Externalizables or arrays do not need FieldDesc info
setFields(NO_FIELDS);
}
}
return fields;
}
/**
* Returns a collection of field descriptors for the serialized fields of
* the class represented by this class descriptor.
*
* @return an array of field descriptors or an array of length zero if there
* are no fields in this descriptor's class.
*/
public ObjectStreamField[] getFields() {
copyFieldAttributes();
return loadFields == null ? fields().clone() : loadFields.clone();
}
private transient volatile List<ObjectStreamClass> cachedHierarchy;
List<ObjectStreamClass> getHierarchy() {
List<ObjectStreamClass> result = cachedHierarchy;
if (result == null) {
cachedHierarchy = result = makeHierarchy();
}
return result;
}
private List<ObjectStreamClass> makeHierarchy() {
ArrayList<ObjectStreamClass> result = new ArrayList<ObjectStreamClass>();
for (ObjectStreamClass osc = this; osc != null; osc = osc.getSuperclass()) {
result.add(0, osc);
}
return result;
}
/**
* If a Class uses "serialPersistentFields" to define the serialized fields,
* this.loadFields cannot get the "unshared" information when deserializing
* fields using current implementation of ObjectInputStream. This method
* provides a way to copy the "unshared" attribute from this.fields.
*
*/
private void copyFieldAttributes() {
if ((loadFields == null) || fields == null) {
return;
}
for (int i = 0; i < loadFields.length; i++) {
ObjectStreamField loadField = loadFields[i];
String name = loadField.getName();
for (int j = 0; j < fields.length; j++) {
ObjectStreamField field = fields[j];
if (name.equals(field.getName())) {
loadField.setUnshared(field.isUnshared());
loadField.setOffset(field.getOffset());
break;
}
}
}
}
/**
* Returns the collection of field descriptors for the input fields of the
* corresponding class
*
* @return the receiver's collection of input fields for the class it
* represents
*/
ObjectStreamField[] getLoadFields() {
return loadFields;
}
/**
* Return a String representing the signature for a field {@code f}.
*
* @param f
* a java.lang.reflect.Field for which to compute the signature
* @return the field's signature
*/
private static native String getFieldSignature(Field f);
/**
* Returns the flags for this descriptor, where possible combined values are
*
* ObjectStreamConstants.SC_WRITE_METHOD
* ObjectStreamConstants.SC_SERIALIZABLE
* ObjectStreamConstants.SC_EXTERNALIZABLE
*
* @return byte the receiver's flags for the class it represents
*/
byte getFlags() {
return flags;
}
/**
* Return a String representing the signature for a method {@code m}.
*
* @param m
* a java.lang.reflect.Method for which to compute the signature
* @return the method's signature
*/
static native String getMethodSignature(Method m);
/**
* Returns the name of the class represented by this descriptor.
*
* @return the fully qualified name of the class this descriptor represents.
*/
public String getName() {
return className;
}
/**
* Returns the Serial Version User ID of the class represented by this
* descriptor.
*
* @return the SUID for the class represented by this descriptor.
*/
public long getSerialVersionUID() {
return svUID;
}
/**
* Returns the descriptor (ObjectStreamClass) of the superclass of the class
* represented by the receiver.
*
* @return an ObjectStreamClass representing the superclass of the class
* represented by the receiver.
*/
ObjectStreamClass getSuperclass() {
return superclass;
}
/**
* Return true if the given class {@code cl} has the
* compiler-generated method {@code clinit}. Even though it is
* compiler-generated, it is used by the serialization code to compute SUID.
* This is unfortunate, since it may depend on compiler optimizations in
* some cases.
*
* @param cl
* a java.lang.Class which to test
* @return {@code true} if the class has <clinit> {@code false}
* if the class does not have <clinit>
*/
private static native boolean hasClinit(Class<?> cl);
/**
* Return true if instances of class {@code cl} are Externalizable,
* false otherwise.
*
* @param cl
* a java.lang.Class which to test
* @return {@code true} if instances of the class are Externalizable
* {@code false} if instances of the class are not
* Externalizable
*
* @see Object#hashCode
*/
static boolean isExternalizable(Class<?> cl) {
return EXTERNALIZABLE.isAssignableFrom(cl);
}
/**
* Return true if the type code
* <code>typecode<code> describes a primitive type
*
* @param typecode a char describing the typecode
* @return {@code true} if the typecode represents a primitive type
* {@code false} if the typecode represents an Object type (including arrays)
*
* @see Object#hashCode
*/
static boolean isPrimitiveType(char typecode) {
return !(typecode == '[' || typecode == 'L');
}
/**
* Return true if instances of class {@code cl} are Serializable,
* false otherwise.
*
* @param cl
* a java.lang.Class which to test
* @return {@code true} if instances of the class are Serializable
* {@code false} if instances of the class are not
* Serializable
*
* @see Object#hashCode
*/
static boolean isSerializable(Class<?> cl) {
return SERIALIZABLE.isAssignableFrom(cl);
}
/**
* Resolves the class properties, if they weren't already
*/
private void resolveProperties() {
if (arePropertiesResolved) {
return;
}
Class<?> cl = forClass();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
isSerializable = isSerializable(cl);
isExternalizable = isExternalizable(cl);
arePropertiesResolved = true;
}
boolean isSerializable() {
resolveProperties();
return isSerializable;
}
boolean isExternalizable() {
resolveProperties();
return isExternalizable;
}
boolean isProxy() {
resolveProperties();
return isProxy;
}
boolean isEnum() {
resolveProperties();
return isEnum;
}
/**
* Returns the descriptor for a serializable class.
* Returns null if the class doesn't implement {@code Serializable} or {@code Externalizable}.
*
* @param cl
* a java.lang.Class for which to obtain the corresponding
* descriptor
* @return the corresponding descriptor if the class is serializable or
* externalizable; null otherwise.
*/
public static ObjectStreamClass lookup(Class<?> cl) {
ObjectStreamClass osc = lookupStreamClass(cl);
return (osc.isSerializable() || osc.isExternalizable()) ? osc : null;
}
/**
* Returns the descriptor for any class, whether or not the class
* implements Serializable or Externalizable.
*
* @param cl
* a java.lang.Class for which to obtain the corresponding
* descriptor
* @return the descriptor
* @since 1.6
*/
public static ObjectStreamClass lookupAny(Class<?> cl) {
return lookupStreamClass(cl);
}
/**
* Return the descriptor (ObjectStreamClass) corresponding to the class
* {@code cl}. Returns an ObjectStreamClass even if instances of the
* class cannot be serialized
*
* @param cl
* a java.langClass for which to obtain the corresponding
* descriptor
* @return the corresponding descriptor
*/
static ObjectStreamClass lookupStreamClass(Class<?> cl) {
WeakHashMap<Class<?>, ObjectStreamClass> tlc = getCache();
ObjectStreamClass cachedValue = tlc.get(cl);
if (cachedValue == null) {
cachedValue = createClassDesc(cl);
tlc.put(cl, cachedValue);
}
return cachedValue;
}
/**
* A ThreadLocal cache for lookupStreamClass, with the possibility of discarding the thread
* local storage content when the heap is exhausted.
*/
private static SoftReference<ThreadLocal<WeakHashMap<Class<?>, ObjectStreamClass>>> storage =
new SoftReference<ThreadLocal<WeakHashMap<Class<?>, ObjectStreamClass>>>(null);
private static WeakHashMap<Class<?>, ObjectStreamClass> getCache() {
ThreadLocal<WeakHashMap<Class<?>, ObjectStreamClass>> tls = storage.get();
if (tls == null) {
tls = new ThreadLocal<WeakHashMap<Class<?>, ObjectStreamClass>>() {
public WeakHashMap<Class<?>, ObjectStreamClass> initialValue() {
return new WeakHashMap<Class<?>, ObjectStreamClass>();
}
};
storage = new SoftReference<ThreadLocal<WeakHashMap<Class<?>, ObjectStreamClass>>>(tls);
}
return tls.get();
}
/**
* Return the java.lang.reflect.Method if class <code>cl</code> implements
* <code>methodName</code> . Return null otherwise.
*
* @param cl
* a java.lang.Class which to test
* @return <code>java.lang.reflect.Method</code> if the class implements
* writeReplace <code>null</code> if the class does not implement
* writeReplace
*/
static Method findMethod(Class<?> cl, String methodName) {
Class<?> search = cl;
Method method = null;
while (search != null) {
try {
method = search.getDeclaredMethod(methodName, (Class[]) null);
if (search == cl
|| (method.getModifiers() & Modifier.PRIVATE) == 0) {
method.setAccessible(true);
return method;
}
} catch (NoSuchMethodException nsm) {
}
search = search.getSuperclass();
}
return null;
}
/**
* Return the java.lang.reflect.Method if class <code>cl</code> implements
* private <code>methodName</code> . Return null otherwise.
*
* @param cl
* a java.lang.Class which to test
* @return {@code java.lang.reflect.Method} if the class implements
* writeReplace {@code null} if the class does not implement
* writeReplace
*/
static Method findPrivateMethod(Class<?> cl, String methodName,
Class<?>[] param) {
try {
Method method = cl.getDeclaredMethod(methodName, param);
if (Modifier.isPrivate(method.getModifiers()) && method.getReturnType() == void.class) {
method.setAccessible(true);
return method;
}
} catch (NoSuchMethodException nsm) {
// Ignored
}
return null;
}
boolean hasMethodWriteReplace() {
return (methodWriteReplace != null);
}
Method getMethodWriteReplace() {
return methodWriteReplace;
}
boolean hasMethodReadResolve() {
return (methodReadResolve != null);
}
Method getMethodReadResolve() {
return methodReadResolve;
}
boolean hasMethodWriteObject() {
return (methodWriteObject != null);
}
Method getMethodWriteObject() {
return methodWriteObject;
}
boolean hasMethodReadObject() {
return (methodReadObject != null);
}
Method getMethodReadObject() {
return methodReadObject;
}
boolean hasMethodReadObjectNoData() {
return (methodReadObjectNoData != null);
}
Method getMethodReadObjectNoData() {
return methodReadObjectNoData;
}
void initPrivateFields(ObjectStreamClass desc) {
methodWriteReplace = desc.methodWriteReplace;
methodReadResolve = desc.methodReadResolve;
methodWriteObject = desc.methodWriteObject;
methodReadObject = desc.methodReadObject;
methodReadObjectNoData = desc.methodReadObjectNoData;
}
/**
* Set the class (java.lang.Class) that the receiver represents
*
* @param c
* aClass, the new class that the receiver describes
*/
void setClass(Class<?> c) {
resolvedClass = c;
}
/**
* Set the collection of field descriptors for the fields of the
* corresponding class
*
* @param f
* ObjectStreamField[], the receiver's new collection of declared
* fields for the class it represents
*/
void setFields(ObjectStreamField[] f) {
fields = f;
}
/**
* Set the collection of field descriptors for the input fields of the
* corresponding class
*
* @param f
* ObjectStreamField[], the receiver's new collection of input
* fields for the class it represents
*/
void setLoadFields(ObjectStreamField[] f) {
loadFields = f;
}
/**
* Set the flags for this descriptor, where possible combined values are
*
* ObjectStreamConstants.SC_WRITE_METHOD
* ObjectStreamConstants.SC_SERIALIZABLE
* ObjectStreamConstants.SC_EXTERNALIZABLE
*
* @param b
* byte, the receiver's new flags for the class it represents
*/
void setFlags(byte b) {
flags = b;
}
/**
* Set the name of the class represented by the receiver
*
* @param newName
* a String, the new fully qualified name of the class the
* receiver represents
*/
void setName(String newName) {
className = newName;
}
/**
* Set the Serial Version User ID of the class represented by the receiver
*
* @param l
* a long, the new SUID for the class represented by the receiver
*/
void setSerialVersionUID(long l) {
svUID = l;
}
/**
* Set the descriptor for the superclass of the class described by the
* receiver
*
* @param c
* an ObjectStreamClass, the new ObjectStreamClass for the
* superclass of the class represented by the receiver
*/
void setSuperclass(ObjectStreamClass c) {
superclass = c;
}
private int primitiveSize(Class<?> type) {
if (type == byte.class || type == boolean.class) {
return 1;
}
if (type == short.class || type == char.class) {
return 2;
}
if (type == int.class || type == float.class) {
return 4;
}
if (type == long.class || type == double.class) {
return 8;
}
throw new AssertionError();
}
/**
* Returns a string containing a concise, human-readable description of this
* descriptor.
*
* @return a printable representation of this descriptor.
*/
@Override
public String toString() {
return getName() + ": static final long serialVersionUID =" + getSerialVersionUID() + "L;";
}
}