/*******************************************************************************
 *  Copyright © 2012-2015 eBay Software Foundation
 *  This program is dual licensed under the MIT and Apache 2.0 licenses.
 *  Please see LICENSE for more information.
 *******************************************************************************/
package com.ebay.jetstream.xmlser;

import java.rmi.MarshalException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.springframework.util.ClassUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public abstract class XMLSerializer implements IXmlSerializer {
  private final Map<Class<?>, IXmlSerializer> m_nodeSerializers = new HashMap<Class<?>, IXmlSerializer>();

  protected Element getRootElement(Object object) {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder;
    try {
        builder = factory.newDocumentBuilder();
    } catch (ParserConfigurationException e) {
        throw new IllegalStateException (e);
    }
    Document document = builder.newDocument();
    return (Element) document.appendChild(document.createElement("bean"));
  }

  protected IXmlSerializer getSerializer(Object object, Class<?> defaultClass) {
    Class<?> clazz = object == null || ClassUtils.isPrimitiveOrWrapper(object.getClass()) ? null : object.getClass();
    IXmlSerializer result = m_nodeSerializers.get(clazz);
    if (result == null) {
      for (Map.Entry<Class<?>, IXmlSerializer> entry : m_nodeSerializers.entrySet()) {
        Class<?> c = entry.getKey();
        if (c != null && c != Object.class && c.isAssignableFrom(clazz)) {
          result = entry.getValue();
          break;
        }
      }
      // Handle XSerializable implicitly and explicitly (note implicit was already checked)
      if (result == null && XMLSerializationManager.isXSerializable(object.getClass())) {
        result = m_nodeSerializers.get(XSerializable.class);
      }
      // Finally, use the default if nothing better found
      if (result == null) {
        result = m_nodeSerializers.get(defaultClass);
      }
    }
    return result;
  }

  /**
   * Serializes an object to its XML representation, in a DOM.
   * 
   * @param object
   *          the object to serialize.
   * @return the root Element of a DOM containing the serialized object.
   */
  public Element getXMLRepresentation(Object object) {
    Element root = getRootElement(object);
    try {
      getSerializer(object, XSerializable.class).serialize(root, object);
    }
    catch (MarshalException e) {
      throw new RuntimeException(e);
    }
    return root;
  }

  /**
   * Serializes an object to its XML representation, as a String.
   * 
   * @param object
   *          the object to serialize.
   * 
   * @return the serialized XML representation of an object as a String.
   */
  public String getXMLStringRepresentation(Object object) {
    return XHelper.getStringFromElement(getXMLRepresentation(object));
  }

  /**
   * Registers a serializer for a class. There are several "special" classes: null represents the primitive object
   * serializer. Object.class represents the basic object serializer. XSerializable represents the bean serializer.
   * 
   * @param clazz
   *          the class to register for.
   * @param serializer
   *          the instance of IXmlSerializer to use for this class.
   * @return the old serializer.
   */
  protected IXmlSerializer registerSerializer(Class<?> clazz, IXmlSerializer serializer) {
    IXmlSerializer old = m_nodeSerializers.get(clazz);
    m_nodeSerializers.put(clazz, serializer);
    return old;
  }

  public void serialize(Element containingElement, Object object) throws MarshalException {
    getSerializer(object, Object.class).serialize(containingElement, object);
  }
}