| /* |
| * 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;"; |
| } |
| |
| /** |
| * Checks the local class to make sure it is valid for {@link ObjectStreamConstants#TC_OBJECT} |
| * deserialization. Also performs some sanity checks of the stream data. This method is used |
| * during deserialization to confirm the local class is likely to be compatible with the coming |
| * stream data, but before an instance is instantiated. |
| * |
| * @hide used internally during deserialization |
| */ |
| public Class<?> checkAndGetTcObjectClass() throws InvalidClassException { |
| // We check some error possibilities that might cause problems later. |
| boolean wasSerializable = (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0; |
| boolean wasExternalizable = (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0; |
| if (wasSerializable == wasExternalizable) { |
| throw new InvalidClassException( |
| getName() + " stream data is corrupt: SC_SERIALIZABLE=" + wasSerializable |
| + " SC_EXTERNALIZABLE=" + wasExternalizable |
| + ", classDescFlags must have one or the other"); |
| } |
| |
| // TC_ENUM is handled elsewhere. See checkAndGetTcEnumClass(). |
| if (isEnum()) { |
| throw new InvalidClassException( |
| getName() + " local class is incompatible: Local class is an enum, streamed" |
| + " data is tagged with TC_OBJECT"); |
| } |
| |
| // isSerializable() is true if the local class implements Serializable. Externalizable |
| // classes are also Serializable via inheritance. |
| if (!isSerializable()) { |
| throw new InvalidClassException(getName() + " local class is incompatible: Not" |
| + " Serializable"); |
| } |
| |
| // The stream class was externalizable, but is only serializable locally. |
| if (wasExternalizable != isExternalizable()) { |
| throw new InvalidClassException( |
| getName() + " local class is incompatible: Local class is Serializable, stream" |
| + " data requires Externalizable"); |
| } |
| |
| // The following are left unchecked and thus are treated leniently at this point. |
| // SC_BLOCK_DATA may be set iff SC_EXTERNALIZABLE is set AND version 2 of the protocol is in |
| // use. |
| // SC_ENUM should not be set. |
| |
| return forClass(); |
| } |
| |
| /** |
| * Checks the local class to make sure it is valid for {@link ObjectStreamConstants#TC_ENUM} |
| * deserialization. This method is used during deserialization to confirm the local class is |
| * likely to be compatible with the coming stream data, but before an instance is instantiated. |
| * |
| * @hide used internally during deserialization |
| */ |
| public Class<?> checkAndGetTcEnumClass() throws InvalidClassException { |
| if (!isEnum()) { |
| throw new InvalidClassException( |
| getName() + " local class is incompatible: Local class is not an enum," |
| + " streamed data is tagged with TC_ENUM"); |
| } |
| |
| // The stream flags are expected to be SC_SERIALIZABLE | SC_ENUM but these and the |
| // other flags are not used when reading enum data so they are treated leniently. |
| |
| return forClass(); |
| } |
| } |