in xstream/src/java/com/thoughtworks/xstream/converters/reflection/SerializableConverter.java [126:294]
public void doMarshal(final Object source, final HierarchicalStreamWriter writer,
final MarshallingContext context) {
final String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_SERIALIZATION);
if (attributeName != null) {
writer.addAttribute(attributeName, ATTRIBUTE_VALUE_CUSTOM);
}
// this is an array as it's a non final value that's accessed from an anonymous inner class.
final Class<?>[] currentTypeRef = new Class<?>[1];
final boolean[] writtenClassWrapper = {false};
final CustomObjectOutputStream.StreamCallback callback = new CustomObjectOutputStream.StreamCallback() {
@Override
public void writeToStream(final Object object) {
if (object == null) {
writer.startNode(ELEMENT_NULL);
writer.endNode();
} else {
writer.startNode(mapper.serializedClass(object.getClass()), object.getClass());
context.convertAnother(object);
writer.endNode();
}
}
@Override
public void writeFieldsToStream(final Map<String, Object> fields) {
final Class<?> currentType = currentTypeRef[0];
final ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType);
writer.startNode(ELEMENT_DEFAULT);
for (final String name : fields.keySet()) {
if (!mapper.shouldSerializeMember(currentType, name)) {
continue;
}
final ObjectStreamField field = objectStreamClass.getField(name);
final Object value = fields.get(name);
if (field == null) {
throw new MissingFieldException(value.getClass().getName(), name);
}
if (value != null) {
writer.startNode(mapper.serializedMember(source.getClass(), name), value.getClass());
if (field.getType() != value.getClass() && !field.getType().isPrimitive()) {
final String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
if (attributeName != null) {
writer.addAttribute(attributeName, mapper.serializedClass(value.getClass()));
}
}
context.convertAnother(value);
writer.endNode();
}
}
writer.endNode();
}
@Override
public void defaultWriteObject() {
boolean writtenDefaultFields = false;
final Class<?> currentType = currentTypeRef[0];
final ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(currentType);
if (objectStreamClass == null) {
return;
}
for (final ObjectStreamField field : objectStreamClass.getFields()) {
final Object value = readField(field, currentType, source);
if (value != null) {
if (!writtenClassWrapper[0]) {
writer.startNode(mapper.serializedClass(currentType));
writtenClassWrapper[0] = true;
}
if (!writtenDefaultFields) {
writer.startNode(ELEMENT_DEFAULT);
writtenDefaultFields = true;
}
if (!mapper.shouldSerializeMember(currentType, field.getName())) {
continue;
}
final Class<?> actualType = value.getClass();
writer.startNode(mapper.serializedMember(source.getClass(), field.getName()), actualType);
final Class<?> defaultType = mapper.defaultImplementationOf(field.getType());
if (!actualType.equals(defaultType)) {
final String attributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
if (attributeName != null) {
writer.addAttribute(attributeName, mapper.serializedClass(actualType));
}
}
context.convertAnother(value);
writer.endNode();
}
}
if (writtenClassWrapper[0] && !writtenDefaultFields) {
writer.startNode(ELEMENT_DEFAULT);
writer.endNode();
} else if (writtenDefaultFields) {
writer.endNode();
}
}
@Override
public void flush() {
writer.flush();
}
@Override
public void close() {
throw new UnsupportedOperationException(
"Objects are not allowed to call ObjectOutputStream.close() from writeObject()");
}
};
try {
boolean mustHandleUnserializableParent = false;
for (final Class<?> currentType : hierarchyFor(source.getClass())) {
currentTypeRef[0] = currentType;
if (!Serializable.class.isAssignableFrom(currentType)) {
mustHandleUnserializableParent = true;
continue;
} else {
if (mustHandleUnserializableParent) {
marshalUnserializableParent(writer, context, source);
mustHandleUnserializableParent = false;
}
if (serializationMembers.supportsWriteObject(currentType, false)) {
writtenClassWrapper[0] = true;
writer.startNode(mapper.serializedClass(currentType));
if (currentType != mapper.defaultImplementationOf(currentType)) {
final String classAttributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
if (classAttributeName != null) {
writer.addAttribute(classAttributeName, currentType.getName());
}
}
@SuppressWarnings("resource")
final CustomObjectOutputStream objectOutputStream = CustomObjectOutputStream.getInstance(
context, callback);
serializationMembers.callWriteObject(currentType, source, objectOutputStream);
objectOutputStream.popCallback();
writer.endNode();
} else if (serializationMembers.supportsReadObject(currentType, false)) {
// Special case for objects that have readObject(), but not writeObject().
// The class wrapper is always written, whether or not this class in the hierarchy has
// serializable fields. This guarantees that readObject() will be called upon deserialization.
writtenClassWrapper[0] = true;
writer.startNode(mapper.serializedClass(currentType));
if (currentType != mapper.defaultImplementationOf(currentType)) {
final String classAttributeName = mapper.aliasForSystemAttribute(ATTRIBUTE_CLASS);
if (classAttributeName != null) {
writer.addAttribute(classAttributeName, currentType.getName());
}
}
callback.defaultWriteObject();
writer.endNode();
} else {
writtenClassWrapper[0] = false;
callback.defaultWriteObject();
if (writtenClassWrapper[0]) {
writer.endNode();
}
}
}
}
} catch (final IOException e) {
throw new StreamException("Cannot write defaults", e);
}
}