DataCommonXmlImpl.java

package eu.javaexperience.datareprez.xmlImpl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.transform.TransformerException;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import eu.javaexperience.datareprez.DataArray;
import eu.javaexperience.datareprez.DataCommonAbstractImpl;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.datareprez.DataReprezTools;
import eu.javaexperience.datareprez.abstractImpl.DataProtocol;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.text.Format;
import eu.javaexperience.text.StringTools;
import eu.javaexperience.document.DocumentTools;
import eu.javaexperience.io.primitive.LineReader;
import eu.javaexperience.io.primitive.LineReader.LineMode;

public abstract class DataCommonXmlImpl
{
	static DataObjectXmlImpl objPrototype = new DataObjectXmlImpl();
	static DataArrayXmlImpl arrPrototype = new DataArrayXmlImpl();
	
	public static final String NODE_NAME_MARK_NEED_ADOPT = "DataReprezRoot";
	
	public static final String ATTRIBUTE_NAME_PREFIX = "-";
	
	public static final DataProtocol PROTOCOL = new DataProtocol()
	{
		@Override
		public void sendPacket(byte[] data, OutputStream os) throws IOException
		{
			os.write(data);
			os.write('\n');
		}
		
		@Override
		public DataObject objectFromBlob(byte[] data)
		{
			return DataCommonXmlImpl.xmlObjectFromBlob(data);
		}
		
		@Override
		public DataObject newObjectInstance()
		{
			return new DataObjectXmlImpl();
		}
		
		@Override
		public DataArray newArrayInstance()
		{
			return new DataArrayXmlImpl();
		}
		
		@Override
		public Class getCommonsClass()
		{
			return Node.class;
		}
		
		@Override
		public DataArray arrayFromBlob(byte[] data)
		{
			return DataCommonXmlImpl.xmlArrayFromBlob(data);
		}
		
		@Override
		public byte[] acquirePacket(InputStream is) throws IOException
		{
			return LineReader.readByteLine(is, LineMode.Unix);
		}

		@Override
		public Object getNullObject()
		{
			return null;
		}
	};
	
