using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
namespace MChen.Linq.Google
{
internal static class ImageQueryBuilder
{
public static ImageQueryInfo BuildQuery(Expression exp)
{
ImageQueryInfo qinfo = new ImageQueryInfo();
return Visit(exp, qinfo);
}
private static ImageQueryInfo Visit(Expression node, ImageQueryInfo qinfo)
{
try
{
switch (node.NodeType)
{
//expressions we will implement
case ExpressionType.AndAlso:
return VisitAnd((BinaryExpression)node, qinfo);
case ExpressionType.OrElse:
return VisitOr((BinaryExpression)node, qinfo);
case ExpressionType.Equal:
return VisitEquals((BinaryExpression)node, qinfo);
case ExpressionType.Call:
return VisitMethodCall((MethodCallExpression)node, qinfo, false, false);
case ExpressionType.Lambda:
return VisitLambda((LambdaExpression)node, qinfo);
case ExpressionType.Not:
return VisitNot((UnaryExpression)node, qinfo);
#region NOT IMPLEMENTED EXPRESSION TYPES
//exp types we don't intend to implement
case ExpressionType.MemberAccess:
case ExpressionType.Parameter:
case ExpressionType.Quote: //this is striped... Visit(((UnaryExpression)node).Operand, qinfo); break;
case ExpressionType.Constant:
case ExpressionType.Invoke:
case ExpressionType.ExclusiveOr:
case ExpressionType.And:
case ExpressionType.Or:
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Coalesce:
case ExpressionType.Divide:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.Modulo:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.NotEqual:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.ArrayLength:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.Conditional:
case ExpressionType.Funclet:
case ExpressionType.LeftShift:
case ExpressionType.RightShift:
case ExpressionType.TypeAs:
case ExpressionType.TypeIs:
case ExpressionType.MemberInit:
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.New:
case ExpressionType.NewArrayInit:
default:
throw new ArgumentException(
string.Format("Expression of type {0} not supported.", node.NodeType),
"exp");
#endregion
}
}
finally
{
}
}
#region methods from DLINQ implementation
private static Expression RemoveQuotes(Expression expression)
{
while (expression.NodeType == ExpressionType.Quote)
{
expression = ((UnaryExpression)expression).Operand;
}
return expression;
}
private static Expression RemoveConvert(Expression expression)
{
while (expression.NodeType == ExpressionType.Convert)
{
expression = ((UnaryExpression)expression).Operand;
}
return expression;
}
private static bool IsLambda(Expression expression)
{
return (RemoveQuotes(expression).NodeType == ExpressionType.Lambda);
}
private static bool IsMember(Expression expression)
{
return expression.NodeType == ExpressionType.MemberAccess ||
(RemoveConvert(expression).NodeType == ExpressionType.MemberAccess);
}
private static LambdaExpression GetLambda(Expression expression)
{
return (RemoveQuotes(expression) as LambdaExpression);
}
private static MemberExpression GetMember(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
return expression as MemberExpression;
return (RemoveConvert(expression) as MemberExpression);
}
private static bool IsSequenceOperatorCall(MethodCallExpression mc)
{
Type declaringType = mc.Method.DeclaringType;
if ((declaringType != typeof(Enumerable)) && (declaringType != typeof(Queryable)))
{
return false;
}
return true;
}
private static ImageQueryInfo VisitSequenceOperatorCall(MethodCallExpression mc, ImageQueryInfo qinfo)
{
Type declaringType = mc.Method.DeclaringType;
//pass through the Constant expression
if (mc.Arguments.Count == 0x2 && mc.Arguments[0].NodeType == ExpressionType.Constant)
{
ImageQuery<Image> qimg =
((ConstantExpression)mc.Arguments[0]).Value as ImageQuery<Image>;
if (qimg != null) qinfo = qimg._info;
}
//check "Where" and "OrderBy"
switch (mc.Method.Name)
{
case "Where":
if (((mc.Arguments.Count != 0x2) || !IsLambda(mc.Arguments[0x1])) || (GetLambda(mc.Arguments[0x1]).Parameters.Count != 0x1))
{
break;
}
return VisitLambda(GetLambda(mc.Arguments[0x1]), qinfo); ;
case "OrderBy":
if (((mc.Arguments.Count != 0x2) || !IsLambda(mc.Arguments[0x1])) || (GetLambda(mc.Arguments[0x1]).Parameters.Count != 0x1))
{
break;
}
//check order by
LambdaExpression lexp = GetLambda(mc.Arguments[1]);
if (IsMember(lexp.Body))
{
MemberExpression mexp = GetMember(lexp.Body);
if (mexp.Member.DeclaringType == typeof(Image) &&
mexp.Member.Name == "Rank") return qinfo;
}
throw new ArgumentException(
"Only order by Rank is supported.", "mc");
default:
break;
}
throw new ArgumentException(
string.Format("Sequence Call {0} not yet supported.", mc.Method.Name));
}
#endregion
//visitors
private static ImageQueryInfo VisitAnd(BinaryExpression node, ImageQueryInfo qinfo)
{
//dumb implementation for AND
if (node.NodeType != ExpressionType.AndAlso)
throw new ArgumentException("Argument is not AND.", "node");
//simply visit left and right
qinfo = Visit(node.Left, qinfo);
qinfo = Visit(node.Right, qinfo);
return qinfo;
}
//visitors
private static ImageQueryInfo VisitOr(BinaryExpression node, ImageQueryInfo qinfo)
{
//dumb implementation for OR, has to be on leaf nodes
if (node.NodeType != ExpressionType.OrElse)
throw new ArgumentException("Argument is not OR expression.", "node");
//left leaf check
if (node.Left.NodeType == ExpressionType.Call)
{
qinfo = VisitMethodCall((MethodCallExpression)node.Left, qinfo, true, false);
}
else if (node.Left.NodeType == ExpressionType.OrElse)
{
qinfo = VisitOr((BinaryExpression)node.Left, qinfo);
}
else
{
throw new ArgumentException("OR operator must be used on leaf expression nodes.", "node");
}
//simply visit left and right
if (node.Right.NodeType == ExpressionType.Call)
{
qinfo = VisitMethodCall((MethodCallExpression)node.Right, qinfo, true, false);
}
else if (node.Right.NodeType == ExpressionType.OrElse)
{
qinfo = VisitOr((BinaryExpression)node.Right, qinfo);
}
else
{
throw new ArgumentException("OR operator must be used on leaf expression nodes.", "node");
}
return qinfo;
}
private static ImageQueryInfo VisitNot(UnaryExpression node, ImageQueryInfo qinfo)
{
//only not over method call is supported!
if (node.Operand.NodeType == ExpressionType.Call)
{
qinfo = VisitMethodCall((MethodCallExpression)node.Operand, qinfo, false, true);
}
else
{
throw new ArgumentException(
string.Format("Not operator on {0} not supported.", node.Operand.NodeType));
}
return qinfo;
}
private static ImageQueryInfo VisitEquals(BinaryExpression node, ImageQueryInfo qinfo)
{
//has to be memeber = constant or constant = member
MemberExpression member = null;
ConstantExpression constant = null;
if (IsMember(node.Left)) member = GetMember(node.Left);
else if (node.Left.NodeType == ExpressionType.Constant)
constant = node.Left as ConstantExpression;
if (IsMember(node.Right)) member = GetMember(node.Right);
else if (node.Right.NodeType == ExpressionType.Constant)
constant = node.Right as ConstantExpression;
if (member == null || constant == null ||
member.Member.DeclaringType != typeof(Image))
throw new ArgumentException(
"Equals operator must apply to a Image member and a constant.");
//only Domain, Type, Size and Color are supported
switch (member.Member.Name)
{
case "Domain":
qinfo.Domain = constant.Value.ToString();
break;
case "Format":
if (constant.Value.GetType() != typeof(Int32))
throw new ArgumentException("Image format is not valid.");
qinfo.Format = (ImageFormat)constant.Value;
break;
case "Size":
if (constant.Value.GetType() != typeof(Int32))
throw new ArgumentException("Image size is not valid.");
qinfo.Size = (ImageSize)constant.Value;
break;
case "Color":
if (constant.Value.GetType() != typeof(Int32))
throw new ArgumentException("Image color is not valid.");
qinfo.Color = (ImageColor)constant.Value;
break;
default:
throw new ArgumentException(
"Only Size, Type, Color and Domain fields are supported for Equals operator.");
}
return qinfo;
}
//besides Where, only two function call: RelateTo && UnrelateTo are supported
private static ImageQueryInfo VisitMethodCall(
MethodCallExpression node, ImageQueryInfo qinfo, bool forceOr, bool forceNot)
{
Type declaringType = node.Method.DeclaringType;
if (IsSequenceOperatorCall(node))
{
qinfo = VisitSequenceOperatorCall(node, qinfo);
}
else if (declaringType == typeof(Image))
{
if (node.Method.Name == "RelatesTo")
{
//parse the parameter
if (node.Arguments.Count != 1 ||
node.Arguments[0].NodeType != ExpressionType.Constant)
throw new ArgumentException("Only constant search terms are supported.");
ConstantExpression cont = node.Arguments[0] as ConstantExpression;
string term = cont.Value.ToString();
if (forceNot) qinfo.NotWords.Add(term);
else if (forceOr) qinfo.OrWords.Add(term);
else qinfo.AllWords.Add(term);
}
else
{
throw new ArgumentException(
string.Format("Method {0} is not supported.", node.Method.Name));
}
}
else
{
throw new ArgumentException(
string.Format("Method {0} is not supported.", node.Method.Name));
}
return qinfo;
}
private static ImageQueryInfo VisitLambda(LambdaExpression node, ImageQueryInfo qinfo)
{
return Visit(node.Body, qinfo);
}
}
}