/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.utils.xml.impl.v26;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.utils.xml.XMLBinding;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.NodeType;
import org.exolab.castor.xml.Unmarshaller;
import org.exolab.castor.xml.XMLClassDescriptor;
import org.exolab.castor.xml.XMLFieldDescriptor;
import org.w3c.dom.Node;

import java.io.Writer;
import java.util.HashMap;

/**
 * <p>Castor implementation of xml bindings</p>
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:31:27 $
 */

public class CastorXMLBinding
        implements XMLBinding {

    public static final String MAP_FILE = "xmlbinding.mapp.file";
    public static final String DRIVE_CLASS_POSTFIX = "Descriptor";

    private HashMap hm = new HashMap();

    /**
     * Make xml to java object conversation
     *
     * @param cl   java object class
     * @param node DOCUMENT node
     * @return new java object
     */
    public Object xmlToJava(Class cl, Node node) {

        try {
            Unmarshaller unmarshaller = new Unmarshaller(cl);
            Object map_file = getAttribute(MAP_FILE);
            if(map_file != null) {
                Mapping mapping = new Mapping(getClass().getClassLoader());
                mapping.loadMapping((String) map_file);
                unmarshaller.setMapping(mapping);
            }
            return unmarshaller.unmarshal(node);

        } catch (Throwable t) {
            t.printStackTrace();
            throw new GenericSystemException(
                    "Cannot make xml to java transformation. Error: " +
                            t.getMessage(), t);
        }

    }

    /**
     * Make java object to xml conversation
     *
     * @param o   java object
     * @param out IO.WRITER
     */
    public void javaToXml(Object o, Writer out) {

        try {
            Marshaller marshaller = new Marshaller(out);
            marshaller.setSuppressXSIType(true);
            marshaller.marshal(o);

        } catch (Throwable t) {
            t.printStackTrace();
            throw new GenericSystemException(
                    "Cannot make java to xml transformation. Error: " +
                            t.getMessage(), t);
        }
    }

    /**
     * Make java object to xml conversation
     *
     * @param o    java object
     * @param node DOM.NODE
     */
    public void javaToXml(Object o, Node node) {

        try {
            Marshaller marshaller = new Marshaller(node);
            marshaller.setSuppressXSIType(true);
            marshaller.marshal(o);

        } catch (Throwable t) {
            t.printStackTrace();
            throw new GenericSystemException(
                    "Cannot make java to xml transformation. Error: " +
                            t.getMessage(), t);
        }
    }

    /**
     * Get java object attribute by the name
     *
     * @param obj  java object
     * @param name attribute name
     * @param cl   class
     * @return attribute value
     */
    public Object getAttribute(Object obj, String name, Class cl) {

        try {
            Class descrClass = getFieldDescriptorClass(cl);
            XMLClassDescriptor classDescr = (XMLClassDescriptor) descrClass
                    .newInstance();
            XMLFieldDescriptor fieldDescr = classDescr.getFieldDescriptor(name,
                    null, NodeType.Attribute);
            return fieldDescr.getHandler().getValue(obj);

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new GenericSystemException("Cannot get attribute '" + name +
                    "' for object '" + cl.getName() +
                    "': " + ex.getMessage(), ex);
        }
    }

    /**
     * Copy all attributes from <code>src</code> to <code>dest</code>
     *
     * @param src  source object
     * @param dest destination object
     * @param cl   class for driving process
     */
    public void copyAttributes(Object src, Object dest, Class cl) {

        try {
            Class descrClass = getFieldDescriptorClass(cl);
            XMLClassDescriptor classDescr = (XMLClassDescriptor) descrClass
                    .newInstance();
            XMLFieldDescriptor[] fieldDescrs = classDescr
                    .getAttributeDescriptors();
            for(int i = 0; i < fieldDescrs.length; i++) {
                Object srcObj = fieldDescrs[i].getHandler().getValue(src);
                if(srcObj != null) {
                    fieldDescrs[i].getHandler().setValue(dest, srcObj);
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new GenericSystemException("Cannot copy attributes", ex);
        }
    }

    /**
     * Copy common attributes from <code>src</code> to <code>dest</code>
     *
     * @param src    source object
     * @param dest   destination object
     * @param srcCl  source class for driving process
     * @param destCl destination class for driving process
     */
    public void copyAttributes(Object src, Object dest, Class srcCl,
                               Class destCl) {

        try {
            Class descrSrcClass = getFieldDescriptorClass(srcCl);
            Class descrDestClass = getFieldDescriptorClass(destCl);
            XMLClassDescriptor srcClassDescr
                    = (XMLClassDescriptor) descrSrcClass.newInstance();
            XMLClassDescriptor destClassDescr
                    = (XMLClassDescriptor) descrDestClass.newInstance();
            XMLFieldDescriptor[] srcFieldDescrs = srcClassDescr
                    .getAttributeDescriptors();
            for(int i = 0; i < srcFieldDescrs.length; i++) {
                Object srcObj = srcFieldDescrs[i].getHandler().getValue(src);

                if(srcObj != null) {
                    XMLFieldDescriptor destFieldDescr = destClassDescr
                            .getFieldDescriptor(
                                    srcFieldDescrs[i].getXMLName(), null,
                                    NodeType.Attribute);

                    if(destFieldDescr != null) {
                        destFieldDescr.getHandler().setValue(dest, srcObj);
                    }
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new GenericSystemException("Cannot copy common attributes",
                    ex);
        }
    }

    /**
     * Copy only undefined attributes from <code>src</code> to <code>dest</code>
     *
     * @param src  source object
     * @param dest destination object
     * @param cl   class for driving process
     */
    public void copyUndefinedAttributes(Object src, Object dest, Class cl) {

        try {
            Class descrClass = getFieldDescriptorClass(cl);
            XMLClassDescriptor classDescr = (XMLClassDescriptor) descrClass
                    .newInstance();
            XMLFieldDescriptor[] fieldDescrs = classDescr
                    .getAttributeDescriptors();
            for(int i = 0; i < fieldDescrs.length; i++) {
                Object destObj = fieldDescrs[i].getHandler().getValue(dest);
                if(destObj == null) {
                    Object srcObj = fieldDescrs[i].getHandler().getValue(src);
                    if(srcObj != null) {
                        fieldDescrs[i].getHandler().setValue(dest, srcObj);
                    }
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new GenericSystemException("Cannot copy undefined attributes",
                    ex);
        }
    }

    /**
     * Clone <code>src</code> object
     *
     * @param src source object
     * @param cl  class for driving process
     * @return new object
     */
    public Object clone(Object src, Class cl) {

        Object dest = null;

        try {
            dest = cl.newInstance();
            if(cl.equals(java.lang.String.class)) {
                // simple string - clone it
                return new String((String) src);
            }

            Class descrClass = getFieldDescriptorClass(cl);
            XMLClassDescriptor classDescr = (XMLClassDescriptor) descrClass
                    .newInstance();

            {
                // copy attributes
                XMLFieldDescriptor[] fieldDescrs = classDescr
                        .getAttributeDescriptors();
                if(fieldDescrs != null) {
                    for(int i = 0; i < fieldDescrs.length; i++) {
                        Object srcObj = fieldDescrs[i].getHandler().getValue(
                                src);
                        if(srcObj != null) {
                            fieldDescrs[i].getHandler().setValue(dest, srcObj);
                        }
                    }
                }
            }

            {
                // copy elements
                XMLFieldDescriptor[] fieldDescrs = classDescr
                        .getElementDescriptors();
                if(fieldDescrs != null) {
                    for(int i = 0; i < fieldDescrs.length; i++) {
                        Object child = fieldDescrs[i].getHandler().getValue(
                                src);
                        XMLFieldDescriptor fieldDescr = fieldDescrs[i];
                        FieldHandler fieldHnd = fieldDescr.getHandler();

                        if(fieldDescr.isMultivalued()) {
                            // array of fields
                            Object[] childs = (Object[]) child;
                            for(int j = 0; j < childs.length; j++) {
                                if(childs[j] != null) {
                                    fieldHnd.setValue(dest, clone(childs[j],
                                            childs[j].getClass()));
                                }
                            }

                        } else {
                            if(child != null) {
                                fieldHnd.setValue(dest, clone(child,
                                        child.getClass()));
                            }
                        }
                    }
                }
            }

            {
                // copy content
                XMLFieldDescriptor fieldDescr = classDescr
                        .getContentDescriptor();
                if(fieldDescr != null) {
                    Object srcObj = fieldDescr.getHandler().getValue(src);
                    if(srcObj != null) {
                        fieldDescr.getHandler().setValue(dest, srcObj);
                    }
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
            throw new GenericSystemException("Cannot clone object", ex);
        }

        return dest;
    }

    /**
     * Allows the user to set specific attributes on the underlying implementation
     *
     * @param name The name of the attribute.
     * @param o    The value of the attribute
     * @throws IllegalArgumentException
     */
    public void setAttribute(String name, Object o) {
        hm.put(name, o);
    }

    /**
     * Allows the user to retrieve specific attributes on the underlying implementation
     *
     * @param name The name of the attribute.
     * @return The value of the attribute
     * @throws IllegalArgumentException
     */
    public Object getAttribute(String name) {
        return hm.get(name);
    }

    /* -------------- PRIVATE METHODS -------------- */
    /* Get field descriptor class */
    private Class getFieldDescriptorClass(Class fieldClass)
            throws Exception {
        return Class.forName(fieldClass.getName() + DRIVE_CLASS_POSTFIX);
    }
}
