blob: c208b77499b98120993d357a6810fb54c34eb135 [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.WeakReference;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.harmony.misc.accessors.ObjectAccessor;
/**
* Describes a field for the purpose of serialization. Classes can define the
* collection of fields that are serialized, which may be different from the set
* of all declared fields.
*
* @see ObjectOutputStream#writeFields()
* @see ObjectInputStream#readFields()
*/
public class ObjectStreamField implements Comparable<Object> {
static final int FIELD_IS_NOT_RESOLVED = -1;
static final int FIELD_IS_ABSENT = -2;
// Declared name of the field
private String name;
// Declared type of the field
private Object type;
// offset of this field in the object
int offset;
// Cached version of intern'ed type String
private String typeString;
private boolean unshared;
private boolean isDeserialized;
private long assocFieldID = FIELD_IS_NOT_RESOLVED;
long getFieldID(ObjectAccessor accessor, Class<?> declaringClass) {
if (assocFieldID != FIELD_IS_NOT_RESOLVED) {
return assocFieldID;
} else {
try {
assocFieldID = accessor.getFieldID(declaringClass, name);
} catch(NoSuchFieldError e) {
assocFieldID = FIELD_IS_ABSENT;
}
return assocFieldID;
}
}
/**
* Constructs an ObjectStreamField with the specified name and type.
*
* @param name
* the name of the field.
* @param cl
* the type of the field.
* @throws NullPointerException
* if {@code name} or {@code cl} is {@code null}.
*/
public ObjectStreamField(String name, Class<?> cl) {
if (name == null || cl == null) {
throw new NullPointerException();
}
this.name = name;
this.type = new WeakReference<Class<?>>(cl);
}
/**
* Constructs an ObjectStreamField with the specified name, type and the
* indication if it is unshared.
*
* @param name
* the name of the field.
* @param cl
* the type of the field.
* @param unshared
* {@code true} if the field is written and read unshared;
* {@code false} otherwise.
* @throws NullPointerException
* if {@code name} or {@code cl} is {@code null}.
* @see ObjectOutputStream#writeUnshared(Object)
*/
public ObjectStreamField(String name, Class<?> cl, boolean unshared) {
if (name == null || cl == null) {
throw new NullPointerException();
}
this.name = name;
this.type = (cl.getClassLoader() == null) ? cl
: new WeakReference<Class<?>>(cl);
this.unshared = unshared;
}
/**
* Constructs an ObjectStreamField with the given name and the given type.
* The type may be null.
*
* @param signature
* A String representing the type of the field
* @param name
* a String, the name of the field, or null
*/
ObjectStreamField(String signature, String name) {
if (name == null) {
throw new NullPointerException();
}
this.name = name;
this.typeString = signature.replace('.', '/').intern();
this.isDeserialized = true;
}
/**
* Compares this field descriptor to the specified one. Checks first if one
* of the compared fields has a primitive type and the other one not. If so,
* the field with the primitive type is considered to be "smaller". If both
* fields are equal, their names are compared.
*
* @param o
* the object to compare with.
* @return -1 if this field is "smaller" than field {@code o}, 0 if both
* fields are equal; 1 if this field is "greater" than field {@code
* o}.
*/
public int compareTo(Object o) {
ObjectStreamField f = (ObjectStreamField) o;
boolean thisPrimitive = this.isPrimitive();
boolean fPrimitive = f.isPrimitive();
// If one is primitive and the other isn't, we have enough info to
// compare
if (thisPrimitive != fPrimitive) {
return thisPrimitive ? -1 : 1;
}
// Either both primitives or both not primitives. Compare based on name.
return this.getName().compareTo(f.getName());
}
@Override
public boolean equals(Object arg0) {
return (arg0 instanceof ObjectStreamField) && (compareTo(arg0) == 0);
}
@Override
public int hashCode() {
return getName().hashCode();
}
/**
* Gets the name of this field.
*
* @return the field's name.
*/
public String getName() {
return name;
}
/**
* Gets the offset of this field in the object.
*
* @return this field's offset.
*/
public int getOffset() {
return offset;
}
/**
* Return the type of the field the receiver represents, this is an internal
* method
*
* @return A Class object representing the type of the field
*/
private Class<?> getTypeInternal() {
if (type instanceof WeakReference) {
return (Class<?>) ((WeakReference<?>) type).get();
}
return (Class<?>) type;
}
/**
* Gets the type of this field.
*
* @return a {@code Class} object representing the type of the field.
*/
public Class<?> getType() {
Class<?> cl = getTypeInternal();
if (isDeserialized && !cl.isPrimitive()) {
return Object.class;
}
return cl;
}
/**
* Gets a character code for the type of this field. The following codes are
* used:
*
* <pre>
* B byte
* C char
* D double
* F float
* I int
* J long
* L class or interface
* S short
* Z boolean
* [ array
* </pre>
*
* @return the field's type code.
*/
public char getTypeCode() {
Class<?> t = getTypeInternal();
if (t == Integer.TYPE) {
return 'I';
}
if (t == Byte.TYPE) {
return 'B';
}
if (t == Character.TYPE) {
return 'C';
}
if (t == Short.TYPE) {
return 'S';
}
if (t == Boolean.TYPE) {
return 'Z';
}
if (t == Long.TYPE) {
return 'J';
}
if (t == Float.TYPE) {
return 'F';
}
if (t == Double.TYPE) {
return 'D';
}
if (t.isArray()) {
return '[';
}
return 'L';
}
/**
* Gets the type signature used by the VM to represent the type of this
* field.
*
* @return the signature of this field's class or {@code null} if this
* field's type is primitive.
*/
public String getTypeString() {
if (isPrimitive()) {
return null;
}
if (typeString == null) {
Class<?> t = getTypeInternal();
String typeName = t.getName().replace('.', '/');
String str = (t.isArray()) ? typeName : ("L" + typeName + ';'); //$NON-NLS-1$
typeString = str.intern();
}
return typeString;
}
/**
* Indicates whether this field's type is a primitive type.
*
* @return {@code true} if this field's type is primitive; {@code false} if
* the type of this field is a regular class.
*/
public boolean isPrimitive() {
Class<?> t = getTypeInternal();
return t != null && t.isPrimitive();
}
/**
* Sets this field's offset in the object.
*
* @param newValue
* the field's new offset.
*/
protected void setOffset(int newValue) {
this.offset = newValue;
}
/**
* Returns a string containing a concise, human-readable description of this
* field descriptor.
*
* @return a printable representation of this descriptor.
*/
@Override
public String toString() {
return this.getClass().getName() + '(' + getName() + ':'
+ getTypeInternal() + ')';
}
/**
* Sorts the fields for dumping. Primitive types come first, then regular
* types.
*
* @param fields
* ObjectStreamField[] fields to be sorted
*/
static void sortFields(ObjectStreamField[] fields) {
// Sort if necessary
if (fields.length > 1) {
Comparator<ObjectStreamField> fieldDescComparator = new Comparator<ObjectStreamField>() {
public int compare(ObjectStreamField f1, ObjectStreamField f2) {
return f1.compareTo(f2);
}
};
Arrays.sort(fields, fieldDescComparator);
}
}
void resolve(ClassLoader loader) {
if (typeString.length() == 1) {
switch (typeString.charAt(0)) {
case 'I':
type = Integer.TYPE;
return;
case 'B':
type = Byte.TYPE;
return;
case 'C':
type = Character.TYPE;
return;
case 'S':
type = Short.TYPE;
return;
case 'Z':
type = Boolean.TYPE;
return;
case 'J':
type = Long.TYPE;
return;
case 'F':
type = Float.TYPE;
return;
case 'D':
type = Double.TYPE;
return;
}
}
String className = typeString.replace('/', '.');
if (className.charAt(0) == 'L') {
// remove L and ;
className = className.substring(1, className.length() - 1);
}
try {
Class<?> cl = Class.forName(className, false, loader);
type = (cl.getClassLoader() == null) ? cl
: new WeakReference<Class<?>>(cl);
} catch (ClassNotFoundException e) {
// Ignored
}
}
/**
* Indicats whether this field is unshared.
*
* @return {@code true} if this field is unshared, {@code false} otherwise.
*/
public boolean isUnshared() {
return unshared;
}
void setUnshared(boolean unshared) {
this.unshared = unshared;
}
}