TargetObject.java

package eu.linuxengineering.zabbix.api;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
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.concurrent.ConcurrentHashMap;

import org.json.JSONArray;

import eu.javaexperience.arrays.ArrayTools;
import eu.javaexperience.collection.enumerations.EnumTools;
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.DataReprezType;
import eu.javaexperience.datareprez.convertFrom.ModifiableObject;
import eu.javaexperience.datareprez.convertFrom.ObjectLike;
import eu.javaexperience.datareprez.jsonImpl.DataArrayJsonImpl;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Caster;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.semantic.references.MayNotModified;

public class TargetObject implements ModifiableObject, ObjectLike
{
	@Override
	public DataReprezType getDataReprezType()
	{
		return DataReprezType.OBJECT;
	}

	@Override
	public boolean has(String key)
	{
		return ArrayTools.contains(keys(), key);
	}
	
	protected static class ModifiableFields
	{
		public ModifiableFields(Class cls)
		{
			for(Field f:Mirror.collectClassFields(cls, true))
			{
				fields.put(f.getName(), f);
			}
			
			keys = fields.keySet().toArray(Mirror.emptyStringArray);
		}

		public String[] keys;
		public Map<String, Field> fields = new HashMap<>();
	}
	
	protected static Map<Class, ModifiableFields> MF = new ConcurrentHashMap<>();
	
	protected static ModifiableFields getMappingData(Class cls)
	{
		ModifiableFields ret = MF.get(cls);
		if(null == ret)
		{
			ret = new ModifiableFields(cls);
			MF.put(cls, ret);
		}
		return ret;
	}
	
	
	@Override
	public String[] keys()
	{
		try
		{
			return getMappingData(getClass()).keys;
		}
		catch (Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}

	@Override
	public int size()
	{
		return keys().length;
	}

	@Override
	public boolean set(String key, Object src)
	{
		return set(key, src, false);
	}
	
	@Override
	public Object get(String key)
	{
		ModifiableFields mapping;
		try
		{
			return getMappingData(getClass()).fields.get(key).get(this);
		
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public boolean set(String key, Object src, boolean filter)
	{
		try
		{
			ModifiableFields mapping = getMappingData(this.getClass());
			Field f = mapping.fields.get(key);
			
			if(null != f)
			{
				if(filter && null != f.getAnnotation(MayNotModified.class))
				{
					return false;
				}
				
				if(null != src)
				{
					src = tryCast(this, f.getGenericType(), src);
					f.set(this, src);
				}
			}
		}
		catch(Exception e)
		{
			Mirror.propagateAnyway(e);
		}
		return false;
	}
	
	public static Type tryExtractGenericTypeOne(Type genRet)
	{
		if(genRet instanceof ParameterizedType)
		{
			Type[] args = ((ParameterizedType)genRet).getActualTypeArguments();
			if(args.length > 0)
			{
				return args[0];
			}
		}
		
		return null;
	}
	
	public static <T> Object tryCast(Object subject, Type reqType, Object src) throws Exception
	{
		Class<?> raw = Mirror.extracClass(reqType);

		if(null != reqType && null != src && raw.isAssignableFrom(src.getClass()))
		{
			return src;
		}
		else if(raw.isEnum())
		{
			if(src instanceof DataObject)
			{
				return EnumTools.recogniseSymbol(raw, ((DataObject) src).optString("name"));
			}
			return EnumTools.recogniseSymbol(raw, src);
		}
		else if(Collection.class.isAssignableFrom(raw))
		{
			Collection ret = null;
			Type generic = tryExtractGenericTypeOne(reqType);
			if(!Modifier.isAbstract(raw.getModifiers()) && !raw.isInterface())
			{
				try
				{
					ret = (Collection) raw.newInstance();
				}
				catch(Exception e)
				{}
			}
			
			if(null == ret)
			{
				if(Set.class.isAssignableFrom(raw))
				{
					ret = new HashSet<>();
				}
				else if(List.class.isAssignableFrom(raw))
				{
					ret = new ArrayList<>();
				}
			}
			
			if(null == ret)
			{
				return null;
			}
			
			DataArray arr = null;
			if(src instanceof String)
			{
				arr = new DataArrayJsonImpl(new JSONArray(src.toString()));
			}
			else if(src instanceof DataArray)
			{
				arr = (DataArray) src;
			}
			
			if(null == arr)
			{
				return null;
			}
			
			for(int i=0;i<arr.size();++i)
			{
				if(null != generic)
				{
					ret.add((T) tryCast(subject, generic, arr.get(i)));
				}
				else
				{
					ret.add(arr.get(i));
				}
			}
			
			return ret;
		}
		else if(raw.isArray())
		{
			DataArray arr = null;
			if(src instanceof String)
			{
				arr = new DataArrayJsonImpl(new JSONArray(src.toString()));
			}
			else if(src instanceof DataArray)
			{
				arr = (DataArray) src;
			}
			
			Class nType = raw.getComponentType();
			
			T[] ret = (T[]) Array.newInstance(nType, arr.size());
			for(int i=0;i<ret.length;++i)
			{
				ret[i] = (T) tryCast(subject, nType, arr.get(i));
			}
			
			return ret;
		}
		else if(String.class == reqType && src instanceof DataCommon)
		{
			byte[] ret = ((DataCommon)src).toBlob();
			return null  == ret?null:new String(ret);
		}
		else if(ModifiableObject.class.isAssignableFrom(raw) && src instanceof DataObject)
		{
			ModifiableObject ret = null;
			if(!Modifier.isAbstract(raw.getModifiers()) && !raw.isInterface())
			{
				try
				{
					ret = (ModifiableObject) raw.newInstance();
				}
				catch(Exception e)
				{}
			}
			
			if(null == ret)
			{
				ret = new TargetObject();
			}
			
			DataObject from = (DataObject) src;
			
			DataReprezTools.copyInto(ret, from);
			
			return ret;
		}
		
		Caster c = CastTo.getCasterRestrictlyForTargetClass(raw);
		if(null != c)
		{
			return c.cast(src);
		}
		
		return null;
	}

	@Override
	public void unset(String key)
	{
		set(key, null);
	}
	
	public void loadFrom(ObjectLike from)
	{
		for(String k:from.keys())
		{
			set(k, from.get(k));
		}
	}
	
	public void loadFromUser(ObjectLike from)
	{
		for(String k:from.keys())
		{
			set(k, from.get(k), true);
		}
	}
	
	@Override
	public String toString()
	{
		return Mirror.usualToString(this);
	}
}