/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.util;

import icy.file.FileUtil;
import icy.network.AuthenticationInfo;
import icy.network.NetworkUtil;
import icy.network.URLUtil;
import icy.system.IcyExceptionHandler;
import icy.type.DataType;
import icy.type.collection.array.ArrayUtil;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * XML utilities class (parse, read, create and write XML documents).
 * 
 * @author Stephane
 */
public class XMLUtil
{
    public static final String FILE_EXTENSION = "xml";
    public static final String FILE_DOT_EXTENSION = "." + FILE_EXTENSION;

    public static final String NODE_ROOT_NAME = "root";

    private static final String ATTR_NAME_NAME = "name";
    private static final String ATTR_VALUE_NAME = "value";

    // static document builder factory
    private static DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    // static transformer factory
    private static TransformerFactory transformerFactory = TransformerFactory.newInstance();
    // static Deflater
    // private static Deflater deflater = new Deflater(2, true);
    private static Deflater deflater = new Deflater(2);
    // static Inflater
    // private static Inflater inflater = new Inflater(true);
    private static Inflater inflater = new Inflater();

    static
    {
        try
        {
            docBuilderFactory.setNamespaceAware(false);
            docBuilderFactory.setValidating(false);
            docBuilderFactory.setFeature("http://xml.org/sax/features/namespaces", false);
            docBuilderFactory.setFeature("http://xml.org/sax/features/validation", false);
            docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
            docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        }
        catch (Exception e)
        {
            // ignore this
        }
    }

    // private static synchronized void init()
    // {
    // // initialize static builder
    // if (docBuilder == null)
    // docBuilder = createDocumentBuilder();
    // // initialize static transformer
    // if (transformer == null)
    // transformer = createTransformer();
    // }
    // docBuilder = createDocumentBuilder();
    // transformer = createTransformer();

