PropertyAccessTools.java

package eu.javaexperience.reflect.property;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import eu.javaexperience.collection.CollectionTools;
import eu.javaexperience.collection.map.SmallMap;
import eu.javaexperience.interfaces.IndexedStorage;
import eu.javaexperience.interfaces.ObjectWithProperty;
import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.reflect.PrimitiveTools;
import eu.javaexperience.regex.RegexTools;

public class PropertyAccessTools
{
	public static Map<String, Object> dotAccessWrap(final Map<String, Object> ret)
	{
		return new Map<String, Object>()
		{
			protected String[] lk = new String[1];
			protected Map[] lm = new Map[1];
					
			@Override
			public int size()
			{
				return ret.size();
			}

			@Override
			public boolean isEmpty()
			{
				return ret.isEmpty();
			}

			@Override
			public boolean containsKey(Object key)
			{
				if(null == key)
				{
					return false;
				}
				
				if(!accessLeaf(lm, lk, ret, String.valueOf(key), false))
				{
					return false;
				}
				
				if(lm[0] instanceof Map)
				{
					return ((Map)lm[0]).containsKey(lk[0]);
				}
				
				return false;
			}

			@Override
			public boolean containsValue(Object value)
			{
				return ret.containsValue(value);
			}

			@Override
			public Object get(Object key)
			{
				if(null == key || "".equals(key) || ".".equals(key))
				{
					return ret;
				}
				
				if(!accessLeaf(lm, lk, ret, String.valueOf(key), false))
				{
					return null;
				}
				
				if(lm[0] instanceof Map)
				{
					return ((Map)lm[0]).get(lk[0]);
				}
				
				return null;
			}

			@Override
			public Object put(String key, Object value)
			{
				if(null == key)
				{
					return false;
				}
				
				if(!accessLeaf(lm, lk, ret, String.valueOf(key), false))
				{
					return false;
				}
				
				if(lm[0] instanceof Map)
				{
					return ((Map)lm[0]).put(lk[0], value);
				}
				
				return false;
			}

			@Override
			public Object remove(Object key)
			{
				// TODO Auto-generated method stub
				return null;
			}

			@Override
			public void putAll(Map<? extends String, ? extends Object> m)
			{
				for(Entry<? extends String, ? extends Object> kv:m.entrySet())
				{
					put(kv.getKey(), kv.getValue());
				}
			}

			@Override
			public void clear()
			{
				ret.clear();
			}

			@Override
			public Set<String> keySet()
			{
				return ret.keySet();
			}

			@Override
			public Collection<Object> values()
			{
				return ret.values();
			}

			@Override
			public Set<Entry<String, Object>> entrySet()
			{
				return ret.entrySet();
			}
		};
	}
	
	public static boolean accessLeaf
	(
		Object[/*1*/] ret_leaf,
		String[/*1*/] ret_leaf_key,
		Map<String, Object> map,
		String path,
		boolean mkpath
	)
	{
		if(null == path || null == map)
		{
			return false;
		}
		
		if(!path.contains("."))
		{
			ret_leaf[0] = map;
			ret_leaf_key[0] = path;
			return true;
		}
		
		String[] p = RegexTools.DOT.split(path);
		Object val = null;
		for(int i=0;i<p.length-1;++i)
		{
			val = map.get(p[i]);
			if(null == val)
			{
				if(!mkpath)
				{
					return false;
				}
				else
				{
					map = new SmallMap<>();
				}
			}
			else
			{
				if(val instanceof Map)
				{
					map = (Map<String, Object>) val;
				}
				else
				{
					//we may access the last
					//if(i != p.length-2)
					{
						return false;
					}
				}
			}
		}
		
		ret_leaf[0] = val;
		ret_leaf_key[0] = p[p.length-1];
		return true;
	}
	
	public static Map<String, Object> wrap(Object o, DataAccessor... accessors)
	{
		return dotAccessWrap
		(
			DataAccessorTools.mixedAccessWrap
			(
				o,
				accessors
			)
		);
	}
	
	public static DataAccessor createExtractorExceptClasses(Class<?>... clss)
	{
		final HashSet<Class> exCls = new HashSet<>();
		CollectionTools.copyInto(clss, exCls);
		
		return createExtractorExceptClasses(new GetBy1<Boolean, Class<?>>()
		{
			@Override
			public Boolean getBy(Class a)
			{
				return !exCls.contains(a);
			}
		});
	}
	
