All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feilong.lib.xstream.mapper.AnnotationMapper Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

There is a newer version: 4.0.8
Show newest version
/*
 * Copyright (C) 2007, 2008, 2009, 2011, 2012, 2013, 2016 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 07. November 2007 by Joerg Schaible
 */
package com.feilong.lib.xstream.mapper;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.feilong.lib.xstream.InitializationException;
import com.feilong.lib.xstream.annotations.XStreamAlias;
import com.feilong.lib.xstream.annotations.XStreamAliasType;
import com.feilong.lib.xstream.annotations.XStreamAsAttribute;
import com.feilong.lib.xstream.annotations.XStreamConverter;
import com.feilong.lib.xstream.annotations.XStreamConverters;
import com.feilong.lib.xstream.annotations.XStreamImplicit;
import com.feilong.lib.xstream.annotations.XStreamImplicitCollection;
import com.feilong.lib.xstream.annotations.XStreamInclude;
import com.feilong.lib.xstream.annotations.XStreamOmitField;
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.ConverterRegistry;
import com.feilong.lib.xstream.converters.SingleValueConverter;
import com.feilong.lib.xstream.converters.SingleValueConverterWrapper;
import com.feilong.lib.xstream.converters.reflection.ReflectionProvider;
import com.feilong.lib.xstream.core.ClassLoaderReference;
import com.feilong.lib.xstream.core.JVM;
import com.feilong.lib.xstream.core.util.DependencyInjectionFactory;
import com.feilong.lib.xstream.core.util.TypedNull;

/**
 * A mapper that uses annotations to prepare the remaining mappers in the chain.
 * 
 * @author Jörg Schaible
 * @since 1.3
 */
public class AnnotationMapper extends MapperWrapper implements AnnotationConfiguration{

    private boolean                                           locked;

    private transient Object[]                                arguments;

    private final ConverterRegistry                           converterRegistry;

    private transient ClassAliasingMapper                     classAliasingMapper;

    private transient DefaultImplementationsMapper            defaultImplementationsMapper;

    private transient ImplicitCollectionMapper                implicitCollectionMapper;

    private transient FieldAliasingMapper                     fieldAliasingMapper;

    private transient ElementIgnoringMapper                   elementIgnoringMapper;

    private transient AttributeMapper                         attributeMapper;

    private transient LocalConversionMapper                   localConversionMapper;

    private final Map, Map, Converter>> converterCache = new HashMap<>();

    private final Set>                               annotatedTypes = Collections.synchronizedSet(new HashSet>());

    /**
     * Construct an AnnotationMapper.
     * 
     * @param wrapped
     *            the next {@link Mapper} in the chain
     * @since 1.4.5
     */
    public AnnotationMapper(final Mapper wrapped, final ConverterRegistry converterRegistry, final ConverterLookup converterLookup,
                    final ClassLoaderReference classLoaderReference, final ReflectionProvider reflectionProvider){
        super(wrapped);
        this.converterRegistry = converterRegistry;
        annotatedTypes.add(Object.class);
        setupMappers();
        locked = true;

        final ClassLoader classLoader = classLoaderReference.getReference();
        arguments = new Object[] {
                                   this,
                                   classLoaderReference,
                                   reflectionProvider,
                                   converterLookup,
                                   new JVM(),
                                   classLoader != null ? classLoader : new TypedNull(ClassLoader.class) };
    }

    @Override
    public String realMember(final Class type,final String serialized){
        if (!locked){
            processAnnotations(type);
        }
        return super.realMember(type, serialized);
    }

    @Override
    public String serializedClass(final Class type){
        if (!locked){
            processAnnotations(type);
        }
        return super.serializedClass(type);
    }

    @Override
    public Class defaultImplementationOf(final Class type){
        if (!locked){
            processAnnotations(type);
        }
        final Class defaultImplementation = super.defaultImplementationOf(type);
        if (!locked){
            processAnnotations(defaultImplementation);
        }
        return defaultImplementation;
    }

