RpcDefaultProtocol.java

package eu.javaexperience.rpc;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import eu.javaexperience.asserts.AssertArgument;
import eu.javaexperience.collection.map.KeyVal;
import eu.javaexperience.datareprez.DataArray;
import eu.javaexperience.datareprez.DataCommon;
import eu.javaexperience.datareprez.DataObject;
import eu.javaexperience.datareprez.DataReprezTools;
import eu.javaexperience.datareprez.convertFrom.DataWrapper;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.reflect.Mirror.BelongTo;
import eu.javaexperience.reflect.Mirror.ClassData;
import eu.javaexperience.reflect.Mirror.FieldSelector;
import eu.javaexperience.reflect.Mirror.Select;
import eu.javaexperience.reflect.Mirror.Visibility;
import eu.javaexperience.rpc.bidirectional.BidirectionalRpcDefaultProtocol;
import eu.javaexperience.rpc.function.RpcFunctionParameter;
import eu.javaexperience.text.Format;

/**
 * for basic value extractions, for the exact parameter value tranformation
 * the function's cast is the responsible unit.
 * 
 * */
public class RpcDefaultProtocol implements RpcProtocolHandler
{
	protected final DataCommon transferDatatype;
	protected DataWrapper dataWrapper;
	
	public RpcDefaultProtocol(DataCommon proto)
	{
		this(proto, BidirectionalRpcDefaultProtocol.DEFAULT_RPC_DATA_WRAPPER);
	}
	
	public RpcDefaultProtocol(DataCommon proto, DataWrapper wrapper)
	{
		AssertArgument.assertNotNull(transferDatatype = proto, "transferDatatype");
		AssertArgument.assertNotNull(dataWrapper = wrapper, "dataWrapper");
	}
	
	protected static final FieldSelector fs = new FieldSelector(true, Visibility.Public, BelongTo.Instance, Select.All, Select.IsNot, Select.IsNot);
	
	protected FieldSelector object_mapper_field_selector = fs;
	
	public void setObjectMapperFieldSelector(FieldSelector fs)
	{
		AssertArgument.assertNotNull(fs, "fieldSelector");
		object_mapper_field_selector = fs;
	}
	
	@Override
	public Object wrap(Object in)
	{
		if(null == in)
		{
			return null;
		}
		
		if(null != DataReprezTools.isStorable(in))
		{
			return in;
		}
		
		return dataWrapper.wrap(dataWrapper, transferDatatype, in);
	}

	@Override
	public Object extract(Object in)
	{
		return extract(null, in);
	}

	@Override
	public Object extractValue(DataObject object, String key)
	{
		return extract(object.get(key));
	}

	@Override
	public Object extractValue(DataArray array, int index)
	{
		return extract(array.get(index));
	}
	
	@Override
	public Object extractThisContext(RpcRequest req)
	{
		DataObject obj = req.getRequestData();
		if(obj.has("_"))
		{
			return extract(obj.get("_"));
		}
		
		return null;
	}

	@Override
	public boolean putValue(DataObject object, String key, Object value)
	{
		return null != DataReprezTools.put(object, key, wrap(value));
	}

	@Override
	public boolean putValue(DataArray arr, int index, Object value)
	{
		return null != DataReprezTools.put(arr, index, wrap(value));
	}

	@Override
	public DataCommon getDefaultCommunicationProtocolPrototype()
	{
		return transferDatatype;
	}
	
	@Override
	public String extractNamespace(RpcRequest requestObject)
	{
		return requestObject.getRequestData().optString("N");
	}
	
	@Override
	public String getPacketTraceId(RpcRequest requestObject)
	{
		DataObject obj = requestObject.getRequestData();
		if(obj.has("t"))
		{
			return requestObject.getRequestData().get("t").toString();
		}
		return null;
	}

	@Override
	public String getRequestFunctionName(RpcRequest requestObject)
	{
		return requestObject.getRequestData().optString("f");
	}
	
	protected void putPacketTrace(RpcRequest request, DataObject result)
	{
		String trace = getPacketTraceId(request);
		if(null != trace)
		{
			result.putString("t", trace);
		}
	}

	@Override
	public Object[] extractParameters(RpcRequest requestObject)
	{
		DataArray o = requestObject.getRequestData().optArray("p");
		if(null != o)
		{
			Object[] ret = new Object[o.size()];
			for(int i=0;i<ret.length;++i)
			{
				ret[i] = extract(o.get(i));
			}
			return ret;
		}
		
		return Mirror.emptyObjectArray;
	}

