/*
 * Created on 2004.04.17.
 *
 */


package gzsombor.util.xstream;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.alias.ClassMapper;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.ConverterLookup;
import com.thoughtworks.xstream.converters.ErrorWriter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

/**
 * Creates a more 'compact' xml, instead of the verbose default.
 * Usage:
 *    XStream xs = new XStream();
 *    xs.registerConverter(new CompactReflectionConverter(xs));
 * 
 * @author zombi@mailbox.hu
 * 
 *  
 */
public class CompactReflectionConverter implements Converter {

    private ClassMapper classMapper;
    private String classAttributeIdentifier;
    private ReflectionProvider reflectionProvider;
    private ConverterLookup lookup;
    private String definedInAttributeIdentifier = "defined-in";

    public CompactReflectionConverter(ClassMapper classMapper, String classAttributeIdentifier,
            ReflectionProvider reflectionProvider,ConverterLookup lookup) {
        this.classMapper = classMapper;
        this.classAttributeIdentifier = classAttributeIdentifier;
        this.reflectionProvider = reflectionProvider;
        this.lookup = lookup;
    }

    /**
     * @param xstream
     */
    public CompactReflectionConverter(XStream xstream) {
        this(xstream.getClassMapper(), "class-name", JVM.bestReflectionProvider(),xstream.getConverterLookup());
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.thoughtworks.xstream.converters.reflection.ReflectionConverter#canConvert(java.lang.Class)
     */
    public boolean canConvert(Class cls) {
	if (!cls.isPrimitive() && !cls.isArray() && !cls.isInterface()) {
	    return !cls.getName().startsWith("java.");
    	}
        return false;
    }


    class Record {
        String fieldName;
        Class fieldType;
        Object newObj;

        /**
         * @param fieldName
         * @param fieldType
         * @param newObj
         */
        public Record(String fieldName, Class fieldType, Object newObj) {
            super();
            this.fieldName = fieldName;
            this.fieldType = fieldType;
            this.newObj = newObj;
        }
    }

    public void marshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
        final List complexFields = new ArrayList();
        reflectionProvider.visitSerializableFields(source, new ReflectionProvider.Visitor() {

            public void visit(String fieldName, Class fieldType, Class definedIn, Object value) {
                
                if (value != null) {
                    Class actualType = value.getClass();
                    if (actualType.getName().startsWith("java.lang.")) {
                        writer.addAttribute(fieldName, value.toString());
                    } else {
                        complexFields.add(new Record(fieldName, fieldType, value));
                    }
                }
            }
        });
        for (Iterator iter = complexFields.iterator(); iter.hasNext();) {
            Record r = (Record) iter.next();
            writer.startNode(classMapper.mapNameToXML(r.fieldName));
            Class defaultType = classMapper.lookupDefaultType(r.fieldType);
            Class actualType = r.newObj.getClass();
            if (!actualType.equals(defaultType)) {
                writer.addAttribute(classAttributeIdentifier, classMapper.lookupName(actualType));
            }

            context.convertAnother(r.newObj);

            writer.endNode();

        }

    }

    public Object unmarshal(final HierarchicalStreamReader reader, UnmarshallingContext context) {
        // copy from ReflectionProvider 
        Object result = context.currentObject();

        if (result == null) {
            result = reflectionProvider.newInstance(context.getRequiredType());
        }
        // de-serialize simple fields

        final Object finalResult = result;
        reflectionProvider.visitSerializableFields(result, new ReflectionProvider.Visitor() {

            /* (non-Javadoc)
             * @see com.thoughtworks.xstream.converters.reflection.ReflectionProvider.Visitor#visit(java.lang.String, java.lang.Class, java.lang.Class, java.lang.Object)
             */
            public void visit(String fieldName, Class fieldType, Class definedIn, Object value) {
                if (fieldType.getName().startsWith("java.lang.") || fieldType.isPrimitive()) {
                    Converter converter = lookup.lookupConverterForType(fieldType);
                    final String fieldAttr = reader.getAttribute(fieldName);
                    if (fieldAttr!=null) {


	                    Object fieldValue = converter.unmarshal(new HierarchicalStreamReader() {
	                        /* (non-Javadoc)
	                         * @see com.thoughtworks.xstream.io.HierarchicalStreamReader#getValue()
	                         */
	                        public String getValue() {
	                            return fieldAttr;
	                        }
	
	                        public boolean hasMoreChildren() {
	                            return false;
	                        }
	
	                        public void moveDown() {
	                            
	                        }
	
	                        public void moveUp() {
	                            
	                        }
	
	                        public String getNodeName() {
	                            return null;
	                        }
	
	                        public String getAttribute(String arg0) {
	                            return null;
	                        }
	
	                        public Object peekUnderlyingNode() {
	                            return null;
	                        }

                            public void appendErrors(ErrorWriter errorWriter) {
                                // TODO Auto-generated method stub
                                
                            }
	                    }, null);
	                    reflectionProvider.writeField(finalResult, fieldName, fieldValue, definedIn);
	                    	//writeField(finalResult, fieldName, fieldValue);
                    }
                }
            }
        });
        
        // copy from ReflectionProvider 

        while (reader.hasMoreChildren()) {
            reader.moveDown();

            String fieldName = classMapper.mapNameFromXML(reader.getNodeName());

            String definedIn = reader.getAttribute(definedInAttributeIdentifier);
            Class definedInCls = definedIn == null ? null : classMapper.lookupType(definedIn);

            Class type;
            String classAttribute = reader.getAttribute(classAttributeIdentifier);
            if (classAttribute == null) {
                type = classMapper.lookupDefaultType(reflectionProvider.getFieldType(result, fieldName, definedInCls));
            } else {
                type = classMapper.lookupType(classAttribute);
            }

            Object fieldValue = context.convertAnother(result, type);

            reflectionProvider.writeField(result, fieldName, fieldValue, definedInCls);

            reader.moveUp();
        }
        
        return result;
    }
}