    /**
     * Create and returns a new DocumentBuilder.
     */
    public static DocumentBuilder createDocumentBuilder()
    {
        try
        {
            return docBuilderFactory.newDocumentBuilder();
        }
        catch (ParserConfigurationException e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Create and returns a new Transformer.
     */
    public static Transformer createTransformer()
    {
        final Transformer result;

        try
        {
            result = transformerFactory.newTransformer();
        }
        catch (TransformerConfigurationException e)
        {
            IcyExceptionHandler.showErrorMessage(e, true, true);
            return null;
        }

        result.setOutputProperty(OutputKeys.METHOD, "xml");
        result.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
        result.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        result.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        result.setOutputProperty(OutputKeys.INDENT, "yes");

        return result;
    }

    /**
     * Create and returns a new Transformer.
     */
    public static Transformer createTransformerSafe() throws TransformerConfigurationException
    {
        final Transformer result = transformerFactory.newTransformer();

        result.setOutputProperty(OutputKeys.METHOD, "xml");
        result.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
        result.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        result.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        result.setOutputProperty(OutputKeys.INDENT, "yes");

        return result;
    }

    /**
     * Create and return an empty XML Document.
     */
    public static Document createDocument(boolean createRoot)
    {
        final DocumentBuilder docBuilder = createDocumentBuilder();

        // an error occurred
        if (docBuilder == null)
            return null;

        // create document
        final Document result = docBuilder.newDocument();

        // add default "root" element if wanted
        if (createRoot)
            createRootElement(result);

        return result;
    }

    /**
     * Parse the specified string and convert it to XML Document (throw an exception if an error occurred).
     * 
     * @throws IOException
     * @throws SAXException
     */
    public static Document createDocument(String xmlString) throws SAXException, IOException
    {
        final DocumentBuilder docBuilder = createDocumentBuilder();

        // an error occurred
        if (docBuilder == null)
            return null;

        return docBuilder.parse(new InputSource(new StringReader(filterString(xmlString))));
    }

    /**
     * @deprecated Use {@link #createDocument(String)} instead.
     */
    @Deprecated
    public static Document getDocument(String xmlString)
    {
        final DocumentBuilder docBuilder = createDocumentBuilder();

        // an error occurred
        if (docBuilder == null)
            return null;

        try
        {
            return docBuilder.parse(new InputSource(new StringReader(filterString(xmlString))));
        }
        catch (Exception e)
        {
            IcyExceptionHandler.showErrorMessage(e, true);
        }

        // return empty document
        return createDocument(false);
    }

    /**
     * @deprecated Use {@link #createDocument(String)} instead.
     */
    @Deprecated
    public static Document getDocumentSafe(String xmlString) throws SAXException, IOException
    {
        return createDocument(xmlString);
    }

    /**
     * Load XML Document from specified path.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(String path)
    {
        return loadDocument(path, null, false);
    }

    /**
     * Load XML Document from specified path.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(String path, boolean showError)
    {
        return loadDocument(path, null, showError);
    }

    /**
     * Load XML Document from specified path with specified authentication.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(String path, AuthenticationInfo auth, boolean showError)
    {
        if (StringUtil.isEmpty(path))
        {
            if (showError)
                System.err.println("XMLUtil.loadDocument('" + path + "') error: empty path !");

            return null;
        }

        final URL url = URLUtil.getURL(path);

        // load from URL
        if (url != null)
            return loadDocument(url, auth, showError);

        // try to load from file instead (no authentication needed then)
        return loadDocument(new File(path), showError);
    }

    /**
     * Load XML Document from specified file.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(File f)
    {
        return loadDocument(f, false);
    }

    /**
     * Load XML Document from specified file.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(File f, boolean showError)
    {
        if ((f == null) || !f.exists())
        {
            if (showError)
                System.err.println("XMLUtil.loadDocument('" + f + "') error: file not found !");

            return null;
        }

        final DocumentBuilder builder = createDocumentBuilder();

        if (builder != null)
        {
            try
            {
                return builder.parse(f);
            }
            catch (Exception e)
            {
                if (showError)
                {
                    System.err.println("XMLUtil.loadDocument('" + f.getPath() + "') error:");
                    IcyExceptionHandler.showErrorMessage(e, false);
                }
            }
        }

        return null;
    }

    /**
     * Load XML Document from specified URL.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(URL url)
    {
        return loadDocument(url, null, false);
    }

    /**
     * Load XML Document from specified URL.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(URL url, boolean showError)
    {
        return loadDocument(url, null, showError);
    }

    /**
     * Load XML Document from specified URL with authentication informations.<br>
     * Return null if no document can be loaded.
     */
    public static Document loadDocument(URL url, AuthenticationInfo auth, boolean showError)
    {
        // use file loading if possible
        if (URLUtil.isFileURL(url))
        {
            File f;

            try
            {
                f = new File(url.toURI());
            }
            catch (URISyntaxException e)
            {
                f = new File(url.getPath());
            }

            return loadDocument(f);
        }

        final DocumentBuilder builder = createDocumentBuilder();

        if (builder != null)
        {
            final InputStream ip = NetworkUtil.getInputStream(url, auth, true, showError);

            if (ip != null)
            {
                try
                {
                    return builder.parse(ip);
                }
                catch (Exception e)
                {
                    System.err.println("XMLUtil.loadDocument('" + url + "') error :");
                    IcyExceptionHandler.showErrorMessage(e, false);
                }
                finally
                {
                    try
                    {
                        ip.close();
                    }
                    catch (IOException e)
                    {
                        // ignore
                    }
                }
            }
            else if (showError)
                System.err.println("XMLUtil.loadDocument('" + url + "') error :");
        }

        return null;
    }

    /**
     * Save the specified XML Document to specified filename.<br>
     * Return false if an error occurred.
     */
    public static boolean saveDocument(Document doc, String filename)
    {
        return saveDocument(doc, FileUtil.createFile(filename));
    }

    /**
     * Save the specified XML Document to specified file.<br>
     * Return false if an error occurred.
     */
    public static boolean saveDocument(Document doc, File f)
    {
        if ((doc == null) || (f == null))
        {
            System.err.println("XMLUtil.saveDocument(...) error: specified document or file is null !");

            return false;
        }

        final Transformer transformer = createTransformer();

        // an error occurred
        if (transformer == null)
            return false;

        doc.normalizeDocument();

        final DocumentType doctype = doc.getDoctype();
        final DOMSource domSource = new DOMSource(doc);
        final StreamResult streamResult = new StreamResult(f.getAbsolutePath());

        try
        {
            if (doctype != null)
            {
                transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, doctype.getPublicId());
                transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, doctype.getSystemId());
            }

            transformer.transform(domSource, streamResult);

            return true;
        }
        catch (Exception e)
        {
            IcyExceptionHandler.showErrorMessage(e, true);
        }

        return false;
    }

    /**
     * Return the XML String from the specified document.
     * 
     * @throws TransformerException
     */
    public static String getXMLString(Document document) throws TransformerException
    {
        if (document == null)
            return "";

        final Transformer transformer = createTransformerSafe();
        final StringWriter writer = new StringWriter();

        transformer.transform(new DOMSource(document), new StreamResult(writer));

        return writer.toString();
    }

    /**
     * Create root element for specified document if it does not already exist and return it
     */
    public static Element createRootElement(Document doc)
    {
        return createRootElement(doc, NODE_ROOT_NAME);
    }

    /**
     * Create root element for specified document if it does not already exist and return it
     */
    public static Element createRootElement(Document doc, String name)
    {
        return getRootElement(doc, true, name);
    }

    /**
     * Return the root element for specified document<br>
     * Create if it does not already exist with the specified name
     */
    private static Element getRootElement(Document doc, boolean create, String name)
    {
        if (doc != null)
        {
            Element result = doc.getDocumentElement();

            if ((result == null) && create)
            {
                result = doc.createElement(name);
                doc.appendChild(result);
            }

            return result;
        }

        return null;
    }

    /**
     * Return the root element for specified document<br>
     * Create if it does not already exist with the default {@link #NODE_ROOT_NAME}
     */
    public static Element getRootElement(Document doc, boolean create)
    {
        return getRootElement(doc, create, NODE_ROOT_NAME);
    }

    /**
     * Return the root element for specified document (null if not found)<br>
     */
    public static Element getRootElement(Document doc)
    {
        return getRootElement(doc, false);
    }