	@Override
	public DataObject createEmptyResponsePacket(RpcRequest request)
	{
		DataObject ret = transferDatatype.newObjectInstance();
		String trace = getPacketTraceId(request);
		if(null != trace)
		{
			ret.putString("t", trace);
		}
		return ret;
	}

	@Override
	public DataObject createReturningValue(RpcRequest req, Object result)
	{
		//TODO
		DataObject ret = transferDatatype.newObjectInstance();
		putObject(ret, "r", wrap(result));
		putPacketTrace(req, ret);
		return ret;
	}

	@Override
	public DataObject createException(RpcRequest req, Throwable exception)
	{
		DataObject ret = reportException(req.getRequestData(), exception);
		putPacketTrace(req, ret);
		return ret;
	}
	
	public static DataObject reportException(DataCommon proto, Throwable t)
	{
		DataObject ret = proto.newObjectInstance();
		DataObject ex = proto.newObjectInstance();
		String msg = t.getMessage();
		if(null != msg)
		{
			ex.putString("message", msg);
		}
		ex.putString("type", t.getClass().getSimpleName());
		ex.putString("detail", Format.getPrintedStackTrace(t));
		
		putObject(ret, "e", ex);
		return ret;
	}
	
	public static Object castObject(Object in, RpcFunctionParameter param)
	{
		return param.getCaster().cast(in);
	}

	public static void putObject(DataObject ret, String key, Object value)
	{
		DataReprezTools.put(ret, key, value);
	}

	@Override
	public Object extract(Class retType, Object src)
	{
		if(null == src)
		{
			return null;
		}
		
		if(null != retType && retType.isAssignableFrom(src.getClass()))
		{
			return src;
		}
		
		if(src instanceof DataArray)
		{
			try
			{
				DataArray arr = (DataArray) src;
				
				if(null != retType && retType.isArray())
				{
					ArrayList ret = new ArrayList<>();
					Class cls = retType.getComponentType();
					for(int i=0;i<arr.size();++i)
					{
						ret.add(extract(cls, arr.get(i)));
					}
					
					return ret.toArray((Object[]) Array.newInstance(cls, 0));
				}
				else
				{
					Collection ret = null;
					
					if(null != retType)
					{
						for(Entry<Class, Class> a:collImpl)
						{
							if(retType.isAssignableFrom(a.getKey()))
							{
								ret = (Collection) a.getValue().newInstance();
							}
						}
					}
					
					if(null == ret)
					{
						ret = new ArrayList<>();
					}
					
					for(int i=0;i<arr.size();++i)
					{
						ret.add(extract(null, arr.get(i)));
					}
					return ret;
				}
			}
			catch(Exception e)
			{
				Mirror.propagateAnyway(e);
			}
		}
		else if(src instanceof DataObject)
		{
			try
			{
				DataObject obj = (DataObject) src;
				
				if(null != retType)
				{
					for(Entry<Class, Class> a:mapImpl)
					{
						if(retType.isAssignableFrom(a.getKey()))
						{
							Map ret = (Map) a.getValue().newInstance();
							
							for(String k:obj.keys())
							{
								ret.put(k, extract(null, obj.get(k)));
							}
							
							return ret;
						}
					}
					
					//if not a map type, try wrap into an object
					Object ret = createObject(retType, obj);
					if(null != ret)
					{
						ClassData cd = Mirror.getClassData(ret.getClass());
						
						for(String k:obj.keys())
						{
							Field f = cd.getFieldByName(k);
							if(null != f)
							{
								f.set(ret, extract(f.getType(), obj.get(k)));
							}
						}
						
						return ret;
					}
				}
				
				//otherwise return raw without any wrap.
			}
			catch(Exception e)
			{
				Mirror.propagateAnyway(e);
			}
		}
		
		return src;
	}
	
	protected static List<Entry<Class,Class>> mapImpl  = new ArrayList<>();
	protected static List<Entry<Class,Class>> collImpl  = new ArrayList<>();
	static
	{
		mapImpl.add(new KeyVal(ConcurrentMap.class, ConcurrentHashMap.class));
		mapImpl.add(new KeyVal(Map.class, HashMap.class));
		collImpl.add(new KeyVal(Set.class, HashSet.class));
		collImpl.add(new KeyVal(List.class, ArrayList.class));
		collImpl.add(new KeyVal(Collection.class, ArrayList.class));
		//implementation.add(new KeyVal(Entry.class, KeyVal.class));
	}
	
	protected Object createObject(Class request, DataObject obj) throws Exception
	{
		if(obj.has("class"))
		{
			return Class.forName(obj.getString("class")).newInstance();
		}
		else
		{
			return request.newInstance();
		}
	}
}