QueryEvaluatorBuilder.java

package eu.javaexperience.query;

import java.util.Collection;
import java.util.regex.Pattern;

import eu.javaexperience.exceptions.UnimplementedCaseException;
import eu.javaexperience.interfaces.ObjectWithProperty;
import eu.javaexperience.interfaces.simple.getBy.GetBy2;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Mirror;
import eu.javaexperience.reflect.PrimitiveTools;

public class QueryEvaluatorBuilder<T>
{
	public GetBy2<Object, T, String> extractor = getMirrorFieldExtractor();
	
	public static <T> GetBy2<Object, T, String> getMirrorFieldExtractor()
	{
		return Mirror::tryGetFieldValue;
	}
	
	public static <T> GetBy2<Object, T, String> getObjectWithPropertyExtractor()
	{
		return (a, b)->
		{
			if(a instanceof ObjectWithProperty)
			{
				return ((ObjectWithProperty)a).get(b);
			}
			
			return null;
		};
	}
	
	public GetBy2<Boolean, Object, Object> equals = Mirror::equals;
	
	protected static Boolean processPossibleCollections
	(
		Object expected,
		Object actual,
		boolean restrictToAll_or_consessiveToAll,
		GetBy2<Boolean, Object, Object> op
	)
	{
		if(null == actual || null == expected)
		{
			return null;
		}
		
		if(actual instanceof Collection)
		{
			Collection ca = (Collection) actual;
			if(expected instanceof Collection)
			{
				Collection ce = (Collection) expected;
				if(ca.isEmpty() && ce.isEmpty())
				{
					return false;
				}
				
				boolean hasPassing = false;
				for(Object a:ca)
				{
					for(Object e:ce)
					{
						Boolean eq = op.getBy(e, a);
						if(restrictToAll_or_consessiveToAll && Boolean.TRUE != eq)
						{
							return false;
						}
						
						if(Boolean.TRUE == eq)
						{
							if(!restrictToAll_or_consessiveToAll)
							{
								return true;
							}
							hasPassing = true;
						}
						else if(restrictToAll_or_consessiveToAll)
						{
							return false;
						}
					}
				}
				return hasPassing;
			}
			else
			{
				boolean hasPassing = false;
				for(Object a:ca)
				{
					Boolean eq = op.getBy(expected, a);
					if(restrictToAll_or_consessiveToAll && Boolean.TRUE != eq)
					{
						return false;
					}
					
					if(Boolean.TRUE == eq)
					{
						if(!restrictToAll_or_consessiveToAll)
						{
							return true;
						}
						hasPassing = true;
					}
					else if(restrictToAll_or_consessiveToAll)
					{
						return false;
					}
				}
				return hasPassing;
			}
		}
		else if(expected instanceof Collection)
		{
			//actual can only be a non collection object
			boolean hasPassing = false;
			Collection ce = (Collection) expected;
			for(Object e:ce)
			{
				Boolean eq = op.getBy(e, actual);
				if(restrictToAll_or_consessiveToAll && Boolean.TRUE != eq)
				{
					return false;
				}
				
				if(Boolean.TRUE == eq)
				{
					if(!restrictToAll_or_consessiveToAll)
					{
						return true;
					}
					hasPassing = true;
				}
				else if(restrictToAll_or_consessiveToAll)
				{
					return false;
				}
			}
			return hasPassing;
		}
		else
		{
			return op.getBy(expected, actual);
		}
	}
	
	public GetBy2<Boolean, Object, Object> contains = new GetBy2<Boolean, Object, Object>()
	{
		@Override
		public Boolean getBy(Object a, Object b)
		{
			return processPossibleCollections(a, b, true, (x, y)->x.toString().contains(y.toString()));
		}
	};
	
	public GetBy2<Integer, Object, Object> compare = new GetBy2<Integer, Object, Object>()
	{
		@Override
		public Integer getBy(Object a, Object b)
		{
			if(null == a || null == b)
			{
				return null;
			}
			
			if(a.getClass() != b.getClass())
			{
				if(a instanceof String)
				{
					b = b.toString();
				}
				else if(b instanceof Number)
				{
					b = CastTo.getCasterForTargetClass(a.getClass()).cast(b);
				}
			}
			
			if(a.getClass() == b.getClass())
			{
				if(a instanceof Comparable)
				{
					return ((Comparable) b).compareTo(a);
				}
			}
			
			return null;
		}
	};
	
	public GetBy2<Boolean, Object, Object> in = new GetBy2<Boolean, Object, Object>()
	{
		@Override
		public Boolean getBy(Object a, Object b)
		{
			return processPossibleCollections(a, b, false, Mirror::equals);
		}
	};
	
	public GetBy2<Boolean, Object, Object> match = new GetBy2<Boolean, Object, Object>()
	{
		@Override
		public Boolean getBy(Object a, Object b)
		{
			return processPossibleCollections
			(
				a,
				b,
				true,
				(x, y)->
				{
					Pattern p = null;
					
					if(a instanceof Pattern)
					{
						p = (Pattern) x;
					}
					else
					{
						p = Pattern.compile(x.toString());
					}
					
					return p.matcher(y.toString()).matches();
				}
			);
		}
	};
	
	//TO contains, matches, in, using object extractor function
	protected static Boolean tryCmp(boolean gt_lt, boolean eq, Integer val)
	{
		if(null == val)
		{
			return null;
		}
		
		if(eq && PrimitiveTools.INT_ZERO.equals(val))
		{
			return true;
		}
		
		if(gt_lt)
		{
			return val > 0;
		}
		else
		{
			return val < 0;
		}
	}
	
	public GetBy2<Boolean, T, AtomicCondition> build()
	{
		return new GetBy2<Boolean, T, AtomicCondition>()
		{
			@Override
			public Boolean getBy(T a, AtomicCondition b)
			{
				F op = b.getOperator();
				Object expected = b.getValue();
				Object actual = extractor.getBy(a, b.getFieldName());
				Boolean eval = null;
				switch(op)
				{
					case contains:
						eval = contains.getBy(expected, actual);
						break;
						
					case eq:
						eval = equals.getBy(expected, actual);
						break;
						
					case gt:
						eval = tryCmp(true, false, compare.getBy(expected, actual));
						break;
						
					case gte:
						eval = tryCmp(true, true, compare.getBy(expected, actual));
						break;
						
					case lt:
						eval = tryCmp(false, false, compare.getBy(expected, actual));
						break;
						
					case lte:
						eval = tryCmp(false, true, compare.getBy(expected, actual));
						break;
						
					case in:
						eval = in.getBy(expected, actual);
						break;
						
					case match:
						eval = match.getBy(expected, actual);
						break;
						
					default: throw new UnimplementedCaseException(op);
				}
				
				if(null == eval)
				{
					return false;
				}
				
				return b.isNegated() != eval;
			}
		};
	}
}