    @Override
    public Converter getLocalConverter(final Class definedIn,final String fieldName){
        if (!locked){
            processAnnotations(definedIn);
        }
        return super.getLocalConverter(definedIn, fieldName);
    }

    @Override
    public void autodetectAnnotations(final boolean mode){
        locked = !mode;
    }

    @Override
    public void processAnnotations(final Class[] initialTypes){
        if (initialTypes == null || initialTypes.length == 0){
            return;
        }
        locked = true;

        final Set> types = new UnprocessedTypesSet();
        for (final Class initialType : initialTypes){
            types.add(initialType);
        }
        processTypes(types);
    }

    private void processAnnotations(final Class initialType){
        if (initialType == null){
            return;
        }

        final Set> types = new UnprocessedTypesSet();
        types.add(initialType);
        processTypes(types);
    }

    private void processTypes(final Set> types){
        while (!types.isEmpty()){
            final Iterator> iter = types.iterator();
            final Class type = iter.next();
            iter.remove();

            synchronized (type){
                if (annotatedTypes.contains(type)){
                    continue;
                }
                try{
                    if (type.isPrimitive()){
                        continue;
                    }

                    addParametrizedTypes(type, types);

                    processConverterAnnotations(type);
                    processAliasAnnotation(type, types);
                    processAliasTypeAnnotation(type);

                    if (type.isInterface()){
                        continue;
                    }

                    processImplicitCollectionAnnotation(type);

                    final Field[] fields = type.getDeclaredFields();
                    for (final Field field : fields){
                        if (field.isEnumConstant() || (field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) > 0){
                            continue;
                        }

                        addParametrizedTypes(field.getGenericType(), types);

                        if (field.isSynthetic()){
                            continue;
                        }

                        processFieldAliasAnnotation(field);
                        processAsAttributeAnnotation(field);
                        processImplicitAnnotation(field);
                        processOmitFieldAnnotation(field);
                        processLocalConverterAnnotation(field);
                    }
                }finally{
                    annotatedTypes.add(type);
                }
            }
        }
    }

    private void addParametrizedTypes(Type type,final Set> types){
        final Set processedTypes = new HashSet<>();
        final Set localTypes = new LinkedHashSet(){

            /**
             * 
             */
            private static final long serialVersionUID = -2331069618287693772L;

            @Override
            public boolean add(final Type o){
                if (o instanceof Class){
                    return types.add((Class) o);
                }
                return o == null || processedTypes.contains(o) ? false : super.add(o);
            }

        };
        while (type != null){
            processedTypes.add(type);
            if (type instanceof Class){
                final Class clazz = (Class) type;
                types.add(clazz);
                if (!clazz.isPrimitive()){
                    final TypeVariable[] typeParameters = clazz.getTypeParameters();
                    for (final TypeVariable typeVariable : typeParameters){
                        localTypes.add(typeVariable);
                    }
                    localTypes.add(clazz.getGenericSuperclass());
                    for (final Type iface : clazz.getGenericInterfaces()){
                        localTypes.add(iface);
                    }
                }
            }else if (type instanceof TypeVariable){
                final TypeVariable typeVariable = (TypeVariable) type;
                final Type[] bounds = typeVariable.getBounds();
                for (final Type bound : bounds){
                    localTypes.add(bound);
                }
            }else if (type instanceof ParameterizedType){
                final ParameterizedType parametrizedType = (ParameterizedType) type;
                localTypes.add(parametrizedType.getRawType());
                final Type[] actualArguments = parametrizedType.getActualTypeArguments();
                for (final Type actualArgument : actualArguments){
                    localTypes.add(actualArgument);
                }
            }else if (type instanceof GenericArrayType){
                final GenericArrayType arrayType = (GenericArrayType) type;
                localTypes.add(arrayType.getGenericComponentType());
            }

            if (!localTypes.isEmpty()){
                final Iterator iter = localTypes.iterator();
                type = iter.next();
                iter.remove();
            }else{
                type = null;
            }
        }
    }

