in xstream/src/java/com/thoughtworks/xstream/converters/reflection/AbstractReflectionConverter.java [96:266]
protected void doMarshal(final Object source, final HierarchicalStreamWriter writer,
final MarshallingContext context) {
final List<FieldInfo> fields = new ArrayList<>();
final Map<String, Field> defaultFieldDefinition = new HashMap<>();
final Class<?> sourceType = source.getClass();
// Attributes might be preferred to child elements ...
reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {
final Set<String> writtenAttributes = new HashSet<>();
@Override
public void visit(final String fieldName, final Class<?> type, final Class<?> definedIn,
final Object value) {
if (!mapper.shouldSerializeMember(definedIn, fieldName)) {
return;
}
if (!defaultFieldDefinition.containsKey(fieldName)) {
Class<?> lookupType = sourceType;
// See XSTR-457 and OmitFieldsTest
if (definedIn != sourceType && !mapper.shouldSerializeMember(lookupType, fieldName)) {
lookupType = definedIn;
}
defaultFieldDefinition.put(fieldName, reflectionProvider.getField(lookupType, fieldName));
}
final SingleValueConverter converter = mapper.getConverterFromItemType(fieldName, type, definedIn);
if (converter != null) {
final String attribute = mapper.aliasForAttribute(mapper.serializedMember(definedIn, fieldName));
if (value != null) {
if (writtenAttributes.contains(fieldName)) {
final ConversionException exception = new ConversionException(
"Cannot write field as attribute for object, attribute name already in use");
exception.add("field-name", fieldName);
exception.add("object-type", sourceType.getName());
throw exception;
}
final String str = converter.toString(value);
if (str != null) {
writer.addAttribute(attribute, str);
}
}
writtenAttributes.add(fieldName);
} else {
fields.add(new FieldInfo(fieldName, type, definedIn, value));
}
}
});
final FieldMarshaller fieldMarshaller = new FieldMarshaller() {
@Override
public void writeField(final String fieldName, final String aliasName, final Class<?> fieldType,
final Class<?> definedIn, final Object newObj) {
final Class<?> actualType = newObj != null ? newObj.getClass() : fieldType;
writer.startNode(aliasName != null ? aliasName : mapper.serializedMember(sourceType, fieldName),
actualType);
if (newObj != null) {
final Class<?> defaultType = mapper.defaultImplementationOf(fieldType);
if (!actualType.equals(defaultType)) {
final String serializedClassName = mapper.serializedClass(actualType);
if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
final String attributeName = mapper.aliasForSystemAttribute("class");
if (attributeName != null) {
writer.addAttribute(attributeName, serializedClassName);
}
}
}
final Field defaultField = defaultFieldDefinition.get(fieldName);
if (defaultField.getDeclaringClass() != definedIn) {
final String attributeName = mapper.aliasForSystemAttribute("defined-in");
if (attributeName != null) {
writer.addAttribute(attributeName, mapper.serializedClass(definedIn));
}
}
final Field field = reflectionProvider.getField(definedIn, fieldName);
marshallField(context, newObj, field);
}
writer.endNode();
}
@Override
public void writeItem(final Object item) {
if (item == null) {
final String name = mapper.serializedClass(null);
writer.startNode(name, Mapper.Null.class);
writer.endNode();
} else {
final String name = mapper.serializedClass(item.getClass());
writer.startNode(name, item.getClass());
context.convertAnother(item);
writer.endNode();
}
}
};
final Map<String, Set<Mapper.ImplicitCollectionMapping>> hiddenMappers =
new HashMap<>();
for (final FieldInfo info : fields) {
if (info.value != null) {
final boolean isCollection = info.value instanceof Collection;
final boolean isMap = info.value instanceof Map;
final boolean isArray = info.value.getClass().isArray();
final Field defaultField = defaultFieldDefinition.get(info.fieldName);
Mapper.ImplicitCollectionMapping mapping = isCollection || isMap || isArray
? mapper.getImplicitCollectionDefForFieldName(defaultField.getDeclaringClass() == info.definedIn
? sourceType
: info.definedIn, info.fieldName)
: null;
if (mapping != null) {
Set<Mapper.ImplicitCollectionMapping> mappings = hiddenMappers.get(info.fieldName);
if (mappings == null) {
mappings = new HashSet<>();
mappings.add(mapping);
hiddenMappers.put(info.fieldName, mappings);
} else {
if (!mappings.add(mapping)) {
mapping = null;
}
}
}
if (mapping != null) {
if (context instanceof ReferencingMarshallingContext) {
if (info.value != Collections.EMPTY_LIST
&& info.value != Collections.EMPTY_SET
&& info.value != Collections.EMPTY_MAP) {
final ReferencingMarshallingContext<?> refContext =
(ReferencingMarshallingContext<?>)context;
refContext.registerImplicit(info.value);
}
}
final boolean isEntry = isMap && mapping.getKeyFieldName() == null;
for (final Iterator<?> iter = isArray
? new ArrayIterator(info.value)
: isCollection
? ((Collection<?>)info.value).iterator()
: isEntry
? ((Map<?, ?>)info.value).entrySet().iterator()
: ((Map<?, ?>)info.value).values().iterator(); iter.hasNext();) {
final Object obj = iter.next();
final String itemName;
final Class<?> itemType;
if (obj == null) {
itemType = Object.class;
itemName = mapper.serializedClass(null);
} else if (isEntry) {
final String entryName = mapping.getItemFieldName() != null
? mapping.getItemFieldName()
: mapper.serializedClass(Map.Entry.class);
final Map.Entry<?, ?> entry = (Map.Entry<?, ?>)obj;
writer.startNode(entryName, entry.getClass());
fieldMarshaller.writeItem(entry.getKey());
fieldMarshaller.writeItem(entry.getValue());
writer.endNode();
continue;
} else if (mapping.getItemFieldName() != null) {
itemType = mapping.getItemType();
itemName = mapping.getItemFieldName();
} else {
itemType = obj.getClass();
itemName = mapper.serializedClass(itemType);
}
fieldMarshaller.writeField(info.fieldName, itemName, itemType, info.definedIn, obj);
}
} else {
fieldMarshaller.writeField(info.fieldName, null, info.type, info.definedIn, info.value);
}
}
}
}