    /**
     * Get parent element of specified element
     */
    public static Element getParentElement(Element element)
    {
        Node parent = element.getParentNode();

        while (parent != null)
        {
            if (parent instanceof Element)
                return (Element) parent;

            parent = parent.getParentNode();
        }

        return null;
    }

    /**
     * Get all child node of specified node.
     */
    @SuppressWarnings("null")
    public static ArrayList<Node> getChildren(Node node)
    {
        final ArrayList<Node> result = new ArrayList<Node>();
        int tries = 3;
        RuntimeException exception = null;

        // sometime the XML library fails so we make several attempts
        while (tries > 0)
        {
            try
            {
                final NodeList nodeList = node.getChildNodes();

                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if (n != null)
                            result.add(n);
                    }
                }

                return result;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get the first child node with specified name from node.<br>
     * Return null if not found.
     */
    @SuppressWarnings("null")
    public static Node getChild(Node node, String name)
    {
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n != null) && n.getNodeName().equals(name))
                            return n;
                    }
                }

                return null;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get all child nodes with specified name from node.
     */
    @SuppressWarnings("null")
    public static ArrayList<Node> getChildren(Node node, String name)
    {
        final ArrayList<Node> result = new ArrayList<Node>();
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n != null) && n.getNodeName().equals(name))
                            result.add(n);
                    }
                }

                return result;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * @deprecated Use {@link #getChildren(Node)} instead.
     */
    @Deprecated
    public static ArrayList<Node> getSubNodes(Node node)
    {
        return getChildren(node);
    }

    /**
     * @deprecated Use {@link #getChild(Node, String)} instead.
     */
    @Deprecated
    public static Node getSubNode(Node node, String name)
    {
        return getChild(node, name);
    }

    /**
     * @deprecated Use {@link #getChildren(Node, String)} instead.
     */
    @Deprecated
    public static ArrayList<Node> getSubNodes(Node node, String name)
    {
        return getChildren(node, name);
    }

    /**
     * Get all child element of specified node.
     */
    @SuppressWarnings("null")
    public static ArrayList<Element> getElements(Node node)
    {
        final ArrayList<Element> result = new ArrayList<Element>();
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if (n instanceof Element)
                            result.add((Element) n);
                    }
                }

                return result;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get the first child element with specified name from node.<br>
     * Return null if not found.
     */
    @SuppressWarnings("null")
    public static Element getElement(Node node, String name)
    {
        if (node == null)
            return null;

        final String filteredName = filterString(name);
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            try
            {
                final NodeList nodeList = node.getChildNodes();

                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n instanceof Element) && n.getNodeName().equals(filteredName))
                            return (Element) n;
                    }
                }

                return null;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get all child element with specified name of specified node.
     */
    @SuppressWarnings("null")
    public static ArrayList<Element> getElements(Node node, String name)
    {
        final ArrayList<Element> result = new ArrayList<Element>();
        final String filteredName = filterString(name);
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n instanceof Element) && n.getNodeName().equals(filteredName))
                            result.add((Element) n);
                    }
                }
                return result;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * @deprecated Use {@link #getElements(Node)} instead.
     */
    @Deprecated
    public static ArrayList<Element> getSubElements(Node node)
    {
        return getElements(node);
    }

    /**
     * @deprecated Use {@link #getElement(Node, String)} instead.
     */
    @Deprecated
    public static Element getSubElement(Node node, String name)
    {
        return getElement(node, name);
    }

    /**
     * @deprecated Use {@link #getElements(Node, String)} instead.
     */
    @Deprecated
    public static ArrayList<Element> getSubElements(Node node, String name)
    {
        return getElements(node, name);
    }

    /**
     * Get all child element with specified type (name) from specified node.
     */
    @SuppressWarnings("null")
    public static ArrayList<Element> getGenericElements(Node node, String type)
    {
        final ArrayList<Element> result = new ArrayList<Element>();
        final String filteredType = filterString(type);
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n instanceof Element) && n.getNodeName().equals(filteredType))
                            result.add((Element) n);
                    }
                }

                return result;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get all child element with specified type (name) and name ('name attribute value')
     * from specified node.
     */
    @SuppressWarnings("null")
    public static ArrayList<Element> getGenericElements(Node node, String type, String name)
    {
        final ArrayList<Element> result = new ArrayList<Element>();
        final String filteredName = filterString(name);
        final String filteredType = filterString(type);
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n instanceof Element) && n.getNodeName().equals(filteredType))
                        {
                            final Element element = (Element) n;

                            if (element.getAttribute(ATTR_NAME_NAME).equals(filteredName))
                                result.add(element);
                        }
                    }
                }

                return result;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * @deprecated Use {@link #getGenericElements(Node, String)} instead.
     */
    @Deprecated
    public static ArrayList<Element> getSubGenericElements(Node node, String type)
    {
        return getGenericElements(node, type);
    }

    /**
     * @deprecated Use {@link #getGenericElements(Node, String, String)} instead.
     */
    @Deprecated
    public static ArrayList<Element> getSubGenericElements(Node node, String type, String name)
    {
        return getGenericElements(node, type, name);
    }

    /**
     * Get child element with specified type (name) and name ('name attribute value')
     * from specified node.
     */
    @SuppressWarnings("null")
    public static Element getGenericElement(Node node, String type, String name)
    {
        final String filteredName = filterString(name);
        final String filteredType = filterString(type);
        int tries = 3;
        RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            final NodeList nodeList = node.getChildNodes();

            try
            {
                if (nodeList != null)
                {
                    for (int i = 0; i < nodeList.getLength(); i++)
                    {
                        final Node n = nodeList.item(i);

                        if ((n instanceof Element) && n.getNodeName().equals(filteredType))
                        {
                            final Element element = (Element) n;

                            if (element.getAttribute(ATTR_NAME_NAME).equals(filteredName))
                                return element;
                        }
                    }
                }

                return null;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get name of specified generic element
     */
    public static String getGenericElementName(Element element)
    {
        if (element != null)
            return element.getAttribute(ATTR_NAME_NAME);

        return "";
    }

    /**
     * Get value of specified generic element
     */
    public static String getGenericElementValue(Element element, String def)
    {
        return getAttributeValue(element, ATTR_VALUE_NAME, def);
    }

    /**
     * Get all attributes of the specified element
     */
    public static ArrayList<Attr> getAllAttributes(Element element)
    {
        final NamedNodeMap nodeMap = element.getAttributes();
        final ArrayList<Attr> result = new ArrayList<Attr>();

        for (int i = 0; i < nodeMap.getLength(); i++)
            result.add((Attr) nodeMap.item(i));

        return result;
    }

    private static boolean getBoolean(String value, boolean def)
    {
        return StringUtil.parseBoolean(value, def);
    }

    private static int getInt(String value, int def)
    {
        return StringUtil.parseInt(value, def);
    }

    private static long getLong(String value, long def)
    {
        return StringUtil.parseLong(value, def);
    }

    private static float getFloat(String value, float def)
    {
        return StringUtil.parseFloat(value, def);
    }

    private static double getDouble(String value, double def)
    {
        return StringUtil.parseDouble(value, def);
    }

    private static byte[] getBytes(String value, byte[] def) throws DataFormatException
    {
        if (value == null)
            return def;

        // get packed byte data
        final byte[] result = (byte[]) ArrayUtil.stringToArray1D(value, DataType.BYTE, true, ":");

        synchronized (inflater)
        {
            // unpack and return
            return ZipUtil.unpack(inflater, result);
        }
    }

    private static String toString(boolean value)
    {
        return StringUtil.toString(value);
    }

    private static String toString(int value)
    {
        return StringUtil.toString(value);
    }

    private static String toString(long value)
    {
        return StringUtil.toString(value);
    }

    private static String toString(float value)
    {
        return StringUtil.toString(value);
    }

    private static String toString(double value)
    {
        return StringUtil.toString(value);
    }

    private static String toString(byte[] value)
    {
        final byte[] packed;

        synchronized (deflater)
        {
            packed = ZipUtil.pack(deflater, value, -1);
        }

        // pack data and convert to string
        return ArrayUtil.array1DToString(packed, false, true, ":", -1);
    }

    /**
     * Get an attribute from the specified Element
     */
    public static Attr getAttribute(Element element, String attribute)
    {
        if (element != null)
            return element.getAttributeNode(attribute);

        return null;
    }

    /**
     * Get attribute value from the specified Element.<br>
     * If no attribute found 'def' value is returned.
     */
    @SuppressWarnings("null")
    public static String getAttributeValue(Element element, String attribute, String def)
    {
        if (element == null)
            return def;

        final String filteredAttr = filterString(attribute);
        int tries = 3;
        RuntimeException exception = null;

        // sometime the XML library fails so we make several attempts
        while (tries > 0)
        {
            try
            {
                final Attr attr = element.getAttributeNode(filteredAttr);

                if (attr != null)
                    return attr.getValue();

                return def;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get attribute value as Boolean from the specified Element.<br>
     * If no attribute found 'def' value is returned.
     */
    public static boolean getAttributeBooleanValue(Element element, String attribute, boolean def)
    {
        return getBoolean(getAttributeValue(element, attribute, ""), def);
    }

    /**
     * Get attribute value as byte array from the specified Element.<br>
     * If the attribute is not found 'def' value is returned.<br>
     * If an error occurred or if element is <code>null</code> then <code>null</code> is returned.
     */
    public static byte[] getAttributeBytesValue(Element element, String attribute, byte[] def)
    {
        try
        {
            return getBytes(getAttributeValue(element, attribute, ""), def);
        }
        catch (Exception e)
        {
            if (e instanceof RuntimeException)
                throw (RuntimeException) e;

            IcyExceptionHandler.showErrorMessage(e, true);
            return null;
        }
    }

    /**
     * Get attribute value as integer from the specified Element.<br>
     * If no attribute found 'def' value is returned.
     */
    public static int getAttributeIntValue(Element element, String attribute, int def)
    {
        return getInt(getAttributeValue(element, attribute, ""), def);
    }

    /**
     * Get attribute value as long from the specified Element.<br>
     * If no attribute found 'def' value is returned.
     */
    public static long getAttributeLongValue(Element element, String attribute, long def)
    {
        return getLong(getAttributeValue(element, attribute, ""), def);
    }

    /**
     * Get attribute value as float from the specified Element.<br>
     * If no attribute found 'def' value is returned.
     */
    public static float getAttributeFloatValue(Element element, String attribute, float def)
    {
        return getFloat(getAttributeValue(element, attribute, ""), def);
    }

    /**
     * Get attribute value as double from the specified Element.<br>
     * If no attribute found 'def' value is returned.
     */
    public static double getAttributeDoubleValue(Element element, String attribute, double def)
    {
        return getDouble(getAttributeValue(element, attribute, ""), def);
    }

    /**
     * Get first value (value of first child) from the specified Element.<br>
     * If no value found 'def' value is returned.
     */
    @SuppressWarnings("null")
    public static String getFirstValue(Element element, String def)
    {
        if (element == null)
            return def;

        int tries = 3;
        RuntimeException exception = null;

        // sometime the XML library fails so we make several attempts
        while (tries > 0)
        {
            try
            {
                final Node child = element.getFirstChild();

                if (child != null)
                    return child.getNodeValue();

                return def;
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get all values (value of all child) from the specified Element.<br>
     * If no value found 'def' value is returned.
     */
    @SuppressWarnings("null")
    public static String getAllValues(Element element, String def)
    {
        if (element == null)
            return def;

        int tries = 3;
        RuntimeException exception = null;

        // sometime the XML library fails so we make several attempts
        while (tries > 0)
        {
            try
            {
                final StringBuilder str = new StringBuilder();

                Node child = element.getFirstChild();
                while (child != null)
                {
                    str.append(child.getNodeValue());
                    child = child.getNextSibling();
                }

                return str.toString();
            }
            catch (RuntimeException e)
            {
                // try again
                exception = e;
                tries--;
            }
        }

        throw exception;
    }

    /**
     * Get all values (value of all child) as String from the specified Element.<br>
     * If no value found 'def' value is returned.
     */
    public static String getValue(Element element, String def)
    {
        return getAllValues(element, def);
    }

    /**
     * Get all values (value of all child) as Boolean from the specified Element.
     */
    public static boolean getBooleanValue(Element element, boolean def)
    {
        return getBoolean(getFirstValue(element, ""), def);
    }

    /**
     * Get value as integer from the specified Element.<br>
     * If no integer value found 'def' value is returned.
     */
    public static int getIntValue(Element element, int def)
    {
        return getInt(getFirstValue(element, ""), def);
    }

    /**
     * Get value as long from the specified Element.<br>
     * If no integer value found 'def' value is returned.
     */
    public static long getLongValue(Element element, long def)
    {
        return getLong(getFirstValue(element, ""), def);
    }

    /**
     * Get value as float from the specified Element.<br>
     * If no float value found 'def' value is returned.
     */
    public static float getFloatValue(Element element, float def)
    {
        return getFloat(getFirstValue(element, ""), def);
    }

    /**
     * Get value as double from the specified Element.<br>
     * If no double value found 'def' value is returned.
     */
    public static double getDoubleValue(Element element, double def)
    {
        return getDouble(getFirstValue(element, ""), def);
    }

    /**
     * Get value as byte array from the specified Element.<br>
     * If no byte array value found 'def' value is returned.<br>
     * Return <code>null</code> if an error happened.
     */
    public static byte[] getBytesValue(Element element, byte[] def)
    {
        try
        {
            return getBytes(getFirstValue(element, ""), def);
        }
        catch (Exception e)
        {
            if (e instanceof RuntimeException)
                throw (RuntimeException) e;

            IcyExceptionHandler.showErrorMessage(e, true);
            return null;
        }
    }

    /**
     * Get first element value from the specified node.<br>
     * If no value found 'def' value is returned.
     */
    public static String getElementFirstValue(Node node, String name, String def)
    {
        return getFirstValue(getElement(node, name), def);
    }

    /**
     * Get all element values from the specified node.<br>
     * If no value found 'def' value is returned.
     */
    public static String getElementAllValues(Node node, String name, String def)
    {
        return getAllValues(getElement(node, name), def);
    }

    /**
     * Get element value as string from the specified node.<br>
     * If no value found 'def' value is returned.
     */
    public static String getElementValue(Node node, String name, String def)
    {
        return getValue(getElement(node, name), def);
    }

    /**
     * Get element value as boolean from the specified node.
     */
    public static boolean getElementBooleanValue(Node node, String name, boolean def)
    {
        return getBoolean(getElementValue(node, name, ""), def);
    }

    /**
     * Get element value as integer from the specified node.<br>
     * If no integer value found 'def' value is returned.
     */
    public static int getElementIntValue(Node node, String name, int def)
    {
        return getInt(getElementValue(node, name, ""), def);
    }

    /**
     * Get element value as long from the specified node.<br>
     * If no integer value found 'def' value is returned.
     */
    public static long getElementLongValue(Node node, String name, long def)
    {
        return getLong(getElementValue(node, name, ""), def);
    }

    /**
     * Get element value as float from the specified node.<br>
     * If no float value found 'def' value is returned.
     */
    public static float getElementFloatValue(Node node, String name, float def)
    {
        return getFloat(getElementValue(node, name, ""), def);
    }

    /**
     * Get element value as double from the specified node.<br>
     * If no double value found 'def' value is returned.
     */
    public static double getElementDoubleValue(Node node, String name, double def)
    {
        return getDouble(getElementValue(node, name, ""), def);
    }

    /**
     * Get element value as byte array from the specified node.<br>
     * If no byte array value found 'def' value is returned.<br>
     * Return <code>null</code> if an error happened.
     */
    public static byte[] getElementBytesValue(Node node, String name, byte[] def)
    {
        try
        {
            return getBytes(getElementValue(node, name, ""), def);
        }
        catch (Exception e)
        {
            if (e instanceof RuntimeException)
                throw (RuntimeException) e;

            IcyExceptionHandler.showErrorMessage(e, true);
            return null;
        }
    }

    /**
     * Get value ('value' attribute value) from element with specified type
     * and name ('name' attribute value).<br>
     * If no value found 'def' value is returned.
     */
    public static String getGenericElementValue(Node node, String type, String name, String def)
    {
        return getGenericElementValue(getGenericElement(node, type, name), def);
    }

    /**
     * Get value ('value' attribute value) as boolean from element with specified type
     * and name ('name' attribute value).<br>
     * If no byte array value found 'def' value is returned.
     */
    public static boolean getGenericElementBooleanValue(Node node, String type, String name, boolean def)
    {
        return getBoolean(getGenericElementValue(node, type, name, ""), def);
    }

    /**
     * Get value ('value' attribute value) as integer from element with specified type
     * and name ('name' attribute value).<br>
     * If no integer value found 'def' value is returned.
     */
    public static int getGenericElementIntValue(Node node, String type, String name, int def)
    {
        return getInt(getGenericElementValue(node, type, name, ""), def);
    }

    /**
     * Get value ('value' attribute value) as long from element with specified type
     * and name ('name' attribute value).<br>
     * If no integer value found 'def' value is returned.
     */
    public static long getGenericElementLongValue(Node node, String type, String name, long def)
    {
        return getLong(getGenericElementValue(node, type, name, ""), def);
    }

    /**
     * Get value ('value' attribute value) as float from element with specified type
     * and name ('name' attribute value).<br>
     * If no float value found 'def' value is returned.
     */
    public static float getGenericElementFloatValue(Node node, String type, String name, float def)
    {
        return getFloat(getGenericElementValue(node, type, name, ""), def);
    }

    /**
     * Get value ('value' attribute value) as double from element with specified type
     * and name ('name' attribute value).<br>
     * If no double value found 'def' value is returned.
     */
    public static double getGenericElementDoubleValue(Node node, String type, String name, double def)
    {
        return getDouble(getGenericElementValue(node, type, name, ""), def);
    }

    /**
     * Get value ('value' attribute value) as byte array from element with specified type
     * and name ('name' attribute value).<br>
     * If no byte array value found 'def' value is returned.<br>
     * Return <code>null</code> if an error happened.
     */
    public static byte[] getGenericElementBytesValue(Node node, String type, String name, byte[] def)
    {
        try
        {
            return getBytes(getElementValue(node, name, ""), def);
        }
        catch (Exception e)
        {
            if (e instanceof RuntimeException)
                throw (RuntimeException) e;

            IcyExceptionHandler.showErrorMessage(e, true);
            return null;
        }
    }

    /**
     * Add the specified node to specified parent node
     */
    public static Node addNode(Node parent, Node node)
    {
        return parent.appendChild(node);
    }

    /**
     * Add a value to the specified node
     */
    public static Node addValue(Node node, String value)
    {
        final Node newNode;
        final String filteredValue = filterString(value);

        if (node instanceof Document)
            newNode = ((Document) node).createTextNode(filteredValue);
        else
            newNode = node.getOwnerDocument().createTextNode(filteredValue);

        if (newNode != null)
            node.appendChild(newNode);

        return newNode;
    }

    /**
     * Add a named element to the specified node
     */
    public static Element addElement(Node node, String name)
    {
        final Element element;
        final String filteredName = filterString(name);

        if (node instanceof Document)
            element = ((Document) node).createElement(filteredName);
        else
            element = node.getOwnerDocument().createElement(filteredName);

        node.appendChild(element);

        return element;
    }

    /**
     * Add a named element with a value to the specified node
     */
    public static Element addElement(Node node, String name, String value)
    {
        final Element element = addElement(node, name);

        if (!StringUtil.isEmpty(value))
            addValue(element, value);

        return element;
    }

    /**
     * Add a generic element with specified type and name to the specified node
     */
    public static Element addGenericElement(Node node, String type, String name)
    {
        final Element element = addElement(node, type);

        setGenericElementName(element, name);

        return element;
    }

    /**
     * Add a generic element with specified type, name and value to the specified node
     */
    public static Element addGenericElement(Node node, String type, String name, String value)
    {
        final Element element = addElement(node, type);

        setGenericElementName(element, name);
        setGenericElementValue(element, value);

        return element;
    }

    /**
     * Set name of specified generic element
     */
    public static void setGenericElementName(Element element, String name)
    {
        if (element != null)
            element.setAttribute(ATTR_NAME_NAME, filterString(name));
    }

    /**
     * Set value of specified generic element
     */
    public static void setGenericElementValue(Element element, String value)
    {
        if (element != null)
            element.setAttribute(ATTR_VALUE_NAME, filterString(value));
    }

    /**
     * Set the specified node to the specified parent node.<br>
     * The new node replace the previous existing node with the same name.
     */
    public static Node setNode(Node parent, Node node)
    {
        final String name = node.getNodeName();

        XMLUtil.removeNode(parent, name);

        return XMLUtil.addNode(parent, node);
    }

    /**
     * Set a element with specified name to specified node.<br>
     * If the Element was already existing then it's just returned.
     */
    public static Element setElement(Node node, String name)
    {
        // get element
        final Element element = getElement(node, name);
        if (element != null)
            return element;

        return addElement(node, name);
    }

    /**
     * Set a generic element with specified type and name to specified node.<br>
     * If the generic element was already existing then it's just returned.
     */
    public static Element setGenericElement(Node node, String type, String name)
    {
        // get generic element
        final Element element = getGenericElement(node, type, name);
        if (element != null)
            return element;

        return addGenericElement(node, type, name);
    }

    /**
     * Set an attribute and his value to the specified node
     */
    public static void setAttributeValue(Element element, String attribute, String value)
    {
        element.setAttribute(attribute, filterString(value));
    }

    /**
     * Set an attribute and his value as boolean to the specified node
     */
    public static void setAttributeBooleanValue(Element element, String attribute, boolean value)
    {
        setAttributeValue(element, attribute, toString(value));
    }

    /**
     * Set an attribute and his value as integer to the specified node
     */
    public static void setAttributeIntValue(Element element, String attribute, int value)
    {
        setAttributeValue(element, attribute, toString(value));
    }

    /**
     * Set an attribute and his value as integer to the specified node
     */
    public static void setAttributeLongValue(Element element, String attribute, long value)
    {
        setAttributeValue(element, attribute, toString(value));
    }

    /**
     * Set an attribute and his value as float to the specified node
     */
    public static void setAttributeFloatValue(Element element, String attribute, float value)
    {
        setAttributeValue(element, attribute, toString(value));
    }

    /**
     * Set an attribute and his value as double to the specified node
     */
    public static void setAttributeDoubleValue(Element element, String attribute, double value)
    {
        setAttributeValue(element, attribute, toString(value));
    }

    /**
     * Set an attribute and his value as byte array to the specified node
     */
    public static void setAttributeBytesValue(Element element, String attribute, byte[] value)
    {
        setAttributeValue(element, attribute, toString(value));
    }

    /**
     * Set value to the specified element
     */
    public static void setValue(Element element, String value)
    {
        // remove child nodes
        removeAllChildren(element);
        // add value
        addValue(element, value);
    }

    /**
     * Remove all characters that are valid XML markups.
     */
    public static String removeXMLMarkups(String s)
    {
        final StringBuffer out = new StringBuffer();

        for (char c : s.toCharArray())
        {
            if ((c == '\'') || (c == '<') || (c == '>') || (c == '&') || (c == '\"'))
                continue;

            out.append(c);
        }

        return out.toString();
    }

    /**
     * Remove any invalid XML character from the specified string.
     */
    public static String removeInvalidXMLCharacters(String text)
    {
        if (text == null)
            return "";

        final String xml10pattern = "[^" + "\u0009\r\n" + "\u0020-\uD7FF" + "\uE000-\uFFFD"
                + "\ud800\udc00-\udbff\udfff" + "]";
        // final String xml11pattern = "[^" + "\u0001-\uD7FF" + "\uE000-\uFFFD" +
        // "\ud800\udc00-\udbff\udfff" + "]+";

        // some OME generate incorrect "&#" sequence so we just replace them with "#"
        return text.replaceAll(xml10pattern, "").replaceAll("&#", "#");
    }

    /**
     * Same as {@link #removeInvalidXMLCharacters(String)}
     */
    public static String filterString(String text)
    {
        return removeInvalidXMLCharacters(text);
    }

    /**
     * Set value as boolean to the specified element
     */
    public static void setBooleanValue(Element element, boolean value)
    {
        setValue(element, toString(value));
    }

    /**
     * Set value as integer to the specified element
     */
    public static void setIntValue(Element element, int value)
    {
        setValue(element, toString(value));
    }

    /**
     * Set value as long to the specified element
     */
    public static void setLongValue(Element element, long value)
    {
        setValue(element, toString(value));
    }

    /**
     * Set value as float to the specified element
     */
    public static void setFloatValue(Element element, float value)
    {
        setValue(element, toString(value));
    }

    /**
     * Set value as double to the specified element
     */
    public static void setDoubleValue(Element element, double value)
    {
        setValue(element, toString(value));
    }

    /**
     * Set value as byte array to the specified element
     */
    public static void setBytesValue(Element element, byte[] value)
    {
        setValue(element, toString(value));
    }

    /**
     * Set an element with specified name and his value to the specified node
     */
    public static void setElementValue(Node node, String name, String value)
    {
        // get element (create it if needed)
        final Element element = setElement(node, name);
        // set value
        setValue(element, value);
    }

    /**
     * Set an element with specified name and his value as boolean to the specified node
     */
    public static void setElementBooleanValue(Node node, String name, boolean value)
    {
        setElementValue(node, name, toString(value));
    }

    /**
     * Set an element with specified name and his value as integer to the specified node
     */
    public static void setElementIntValue(Node node, String name, int value)
    {
        setElementValue(node, name, toString(value));
    }

    /**
     * Set an element with specified name and his value as long to the specified node
     */
    public static void setElementLongValue(Node node, String name, long value)
    {
        setElementValue(node, name, toString(value));
    }

    /**
     * Set an element with specified name and his value as float to the specified node
     */
    public static void setElementFloatValue(Node node, String name, float value)
    {
        setElementValue(node, name, toString(value));
    }

    /**
     * Set an element with specified name and his value as double to the specified node
     */
    public static void setElementDoubleValue(Node node, String name, double value)
    {
        setElementValue(node, name, toString(value));
    }

    /**
     * Set an element with specified name and his value as byte array to the specified node
     */
    public static void setElementBytesValue(Node node, String name, byte[] value)
    {
        setElementValue(node, name, toString(value));
    }

    /**
     * Set a generic element with specified type and name and his value to the specified node
     */
    public static void setGenericElementValue(Node node, String type, String name, String value)
    {
        // get generic element (create it if needed)
        final Element element = setGenericElement(node, type, name);

        if (element != null)
            element.setAttribute(ATTR_VALUE_NAME, value);
    }

    /**
     * Set an element with specified type and name and his value as boolean to the specified node
     */
    public static void setGenericElementBooleanValue(Node node, String type, String name, boolean value)
    {
        setGenericElementValue(node, type, name, toString(value));
    }

    /**
     * Set an element with specified type and name and his value as integer to the specified node
     */
    public static void setGenericElementIntValue(Node node, String type, String name, int value)
    {
        setGenericElementValue(node, type, name, toString(value));
    }

    /**
     * Set an element with specified type and name and his value as long to the specified node
     */
    public static void setGenericElementLongValue(Node node, String type, String name, long value)
    {
        setGenericElementValue(node, type, name, toString(value));
    }

    /**
     * Set an element with specified type and name and his value as float to the specified node
     */
    public static void setGenericElementFloatValue(Node node, String type, String name, float value)
    {
        setGenericElementValue(node, type, name, toString(value));
    }

    /**
     * Set an element with specified type and name and his value as double to the specified node
     */
    public static void setGenericElementDoubleValue(Node node, String type, String name, double value)
    {
        setGenericElementValue(node, type, name, toString(value));
    }

    /**
     * Set an element with specified type and name and his value as byte array to the specified node
     */
    public static void setGenericElementBytesValue(Node node, String type, String name, byte[] value)
    {
        setGenericElementValue(node, type, name, toString(value));
    }

    /**
     * Remove a node with specified name from the specified node
     */
    public static boolean removeNode(Node node, String name)
    {
        final Node subNode = getSubNode(node, name);

        if (subNode != null)
            return removeNode(node, subNode);

        return false;
    }

    /**
     * Remove the specified node from the specified parent node
     */
    public static boolean removeNode(Node parent, Node child)
    {
        int tries = 3;
        // RuntimeException exception = null;

        // have to make several attempts as sometime XML library fails to correctly retrieve XML data
        while (tries > 0)
        {
            try
            {
                parent.removeChild(child);
                return true;
            }
            catch (RuntimeException e)
            {
                // exception = e;
                tries--;
            }
        }

        // we just ignore here
        return false;
    }

    /**
     * @deprecated Use {@link #removeAllChildren(Node)} instead
     */
    @Deprecated
    public static void removeAllChilds(Node node)
    {
        removeAllChildren(node);
    }

    /**
     * Remove all children from the specified node
     */
    public static void removeAllChildren(Node node)
    {
        while (node.hasChildNodes())
            node.removeChild(node.getLastChild());
    }

    /**
     * @deprecated Use {@link #removeChildren(Node, String)} instead
     */
    @Deprecated
    public static void removeChilds(Node node, String name)
    {
        removeChildren(node, name);
    }

    /**
     * Remove all children with specified name from the specified node
     */
    public static void removeChildren(Node node, String name)
    {
        Node currentChild = node.getFirstChild();

        while (currentChild != null)
        {
            final Node nextChild = currentChild.getNextSibling();

            if (currentChild.getNodeName().equals(name))
                node.removeChild(currentChild);

            currentChild = nextChild;
        }
    }

    /**
     * Remove an attribute from the specified element
     */
    public static void removeAttribute(Element element, String name)
    {
        element.removeAttribute(name);
    }

    /**
     * Remove all attribute from the specified element
     */
    public static void removeAllAttributes(Element element)
    {
        final NamedNodeMap nodeMap = element.getAttributes();

        for (int i = 0; i < nodeMap.getLength(); i++)
            element.removeAttribute(nodeMap.item(i).getNodeName());
    }
}