com.feilong.lib.xstream.converters.extended.ToAttributedValueConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of feilong Show documentation
Show all versions of feilong Show documentation
feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.
/*
* Copyright (C) 2011, 2013, 2016, 2018 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 30. July 2011 by Joerg Schaible
*/
package com.feilong.lib.xstream.converters.extended;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.feilong.lib.xstream.converters.ConversionException;
import com.feilong.lib.xstream.converters.Converter;
import com.feilong.lib.xstream.converters.ConverterLookup;
import com.feilong.lib.xstream.converters.ConverterMatcher;
import com.feilong.lib.xstream.converters.MarshallingContext;
import com.feilong.lib.xstream.converters.SingleValueConverter;
import com.feilong.lib.xstream.converters.UnmarshallingContext;
import com.feilong.lib.xstream.converters.reflection.AbstractReflectionConverter.DuplicateFieldException;
import com.feilong.lib.xstream.converters.reflection.ReflectionProvider;
import com.feilong.lib.xstream.core.JVM;
import com.feilong.lib.xstream.core.util.FastField;
import com.feilong.lib.xstream.core.util.HierarchicalStreams;
import com.feilong.lib.xstream.core.util.Primitives;
import com.feilong.lib.xstream.io.HierarchicalStreamReader;
import com.feilong.lib.xstream.io.HierarchicalStreamWriter;
import com.feilong.lib.xstream.mapper.Mapper;
/**
* Converter that supports the definition of one field member that will be written as value and
* all other field members are written as attributes. The converter requires that all the field
* types (expect the one with the value) are handled by a {@link SingleValueConverter}. The
* value field is defined using the name of the type that declares the field and the field name
* itself. Therefore it is possible to define an inherited field as value. It is also possible
* to provide no value field at all, so that all fields are written as attributes.
*
* @author Jörg Schaible
* @since 1.4
*/
public class ToAttributedValueConverter implements Converter{
private static final String STRUCTURE_MARKER = "";
private final Class type;
private final Mapper mapper;
private final Mapper enumMapper;
private final ReflectionProvider reflectionProvider;
private final ConverterLookup lookup;
private final Field valueField;
/**
* Creates a new ToAttributedValueConverter instance.
*
* All field elements will be attributes, the element itself will have no value.
*
* @param type
* the type that is handled by this converter instance
* @param mapper
* the mapper in use
* @param reflectionProvider
* the reflection provider in use
* @param lookup
* the converter lookup in use
* @since 1.4.9
*/
public ToAttributedValueConverter(final Class type, final Mapper mapper, final ReflectionProvider reflectionProvider,
final ConverterLookup lookup){
this(type, mapper, reflectionProvider, lookup, null, null);
}
/**
* Creates a new ToAttributedValueConverter instance.
*
* @param type
* the type that is handled by this converter instance
* @param mapper
* the mapper in use
* @param reflectionProvider
* the reflection provider in use
* @param lookup
* the converter lookup in use
* @param valueFieldName
* the field defining the tag's value (may be null)
*/
public ToAttributedValueConverter(final Class type, final Mapper mapper, final ReflectionProvider reflectionProvider,
final ConverterLookup lookup, final String valueFieldName){
this(type, mapper, reflectionProvider, lookup, valueFieldName, null);
}
/**
* Creates a new ToAttributedValueConverter instance.
*
* @param type
* the type that is handled by this converter instance
* @param mapper
* the mapper in use
* @param reflectionProvider
* the reflection provider in use
* @param lookup
* the converter lookup in use
* @param valueFieldName
* the field defining the tag's value (may be null)
* @param valueDefinedIn
* the type defining the field
*/
public ToAttributedValueConverter(final Class type, final Mapper mapper, final ReflectionProvider reflectionProvider,
final ConverterLookup lookup, final String valueFieldName, Class valueDefinedIn){
this.type = type;
this.mapper = mapper;
this.reflectionProvider = reflectionProvider;
this.lookup = lookup;
if (valueFieldName == null){
valueField = null;
}else{
Field field = null;
try{
field = (valueDefinedIn != null ? valueDefinedIn : type).getDeclaredField(valueFieldName);
if (!field.isAccessible()){
field.setAccessible(true);
}
}catch (NoSuchFieldException e){
throw new IllegalArgumentException(e.getMessage() + ": " + valueFieldName);
}
this.valueField = field;
}
enumMapper = JVM.isVersion(5) ? UseAttributeForEnumMapper.createEnumMapper(mapper) : null;
}
@Override
public boolean canConvert(final Class type){
return this.type == type;
}
@Override
public void marshal(final Object source,final HierarchicalStreamWriter writer,final MarshallingContext context){
final Class sourceType = source.getClass();
final Map defaultFieldDefinition = new HashMap();
final String[] tagValue = new String[1];
final Object[] realValue = new Object[1];
final Class[] fieldType = new Class[1];
final Class[] definingType = new Class[1];
reflectionProvider.visitSerializableFields(source, (fieldName,type,definedIn,value) -> {
if (!mapper.shouldSerializeMember(definedIn, fieldName)){
return;
}
final FastField field = new FastField(definedIn, fieldName);
final String alias = mapper.serializedMember(definedIn, fieldName);
if (!defaultFieldDefinition.containsKey(alias)){
final Class lookupType = sourceType;
defaultFieldDefinition.put(alias, reflectionProvider.getField(lookupType, fieldName));
}else if (!fieldIsEqual(field)){
final ConversionException exception1 = new ConversionException("Cannot write attribute twice for object");
exception1.add("alias", alias);
exception1.add("type", sourceType.getName());
throw exception1;
}
ConverterMatcher converter = UseAttributeForEnumMapper.isEnum(type)
? (ConverterMatcher) enumMapper.getConverterFromItemType(null, type, null)
: (ConverterMatcher) mapper.getLocalConverter(definedIn, fieldName);
if (converter == null){
converter = lookup.lookupConverterForType(type);
}
if (value != null){
boolean isValueField = valueField != null && fieldIsEqual(field);
if (isValueField){
definingType[0] = definedIn;
fieldType[0] = type;
realValue[0] = value;
tagValue[0] = STRUCTURE_MARKER;
}
if (converter instanceof SingleValueConverter){
final String str = ((SingleValueConverter) converter).toString(value);
if (isValueField){
tagValue[0] = str;
}else{
if (str != null){
writer.addAttribute(alias, str);
}
}
}else{
if (!isValueField){
final ConversionException exception2 = new ConversionException("Cannot write element as attribute");
exception2.add("alias", alias);
exception2.add("type", sourceType.getName());
throw exception2;
}
}
}
});
if (tagValue[0] != null){
final Class actualType = realValue[0].getClass();
final Class defaultType = mapper.defaultImplementationOf(fieldType[0]);
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);
}
}
}
if (tagValue[0] == STRUCTURE_MARKER){
context.convertAnother(realValue[0]);
}else{
writer.setValue(tagValue[0]);
}
}
}
@Override
public Object unmarshal(final HierarchicalStreamReader reader,final UnmarshallingContext context){
final Object result = reflectionProvider.newInstance(context.getRequiredType());
final Class resultType = result.getClass();
final Set seenFields = new HashSet();
final Iterator it = reader.getAttributeNames();
final Set systemAttributes = new HashSet();
systemAttributes.add(mapper.aliasForSystemAttribute("class"));
// Process attributes before recursing into child elements.
while (it.hasNext()){
final String attrName = (String) it.next();
if (systemAttributes.contains(attrName)){
continue;
}
final String fieldName = mapper.realMember(resultType, attrName);
final Field field = reflectionProvider.getFieldOrNull(resultType, fieldName);
if (field != null){
if (Modifier.isTransient(field.getModifiers())){
continue;
}
Class type = field.getType();
final Class declaringClass = field.getDeclaringClass();
ConverterMatcher converter = UseAttributeForEnumMapper.isEnum(type)
? (ConverterMatcher) enumMapper.getConverterFromItemType(null, type, null)
: (ConverterMatcher) mapper.getLocalConverter(declaringClass, fieldName);
if (converter == null){
converter = lookup.lookupConverterForType(type);
}
if (!(converter instanceof SingleValueConverter)){
final ConversionException exception = new ConversionException("Cannot read field as a single value for object");
exception.add("field", fieldName);
exception.add("type", resultType.getName());
throw exception;
}
if (converter != null){
final Object value = ((SingleValueConverter) converter).fromString(reader.getAttribute(attrName));
if (type.isPrimitive()){
type = Primitives.box(type);
}
if (value != null && !type.isAssignableFrom(value.getClass())){
final ConversionException exception = new ConversionException("Cannot assign object to type");
exception.add("object type", value.getClass().getName());
exception.add("target type", type.getName());
throw exception;
}
reflectionProvider.writeField(result, fieldName, value, declaringClass);
if (!seenFields.add(new FastField(declaringClass, fieldName))){
throw new DuplicateFieldException(fieldName + " [" + declaringClass.getName() + "]");
}
}
}
}
if (valueField != null){
final Class classDefiningField = valueField.getDeclaringClass();
final String fieldName = valueField.getName();
final Field field = fieldName == null ? null : reflectionProvider.getField(classDefiningField, fieldName);
if (fieldName == null || field == null){
final ConversionException exception = new ConversionException("Cannot assign value to field of type");
exception.add("element", reader.getNodeName());
exception.add("field", fieldName);
exception.add("target type", context.getRequiredType().getName());
throw exception;
}
Class type;
final String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
if (classAttribute != null){
type = mapper.realClass(classAttribute);
}else{
type = mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, classDefiningField));
}
final Object value = context.convertAnother(result, type, mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
final Class definedType = reflectionProvider.getFieldType(result, fieldName, classDefiningField);
if (!definedType.isPrimitive()){
type = definedType;
}
if (value != null && !type.isAssignableFrom(value.getClass())){
final ConversionException exception = new ConversionException("Cannot assign object to type");
exception.add("object type", value.getClass().getName());
exception.add("target type", type.getName());
throw exception;
}
reflectionProvider.writeField(result, fieldName, value, classDefiningField);
if (!seenFields.add(new FastField(classDefiningField, fieldName))){
throw new DuplicateFieldException(fieldName + " [" + classDefiningField.getName() + "]");
}
}
return result;
}
private boolean fieldIsEqual(FastField field){
return valueField.getName().equals(field.getName()) && valueField.getDeclaringClass().getName().equals(field.getDeclaringClass());
}
}