CastTo.java

package eu.javaexperience.reflect;

import java.util.IdentityHashMap;
import java.util.Map;

import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.text.Format.strtotime;

/**
 * boolean
 * byte
 * char
 * short
 * int
 * long
 * float
 * double
 * 
 * String
 * 
 * Date
 * 
 * */
public enum CastTo implements NotatedCaster
{
	Boolean("boolean")
	{
		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			/**
			 * Why cast to boolean?
			 * A wird bug in the Java' serialisation mechansim:
			 * Deserailised Booleans are not one of Boolean.TRUE or Boolean.FALSE
			 * So Boolean.TRUE == ...expression... will fail even at a deserialised true value.
			 * This cast forces to use the Boolean.{VALUE} instances.
			 * */
			if(o instanceof Boolean)
				return (boolean) o;
			
			if(o instanceof Number)
				return ((Number)o).longValue() != 0;
			
			String str = ((String)CastTo.String.cast(o)).toLowerCase();
			try
			{
				return java.lang.Double.parseDouble(str) != 0.0;
			}
			catch(Exception e){}
			
			boolean tr = str.contains("true");
			boolean fa = str.contains("false");

			if(tr != fa)
				return tr;
			
			if(str.length() < 2)
			{
				tr = str.contains("t");
				fa = str.contains("f");
			
				if(tr != fa)
					return tr;
			}
			
			return null;
		}
		
		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Boolean.class.isAssignableFrom(cls) || boolean.class.isAssignableFrom(cls);
		}

		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Boolean.class, boolean.class};
		}
	},
	
	Byte("byte")
	{
		private final Byte zero = 0;
		private final Byte one = 1;

		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return ((Number) o).byteValue();

			if(o instanceof Character)
				return (byte) (char)o;
			
			if(o instanceof Boolean)
					return ((byte) (o.equals(java.lang.Boolean.TRUE)? one:zero));

			String str = o.toString().trim();
			try
			{
				return (byte) java.lang.Long.parseLong(str);
			}
			catch(Exception e){}
			
			try
			{
				return (byte) java.lang.Double.parseDouble(str);
			}
			catch(Exception e){}
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Byte.class.isAssignableFrom(cls) || byte.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Byte.class, byte.class};
		}
	},
	
	Char("char")
	{
		private final Character forTrue  = 't';
		private final Character forFalse = '\0';

		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return (char)((Number) o).longValue();

			if(o instanceof Boolean)
					return ((o.equals(java.lang.Boolean.TRUE)? forTrue:forFalse));
			
			String str = o.toString();
			if(str.length() == 1)
				return (char)str.charAt(0);
			
			str = str.trim();
			
			if(str.length() == 1)
				return (char)str.charAt(0);

			try
			{
				return (char) java.lang.Long.parseLong(str);
			}
			catch(Exception e){}
			
			try
			{
				return (char) java.lang.Double.parseDouble(str);
			}
			catch(Exception e){}
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Character.class.isAssignableFrom(cls) || char.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Character.class, char.class};
		}
	},
	
	
	Short("short")
	{
		private final Short zero = 0;
		private final Short one = 1;

		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return ((Number) o).shortValue();

			if(o instanceof Character)
				return (short) (char)o;
			
			if(o instanceof Boolean)
					return ((o.equals(java.lang.Boolean.TRUE)? one:zero));
			
			String str = o.toString();
			try
			{
				return (short) java.lang.Long.parseLong(str);
			}
			catch(Exception e){}
			
			try
			{
				return (short) java.lang.Double.parseDouble(str);
			}
			catch(Exception e){}
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Short.class.isAssignableFrom(cls) || short.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Short.class, short.class};
		}
	},
	
	Int("int")
	{
		private final Integer zero = 0;
		private final Integer one = 1;
		
		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return ((Number) o).intValue();

			if(o instanceof Character)
				return (int) (char)o;
			
			if(o instanceof Boolean)
				return ((o.equals(java.lang.Boolean.TRUE)? one:zero));
			
			String str = ((String)CastTo.String.cast(o)).trim();
			try
			{
				return (int) java.lang.Long.parseLong(str);
			}
			catch(Exception e){}
			
			try
			{
				return (int) java.lang.Double.parseDouble(str);
			}
			catch(Exception e){}
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Integer.class.isAssignableFrom(cls) || int.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Integer.class, int.class};
		}
	},
	
	Long("long")
	{
		private final Long zero = 0l;
		private final Long one = 1l;
		
		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return ((Number) o).longValue();

			if(o instanceof Character)
				return (long) (char)o;
			
			if(o instanceof Boolean)
				return ((o.equals(java.lang.Boolean.TRUE)? one:zero));
			
			String str = o.toString().trim();
			try
			{
				return java.lang.Long.parseLong(str);
			}
			catch(Exception e){}
			
			try
			{
				return (long) java.lang.Double.parseDouble(str);
			}
			catch(Exception e){}
			
			return null;
		}
		
		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Long.class.isAssignableFrom(cls) || long.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Long.class, long.class};
		}
	},
	
	Float("float")
	{
		private final Float zero = 0.0f;
		private final Float one = 1.0f;
		
		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return ((Number) o).floatValue();

			if(o instanceof Character)
				return (float) (char)o;
			
			if(o instanceof Boolean)
				return ((o.equals(java.lang.Boolean.TRUE)? one:zero));
			
			String str = o.toString().trim();
			try
			{
				return java.lang.Float.parseFloat(str);
			}
			catch(Exception e){}
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Float.class.isAssignableFrom(cls) || float.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Float.class, float.class};
		}
	},
	
	Double("double")
	{
		private final Double zero = 0.0;
		private final Double one = 1.0;
		
		@Override
		public Object cast(Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof Number)
				return ((Number) o).doubleValue();

			if(o instanceof Character)
				return (double) (char)o;
			
			if(o instanceof Boolean)
				return ((o.equals(java.lang.Boolean.TRUE)? one:zero));

			String str = o.toString().trim();
			try
			{
				return java.lang.Double.parseDouble(str);
			}
			catch(Exception e){}
			
			try
			{
				return java.lang.Double.parseDouble(str.replace(',', '.'));
			}
			catch(Exception e){}
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return Double.class.isAssignableFrom(cls) || double.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{Double.class, double.class};
		}
	},
	
	String("String", "java.lang.String")
	{
		@Override
		public Object cast(Object o)
		{
			if(o == null)
			{
				return null;
			}
			
			if(o instanceof byte[])
			{
				return new String((byte[])o);
			}
			
			return o.toString();
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return String.class.isAssignableFrom(cls) || CharSequence.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{String.class, CharSequence.class, StringBuilder.class, StringBuffer.class};
		}
	},
	
	Date("Date", "java.util.Date")
	{
		@Override
		public java.lang.Object cast(java.lang.Object o)
		{
			if(null == o)
			{
				return null;
			}
			
			if(o instanceof java.util.Date)
			{
				return o;
			}
			
			String str = o.toString();
			
			java.util.Date d = strtotime.strtotime(str);
			if(null != d)
			{
				return d;
			}
			
			java.lang.Object time = Long.cast(str);
			
			if(time instanceof Number)
				return new java.util.Date(((Number) time).longValue());
			
			return null;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return java.util.Date.class.isAssignableFrom(cls);
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{java.util.Date.class, java.sql.Date.class};
		}
	},
	
	Object("Object", "java.lang.Object")
	{
		@Override
		public java.lang.Object cast(java.lang.Object o)
		{
			return o;
		}

		@Override
		public boolean canAssignForType(Class<?> cls)
		{
			return true;
		}
		
		@Override
		public Class[] getAssignTypes()
		{
			return new Class[]{};
		}
	}
	
	;
	
	private CastTo(String name)
	{
		shortName = fullQualname = name;
	}
	
	private CastTo(String shortName, String longName)
	{
		this.shortName = shortName;
		this.fullQualname = longName;
	}
	
	protected final String shortName;
	protected final String fullQualname;
	
	@Override
	public java.lang.String getTypeShortName()
	{
		return shortName;
	}
	
	@Override
	public java.lang.String getTypeFullQualifiedName()
	{
		return fullQualname;
	}
	
	/**
	 * Casts tobject to the specified type,
	 * returns null if Object can't casted to the
	 * target type. 
	 * */
	public abstract Object cast(Object o);
	
	protected final GetBy1<Object, Object> converter = new GetBy1<Object, Object>()
	{
		@Override
		public Object getBy(Object a)
		{
			return cast(a);
		}

	};
	
	public abstract Class[] getAssignTypes();
	
	public abstract boolean canAssignForType(Class<?> cls);
	
	private static final CastTo whitoutObject[] = new CastTo[values().length-1];
	
	static
	{
		int ep = 0;
		for(CastTo t:values())
			if(t != Object)
				whitoutObject[ep++] = t;
	}
	
	protected static Map<Class, CastTo> typeToCaster = new IdentityHashMap<>();
	
	static
	{
		for(CastTo t:whitoutObject)
		{
			for(Class c: t.getAssignTypes())
			{
				typeToCaster.put(c, t);
			}
		}
	}
	
	public static CastTo getCasterRestrictlyForTargetClass(Class<?> cls)
	{
		if(Object.class == cls)
		{
			return Object;
		}
		
		return typeToCaster.get(cls);
	}
	

	public static CastTo getCasterForTargetClass(Class<?> cls)
	{
		CastTo ret = typeToCaster.get(cls);
		if(null != ret)
		{
			return ret;
		}
		return Object;
	}
}