    private void processConverterAnnotations(final Class type){
        if (converterRegistry != null){
            final XStreamConverters convertersAnnotation = type.getAnnotation(XStreamConverters.class);
            final XStreamConverter converterAnnotation = type.getAnnotation(XStreamConverter.class);
            final List annotations = convertersAnnotation != null
                            ? new ArrayList<>(Arrays.asList(convertersAnnotation.value()))
                            : new ArrayList<>();
            if (converterAnnotation != null){
                annotations.add(converterAnnotation);
            }
            for (final XStreamConverter annotation : annotations){
                final Converter converter = cacheConverter(annotation, converterAnnotation != null ? type : null);
                if (converter != null){
                    if (converterAnnotation != null || converter.canConvert(type)){
                        converterRegistry.registerConverter(converter, annotation.priority());
                    }else{
                        throw new InitializationException(
                                        "Converter " + annotation.value().getName() + " cannot handle annotated class " + type.getName());
                    }
                }
            }
        }
    }

    private void processAliasAnnotation(final Class type,final Set> types){
        final XStreamAlias aliasAnnotation = type.getAnnotation(XStreamAlias.class);
        if (aliasAnnotation != null){
            if (classAliasingMapper == null){
                throw new InitializationException("No " + ClassAliasingMapper.class.getName() + " available");
            }
            classAliasingMapper.addClassAlias(aliasAnnotation.value(), type);
            if (aliasAnnotation.impl() != Void.class){
                // Alias for Interface/Class with an impl
                defaultImplementationsMapper.addDefaultImplementation(aliasAnnotation.impl(), type);
                if (type.isInterface()){
                    types.add(aliasAnnotation.impl()); // alias Interface's impl
                }
            }
        }
    }

    private void processAliasTypeAnnotation(final Class type){
        final XStreamAliasType aliasAnnotation = type.getAnnotation(XStreamAliasType.class);
        if (aliasAnnotation != null){
            if (classAliasingMapper == null){
                throw new InitializationException("No " + ClassAliasingMapper.class.getName() + " available");
            }
            classAliasingMapper.addTypeAlias(aliasAnnotation.value(), type);
        }
    }