	public static DataArray xmlArrayFromBlob(byte[] data)
	{
		try
		{
			Document doc = DocumentTools.parseDocument(new String(data));
			return new DataArrayXmlImpl(doc.getFirstChild());
		}
		catch (Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}

	public static DataObject xmlObjectFromBlob(byte[] data)
	{
		try
		{
			Document doc = DocumentTools.parseDocument(new String(data));
			if(NODE_NAME_MARK_NEED_ADOPT.equals(doc.getFirstChild().getNodeName()))
			{
				return new DataObjectXmlImpl(doc.getFirstChild());
			}
			
			return new DataObjectXmlImpl(doc);
		}
		catch (Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}
	
	protected static void adoptNodeTo(Node dst, boolean childs, String name, Node src)
	{
		Document doc = DocumentTools.getOwnerDocument(dst);
		
		if(childs)
		{
			DocumentTools.adoptAttributes(doc, dst, src);
			NodeList nl = src.getChildNodes();
			for(int i=0;i<nl.getLength();++i)
			{
				dst.appendChild(adoptOrCopy(doc, name, nl.item(i)));
			}
		}
		else
		{
			dst.appendChild(adoptOrCopy(doc, name, src));
		}
	}
	
	protected static Node adoptOrCopy(Document doc, String name, Node src)
	{
		if(null == name)
		{
			return DocumentTools.copyAttrAndAdopChilds(doc, createTag(doc, src.getNodeName()), src);
		}
		else
		{
			return DocumentTools.copyAttrAndAdopChilds(doc, createTag(doc, name), src);
		}
	}
	
	protected static boolean needAdopt(Node node)
	{
		return NODE_NAME_MARK_NEED_ADOPT.equals(node.getNodeName());
	}
	
	public static void applyNodeValue(Node subject, final Node n, final String name, Class type, Object value)
	{
		if(null == value)
		{
			if(null == type)
			{
				subject.removeChild(n);
			}
			else
			{
				n.setTextContent("");
			}
			return;
		}
		
		if(value instanceof DataObjectXmlImpl)
		{
			DataObjectXmlImpl s = (DataObjectXmlImpl) value;
			adoptNodeTo(n, needAdopt(s.node), null, s.node);
		}
		else if(value instanceof DataObject)
		{
			DataObjectXmlImpl dst = new DataObjectXmlImpl(n);
			DataReprezTools.copyInto(dst, (DataObject) value);
			applyNodeValue(subject, n, name, type, dst);
		}
		else
		{
			value = DataCommonAbstractImpl.wrapValueToStore(type, value);
			n.setTextContent(String.valueOf(value));
		}
	}
	
	protected static void keepSingleNode(Node subject, String name, Node keep)
	{
		NodeList nl = subject.getChildNodes();
		for(int i=0; i < nl.getLength();++i)
		{
			Node n = nl.item(i);
			if(n.getNodeName().equals(name) && !n.equals(keep))
			{
				subject.removeChild(n);
				--i;
			}
		}
	}

	protected static void setSubjectValue(Node xml, String nodeName, Object key, Class type, Object /*String or Node?*/ value)
	{
		String name = null;
		if(key instanceof String)
		{
			name = (String) key;
		}
		else
		{
			name = nodeName;
		}
		
		if(value instanceof DataArray)
		{
			String xKey = null == nodeName?name:nodeName;
			
			if(value instanceof DataArrayXmlImpl)
			{
				Document doc = DocumentTools.getOwnerDocument(xml);
				DataArrayXmlImpl src = (DataArrayXmlImpl) value;
				if(null != nodeName)
				{
					src.node = adoptOrCopy(doc, xKey, src.node);
					xml.appendChild(src.node);
					src.name = nodeName;
				}
				else
				{
					deleteKey(xml, xKey);
					NodeList cl = src.node.getChildNodes();
					for(int i=0;i<cl.getLength();++i)
					{
						xml.appendChild(adoptOrCopy(doc, xKey, cl.item(i)));
					}
					src.node = xml;
					src.name = xKey;
				}
			}
			else
			{
				DataArray src = (DataArray) value;
				DataArray dst = new DataArrayXmlImpl(xKey, xml);
				DataReprezTools.copyInto(dst, src);
			}
		}
		else
		{
			Node node = getOrCreateSubjectValue(xml, nodeName, key, true);
			if(key instanceof String)
			{
				keepSingleNode(xml, name, node);
			}
			applyNodeValue(xml, node, name, type, value);
		}
	}
	
	protected static void deleteKey(Node node, String key)
	{
		boolean attr = key.startsWith(ATTRIBUTE_NAME_PREFIX);
		
		if(attr)
		{
			NamedNodeMap nm = node.getAttributes();
			key = StringTools.getSubstringAfterFirstString(key, ATTRIBUTE_NAME_PREFIX);
			nm.removeNamedItem(key);
		}
		else
		{
			NodeList nl = node.getChildNodes();
			for(int i=0; i < nl.getLength();++i)
			{
				Node n = nl.item(i);
				if(n.getNodeName().equals(key))
				{
					node.removeChild(n);
					--i;
				}
			}
		}
	}
	
	public static Node createTag(Document doc, String name)
	{
		if(name.equals("#text"))
		{
			return doc.createTextNode("");
		}
		else if(name.equals("#comment"))
		{
			return doc.createComment("");
		}
		else
		{
			return doc.createElement(name);
		}
	}
	

	
	
	protected static int getNumberOfNodesWithName(Node node, String name)
	{
		int ret = 0;
		NodeList nl = node.getChildNodes();
		for(int i=0; i < nl.getLength();++i)
		{
			Node n = nl.item(i);
			if(n.getNodeName().equals(name))
			{
				++ret;
			}
		}
		return ret;
	}
	
	protected static <C> C getValueAs(Node node, String nodeName, Object _key, Class<C> cls)
	{
		int nums = getNumberOfNodesWithName(node, null == nodeName?(String)_key:nodeName);
		
		if(0 == nums)
		{
			return null;
		}
		
		Node n = getOrCreateSubjectValue(node, nodeName, _key, false);
		
		if(null == cls || Object.class == cls)
		{
			if(nums > 1)
			{
				if(null == nodeName)
				{
					return (C) new DataArrayXmlImpl((String)_key, node);
				}
				else if(NODE_NAME_MARK_NEED_ADOPT.equals(nodeName) && n.getChildNodes().getLength() > 1)
				{
					return (C) new DataArrayXmlImpl(nodeName, n);
				}
			}
			
			return (C) tryExamineOrWrapObject(n);
		}
		
		if(String.class == cls)
		{
			return (C) CastTo.String.cast(n.getTextContent());
		}
		else if(byte[].class == cls)
		{
			return (C) Format.base64Decode((String)CastTo.String.cast(n.getTextContent()));
		}
		else if(int.class == cls || Integer.class == cls)
		{
			return (C) CastTo.Int.cast(n.getTextContent());
		}
		else if(long.class == cls || Long.class == cls)
		{
			return (C) CastTo.Long.cast(n.getTextContent());
		}
		else if(double.class == cls || Double.class == cls)
		{
			return (C) CastTo.Double.cast(n.getTextContent());
		}
		else if(boolean.class == cls || Boolean.class == cls)
		{
			return (C) CastTo.Boolean.cast(n.getTextContent());
		}
		else if(DataObject.class == cls)
		{
			return (C) new DataObjectXmlImpl(n);
		}
		else if(DataArray.class == cls)
		{
			if(_key instanceof String)
			{
				String key = (String) _key;
				boolean attr = key.startsWith(ATTRIBUTE_NAME_PREFIX);
				if(attr)
				{
					NamedNodeMap nm = node.getAttributes();
					key = StringTools.getSubstringAfterFirstString(key, ATTRIBUTE_NAME_PREFIX);
					Node ret = nm.getNamedItem(key);
					if(null != ret)
					{
						return (C) new Node[]{ret};
					}
					return null;
				}
				else
				{
					return (C) new DataArrayXmlImpl(key, node);
				}
			}
			else
			{
				return (C) new DataArrayXmlImpl(nodeName, n);
			}
		}
		
		return null;
	}
	
	protected static Node getOrCreateSubjectValue(Node node, String nodeName, Object _key, boolean create)
	{
		String key = nodeName;
		if(null == nodeName)
		{
			key = (String) _key;
		}
		boolean attr = key.startsWith(ATTRIBUTE_NAME_PREFIX);
		
		if(attr)
		{
			NamedNodeMap nm = node.getAttributes();
			key = StringTools.getSubstringAfterFirstString(key, ATTRIBUTE_NAME_PREFIX);
			Node ret = nm.getNamedItem(key);
			if(null == ret && create)
			{
				nm.setNamedItem(ret = DocumentTools.getOwnerDocument(node).createAttribute(key));
			}
			return ret;
		}
		else
		{
			NodeList nl = node.getChildNodes();
			int ind = 0;
			if(_key instanceof Integer)
			{
				ind = (int) _key;
			}
			int crnt = 0;
			for(int i=0; i < nl.getLength();++i)
			{
				Node n = nl.item(i);
				if(n.getNodeName().equals(key))
				{
					if(crnt == ind)
					{
						return n;
					}
					else
					{
						++crnt;
					}
				}
			}
			
			if(create)
			{
				Node add = null;
				if(key.equals("#text"))
				{
					add = DocumentTools.getOwnerDocument(node).createTextNode("");
				}
				else if(key.equals("#comment"))
				{
					add = DocumentTools.getOwnerDocument(node).createComment("");
				}
				else
				{
					add = DocumentTools.getOwnerDocument(node).createElement(key);
				}
				node.appendChild(add);
				return add;
			}
			
			return null;
		}
	}
	
	public static Object tryExamineOrWrapObject(Node n)
	{
		NodeList ch = n.getChildNodes();
		switch(ch.getLength())
		{
			case 0:
				return null;
			case 1:
				if(DocumentTools.isTextNode(ch.item(0)))
				{
					return n.getTextContent();
				}
			default:
				return new DataObjectXmlImpl(n);
		}
	}
	
	public static String xmlToString(Node node)
	{
		try
		{
			return DocumentTools.toString(node);
		}
		catch (TransformerException e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}
}