	public static DataAccessor createExtractorExceptClasses(final GetBy1<Boolean, Class<?>> mayExtract)
	{
		return new DataAccessor()
		{
			@Override
			public boolean canHandle(Object subject)
			{
				return !isPrimitiveOrManagedClass(subject.getClass()) && Boolean.TRUE == mayExtract.getBy(subject.getClass());
			}

			@Override
			public Object get(Object subject, String key)
			{
				return EXTRACT_NONPRIMITIVE_OBJECTS.get(subject, key);
			}

			@Override
			public String[] keys(Object subject)
			{
				return EXTRACT_NONPRIMITIVE_OBJECTS.keys(subject);
			}
		};
	}
	
	public static boolean isPrimitiveOrManagedClass(Class cls)
	{
		if(PrimitiveTools.isPrimitiveTypeObject(PrimitiveTools.translatePrimitiveToObjectType(cls)))
		{
			return true;
		}
		
		if
		(
			cls == String.class ||
			cls.isArray() ||
			Map.class.isAssignableFrom(cls) ||
			List.class.isAssignableFrom(cls) ||
			IndexedStorage.class.isAssignableFrom(cls) ||
			ObjectWithProperty.class.isAssignableFrom(cls)
		)
		{
			return true;
		}
		
		return false;
	}
	
	public static DataAccessor EXTRACT_NONPRIMITIVE_OBJECTS = new DataAccessor()
	{
		@Override
		public boolean canHandle(Object subject)
		{
			//always can access even if class has no public field
			return !isPrimitiveOrManagedClass(subject.getClass());
		}

		@Override
		public Object get(Object subject, String key)
		{
			Map<String, Field> acc = WellKnownDataAccessors.getAccessorOfClass(subject.getClass());
			Field f = acc.get(key);
			if(null != f)
			{
				try
				{
					return f.get(subject);
				}
				catch (Exception e)
				{
					Mirror.propagateAnyway(e);
				}
			}
			
			return null;
		}

		/*@Override
		public boolean remove(Object subject, String key)
		{
			return false;
		}*/

		@Override
		public String[] keys(Object subject)
		{
			Map<String, Field> acc = WellKnownDataAccessors.getAccessorOfClass(subject.getClass());
			return acc.keySet().toArray(Mirror.emptyStringArray);
		}
	};
	
	public static Map<String, Object> wrap(Object o)
	{
		return wrap
		(
			o,
			WellKnownDataAccessors.OBJECT_LIKE,
			WellKnownDataAccessors.ARRAY_LIKE,
			WellKnownDataAccessors.ARRAY,
			WellKnownDataAccessors.LIST,
			WellKnownDataAccessors.MAP,
			EXTRACT_NONPRIMITIVE_OBJECTS
		);
	}
	
	public static Map<String, Object> wrapSimplest(Object o)
	{
		return wrap
		(
			o,
			WellKnownDataAccessors.ARRAY_SIMPLEST,
			WellKnownDataAccessors.LIST_SIMPLEST,
			WellKnownDataAccessors.MAP,
			EXTRACT_NONPRIMITIVE_OBJECTS
		);
	}
	
	public static void linearize
	(
		Map<String, Object> dst,
		String prefix,
		Map<String, Object> src
	)
	{
		for(Entry<String, Object> kv:src.entrySet())
		{
			Object o = kv.getValue();
			if(o instanceof Map)
			{
				linearize(dst, prefix+"."+kv.getKey(), (Map)o);
			}
			else
			{
				dst.put(prefix+"."+kv.getKey(), o);
			}
		}
	}
	
	public static void linearize
	(
		Map<String, Object> dst,
		Map<String, Object> src
	)
	{
		for(Entry<String, Object> kv:src.entrySet())
		{
			Object o = kv.getValue();
			if(o instanceof Map)
			{
				linearize(dst, kv.getKey(), (Map)o);
			}
			else
			{
				dst.put(kv.getKey(), o);
			}
		}
	}
	
	public static void linearizeObject(Map<String,Object> dst, Object o)
	{
		linearize(dst, wrap(o));
	}
}