/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */
package org.apache.qpid.management.wsdm.muse.serializer;

import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import org.apache.muse.core.serializer.Serializer;
import org.apache.muse.core.serializer.SerializerRegistry;
import org.apache.muse.ws.addressing.soap.SoapFault;
import org.apache.qpid.management.Names;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;

/**
 * Generic Serializer for objects.
 * It is a general-purpose serializer used for encoding Object values.  
 */
public class ObjectSerializer implements Serializer 
{
	/**
	 * Mapping between xsd and java types.
	 */
	private Map<String, Class<?>> xml2Java = new HashMap<String, Class<?>>();
	{
		xml2Java.put("xsd:long", Long.class);
		xml2Java.put("xsd:long", long.class);
		xml2Java.put("xsd:boolean",Boolean.class);
		xml2Java.put("xsd:boolean",boolean.class);
		xml2Java.put("xsd:double",Double.class);
		xml2Java.put("xsd:double",Double.class);
		xml2Java.put("xsd:float",Float.class);
		xml2Java.put("xsd:float",float.class);
		xml2Java.put("xsd:integer",Integer.class);
		xml2Java.put("xsd:integer",int.class);
		xml2Java.put("xsd:int",Integer.class);
		xml2Java.put("xsd:int",int.class);
		xml2Java.put("xsd:short",Short.class);
		xml2Java.put("xsd:short",short.class);
		xml2Java.put("xsd:string",String.class);
		xml2Java.put("xsd:anyURI",URI.class);
		xml2Java.put("xsd:dateTime",URI.class);
		xml2Java.put("xsd:QName",QName.class);
		xml2Java.put("xsd:element",Element.class);
		xml2Java.put("xsd:base64Binary",byte[].class);
	}
	
	private Map<Class<?>, String> java2Xml = new HashMap<Class<?>, String>();
	{
		java2Xml.put(Long.class,"xsd:long");
		java2Xml.put(long.class,"xsd:long");
		java2Xml.put(Boolean.class,"xsd:boolean");
		java2Xml.put(Boolean.class,"xsd:boolean");
		java2Xml.put(Double.class,"xsd:double");
		java2Xml.put(Double.class,"xsd:double");
		java2Xml.put(Float.class,"xsd:float");
		java2Xml.put(Float.class,"xsd:float");
		java2Xml.put(Integer.class,"xsd:integer");
		java2Xml.put(Integer.class,"xsd:integer");
		java2Xml.put(Integer.class,"xsd:int");
		java2Xml.put(Integer.class,"xsd:int");
		java2Xml.put(Short.class,"xsd:short");
		java2Xml.put(Short.class,"xsd:short");
		java2Xml.put(String.class,"xsd:string");
		java2Xml.put(URI.class,"xsd:anyURI");
		java2Xml.put(Date.class,"xsd:dateTime");
		java2Xml.put(QName.class,"xsd:QName");
		java2Xml.put(Element.class,"xsd:element");
		java2Xml.put(byte[].class,"xsd:base64Binary");
	}
	
	/**
	 * Converts the incoming element into the appropriate Java type.
	 * The method will fail if :
	 * 
	 * <br>1) The element has no xsi:type attribute;
	 * <br>2) The xsi:type attribute has no corresponding java type on this serializer mappings.
	 * 
	 * @param elementData the xml element containing data to be unmarshalled.l
	 * @return the java object as result of xml element unmarshalling.
	 * @throws SoapFault when the marshalling fails.
	 */
	public Object fromXML(Element elementData) throws SoapFault
	{
		Attr typeAttribute = elementData.getAttributeNodeNS(
				XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, 
				Names.TYPE);
		
		if (typeAttribute == null)
		{
			throw new SoapFault(
				"No type attribute was found for the current element. " +
				"If you are using this serializer, in order to unmarshal the opportune type the xsi:type must be specified.");
		}
		
		Class<?> clazz = xml2Java.get(typeAttribute.getValue());
		
		if (clazz == null)
		{
			throw new SoapFault(String.format("No corresponding class was found on this serializer mappings for xsi:type %s.",typeAttribute));
		}
		
		if (clazz == byte[].class) {
			return new ByteArraySerializer().fromXML(elementData);
		}
		
		return SerializerRegistry.getInstance().getSerializer(clazz).fromXML(elementData);
	}

	/**
	 * As this serializer is supposed to deal with generic object types, this method returns Object.class.
	 * 
	 * @return Object.class
	 */
	public Class<?> getSerializableType() 
	{
		return Object.class;
	}

	/**
	 * Converts the given object (with the given qname) in XML format.
	 * This method fails if there's no corresponding xml type for the given runtime type of the input object.
	 * 
	 * @param obj the object to be marshalled.
	 * @param qname the qualified name that will be used in encoding.
	 */
	public Element toXML(Object obj, QName qname) throws SoapFault 
	{
		Class<?> clazz = obj.getClass();		

		Element result = null;
		
		if (clazz == byte[].class) {
			result = new ByteArraySerializer().toXML(obj,qname);
		} else {
			result = SerializerRegistry.getInstance().getSerializer(clazz).toXML(obj,qname);
		}
		result.setAttribute(Names.XSI_TYPE, java2Xml.get(clazz));
		return result;
	}
	
	/**
	 * Returns the xml type associated with the given class. 
	 * 
	 * @param clazz the class.
	 * @return the xml type associated with the given class.
	 */
	public String getXmlType(Class<?> clazz) 
	{
		return java2Xml.get(clazz);
	}
}