    @Deprecated
    private void processImplicitCollectionAnnotation(final Class type){
        final XStreamImplicitCollection implicitColAnnotation = type.getAnnotation(XStreamImplicitCollection.class);
        if (implicitColAnnotation != null){
            if (implicitCollectionMapper == null){
                throw new InitializationException("No " + ImplicitCollectionMapper.class.getName() + " available");
            }
            final String fieldName = implicitColAnnotation.value();
            final String itemFieldName = implicitColAnnotation.item();
            final Field field;
            try{
                field = type.getDeclaredField(fieldName);
            }catch (final NoSuchFieldException e){
                throw new InitializationException(
                                type.getName() + " does not have a field named '" + fieldName + "' as required by "
                                                + XStreamImplicitCollection.class.getName());
            }
            Class itemType = null;
            final Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType){
                final Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
                itemType = getClass(typeArgument);
            }
            if (itemType == null){
                implicitCollectionMapper.add(type, fieldName, null, Object.class);
            }else{
                if (itemFieldName.equals("")){
                    implicitCollectionMapper.add(type, fieldName, null, itemType);
                }else{
                    implicitCollectionMapper.add(type, fieldName, itemFieldName, itemType);
                }
            }
        }
    }

    private void processFieldAliasAnnotation(final Field field){
        final XStreamAlias aliasAnnotation = field.getAnnotation(XStreamAlias.class);
        if (aliasAnnotation != null){
            if (fieldAliasingMapper == null){
                throw new InitializationException("No " + FieldAliasingMapper.class.getName() + " available");
            }
            fieldAliasingMapper.addFieldAlias(aliasAnnotation.value(), field.getDeclaringClass(), field.getName());
        }
    }

    private void processAsAttributeAnnotation(final Field field){
        final XStreamAsAttribute asAttributeAnnotation = field.getAnnotation(XStreamAsAttribute.class);
        if (asAttributeAnnotation != null){
            if (attributeMapper == null){
                throw new InitializationException("No " + AttributeMapper.class.getName() + " available");
            }
            attributeMapper.addAttributeFor(field);
        }
    }

    private void processImplicitAnnotation(final Field field){
        final XStreamImplicit implicitAnnotation = field.getAnnotation(XStreamImplicit.class);
        if (implicitAnnotation != null){
            if (implicitCollectionMapper == null){
                throw new InitializationException("No " + ImplicitCollectionMapper.class.getName() + " available");
            }
            final String fieldName = field.getName();
            final String itemFieldName = implicitAnnotation.itemFieldName();
            final String keyFieldName = implicitAnnotation.keyFieldName();
            boolean isMap = Map.class.isAssignableFrom(field.getType());
            Class itemType = null;
            if (!field.getType().isArray()){
                final Type genericType = field.getGenericType();
                if (genericType instanceof ParameterizedType){
                    final Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
                    final Type typeArgument = actualTypeArguments[isMap ? 1 : 0];
                    itemType = getClass(typeArgument);
                }
            }
            if (isMap){
                implicitCollectionMapper.add(
                                field.getDeclaringClass(),
                                fieldName,
                                itemFieldName != null && !"".equals(itemFieldName) ? itemFieldName : null,
                                itemType,
                                keyFieldName != null && !"".equals(keyFieldName) ? keyFieldName : null);
            }else{
                if (itemFieldName != null && !"".equals(itemFieldName)){
                    implicitCollectionMapper.add(field.getDeclaringClass(), fieldName, itemFieldName, itemType);
                }else{
                    implicitCollectionMapper.add(field.getDeclaringClass(), fieldName, itemType);
                }
            }
        }
    }

    private void processOmitFieldAnnotation(final Field field){
        final XStreamOmitField omitFieldAnnotation = field.getAnnotation(XStreamOmitField.class);
        if (omitFieldAnnotation != null){
            if (elementIgnoringMapper == null){
                throw new InitializationException("No " + ElementIgnoringMapper.class.getName() + " available");
            }
            elementIgnoringMapper.omitField(field.getDeclaringClass(), field.getName());
        }
    }

    private void processLocalConverterAnnotation(final Field field){
        final XStreamConverter annotation = field.getAnnotation(XStreamConverter.class);
        if (annotation != null){
            final Converter converter = cacheConverter(annotation, field.getType());
            if (converter != null){
                if (localConversionMapper == null){
                    throw new InitializationException("No " + LocalConversionMapper.class.getName() + " available");
                }
                localConversionMapper.registerLocalConverter(field.getDeclaringClass(), field.getName(), converter);
            }
        }
    }

    private Converter cacheConverter(final XStreamConverter annotation,final Class targetType){
        Converter result = null;
        final Object[] args;
        final List parameter = new ArrayList<>();
        if (targetType != null && annotation.useImplicitType()){
            parameter.add(targetType);
        }
        final List arrays = new ArrayList<>();
        arrays.add(annotation.booleans());
        arrays.add(annotation.bytes());
        arrays.add(annotation.chars());
        arrays.add(annotation.doubles());
        arrays.add(annotation.floats());
        arrays.add(annotation.ints());
        arrays.add(annotation.longs());
        arrays.add(annotation.shorts());
        arrays.add(annotation.strings());
        arrays.add(annotation.types());
        for (Object array : arrays){
            if (array != null){
                int length = Array.getLength(array);
                for (int i = 0; i < length; i++){
                    parameter.add(Array.get(array, i));
                }
            }
        }
        for (final Class type : annotation.nulls()){
            final TypedNull nullType = new TypedNull(type);
            parameter.add(nullType);
        }
        final Class converterType = annotation.value();
        Map, Converter> converterMapping = converterCache.get(converterType);
        if (converterMapping != null){
            result = converterMapping.get(parameter);
        }
        if (result == null){
            int size = parameter.size();
            if (size > 0){
                args = new Object[arguments.length + size];
                System.arraycopy(arguments, 0, args, size, arguments.length);
                System.arraycopy(parameter.toArray(new Object[size]), 0, args, 0, size);
            }else{
                args = arguments;
            }

            final Converter converter;
            try{
                if (SingleValueConverter.class.isAssignableFrom(converterType) && !Converter.class.isAssignableFrom(converterType)){
                    final SingleValueConverter svc = (SingleValueConverter) DependencyInjectionFactory.newInstance(converterType, args);
                    converter = new SingleValueConverterWrapper(svc);
                }else{
                    converter = (Converter) DependencyInjectionFactory.newInstance(converterType, args);
                }
            }catch (final Exception e){
                throw new InitializationException(
                                "Cannot instantiate converter " + converterType.getName()
                                                + (targetType != null ? " for type " + targetType.getName() : ""),
                                e);
            }
            if (converterMapping == null){
                converterMapping = new HashMap<>();
                converterCache.put(converterType, converterMapping);
            }
            converterMapping.put(parameter, converter);
            result = converter;
        }
        return result;
    }

    private Class getClass(final Type typeArgument){
        Class type = null;
        if (typeArgument instanceof ParameterizedType){
            type = (Class) ((ParameterizedType) typeArgument).getRawType();
        }else if (typeArgument instanceof Class){
            type = (Class) typeArgument;
        }
        return type;
    }

    private void setupMappers(){
        classAliasingMapper = (ClassAliasingMapper) lookupMapperOfType(ClassAliasingMapper.class);
        defaultImplementationsMapper = (DefaultImplementationsMapper) lookupMapperOfType(DefaultImplementationsMapper.class);
        implicitCollectionMapper = (ImplicitCollectionMapper) lookupMapperOfType(ImplicitCollectionMapper.class);
        fieldAliasingMapper = (FieldAliasingMapper) lookupMapperOfType(FieldAliasingMapper.class);
        elementIgnoringMapper = (ElementIgnoringMapper) lookupMapperOfType(ElementIgnoringMapper.class);
        attributeMapper = (AttributeMapper) lookupMapperOfType(AttributeMapper.class);
        localConversionMapper = (LocalConversionMapper) lookupMapperOfType(LocalConversionMapper.class);
    }

    private void writeObject(final ObjectOutputStream out) throws IOException{
        out.defaultWriteObject();
        int max = arguments.length - 2;
        out.writeInt(max);
        for (int i = 0; i < max; i++){
            out.writeObject(arguments[i]);
        }
    }

    private void readObject(final ObjectInputStream in) throws IOException,ClassNotFoundException{
        in.defaultReadObject();
        setupMappers();
        int max = in.readInt();
        arguments = new Object[max + 2];
        for (int i = 0; i < max; i++){
            arguments[i] = in.readObject();
            if (arguments[i] instanceof ClassLoaderReference){
                arguments[max + 1] = ((ClassLoaderReference) arguments[i]).getReference();
            }
        }
        arguments[max] = new JVM();
    }

    private final class UnprocessedTypesSet extends LinkedHashSet>{

        /**
         * 
         */
        private static final long serialVersionUID = 3917766698605664019L;

        @Override
        public boolean add(Class type){
            if (type == null){
                return false;
            }
            while (type.isArray()){
                type = type.getComponentType();
            }
            final String name = type.getName();
            if (name.startsWith("java.") || name.startsWith("javax.")){
                return false;
            }
            final boolean ret = annotatedTypes.contains(type) ? false : super.add(type);
            if (ret){
                final XStreamInclude inc = type.getAnnotation(XStreamInclude.class);
                if (inc != null){
                    final Class[] incTypes = inc.value();
                    if (incTypes != null){
                        for (final Class incType : incTypes){
                            add(incType);
                        }
                    }
                }
            }
            return ret;
        }
    }
}