/*
 * Copyright (C) 2007, 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 01. February 2007 by Joerg Schaible
 */
package com.thoughtworks.xstream.converters.reflection;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
import com.thoughtworks.xstream.core.util.Fields;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.AttributedCharacterIterator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


/**
 * An abstract converter implementation for constants of
 * {@link java.text.AttributedCharacterIterator.Attribute} and derived types.
 * 
 * @author J&ouml;rg Schaible
 * @since 1.2.2
 */
public class AbstractAttributedCharacterIteratorAttributeConverter extends
    AbstractSingleValueConverter {

    private static final Map instanceMaps = new HashMap();
    private static final Method getName;
    static {
        Method method = null;
        try {
            method = AttributedCharacterIterator.Attribute.class.getDeclaredMethod(
                "getName", (Class[])null);
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
        } catch (SecurityException e) {
            // ignore for now
        } catch (NoSuchMethodException e) {
            // ignore for now
        }
        getName = method;
    }

    private final Class type;
    private transient Map attributeMap;

    public AbstractAttributedCharacterIteratorAttributeConverter(final Class type) {
        super();
        if (!AttributedCharacterIterator.Attribute.class.isAssignableFrom(type)) {
            throw new IllegalArgumentException(type.getName()
                + " is not a " + AttributedCharacterIterator.Attribute.class.getName());
        }
        this.type = type;
        readResolve();
    }

    public boolean canConvert(final Class type) {
        return type == this.type && !attributeMap.isEmpty();
    }

    public String toString(final Object source) {
        return getName((AttributedCharacterIterator.Attribute)source);
    }

    private String getName(AttributedCharacterIterator.Attribute attribute) {
        Exception ex = null;
        if (getName != null) {
            try {
                return (String)getName.invoke(attribute, (Object[])null);
            } catch (IllegalAccessException e) {
                ex = e;
            } catch (InvocationTargetException e) {
                ex = e;
            }
        }
        String s = attribute.toString();
        String className = attribute.getClass().getName();
        if (s.startsWith(className)) {
            return s.substring(className.length()+1, s.length()-1);
        }
        ConversionException exception = new ConversionException("Cannot find name of attribute", ex);
        exception.add("attribute-type", className);
        throw exception;
    }

    public Object fromString(final String str) {
        if (attributeMap.containsKey(str)) {
            return attributeMap.get(str);
        }
        ConversionException exception = new ConversionException("Cannot find attribute");
        exception.add("attribute-type", type.getName());
        exception.add("attribute-name", str);
        throw exception;
    }

    private Object readResolve() {
        attributeMap = (Map)instanceMaps.get(type.getName());
        if (attributeMap == null) {
            attributeMap = new HashMap();
            Field instanceMap = Fields.locate(type, Map.class, true);
            if (instanceMap != null) {
                try {
                    Map map = (Map)Fields.read(instanceMap, null);
                    if (map != null) {
                        boolean valid = true;
                        for (Iterator iter = map.entrySet().iterator(); valid && iter.hasNext(); ) {
                            Map.Entry entry = (Map.Entry)iter.next(); 
                            valid = entry.getKey().getClass() == String.class && entry.getValue().getClass() == type;
                        }
                        if (valid) {
                            attributeMap.putAll(map);
                        }
                    }
                } catch (ObjectAccessException e) {
                }
            }
            if (attributeMap.isEmpty()) {
                try {
                    Field[] fields = type.getDeclaredFields();
                    for(int i = 0; i < fields.length; ++i) {
                        if(fields[i].getType() == type == Modifier.isStatic(fields[i].getModifiers())) {
                            AttributedCharacterIterator.Attribute attribute =
                                    (AttributedCharacterIterator.Attribute)Fields.read(fields[i], null);
                            attributeMap.put(toString(attribute), attribute);
                        }
                    }
                } catch (SecurityException e) {
                    attributeMap.clear();
                } catch (ObjectAccessException e) {
                    attributeMap.clear();
                } catch (NoClassDefFoundError e) {
                    attributeMap.clear();
                }
            }
            instanceMaps.put(type.getName(), attributeMap);
        }
        return this;
    }

}
