Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataWeb / Server / System / Data / Services / Parsing / RequestQueryParser.cs / 1 / RequestQueryParser.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a type to parse expressions in request queries.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Parsing
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Data.Services.Parsing;
using System.Data.Services.Providers;
#if ASTORIA_OPEN_OBJECT
using System.Data.Services.OpenTypes;
#endif
#endregion Namespaces.
///
/// This class provides static methods to parse query options and compose
/// them on an existing query.
///
internal static class RequestQueryParser
{
#region Fields.
/// Constant for "null" literal.
internal static readonly ConstantExpression NullLiteral = Expression.Constant(null);
#endregion Fields.
#region Internal methods.
/// Gets a type for that allows null values.
/// Type to base resulting type on.
///
/// if it's a reference or Nullable<> type;
/// Nullable< > otherwise.
///
internal static Type GetTypeAllowingNull(Type type)
{
Debug.Assert(type != null, "type != null");
return TypeAllowsNull(type) ? type : typeof(Nullable<>).MakeGenericType(type);
}
/// Checks whether the specified type is a generic nullable type.
/// Type to check.
/// true if is nullable; false otherwise.
internal static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
/// Checks whether is a null constant.
/// Expression to check.
/// true if is a null constant; false otherwise.
internal static bool IsNullConstant(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
return
expression == NullLiteral ||
(expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Value == null);
}
/// Checks whether the specified can be assigned null.
/// Type to check.
/// true if type is a reference type or a Nullable type; false otherwise.
internal static bool TypeAllowsNull(Type type)
{
Debug.Assert(type != null, "type != null");
return !type.IsValueType || IsNullableType(type);
}
/// Sorts a query like a SQL ORDER BY clause does.
/// Service with data and configuration.
/// Original source for query.
/// Ordering definition to compose.
/// The composed query.
internal static IQueryable OrderBy(IDataService service, IQueryable source, string ordering)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(source != null, "source != null");
Debug.Assert(ordering != null, "ordering != null");
ParameterExpression parameterForIt = Expression.Parameter(source.ElementType, "it");
ExpressionParser parser = new ExpressionParser(service, parameterForIt, ordering);
IEnumerable orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings)
{
Type selectorType = o.Selector.Type;
if (!service.Provider.GetTypeIsOrdered(selectorType))
{
string resourceTypeName = WebUtil.GetTypeName(service.Provider, o.Selector.Type);
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryParser_OrderByDoesNotSupportType(resourceTypeName));
}
queryExpr = Expression.Call(
typeof(Queryable),
o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, selectorType },
queryExpr,
Expression.Quote(Expression.Lambda(o.Selector, parameterForIt)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}
/// Filters a query like a SQL WHERE clause does.
/// Service with data and configuration.
/// Original source for query.
/// Predicate to compose.
/// The composed query.
internal static IQueryable Where(IDataService service, IQueryable source, string predicate)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(service != null, "source != null");
Debug.Assert(predicate != null, "predicate != null");
LambdaExpression lambda = ParseLambdaForWhere(service, source.ElementType, predicate);
////Trace.WriteLine("predicate=" + predicate + "; lambda=" + lambda.ToString());
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda)));
}
#endregion Internal methods.
#region Private methods.
/// Parses a lambda expression.
/// Service with data and configuration.
/// Type for "it" contextual variable.
/// Expression to parse.
/// The parsed expression.
private static LambdaExpression ParseLambdaForWhere(IDataService service, Type typeForIt, string expression)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(typeForIt != null, "typeForIt != null");
Debug.Assert(expression != null, "expression != null");
ParameterExpression parameterForIt = Expression.Parameter(typeForIt, "it");
ExpressionParser parser = new ExpressionParser(service, parameterForIt, expression);
return Expression.Lambda(parser.ParseWhere(), parameterForIt);
}
#endregion Private methods.
/// Use this class to define ordering for resources.
private class DynamicOrdering
{
#region Private fields.
/// Whether the order is ascending.
private readonly bool ascending;
/// Selector expression in ordering.
private readonly Expression selector;
#endregion Private fields.
/// Initializes a new instance.
/// Selector expression in ordering.
/// Whether the order is ascending.
internal DynamicOrdering(Expression selector, bool ascending)
{
this.selector = selector;
this.ascending = ascending;
}
#region Properties.
/// Whether the order is ascending.
internal bool Ascending
{
get { return this.ascending; }
}
/// Selector expression in ordering.
internal Expression Selector
{
get { return this.selector; }
}
#endregion Properties.
}
/// Use this class to parse an expression in the Astoria URI format.
[DebuggerDisplay("ExpressionParser ({lexer.text})")]
private class ExpressionParser
{
#region Fields.
/// Maximum recursion limit on deserializer.
private const int RecursionLimit = 100;
/// A type that is not numeric.
private const int NumericTypeNotNumeric = 0;
/// A type that is a char, single, double or decimal.
private const int NumericTypeNotIntegral = 1;
/// A type that is a signed integral.
private const int NumericTypeSignedIntegral = 2;
/// A type that is an unsigned integral.
private const int NumericTypeUnsignedIntegral = 3;
/// Empty Expressions array.
private static readonly Expression[] emptyExpressions = new Expression[0];
/// Constant for "true" literal.
private static readonly ConstantExpression trueLiteral = Expression.Constant(true);
/// Constant for "false" literal.
private static readonly ConstantExpression falseLiteral = Expression.Constant(false);
/// Dictionary of system functions.
private static readonly Dictionary functions = FunctionDescription.CreateFunctions();
/// Provider of data and metadata.
private readonly IDataServiceProvider provider;
/// Service with data and configuration.
private readonly IDataService service;
/// Literals.
private Dictionary literals;
/// "it" contextual parameter.
private ParameterExpression it;
/// Expression lexer.
private ExpressionLexer lexer;
/// Whether the expression tree should propagate nulls explicitly.
private bool nullPropagationRequired;
/// Depth of recursion.
private int recursionDepth;
#endregion Fields.
#region Constructors.
/// Initializes a new .
/// Service with data and configuration.
/// Parameters for the current "it" context.
/// Expression to parse.
internal ExpressionParser(IDataService service, ParameterExpression parameterForIt, string expression)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(expression != null, "expression != null");
Debug.Assert(parameterForIt != null, "parameterForIt != null");
this.service = service;
this.provider = service.Provider;
this.nullPropagationRequired = this.provider.NullPropagationRequired;
this.literals = new Dictionary();
this.it = parameterForIt;
this.lexer = new ExpressionLexer(expression);
}
#endregion Constructors.
/// Current token being processed.
private Token CurrentToken
{
get { return this.lexer.CurrentToken; }
set { this.lexer.CurrentToken = value; }
}
/// Parses the text expression for a .Where method invocation.
/// The parsed expression.
internal Expression ParseWhere()
{
int exprPos = this.lexer.Position;
Expression expr = this.ParseExpression();
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(expr))
{
expr = Expression.Convert(expr, typeof(bool));
}
else
#endif
if (IsNullConstant(expr))
{
expr = falseLiteral;
}
else if (expr.Type == typeof(bool?))
{
Expression test = Expression.Equal(expr, Expression.Constant(null, typeof(bool?)));
expr = Expression.Condition(test, falseLiteral, Expression.Property(expr, "Value"));
}
else if (expr.Type != typeof(bool))
{
throw ParseError(Strings.RequestQueryParser_ExpressionTypeMismatch(this.GetTypeName(typeof(bool)), exprPos));
}
this.lexer.ValidateToken(TokenId.End);
Debug.Assert(expr != null, "expr != null");
Debug.Assert(expr.Type == typeof(bool), "expr.Type(" + expr.Type + ") == typeof(bool)");
return expr;
}
/// Parses the text expression for ordering.
/// An enumeration of orderings.
internal IEnumerable ParseOrdering()
{
List orderings = new List();
while (true)
{
Expression expr = this.ParseExpression();
bool ascending = true;
if (this.TokenIdentifierIs(ExpressionConstants.KeywordAscending))
{
this.lexer.NextToken();
}
else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDescending))
{
this.lexer.NextToken();
ascending = false;
}
orderings.Add(new DynamicOrdering(expr, ascending));
if (this.CurrentToken.Id != TokenId.Comma)
{
break;
}
this.lexer.NextToken();
}
this.ValidateToken(TokenId.End);
return orderings;
}
/// Compares two byte arrays for equality.
/// First byte array.Second byte array.
/// true if the arrays are equal; false otherwise.
private static bool ByteArraysEqual(byte[] b0, byte[] b1)
{
if (b0 == b1)
{
return true;
}
if (b0 == null || b1 == null)
{
return false;
}
if (b0.Length != b1.Length)
{
return false;
}
for (int i = 0; i < b0.Length; i++)
{
if (b0[i] != b1[i])
{
return false;
}
}
return true;
}
/// Compares two byte arrays for equality.
/// First byte array.Second byte array.
/// true if the arrays are not equal; false otherwise.
private static bool ByteArraysNotEqual(byte[] b0, byte[] b1)
{
return !ByteArraysEqual(b0, b1);
}
/// Gets a non-nullable version of the specified type.
/// Type to get non-nullable version for.
///
/// if type is a reference type or a
/// non-nullable type; otherwise, the underlying value type.
///
private static Type GetNonNullableType(Type type)
{
return Nullable.GetUnderlyingType(type) ?? type;
}
/// Checks whether the specified type is a signed integral type.
/// Type to check.
/// true if is a signed integral type; false otherwise.
private static bool IsSignedIntegralType(Type type)
{
return GetNumericTypeKind(type) == NumericTypeSignedIntegral;
}
/// Checks whether the specified type is an unsigned integral type.
/// Type to check.
/// true if is an unsigned integral type; false otherwise.
private static bool IsUnsignedIntegralType(Type type)
{
return GetNumericTypeKind(type) == NumericTypeUnsignedIntegral;
}
/// Gets a flag for the numeric kind of type.
/// Type to get numeric kind for.
///
/// One of NumericTypeNotNumeric; NumericTypeNotIntegral if it's char,
/// single, double or decimal; NumericTypeSignedIntegral, or NumericTypeUnsignedIntegral.
///
private static int GetNumericTypeKind(Type type)
{
type = GetNonNullableType(type);
Debug.Assert(!type.IsEnum, "!type.IsEnum");
switch (Type.GetTypeCode(type))
{
case TypeCode.Char:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return NumericTypeNotIntegral;
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
return NumericTypeSignedIntegral;
case TypeCode.Byte:
return NumericTypeUnsignedIntegral;
default:
return NumericTypeNotNumeric;
}
}
/// Checks whether type is a (possibly nullable) enumeration type.
/// Type to check.
/// true if type is an enumeration or a nullable enumeration; false otherwise.
private static bool IsEnumType(Type type)
{
return GetNonNullableType(type).IsEnum;
}
/// Returns an object that can enumerate the specified type and its supertypes.
/// Type to based enumeration on.
/// An object that can enumerate the specified type and its supertypes.
private static IEnumerable SelfAndBaseTypes(Type type)
{
if (type.IsInterface)
{
List types = new List();
AddInterface(types, type);
return types;
}
return SelfAndBaseClasses(type);
}
/// Returns an object that can enumerate the specified type and its supertypes.
/// Type to based enumeration on.
/// An object that can enumerate the specified type and its supertypes.
private static IEnumerable SelfAndBaseClasses(Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
/// Adds an interface type to a list of types, including inherited interfaces.
/// Types list ot add to.
/// Interface type to add.
private static void AddInterface(List types, Type type)
{
if (!types.Contains(type))
{
types.Add(type);
foreach (Type t in type.GetInterfaces())
{
AddInterface(types, t);
}
}
}
/// Finds the best applicable methods from the specified array that match the arguments.
/// Candidate methods.
/// Argument expressions.
/// Best applicable methods.
private static MethodData[] FindBestApplicableMethods(MethodData[] applicable, Expression[] args)
{
Debug.Assert(applicable != null, "applicable != null");
List result = new List();
foreach (MethodData method in applicable)
{
bool betterThanAllOthers = true;
foreach (MethodData otherMethod in applicable)
{
if (otherMethod != method && IsBetterThan(args, otherMethod, method))
{
betterThanAllOthers = false;
break;
}
}
if (betterThanAllOthers)
{
result.Add(method);
}
}
return result.ToArray();
}
/// Parses the specified text into a number.
/// Text to parse.
/// Type to parse into.
/// The parsed number.
private static object ParseNumber(string text, Type type)
{
TypeCode tc = Type.GetTypeCode(GetNonNullableType(type));
switch (tc)
{
case TypeCode.SByte:
sbyte sb;
if (sbyte.TryParse(text, out sb))
{
return sb;
}
break;
case TypeCode.Byte:
byte b;
if (byte.TryParse(text, out b))
{
return b;
}
break;
case TypeCode.Int16:
short s;
if (short.TryParse(text, out s))
{
return s;
}
break;
case TypeCode.Int32:
int i;
if (int.TryParse(text, out i))
{
return i;
}
break;
case TypeCode.Int64:
long l;
if (long.TryParse(text, out l))
{
return l;
}
break;
case TypeCode.Single:
float f;
if (float.TryParse(text, out f))
{
return f;
}
break;
case TypeCode.Double:
double d;
if (double.TryParse(text, out d))
{
return d;
}
break;
case TypeCode.Decimal:
decimal e;
if (decimal.TryParse(text, out e))
{
return e;
}
break;
}
return null;
}
/// Checks whether the source type is compatible with the value type.
/// Source type.
/// Target type.
/// true if source can be used in place of target; false otherwise.
private static bool IsCompatibleWith(Type source, Type target)
{
if (source == target)
{
return true;
}
if (!target.IsValueType)
{
return target.IsAssignableFrom(source);
}
Type sourceType = GetNonNullableType(source);
Type targetType = GetNonNullableType(target);
//// This rule stops the parser from considering nullable types as incompatible
//// with non-nullable types. We have however implemented this rule because we just
//// access underlying rules. C# requires an explicit .Value access, and EDM has no
//// nullablity on types and (at the model level) implements null propagation.
////
//// if (sourceType != source && targetType == target)
//// {
//// return false;
//// }
TypeCode sourceCode = sourceType.IsEnum ? TypeCode.Object : Type.GetTypeCode(sourceType);
TypeCode targetCode = targetType.IsEnum ? TypeCode.Object : Type.GetTypeCode(targetType);
switch (sourceCode)
{
case TypeCode.SByte:
switch (targetCode)
{
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Byte:
switch (targetCode)
{
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Int16:
switch (targetCode)
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Int32:
switch (targetCode)
{
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Int64:
switch (targetCode)
{
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Single:
switch (targetCode)
{
case TypeCode.Single:
case TypeCode.Double:
return true;
}
break;
default:
if (sourceType == targetType)
{
return true;
}
break;
}
// Anything can be converted to something that's *exactly* an object.
#if ASTORIA_OPEN_OBJECT
if (target == typeof(object))
{
return true;
}
#endif
return false;
}
///
/// Checks whether one type list is a better fit than other for the
/// specified expressions.
///
/// Expressions for arguments.
/// First type list to check.
/// Second type list to check.
///
/// true if has better parameter matching than .
///
private static bool IsBetterThan(Expression[] args, IEnumerable firstCandidate, IEnumerable secondCandidate)
{
bool better = false;
using (IEnumerator first = firstCandidate.GetEnumerator())
using (IEnumerator second = secondCandidate.GetEnumerator())
{
for (int i = 0; i < args.Length; i++)
{
first.MoveNext();
second.MoveNext();
int c = CompareConversions(args[i].Type, first.Current, second.Current);
if (c < 0)
{
return false;
}
else if (c > 0)
{
better = true;
}
}
}
return better;
}
///
/// Checks whether one method is a better fit than other for the
/// specified expressions.
///
/// Expressions for arguments.
/// First method to check.
/// Second method to check.
///
/// true if has better parameter matching than .
///
private static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2)
{
Debug.Assert(args != null, "args != null");
Debug.Assert(m1 != null, "m1 != null");
Debug.Assert(m2 != null, "m2 != null");
return IsBetterThan(args, m1.ParameterTypes, m2.ParameterTypes);
}
/// Checks which conversion is better.
/// Source type.
/// First candidate type to convert to.
/// Second candidate type to convert to.
///
/// Return 1 if s -> t1 is a better conversion than s -> t2
/// Return -1 if s -> t2 is a better conversion than s -> t1
/// Return 0 if neither conversion is better
///
private static int CompareConversions(Type source, Type targetA, Type targetB)
{
// If both types are exactly the same, there is no preference.
if (targetA == targetB)
{
return 0;
}
// Look for exact matches.
if (source == targetA)
{
return 1;
}
else if (source == targetB)
{
return -1;
}
// If one is compatible and the other is not, choose the compatible type.
bool compatibleT1AndT2 = IsCompatibleWith(targetA, targetB);
bool compatibleT2AndT1 = IsCompatibleWith(targetB, targetA);
if (compatibleT1AndT2 && !compatibleT2AndT1)
{
return 1;
}
else if (compatibleT2AndT1 && !compatibleT1AndT2)
{
return -1;
}
// Prefer to keep the original nullability.
bool sourceNullable = IsNullableType(source);
bool typeNullableA = IsNullableType(targetA);
bool typeNullableB = IsNullableType(targetB);
if (sourceNullable == typeNullableA && sourceNullable != typeNullableB)
{
return 1;
}
else if (sourceNullable != typeNullableA && sourceNullable == typeNullableB)
{
return -1;
}
// Prefer signed to unsigned.
if (IsSignedIntegralType(targetA) && IsUnsignedIntegralType(targetB))
{
return 1;
}
else if (IsSignedIntegralType(targetB) && IsUnsignedIntegralType(targetA))
{
return -1;
}
// Prefer non-object to object.
if (targetA != typeof(object) && targetB == typeof(object))
{
return 1;
}
else if (targetB != typeof(object) && targetA == typeof(object))
{
return -1;
}
return 0;
}
/// Generates an Equal expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.EqualExpression(left, right);
}
#endif
if (left.Type == typeof(byte[]))
{
MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysEqual", BindingFlags.NonPublic | BindingFlags.Static);
return Expression.Equal(left, right, false, byteArrayCompareMethod);
}
return Expression.Equal(left, right);
}
/// Generates a NotEqual expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateNotEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.NotEqualExpression(left, right);
}
#endif
if (left.Type == typeof(byte[]))
{
MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysNotEqual", BindingFlags.NonPublic | BindingFlags.Static);
return Expression.NotEqual(left, right, false, byteArrayCompareMethod);
}
return Expression.NotEqual(left, right);
}
/// Generates a GreaterThan comparison expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateGreaterThan(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.GreaterThanExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.GreaterThan(left, right, false, GetStringCompareMethod("StringGreaterThanMethod"));
}
return Expression.GreaterThan(left, right);
}
/// Generates a GreaterThanOrEqual comparsion expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateGreaterThanEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.GreaterThanOrEqualExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.GreaterThanOrEqual(left, right, false, GetStringCompareMethod("StringGreaterThanOrEqualMethod"));
}
return Expression.GreaterThanOrEqual(left, right);
}
/// Generates a LessThan comparsion expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateLessThan(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.LessThanExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.LessThan(left, right, false, GetStringCompareMethod("StringLessThanMethod"));
}
return Expression.LessThan(left, right);
}
/// Generates a LessThanOrEqual comparsion expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateLessThanEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.LessThanOrEqualExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.LessThanOrEqual(left, right, false, GetStringCompareMethod("StringLessThanOrEqualMethod"));
}
return Expression.LessThanOrEqual(left, right);
}
/// Generates an addition expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateAdd(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.AddExpression(left, right);
}
#endif
return Expression.Add(left, right);
}
/// Generates a subtract expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateSubtract(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.SubtractExpression(left, right);
}
#endif
return Expression.Subtract(left, right);
}
/// Gets a static method to compare strings.
/// Name of method.
/// The for the method.
private static MethodInfo GetStringCompareMethod(string methodName)
{
Debug.Assert(methodName != null, "methodName != null");
MethodInfo result = typeof(ExpressionParser).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(result != null, "result != null");
return result;
}
#if ASTORIA_OPEN_OBJECT
///
/// Checks whether the specified is part of an open property expression.
///
/// Non-null to check.
/// true if is based on an open property; false otherwise.
private static bool IsOpenPropertyExpression(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
return expression != NullLiteral && expression.Type == typeof(object);
}
#endif
/// Checks whether the left string is less than or equal to the right string.
/// Left value.Right value.
/// true if the left string is less than or equal to the right string.
private static bool StringLessThanOrEqualMethod(string left, string right)
{
return string.CompareOrdinal(left, right) <= 0;
}
/// Checks whether the left string is less than the right string.
/// Left value.Right value.
/// true if the left string is less than the right string.
private static bool StringLessThanMethod(string left, string right)
{
return string.CompareOrdinal(left, right) < 0;
}
/// Checks whether the left string is greater than or equal to the right string.
/// Left value.Right value.
/// true if the left string is greater than or equal to the right string.
private static bool StringGreaterThanOrEqualMethod(string left, string right)
{
return string.CompareOrdinal(left, right) >= 0;
}
/// Checks whether the left string is greater than the right string.
/// Left value.Right value.
/// true if the left string is greater than the right string.
private static bool StringGreaterThanMethod(string left, string right)
{
return string.CompareOrdinal(left, right) > 0;
}
/// Gets a static method by name.
/// Name of method to get.
/// Left expression to resolve method from and to use as argument.
/// Right expression.
/// The method.
private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
{
return left.Type.GetMethod(methodName, new Type[] { left.Type, right.Type });
}
/// Generates a static method call.
/// Method name.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
{
return Expression.Call(null, GetStaticMethod(methodName, left, right), new Expression[] { left, right });
}
/// Creates an exception for a parse error.
/// Message text.
/// A new Exception.
private static Exception ParseError(string message)
{
return DataServiceException.CreateSyntaxError(message);
}
/// Checks that the given token has the specified identifier.
/// Token to check
/// Identifier to check.
/// true if is an identifier with the specified text.
private static bool TokenIdentifierIs(Token token, string id)
{
return token.Id == TokenId.Identifier && String.Equals(id, token.Text, StringComparison.OrdinalIgnoreCase);
}
#region Parsing.
/// Handles a ?: operator - not supported.
/// The parsed expression.
private Expression ParseExpression()
{
this.RecurseEnter();
Expression expr = this.ParseLogicalOr();
this.RecurseLeave();
return expr;
}
/// Handles or operator.
/// The parsed expression.
private Expression ParseLogicalOr()
{
this.RecurseEnter();
Expression left = this.ParseLogicalAnd();
while (this.TokenIdentifierIs(ExpressionConstants.KeywordOr))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseLogicalAnd();
this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position);
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.OrElseExpression(left, right);
if (left == null)
{
throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text));
}
}
else
#endif
{
left = Expression.OrElse(left, right);
}
}
this.RecurseLeave();
return left;
}
/// Handles and operator.
/// The parsed expression.
private Expression ParseLogicalAnd()
{
this.RecurseEnter();
Expression left = this.ParseComparison();
while (this.TokenIdentifierIs(ExpressionConstants.KeywordAnd))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseComparison();
this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position);
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.AndAlsoExpression(left, right);
if (left == null)
{
throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text));
}
}
else
#endif
{
left = Expression.AndAlso(left, right);
}
}
this.RecurseLeave();
return left;
}
/// Handles eq, ne, lt, gt, le, ge operators.
/// The parsed expression.
private Expression ParseComparison()
{
this.RecurseEnter();
Expression left = this.ParseAdditive();
while (this.CurrentToken.IsComparisonOperator)
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseAdditive();
bool equality = op.IsEqualityOperator;
if (equality && !left.Type.IsValueType && !right.Type.IsValueType)
{
if (left.Type != right.Type)
{
if (IsNullConstant(left))
{
left = Expression.Constant(null, right.Type);
}
else if (IsNullConstant(right))
{
right = Expression.Constant(null, left.Type);
}
else if (left.Type.IsAssignableFrom(right.Type))
{
right = Expression.Convert(right, left.Type);
}
else if (right.Type.IsAssignableFrom(left.Type))
{
left = Expression.Convert(left, right.Type);
}
else
{
throw this.IncompatibleOperandsError(op.Text, left, right, op.Position);
}
}
}
else if (left == NullLiteral || right == NullLiteral)
{
if (!equality)
{
throw ParseError(
Strings.RequestQueryParser_NullOperatorUnsupported(op.Text, op.Position, this.lexer.ExpressionText));
}
// Because we don't have an explicit "is null" check, literal comparisons
// to null are special.
if (!TypeAllowsNull(left.Type))
{
left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
}
else if (!TypeAllowsNull(right.Type))
{
right = Expression.Convert(right, typeof(Nullable<>).MakeGenericType(right.Type));
}
}
else
{
// Enums should be checked here for promotion when supported, but they aren't in this version.
Debug.Assert(!IsEnumType(left.Type), "!IsEnumType(left.Type)");
Debug.Assert(!IsEnumType(right.Type), "!IsEnumType(right.Type)");
Type signatures = equality ? typeof(OperationSignatures.IEqualitySignatures) : typeof(OperationSignatures.IRelationalSignatures);
this.CheckAndPromoteOperands(signatures, op.Text, ref left, ref right, op.Position);
}
Debug.Assert(op.Id == TokenId.Identifier, "op.id == TokenId.Identifier");
switch (op.Text)
{
case ExpressionConstants.KeywordEqual:
left = GenerateEqual(left, right);
break;
case ExpressionConstants.KeywordNotEqual:
left = GenerateNotEqual(left, right);
break;
case ExpressionConstants.KeywordGreaterThan:
left = GenerateGreaterThan(left, right);
break;
case ExpressionConstants.KeywordGreaterThanOrEqual:
left = GenerateGreaterThanEqual(left, right);
break;
case ExpressionConstants.KeywordLessThan:
left = GenerateLessThan(left, right);
break;
case ExpressionConstants.KeywordLessThanOrEqual:
left = GenerateLessThanEqual(left, right);
break;
}
}
this.RecurseLeave();
return left;
}
/// Handles +, -, & operators (& for string concat, not supported).
/// The parsed expression.
private Expression ParseAdditive()
{
this.RecurseEnter();
Expression left = this.ParseMultiplicative();
while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordAdd) ||
this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordSub))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseMultiplicative();
if (op.IdentifierIs(ExpressionConstants.KeywordAdd))
{
this.CheckAndPromoteOperands(typeof(OperationSignatures.IAddSignatures), op.Text, ref left, ref right, op.Position);
left = GenerateAdd(left, right);
}
else
{
Debug.Assert(ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub), "ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub)");
this.CheckAndPromoteOperands(typeof(OperationSignatures.ISubtractSignatures), op.Text, ref left, ref right, op.Position);
left = GenerateSubtract(left, right);
}
}
this.RecurseLeave();
return left;
}
/// Handles mul, div, mod operators.
/// The parsed expression.
private Expression ParseMultiplicative()
{
this.RecurseEnter();
Expression left = this.ParseUnary();
while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordMultiply) ||
this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordDivide) ||
this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordModulo))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseUnary();
this.CheckAndPromoteOperands(typeof(OperationSignatures.IArithmeticSignatures), op.Text, ref left, ref right, op.Position);
if (op.IdentifierIs(ExpressionConstants.KeywordMultiply))
{
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.MultiplyExpression(left, right);
}
else
#endif
{
left = Expression.Multiply(left, right);
}
}
else if (op.IdentifierIs(ExpressionConstants.KeywordDivide))
{
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.DivideExpression(left, right);
}
else
#endif
{
left = Expression.Divide(left, right);
}
}
else
{
Debug.Assert(op.IdentifierIs(ExpressionConstants.KeywordModulo), "op.IdentifierIs(ExpressionConstants.KeywordModulo)");
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.ModuloExpression(left, right);
}
else
#endif
{
left = Expression.Modulo(left, right);
}
}
}
this.RecurseLeave();
return left;
}
/// Handles -, not unary operators.
/// The parsed expression.
private Expression ParseUnary()
{
this.RecurseEnter();
if (this.CurrentToken.Id == TokenId.Minus || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordNot))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
if (op.Id == TokenId.Minus && (ExpressionLexer.IsNumeric(this.CurrentToken.Id)))
{
Token numberLiteral = this.CurrentToken;
numberLiteral.Text = "-" + numberLiteral.Text;
numberLiteral.Position = op.Position;
this.CurrentToken = numberLiteral;
this.RecurseLeave();
return this.ParsePrimary();
}
Expression expr = this.ParseUnary();
if (op.Id == TokenId.Minus)
{
this.CheckAndPromoteOperand(typeof(OperationSignatures.INegationSignatures), op.Text, ref expr, op.Position);
#if ASTORIA_OPEN_OBJECT
if (expr.Type == typeof(object))
{
expr = LateBoundMethods.NegateExpression(expr);
}
else
#endif
{
expr = Expression.Negate(expr);
}
}
else
{
this.CheckAndPromoteOperand(typeof(OperationSignatures.INotSignatures), op.Text, ref expr, op.Position);
#if ASTORIA_OPEN_OBJECT
if (expr.Type == typeof(object))
{
expr = LateBoundMethods.NotExpression(expr);
}
else
#endif
if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable))
{
// Expression.Not will take numerics and apply '~' to them, thus the extra check here.
expr = Expression.Not(expr);
}
else
{
throw ParseError(Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type));
}
}
this.RecurseLeave();
return expr;
}
this.RecurseLeave();
return this.ParsePrimary();
}
/// Handles primary expressions.
/// The parsed expression.
private Expression ParsePrimary()
{
this.RecurseEnter();
Expression expr = this.ParsePrimaryStart();
while (true)
{
if (this.CurrentToken.Id == TokenId.Slash)
{
this.lexer.NextToken();
expr = this.ParseMemberAccess(expr);
}
else
{
break;
}
}
this.RecurseLeave();
return expr;
}
/// Handles the start of primary expressions.
/// The parsed expression.
private Expression ParsePrimaryStart()
{
switch (this.CurrentToken.Id)
{
case TokenId.BooleanLiteral:
return this.ParseTypedLiteral(typeof(bool), XmlConstants.EdmBooleanTypeName);
case TokenId.DateTimeLiteral:
return this.ParseTypedLiteral(typeof(DateTime), XmlConstants.EdmDateTimeTypeName);
case TokenId.DecimalLiteral:
return this.ParseTypedLiteral(typeof(decimal), XmlConstants.EdmDecimalTypeName);
case TokenId.NullLiteral:
return this.ParseNullLiteral();
case TokenId.Identifier:
return this.ParseIdentifier();
case TokenId.StringLiteral:
return this.ParseTypedLiteral(typeof(string), XmlConstants.EdmStringTypeName);
case TokenId.Int64Literal:
return this.ParseTypedLiteral(typeof(Int64), XmlConstants.EdmInt64TypeName);
case TokenId.IntegerLiteral:
return this.ParseTypedLiteral(typeof(Int32), XmlConstants.EdmInt32TypeName);
case TokenId.DoubleLiteral:
return this.ParseTypedLiteral(typeof(double), XmlConstants.EdmDoubleTypeName);
case TokenId.SingleLiteral:
return this.ParseTypedLiteral(typeof(Single), XmlConstants.EdmSingleTypeName);
case TokenId.GuidLiteral:
return this.ParseTypedLiteral(typeof(Guid), XmlConstants.EdmGuidTypeName);
case TokenId.BinaryLiteral:
return this.ParseTypedLiteral(typeof(byte[]), XmlConstants.EdmBinaryTypeName);
case TokenId.OpenParen:
return this.ParseParenExpression();
default:
throw ParseError(Strings.RequestQueryParser_ExpressionExpected(this.CurrentToken.Position));
}
}
/// Handles parenthesized expressions.
/// The parsed expression.
private Expression ParseParenExpression()
{
if (this.CurrentToken.Id != TokenId.OpenParen)
{
throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
Expression e = this.ParseExpression();
if (this.CurrentToken.Id != TokenId.CloseParen)
{
throw ParseError(Strings.RequestQueryParser_CloseParenOrOperatorExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
return e;
}
/// Handles identifiers.
/// The parsed expression.
private Expression ParseIdentifier()
{
this.ValidateToken(TokenId.Identifier);
bool identifierIsFunction = this.lexer.PeekNextToken().Id == TokenId.OpenParen;
if (identifierIsFunction)
{
return this.ParseIdentifierAsFunction();
}
else
{
return this.ParseMemberAccess(this.it);
}
}
/// Handles identifiers which have been recognized as functions.
/// The parsed expression.
private Expression ParseIdentifierAsFunction()
{
FunctionDescription[] functionDescriptions;
Token functionToken = this.CurrentToken;
if (!functions.TryGetValue(functionToken.Text, out functionDescriptions))
{
throw ParseError(Strings.RequestQueryParser_UnknownFunction(functionToken.Text, functionToken.Position));
}
this.lexer.NextToken();
Expression[] originalArguments = this.ParseArgumentList();
Expression[] arguments = this.nullPropagationRequired ? originalArguments : (Expression[])originalArguments.Clone();
FunctionDescription function = this.FindBestFunction(functionDescriptions, ref arguments);
if (function == null)
{
string message = Strings.RequestQueryParser_NoApplicableFunction(
functionToken.Text,
functionToken.Position,
FunctionDescription.BuildSignatureList(functionToken.Text, functionDescriptions));
throw ParseError(message);
}
// Special case for null propagation - we never strip nullability from expressions.
if (this.nullPropagationRequired && function.IsTypeCast)
{
Expression typeExpression = arguments[arguments.Length - 1];
Debug.Assert(typeExpression != null, "typeExpression != null -- otherwise function finding failed.");
Debug.Assert(typeExpression.Type == typeof(Type), "typeExpression.Type == typeof(Type) -- last argument is always type.");
Type castTargetType = (Type)((ConstantExpression)typeExpression).Value;
if (!TypeAllowsNull(castTargetType))
{
arguments[arguments.Length - 1] = Expression.Constant(typeof(Nullable<>).MakeGenericType(castTargetType));
}
}
Expression result = function.ConversionFunction(this.it, arguments);
if (this.nullPropagationRequired && !function.IsTypeCheck && !function.IsTypeCast)
{
Debug.Assert(
originalArguments != arguments,
"originalArguments != arguments -- arguments should have been cloned");
Debug.Assert(
originalArguments.Length == arguments.Length,
"originalArguments.Length == arguments.Length -- arguments should not be added/removed");
for (int i = 0; i < originalArguments.Length; i++)
{
result = this.ConsiderNullPropagation(originalArguments[i], result);
}
}
return result;
}
/// Handles boolean literals.
/// The parsed expression.
private Expression ParseBooleanLiteral()
{
Debug.Assert(this.CurrentToken.Id == TokenId.BooleanLiteral, "this.CurrentToken.Id == TokenId.BooleanLiteral");
string originalText = this.CurrentToken.Text;
this.lexer.NextToken();
if (originalText == ExpressionConstants.KeywordTrue)
{
return trueLiteral;
}
else
{
Debug.Assert(originalText == ExpressionConstants.KeywordFalse, "originalText == ExpressionConstants.KeywordFalse");
return falseLiteral;
}
}
/// Handles typed literals.
/// Expected type to be parsed.
/// Expected type name.
/// The constants expression produced by building the given literal.
private Expression ParseTypedLiteral(Type targetType, string targetTypeName)
{
object targetValue;
if (!WebConvert.TryKeyStringToPrimitive(this.CurrentToken.Text, targetType, out targetValue))
{
string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, this.CurrentToken.Text, this.CurrentToken.Position);
throw ParseError(message);
}
Expression result = this.CreateLiteral(targetValue, this.CurrentToken.Text);
this.lexer.NextToken();
return result;
}
/// Handles 'null' literals.
/// The parsed expression.
private Expression ParseNullLiteral()
{
Debug.Assert(this.CurrentToken.Id == TokenId.NullLiteral, "this.CurrentToken.Id == TokenId.NullLiteral");
this.lexer.NextToken();
return NullLiteral;
}
/// Handles member access.
/// Instance being accessed.
/// The parsed expression.
private Expression ParseMemberAccess(Expression instance)
{
Debug.Assert(instance != null, "instance != null");
Type type = instance.Type;
int errorPos = this.lexer.Position;
string id = this.CurrentToken.GetIdentifier();
this.lexer.NextToken();
// An open paren here would indicate calling a method in regular C# syntax.
ResourceProperty property = this.FindProperty(type, id);
if (property != null)
{
// We have a strongly-type property.
Expression result = this.ConsiderNullPropagation(instance, Expression.Property(instance, property.PropertyInfo));
if (property.ResourceContainer != null)
{
Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer);
if (filter != null)
{
// We did null propagation for accessing the property, but we may also need
// to do null propagation on the property value itself (otherwise the interception
// lambda needs to check the argument for null, which is probably unexpected).
result = RequestQueryProcessor.ComposePropertyNavigation(
result, (LambdaExpression)filter, this.provider.NullPropagationRequired);
}
}
return result;
}
else
{
// Open types can have any members inside them
#if ASTORIA_OPEN_OBJECT
if (type == typeof(object) || OpenTypeAttribute.IsOpenType(type))
{
// object LateBoundMethods.GetValue(object, string)
Expression call =
Expression.Call(null /* instance */, LateBoundMethods.GetValueMethodInfo, instance, Expression.Constant(id));
return this.ConsiderNullPropagation(instance, call);
}
else
#endif
{
throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, this.GetTypeName(type), errorPos));
}
}
}
/// Handles argument lists.
/// The parsed expressions.
private Expression[] ParseArgumentList()
{
if (this.CurrentToken.Id != TokenId.OpenParen)
{
throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
Expression[] args = this.CurrentToken.Id != TokenId.CloseParen ? this.ParseArguments() : emptyExpressions;
if (this.CurrentToken.Id != TokenId.CloseParen)
{
throw ParseError(Strings.RequestQueryParser_CloseParenOrCommaExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
return args;
}
/// Handles comma-separated arguments.
/// The parsed expressions.
private Expression[] ParseArguments()
{
List argList = new List();
while (true)
{
argList.Add(this.ParseExpression());
if (this.CurrentToken.Id != TokenId.Comma)
{
break;
}
this.lexer.NextToken();
}
return argList.ToArray();
}
#endregion Parsing.
/// Creates a constant expression with the specified literal.
/// Constant value.
/// Value text.
/// The created expression.
private Expression CreateLiteral(object value, string text)
{
ConstantExpression expr = Expression.Constant(value);
this.literals.Add(expr, text);
return expr;
}
/// Finds the best fitting function for the specified arguments.
/// Functions to consider.
/// Arguments; if a best function is found, promoted arguments.
/// The best fitting function; null if none found or ambiguous.
private FunctionDescription FindBestFunction(FunctionDescription[] functions, ref Expression[] arguments)
{
Debug.Assert(functions != null, "functions != null");
List applicableFunctions = new List(functions.Length);
List applicablePromotedArguments = new List(functions.Length);
// Build a list of applicable functions (and cache their promoted arguments).
foreach (FunctionDescription candidate in functions)
{
if (candidate.ParameterTypes.Length != arguments.Length)
{
continue;
}
Expression[] promotedArguments = new Expression[arguments.Length];
bool argumentsMatch = true;
for (int i = 0; i < candidate.ParameterTypes.Length; i++)
{
promotedArguments[i] = this.PromoteExpression(arguments[i], candidate.ParameterTypes[i], true);
if (promotedArguments[i] == null)
{
argumentsMatch = false;
break;
}
}
if (argumentsMatch)
{
applicableFunctions.Add(candidate);
applicablePromotedArguments.Add(promotedArguments);
}
}
// Return the best applicable function.
if (applicableFunctions.Count == 0)
{
// No matching function.
return null;
}
else if (applicableFunctions.Count == 1)
{
arguments = applicablePromotedArguments[0];
return applicableFunctions[0];
}
else
{
// Find a single function which is better than all others.
int bestFunctionIndex = -1;
for (int i = 0; i < applicableFunctions.Count; i++)
{
bool betterThanAllOthers = true;
for (int j = 0; j < applicableFunctions.Count; j++)
{
if (i != j && IsBetterThan(arguments, applicableFunctions[j].ParameterTypes, applicableFunctions[i].ParameterTypes))
{
betterThanAllOthers = false;
break;
}
}
if (betterThanAllOthers)
{
if (bestFunctionIndex == -1)
{
bestFunctionIndex = i;
}
else
{
// Ambiguous.
return null;
}
}
}
if (bestFunctionIndex == -1)
{
return null;
}
arguments = applicablePromotedArguments[bestFunctionIndex];
return applicableFunctions[bestFunctionIndex];
}
}
/// Rewrites an expression to propagate null values if necessary.
/// Expression to check for null.
/// Expression to yield if does not yield null.
/// The possibly rewriteen .
private Expression ConsiderNullPropagation(Expression element, Expression notNullExpression)
{
if (!this.nullPropagationRequired || element == this.it || !TypeAllowsNull(element.Type))
{
return notNullExpression;
}
// Tiny optimization: remove the check on constants which are known not to be null.
// Otherwise every string literal propagates out, which is correct but unnecessarily messy.
if (element is ConstantExpression && element != NullLiteral)
{
return notNullExpression;
}
// ifTrue and ifFalse expressions must match exactly. We need to ensure that the 'false'
// side is nullable, and the 'true' side is a null of the correct type.
Expression test = Expression.Equal(element, Expression.Constant(null, element.Type));
Expression falseIf = notNullExpression;
if (!TypeAllowsNull(falseIf.Type))
{
falseIf = Expression.Convert(falseIf, typeof(Nullable<>).MakeGenericType(falseIf.Type));
}
Expression trueIf = Expression.Constant(null, falseIf.Type);
return Expression.Condition(test, trueIf, falseIf);
}
/// Checks that the operand (possibly promoted) is valid for the specified operation.
/// Type with signatures to match.
/// Name of operation for error reporting.
/// Expression for operand.
/// Position for error reporting.
private void CheckAndPromoteOperand(Type signatures, string operationName, ref Expression expr, int errorPos)
{
Debug.Assert(signatures != null, "signatures != null");
Debug.Assert(operationName != null, "operationName != null");
Expression[] args = new Expression[] { expr };
MethodBase method;
if (this.FindMethod(signatures, "F", args, out method) != 1)
{
throw ParseError(Strings.RequestQueryParser_IncompatibleOperand(operationName, this.GetTypeName(args[0].Type), errorPos));
}
expr = args[0];
}
/// Checks that the operands (possibly promoted) are valid for the specified operation.
/// Type with signatures to match.
/// Name of operation for error reporting.
/// Expression for left operand.
/// Expression for right operand.
/// Position for error reporting.
private void CheckAndPromoteOperands(Type signatures, string operationName, ref Expression left, ref Expression right, int errorPos)
{
Expression[] args = new Expression[] { left, right };
MethodBase method;
if (this.FindMethod(signatures, "F", args, out method) != 1)
{
throw this.IncompatibleOperandsError(operationName, left, right, errorPos);
}
left = args[0];
right = args[1];
}
/// Finds a property in the specified type with the given name.
/// Type in which to look for member.
/// Member name.
/// The ResourceProperty for the specified type.
private ResourceProperty FindProperty(Type type, string memberName)
{
// This could also be a FindPropertyOrField method.
Debug.Assert(type != null, "type != null");
Debug.Assert(memberName != null, "memberName != null");
ResourceProperty property = this.provider.TryResolvePropertyName(type, memberName);
if (property != null)
{
if (property.ResourceContainer != null)
{
this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, true /* singleResult */);
}
return property;
}
else
{
return null;
}
}
/// Finds the named method in the specifid type.
/// Type to look in.
/// Name of method to look for.
/// Arguments to method.
/// Best method found.
/// Number of matching methods.
private int FindMethod(Type type, string methodName, Expression[] args, out MethodBase method)
{
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance;
foreach (Type t in SelfAndBaseTypes(type))
{
MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterName, methodName);
int count = this.FindBestMethod(members.Cast(), args, out method);
if (count != 0)
{
return count;
}
}
method = null;
return 0;
}
/// Finds all applicable methods from the specified enumeration that match the arguments.
/// Enumerable object of candidate methods.
/// Argument expressions.
/// Methods that apply to the specified arguments.
private MethodData[] FindApplicableMethods(IEnumerable methods, Expression[] args)
{
List result = new List();
foreach (MethodBase method in methods)
{
MethodData methodData = new MethodData(method, method.GetParameters());
if (this.IsApplicable(methodData, args))
{
result.Add(methodData);
}
}
return result.ToArray();
}
/// Finds the best methods for the specified arguments given a candidate method enumeration.
/// Enumerable object for candidate methods.
/// Argument expressions to match.
/// Best matched method.
/// The number of "best match" methods.
private int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method)
{
MethodData[] applicable = this.FindApplicableMethods(methods, args);
if (applicable.Length > 1)
{
applicable = FindBestApplicableMethods(applicable, args);
}
int result = applicable.Length;
method = null;
if (applicable.Length == 1)
{
// If we started off with all non-OpenType expressions and end with all-OpenType
// expressions, we've been too aggresive - the transition from non-open-types
// to open types should initially happen only as a result of accessing open properties.
#if ASTORIA_OPEN_OBJECT
MethodData md = applicable[0];
bool originalArgsDefined = true;
bool promotedArgsOpen = true;
for (int i = 0; i < args.Length; i++)
{
originalArgsDefined = originalArgsDefined && !IsOpenPropertyExpression(args[i]);
promotedArgsOpen = promotedArgsOpen && md.Parameters[i].ParameterType == typeof(object);
args[i] = md.Args[i];
}
method = (originalArgsDefined && promotedArgsOpen) ? null : md.MethodBase;
result = (method == null) ? 0 : 1;
#else
for (int i = 0; i < args.Length; i++)
{
args[i] = applicable[0].Args[i];
}
method = applicable[0].MethodBase;
result = 1;
#endif
}
else if (applicable.Length > 1)
{
// We may have the case for operators (which C# doesn't) in which we have a nullable operand
// and a non-nullable operand. We choose to convert the one non-null operand to nullable in that
// case (the binary expression will lift to null).
if (args.Length == 2 && applicable.Length == 2 &&
GetNonNullableType(applicable[0].Parameters[0].ParameterType) ==
GetNonNullableType(applicable[1].Parameters[0].ParameterType))
{
MethodData nullableMethod =
TypeAllowsNull(applicable[0].Parameters[0].ParameterType) ?
applicable[0] :
applicable[1];
args[0] = nullableMethod.Args[0];
args[1] = nullableMethod.Args[1];
return this.FindBestMethod(methods, args, out method);
}
}
return result;
}
/// Gets a name that can be used for error messages.
/// Type to get name for.
/// The name of the specified .
private string GetTypeName(Type type)
{
Debug.Assert(type != null, "type != null");
return WebUtil.GetTypeName(this.provider, type);
}
/// Creates an exception indicated that two operands are incompatible.
/// Name of operation for operands.
/// Expression for left-hand operand.
/// Expression for right-hand operand.
/// Position for error.
/// A new .
private Exception IncompatibleOperandsError(string operationName, Expression left, Expression right, int pos)
{
string message = Strings.RequestQueryParser_IncompatibleOperands(
operationName,
this.GetTypeName(left.Type),
this.GetTypeName(right.Type),
pos);
return ParseError(message);
}
/// Checks whether the specified method is applicable given the argument expressions.
/// Method to check.
/// Argument expressions.
/// true if the method is applicable; false otherwise.
private bool IsApplicable(MethodData method, Expression[] args)
{
if (method.Parameters.Length != args.Length)
{
return false;
}
Expression[] promotedArgs = new Expression[args.Length];
for (int i = 0; i < args.Length; i++)
{
ParameterInfo pi = method.Parameters[i];
Debug.Assert(!pi.IsOut, "!pi.IsOut");
Expression promoted = this.PromoteExpression(args[i], pi.ParameterType, false);
if (promoted == null)
{
return false;
}
promotedArgs[i] = promoted;
}
method.Args = promotedArgs;
return true;
}
/// Promotes the specified expression to the given type if necessary.
/// Expression to promote.
/// Type to change expression to.
/// Whether an exact type is required; false implies a compatible type is OK.
/// Expression with the promoted type.
private Expression PromoteExpression(Expression expr, Type type, bool exact)
{
Debug.Assert(expr != null, "expr != null");
Debug.Assert(type != null, "type != null");
if (expr.Type == type)
{
return expr;
}
ConstantExpression ce = expr as ConstantExpression;
if (ce != null)
{
if (ce == NullLiteral)
{
if (TypeAllowsNull(type))
{
return Expression.Constant(null, type);
}
}
else
{
string text;
if (this.literals.TryGetValue(ce, out text))
{
Type target = GetNonNullableType(type);
object value = null;
if (ce.Type == typeof(string) && target == typeof(Type))
{
if (WebConvert.TryRemoveQuotes(ref text))
{
ResourceType resourceType = this.provider.TryResolveTypeName(text);
if (resourceType != null)
{
value = resourceType.Type;
}
}
}
else
{
switch (Type.GetTypeCode(ce.Type))
{
case TypeCode.Int32:
case TypeCode.Int64:
value = ParseNumber(text, target);
break;
case TypeCode.Double:
if (target == typeof(decimal))
{
value = ParseNumber(text, target);
}
break;
}
}
if (value != null)
{
return Expression.Constant(value, type);
}
}
}
}
if (IsCompatibleWith(expr.Type, type))
{
if (type.IsValueType || exact)
{
return Expression.Convert(expr, type);
}
return expr;
}
// Allow promotion from nullable to non-nullable by directly accessing underlying value.
if (IsNullableType(expr.Type) && type.IsValueType)
{
Expression valueAccessExpression = Expression.Property(expr, "Value");
valueAccessExpression = this.PromoteExpression(valueAccessExpression, type, exact);
return valueAccessExpression;
}
return null;
}
/// Checks that the current token has the specified identifier.
/// Identifier to check.
/// true if the current token is an identifier with the specified text.
private bool TokenIdentifierIs(string id)
{
return this.CurrentToken.IdentifierIs(id);
}
/// Validates the current token is of the specified kind.
/// Expected token kind.
private void ValidateToken(TokenId t)
{
if (this.CurrentToken.Id != t)
{
throw ParseError(Strings.RequestQueryParser_SyntaxError(this.CurrentToken.Position));
}
}
#region Recursion control.
/// Marks the fact that a recursive method was entered, and checks that the depth is allowed.
private void RecurseEnter()
{
this.recursionDepth++;
Debug.Assert(this.recursionDepth <= RecursionLimit, "this.recursionDepth <= recursionLimit");
if (this.recursionDepth == RecursionLimit)
{
throw DataServiceException.CreateDeepRecursion(RecursionLimit);
}
}
/// Marks the fact that a recursive method is leaving..
private void RecurseLeave()
{
this.recursionDepth--;
Debug.Assert(0 <= this.recursionDepth, "0 <= this.recursionDepth");
Debug.Assert(this.recursionDepth < RecursionLimit, "this.recursionDepth < recursionLimit");
}
#endregion Recursion control.
/// Use this class to encapsulate method information.
[DebuggerDisplay("MethodData {methodBase}")]
private class MethodData
{
#region Private fields.
/// Described method.
private readonly MethodBase methodBase;
/// Parameters for method.
private readonly ParameterInfo[] parameters;
/// Argument expressions.
private Expression[] args;
#endregion Private fields.
#region Constructors.
/// Initializes a new instance.
/// Described method
/// Parameters for method.
public MethodData(MethodBase method, ParameterInfo[] parameters)
{
this.methodBase = method;
this.parameters = parameters;
}
#endregion Constructors.
#region Properties.
/// Argument expressions.
public Expression[] Args
{
get { return this.args; }
set { this.args = value; }
}
/// Described method.
public MethodBase MethodBase
{
get { return this.methodBase; }
}
/// Parameters for method.
public ParameterInfo[] Parameters
{
get { return this.parameters; }
}
/// Enumeration of parameter types.
public IEnumerable ParameterTypes
{
get
{
foreach (ParameterInfo parameter in this.Parameters)
{
yield return parameter.ParameterType;
}
}
}
#endregion Properties.
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Provides a type to parse expressions in request queries.
//
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Services.Parsing
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Data.Services.Parsing;
using System.Data.Services.Providers;
#if ASTORIA_OPEN_OBJECT
using System.Data.Services.OpenTypes;
#endif
#endregion Namespaces.
///
/// This class provides static methods to parse query options and compose
/// them on an existing query.
///
internal static class RequestQueryParser
{
#region Fields.
/// Constant for "null" literal.
internal static readonly ConstantExpression NullLiteral = Expression.Constant(null);
#endregion Fields.
#region Internal methods.
/// Gets a type for that allows null values.
/// Type to base resulting type on.
///
/// if it's a reference or Nullable<> type;
/// Nullable< > otherwise.
///
internal static Type GetTypeAllowingNull(Type type)
{
Debug.Assert(type != null, "type != null");
return TypeAllowsNull(type) ? type : typeof(Nullable<>).MakeGenericType(type);
}
/// Checks whether the specified type is a generic nullable type.
/// Type to check.
/// true if is nullable; false otherwise.
internal static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
/// Checks whether is a null constant.
/// Expression to check.
/// true if is a null constant; false otherwise.
internal static bool IsNullConstant(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
return
expression == NullLiteral ||
(expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Value == null);
}
/// Checks whether the specified can be assigned null.
/// Type to check.
/// true if type is a reference type or a Nullable type; false otherwise.
internal static bool TypeAllowsNull(Type type)
{
Debug.Assert(type != null, "type != null");
return !type.IsValueType || IsNullableType(type);
}
/// Sorts a query like a SQL ORDER BY clause does.
/// Service with data and configuration.
/// Original source for query.
/// Ordering definition to compose.
/// The composed query.
internal static IQueryable OrderBy(IDataService service, IQueryable source, string ordering)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(source != null, "source != null");
Debug.Assert(ordering != null, "ordering != null");
ParameterExpression parameterForIt = Expression.Parameter(source.ElementType, "it");
ExpressionParser parser = new ExpressionParser(service, parameterForIt, ordering);
IEnumerable orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings)
{
Type selectorType = o.Selector.Type;
if (!service.Provider.GetTypeIsOrdered(selectorType))
{
string resourceTypeName = WebUtil.GetTypeName(service.Provider, o.Selector.Type);
throw DataServiceException.CreateBadRequestError(Strings.RequestQueryParser_OrderByDoesNotSupportType(resourceTypeName));
}
queryExpr = Expression.Call(
typeof(Queryable),
o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, selectorType },
queryExpr,
Expression.Quote(Expression.Lambda(o.Selector, parameterForIt)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}
/// Filters a query like a SQL WHERE clause does.
/// Service with data and configuration.
/// Original source for query.
/// Predicate to compose.
/// The composed query.
internal static IQueryable Where(IDataService service, IQueryable source, string predicate)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(service != null, "source != null");
Debug.Assert(predicate != null, "predicate != null");
LambdaExpression lambda = ParseLambdaForWhere(service, source.ElementType, predicate);
////Trace.WriteLine("predicate=" + predicate + "; lambda=" + lambda.ToString());
return source.Provider.CreateQuery(
Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda)));
}
#endregion Internal methods.
#region Private methods.
/// Parses a lambda expression.
/// Service with data and configuration.
/// Type for "it" contextual variable.
/// Expression to parse.
/// The parsed expression.
private static LambdaExpression ParseLambdaForWhere(IDataService service, Type typeForIt, string expression)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(typeForIt != null, "typeForIt != null");
Debug.Assert(expression != null, "expression != null");
ParameterExpression parameterForIt = Expression.Parameter(typeForIt, "it");
ExpressionParser parser = new ExpressionParser(service, parameterForIt, expression);
return Expression.Lambda(parser.ParseWhere(), parameterForIt);
}
#endregion Private methods.
/// Use this class to define ordering for resources.
private class DynamicOrdering
{
#region Private fields.
/// Whether the order is ascending.
private readonly bool ascending;
/// Selector expression in ordering.
private readonly Expression selector;
#endregion Private fields.
/// Initializes a new instance.
/// Selector expression in ordering.
/// Whether the order is ascending.
internal DynamicOrdering(Expression selector, bool ascending)
{
this.selector = selector;
this.ascending = ascending;
}
#region Properties.
/// Whether the order is ascending.
internal bool Ascending
{
get { return this.ascending; }
}
/// Selector expression in ordering.
internal Expression Selector
{
get { return this.selector; }
}
#endregion Properties.
}
/// Use this class to parse an expression in the Astoria URI format.
[DebuggerDisplay("ExpressionParser ({lexer.text})")]
private class ExpressionParser
{
#region Fields.
/// Maximum recursion limit on deserializer.
private const int RecursionLimit = 100;
/// A type that is not numeric.
private const int NumericTypeNotNumeric = 0;
/// A type that is a char, single, double or decimal.
private const int NumericTypeNotIntegral = 1;
/// A type that is a signed integral.
private const int NumericTypeSignedIntegral = 2;
/// A type that is an unsigned integral.
private const int NumericTypeUnsignedIntegral = 3;
/// Empty Expressions array.
private static readonly Expression[] emptyExpressions = new Expression[0];
/// Constant for "true" literal.
private static readonly ConstantExpression trueLiteral = Expression.Constant(true);
/// Constant for "false" literal.
private static readonly ConstantExpression falseLiteral = Expression.Constant(false);
/// Dictionary of system functions.
private static readonly Dictionary functions = FunctionDescription.CreateFunctions();
/// Provider of data and metadata.
private readonly IDataServiceProvider provider;
/// Service with data and configuration.
private readonly IDataService service;
/// Literals.
private Dictionary literals;
/// "it" contextual parameter.
private ParameterExpression it;
/// Expression lexer.
private ExpressionLexer lexer;
/// Whether the expression tree should propagate nulls explicitly.
private bool nullPropagationRequired;
/// Depth of recursion.
private int recursionDepth;
#endregion Fields.
#region Constructors.
/// Initializes a new .
/// Service with data and configuration.
/// Parameters for the current "it" context.
/// Expression to parse.
internal ExpressionParser(IDataService service, ParameterExpression parameterForIt, string expression)
{
Debug.Assert(service != null, "service != null");
Debug.Assert(expression != null, "expression != null");
Debug.Assert(parameterForIt != null, "parameterForIt != null");
this.service = service;
this.provider = service.Provider;
this.nullPropagationRequired = this.provider.NullPropagationRequired;
this.literals = new Dictionary();
this.it = parameterForIt;
this.lexer = new ExpressionLexer(expression);
}
#endregion Constructors.
/// Current token being processed.
private Token CurrentToken
{
get { return this.lexer.CurrentToken; }
set { this.lexer.CurrentToken = value; }
}
/// Parses the text expression for a .Where method invocation.
/// The parsed expression.
internal Expression ParseWhere()
{
int exprPos = this.lexer.Position;
Expression expr = this.ParseExpression();
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(expr))
{
expr = Expression.Convert(expr, typeof(bool));
}
else
#endif
if (IsNullConstant(expr))
{
expr = falseLiteral;
}
else if (expr.Type == typeof(bool?))
{
Expression test = Expression.Equal(expr, Expression.Constant(null, typeof(bool?)));
expr = Expression.Condition(test, falseLiteral, Expression.Property(expr, "Value"));
}
else if (expr.Type != typeof(bool))
{
throw ParseError(Strings.RequestQueryParser_ExpressionTypeMismatch(this.GetTypeName(typeof(bool)), exprPos));
}
this.lexer.ValidateToken(TokenId.End);
Debug.Assert(expr != null, "expr != null");
Debug.Assert(expr.Type == typeof(bool), "expr.Type(" + expr.Type + ") == typeof(bool)");
return expr;
}
/// Parses the text expression for ordering.
/// An enumeration of orderings.
internal IEnumerable ParseOrdering()
{
List orderings = new List();
while (true)
{
Expression expr = this.ParseExpression();
bool ascending = true;
if (this.TokenIdentifierIs(ExpressionConstants.KeywordAscending))
{
this.lexer.NextToken();
}
else if (this.TokenIdentifierIs(ExpressionConstants.KeywordDescending))
{
this.lexer.NextToken();
ascending = false;
}
orderings.Add(new DynamicOrdering(expr, ascending));
if (this.CurrentToken.Id != TokenId.Comma)
{
break;
}
this.lexer.NextToken();
}
this.ValidateToken(TokenId.End);
return orderings;
}
/// Compares two byte arrays for equality.
/// First byte array.Second byte array.
/// true if the arrays are equal; false otherwise.
private static bool ByteArraysEqual(byte[] b0, byte[] b1)
{
if (b0 == b1)
{
return true;
}
if (b0 == null || b1 == null)
{
return false;
}
if (b0.Length != b1.Length)
{
return false;
}
for (int i = 0; i < b0.Length; i++)
{
if (b0[i] != b1[i])
{
return false;
}
}
return true;
}
/// Compares two byte arrays for equality.
/// First byte array.Second byte array.
/// true if the arrays are not equal; false otherwise.
private static bool ByteArraysNotEqual(byte[] b0, byte[] b1)
{
return !ByteArraysEqual(b0, b1);
}
/// Gets a non-nullable version of the specified type.
/// Type to get non-nullable version for.
///
/// if type is a reference type or a
/// non-nullable type; otherwise, the underlying value type.
///
private static Type GetNonNullableType(Type type)
{
return Nullable.GetUnderlyingType(type) ?? type;
}
/// Checks whether the specified type is a signed integral type.
/// Type to check.
/// true if is a signed integral type; false otherwise.
private static bool IsSignedIntegralType(Type type)
{
return GetNumericTypeKind(type) == NumericTypeSignedIntegral;
}
/// Checks whether the specified type is an unsigned integral type.
/// Type to check.
/// true if is an unsigned integral type; false otherwise.
private static bool IsUnsignedIntegralType(Type type)
{
return GetNumericTypeKind(type) == NumericTypeUnsignedIntegral;
}
/// Gets a flag for the numeric kind of type.
/// Type to get numeric kind for.
///
/// One of NumericTypeNotNumeric; NumericTypeNotIntegral if it's char,
/// single, double or decimal; NumericTypeSignedIntegral, or NumericTypeUnsignedIntegral.
///
private static int GetNumericTypeKind(Type type)
{
type = GetNonNullableType(type);
Debug.Assert(!type.IsEnum, "!type.IsEnum");
switch (Type.GetTypeCode(type))
{
case TypeCode.Char:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return NumericTypeNotIntegral;
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
return NumericTypeSignedIntegral;
case TypeCode.Byte:
return NumericTypeUnsignedIntegral;
default:
return NumericTypeNotNumeric;
}
}
/// Checks whether type is a (possibly nullable) enumeration type.
/// Type to check.
/// true if type is an enumeration or a nullable enumeration; false otherwise.
private static bool IsEnumType(Type type)
{
return GetNonNullableType(type).IsEnum;
}
/// Returns an object that can enumerate the specified type and its supertypes.
/// Type to based enumeration on.
/// An object that can enumerate the specified type and its supertypes.
private static IEnumerable SelfAndBaseTypes(Type type)
{
if (type.IsInterface)
{
List types = new List();
AddInterface(types, type);
return types;
}
return SelfAndBaseClasses(type);
}
/// Returns an object that can enumerate the specified type and its supertypes.
/// Type to based enumeration on.
/// An object that can enumerate the specified type and its supertypes.
private static IEnumerable SelfAndBaseClasses(Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
/// Adds an interface type to a list of types, including inherited interfaces.
/// Types list ot add to.
/// Interface type to add.
private static void AddInterface(List types, Type type)
{
if (!types.Contains(type))
{
types.Add(type);
foreach (Type t in type.GetInterfaces())
{
AddInterface(types, t);
}
}
}
/// Finds the best applicable methods from the specified array that match the arguments.
/// Candidate methods.
/// Argument expressions.
/// Best applicable methods.
private static MethodData[] FindBestApplicableMethods(MethodData[] applicable, Expression[] args)
{
Debug.Assert(applicable != null, "applicable != null");
List result = new List();
foreach (MethodData method in applicable)
{
bool betterThanAllOthers = true;
foreach (MethodData otherMethod in applicable)
{
if (otherMethod != method && IsBetterThan(args, otherMethod, method))
{
betterThanAllOthers = false;
break;
}
}
if (betterThanAllOthers)
{
result.Add(method);
}
}
return result.ToArray();
}
/// Parses the specified text into a number.
/// Text to parse.
/// Type to parse into.
/// The parsed number.
private static object ParseNumber(string text, Type type)
{
TypeCode tc = Type.GetTypeCode(GetNonNullableType(type));
switch (tc)
{
case TypeCode.SByte:
sbyte sb;
if (sbyte.TryParse(text, out sb))
{
return sb;
}
break;
case TypeCode.Byte:
byte b;
if (byte.TryParse(text, out b))
{
return b;
}
break;
case TypeCode.Int16:
short s;
if (short.TryParse(text, out s))
{
return s;
}
break;
case TypeCode.Int32:
int i;
if (int.TryParse(text, out i))
{
return i;
}
break;
case TypeCode.Int64:
long l;
if (long.TryParse(text, out l))
{
return l;
}
break;
case TypeCode.Single:
float f;
if (float.TryParse(text, out f))
{
return f;
}
break;
case TypeCode.Double:
double d;
if (double.TryParse(text, out d))
{
return d;
}
break;
case TypeCode.Decimal:
decimal e;
if (decimal.TryParse(text, out e))
{
return e;
}
break;
}
return null;
}
/// Checks whether the source type is compatible with the value type.
/// Source type.
/// Target type.
/// true if source can be used in place of target; false otherwise.
private static bool IsCompatibleWith(Type source, Type target)
{
if (source == target)
{
return true;
}
if (!target.IsValueType)
{
return target.IsAssignableFrom(source);
}
Type sourceType = GetNonNullableType(source);
Type targetType = GetNonNullableType(target);
//// This rule stops the parser from considering nullable types as incompatible
//// with non-nullable types. We have however implemented this rule because we just
//// access underlying rules. C# requires an explicit .Value access, and EDM has no
//// nullablity on types and (at the model level) implements null propagation.
////
//// if (sourceType != source && targetType == target)
//// {
//// return false;
//// }
TypeCode sourceCode = sourceType.IsEnum ? TypeCode.Object : Type.GetTypeCode(sourceType);
TypeCode targetCode = targetType.IsEnum ? TypeCode.Object : Type.GetTypeCode(targetType);
switch (sourceCode)
{
case TypeCode.SByte:
switch (targetCode)
{
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Byte:
switch (targetCode)
{
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Int16:
switch (targetCode)
{
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Int32:
switch (targetCode)
{
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Int64:
switch (targetCode)
{
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
return true;
}
break;
case TypeCode.Single:
switch (targetCode)
{
case TypeCode.Single:
case TypeCode.Double:
return true;
}
break;
default:
if (sourceType == targetType)
{
return true;
}
break;
}
// Anything can be converted to something that's *exactly* an object.
#if ASTORIA_OPEN_OBJECT
if (target == typeof(object))
{
return true;
}
#endif
return false;
}
///
/// Checks whether one type list is a better fit than other for the
/// specified expressions.
///
/// Expressions for arguments.
/// First type list to check.
/// Second type list to check.
///
/// true if has better parameter matching than .
///
private static bool IsBetterThan(Expression[] args, IEnumerable firstCandidate, IEnumerable secondCandidate)
{
bool better = false;
using (IEnumerator first = firstCandidate.GetEnumerator())
using (IEnumerator second = secondCandidate.GetEnumerator())
{
for (int i = 0; i < args.Length; i++)
{
first.MoveNext();
second.MoveNext();
int c = CompareConversions(args[i].Type, first.Current, second.Current);
if (c < 0)
{
return false;
}
else if (c > 0)
{
better = true;
}
}
}
return better;
}
///
/// Checks whether one method is a better fit than other for the
/// specified expressions.
///
/// Expressions for arguments.
/// First method to check.
/// Second method to check.
///
/// true if has better parameter matching than .
///
private static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2)
{
Debug.Assert(args != null, "args != null");
Debug.Assert(m1 != null, "m1 != null");
Debug.Assert(m2 != null, "m2 != null");
return IsBetterThan(args, m1.ParameterTypes, m2.ParameterTypes);
}
/// Checks which conversion is better.
/// Source type.
/// First candidate type to convert to.
/// Second candidate type to convert to.
///
/// Return 1 if s -> t1 is a better conversion than s -> t2
/// Return -1 if s -> t2 is a better conversion than s -> t1
/// Return 0 if neither conversion is better
///
private static int CompareConversions(Type source, Type targetA, Type targetB)
{
// If both types are exactly the same, there is no preference.
if (targetA == targetB)
{
return 0;
}
// Look for exact matches.
if (source == targetA)
{
return 1;
}
else if (source == targetB)
{
return -1;
}
// If one is compatible and the other is not, choose the compatible type.
bool compatibleT1AndT2 = IsCompatibleWith(targetA, targetB);
bool compatibleT2AndT1 = IsCompatibleWith(targetB, targetA);
if (compatibleT1AndT2 && !compatibleT2AndT1)
{
return 1;
}
else if (compatibleT2AndT1 && !compatibleT1AndT2)
{
return -1;
}
// Prefer to keep the original nullability.
bool sourceNullable = IsNullableType(source);
bool typeNullableA = IsNullableType(targetA);
bool typeNullableB = IsNullableType(targetB);
if (sourceNullable == typeNullableA && sourceNullable != typeNullableB)
{
return 1;
}
else if (sourceNullable != typeNullableA && sourceNullable == typeNullableB)
{
return -1;
}
// Prefer signed to unsigned.
if (IsSignedIntegralType(targetA) && IsUnsignedIntegralType(targetB))
{
return 1;
}
else if (IsSignedIntegralType(targetB) && IsUnsignedIntegralType(targetA))
{
return -1;
}
// Prefer non-object to object.
if (targetA != typeof(object) && targetB == typeof(object))
{
return 1;
}
else if (targetB != typeof(object) && targetA == typeof(object))
{
return -1;
}
return 0;
}
/// Generates an Equal expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.EqualExpression(left, right);
}
#endif
if (left.Type == typeof(byte[]))
{
MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysEqual", BindingFlags.NonPublic | BindingFlags.Static);
return Expression.Equal(left, right, false, byteArrayCompareMethod);
}
return Expression.Equal(left, right);
}
/// Generates a NotEqual expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateNotEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.NotEqualExpression(left, right);
}
#endif
if (left.Type == typeof(byte[]))
{
MethodInfo byteArrayCompareMethod = typeof(ExpressionParser).GetMethod("ByteArraysNotEqual", BindingFlags.NonPublic | BindingFlags.Static);
return Expression.NotEqual(left, right, false, byteArrayCompareMethod);
}
return Expression.NotEqual(left, right);
}
/// Generates a GreaterThan comparison expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateGreaterThan(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.GreaterThanExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.GreaterThan(left, right, false, GetStringCompareMethod("StringGreaterThanMethod"));
}
return Expression.GreaterThan(left, right);
}
/// Generates a GreaterThanOrEqual comparsion expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateGreaterThanEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.GreaterThanOrEqualExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.GreaterThanOrEqual(left, right, false, GetStringCompareMethod("StringGreaterThanOrEqualMethod"));
}
return Expression.GreaterThanOrEqual(left, right);
}
/// Generates a LessThan comparsion expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateLessThan(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.LessThanExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.LessThan(left, right, false, GetStringCompareMethod("StringLessThanMethod"));
}
return Expression.LessThan(left, right);
}
/// Generates a LessThanOrEqual comparsion expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateLessThanEqual(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.LessThanOrEqualExpression(left, right);
}
#endif
if (left.Type == typeof(string))
{
return Expression.LessThanOrEqual(left, right, false, GetStringCompareMethod("StringLessThanOrEqualMethod"));
}
return Expression.LessThanOrEqual(left, right);
}
/// Generates an addition expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateAdd(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.AddExpression(left, right);
}
#endif
return Expression.Add(left, right);
}
/// Generates a subtract expression.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateSubtract(Expression left, Expression right)
{
#if ASTORIA_OPEN_OBJECT
if (IsOpenPropertyExpression(left) || IsOpenPropertyExpression(right))
{
return LateBoundMethods.SubtractExpression(left, right);
}
#endif
return Expression.Subtract(left, right);
}
/// Gets a static method to compare strings.
/// Name of method.
/// The for the method.
private static MethodInfo GetStringCompareMethod(string methodName)
{
Debug.Assert(methodName != null, "methodName != null");
MethodInfo result = typeof(ExpressionParser).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
Debug.Assert(result != null, "result != null");
return result;
}
#if ASTORIA_OPEN_OBJECT
///
/// Checks whether the specified is part of an open property expression.
///
/// Non-null to check.
/// true if is based on an open property; false otherwise.
private static bool IsOpenPropertyExpression(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
return expression != NullLiteral && expression.Type == typeof(object);
}
#endif
/// Checks whether the left string is less than or equal to the right string.
/// Left value.Right value.
/// true if the left string is less than or equal to the right string.
private static bool StringLessThanOrEqualMethod(string left, string right)
{
return string.CompareOrdinal(left, right) <= 0;
}
/// Checks whether the left string is less than the right string.
/// Left value.Right value.
/// true if the left string is less than the right string.
private static bool StringLessThanMethod(string left, string right)
{
return string.CompareOrdinal(left, right) < 0;
}
/// Checks whether the left string is greater than or equal to the right string.
/// Left value.Right value.
/// true if the left string is greater than or equal to the right string.
private static bool StringGreaterThanOrEqualMethod(string left, string right)
{
return string.CompareOrdinal(left, right) >= 0;
}
/// Checks whether the left string is greater than the right string.
/// Left value.Right value.
/// true if the left string is greater than the right string.
private static bool StringGreaterThanMethod(string left, string right)
{
return string.CompareOrdinal(left, right) > 0;
}
/// Gets a static method by name.
/// Name of method to get.
/// Left expression to resolve method from and to use as argument.
/// Right expression.
/// The method.
private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right)
{
return left.Type.GetMethod(methodName, new Type[] { left.Type, right.Type });
}
/// Generates a static method call.
/// Method name.
/// Left expression.
/// Right expression.
/// The generated expression.
private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right)
{
return Expression.Call(null, GetStaticMethod(methodName, left, right), new Expression[] { left, right });
}
/// Creates an exception for a parse error.
/// Message text.
/// A new Exception.
private static Exception ParseError(string message)
{
return DataServiceException.CreateSyntaxError(message);
}
/// Checks that the given token has the specified identifier.
/// Token to check
/// Identifier to check.
/// true if is an identifier with the specified text.
private static bool TokenIdentifierIs(Token token, string id)
{
return token.Id == TokenId.Identifier && String.Equals(id, token.Text, StringComparison.OrdinalIgnoreCase);
}
#region Parsing.
/// Handles a ?: operator - not supported.
/// The parsed expression.
private Expression ParseExpression()
{
this.RecurseEnter();
Expression expr = this.ParseLogicalOr();
this.RecurseLeave();
return expr;
}
/// Handles or operator.
/// The parsed expression.
private Expression ParseLogicalOr()
{
this.RecurseEnter();
Expression left = this.ParseLogicalAnd();
while (this.TokenIdentifierIs(ExpressionConstants.KeywordOr))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseLogicalAnd();
this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position);
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.OrElseExpression(left, right);
if (left == null)
{
throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text));
}
}
else
#endif
{
left = Expression.OrElse(left, right);
}
}
this.RecurseLeave();
return left;
}
/// Handles and operator.
/// The parsed expression.
private Expression ParseLogicalAnd()
{
this.RecurseEnter();
Expression left = this.ParseComparison();
while (this.TokenIdentifierIs(ExpressionConstants.KeywordAnd))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseComparison();
this.CheckAndPromoteOperands(typeof(OperationSignatures.ILogicalSignatures), op.Text, ref left, ref right, op.Position);
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.AndAlsoExpression(left, right);
if (left == null)
{
throw ParseError(Strings.RequestQueryParser_BooleanExpressionsExpectedFor(op.Text));
}
}
else
#endif
{
left = Expression.AndAlso(left, right);
}
}
this.RecurseLeave();
return left;
}
/// Handles eq, ne, lt, gt, le, ge operators.
/// The parsed expression.
private Expression ParseComparison()
{
this.RecurseEnter();
Expression left = this.ParseAdditive();
while (this.CurrentToken.IsComparisonOperator)
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseAdditive();
bool equality = op.IsEqualityOperator;
if (equality && !left.Type.IsValueType && !right.Type.IsValueType)
{
if (left.Type != right.Type)
{
if (IsNullConstant(left))
{
left = Expression.Constant(null, right.Type);
}
else if (IsNullConstant(right))
{
right = Expression.Constant(null, left.Type);
}
else if (left.Type.IsAssignableFrom(right.Type))
{
right = Expression.Convert(right, left.Type);
}
else if (right.Type.IsAssignableFrom(left.Type))
{
left = Expression.Convert(left, right.Type);
}
else
{
throw this.IncompatibleOperandsError(op.Text, left, right, op.Position);
}
}
}
else if (left == NullLiteral || right == NullLiteral)
{
if (!equality)
{
throw ParseError(
Strings.RequestQueryParser_NullOperatorUnsupported(op.Text, op.Position, this.lexer.ExpressionText));
}
// Because we don't have an explicit "is null" check, literal comparisons
// to null are special.
if (!TypeAllowsNull(left.Type))
{
left = Expression.Convert(left, typeof(Nullable<>).MakeGenericType(left.Type));
}
else if (!TypeAllowsNull(right.Type))
{
right = Expression.Convert(right, typeof(Nullable<>).MakeGenericType(right.Type));
}
}
else
{
// Enums should be checked here for promotion when supported, but they aren't in this version.
Debug.Assert(!IsEnumType(left.Type), "!IsEnumType(left.Type)");
Debug.Assert(!IsEnumType(right.Type), "!IsEnumType(right.Type)");
Type signatures = equality ? typeof(OperationSignatures.IEqualitySignatures) : typeof(OperationSignatures.IRelationalSignatures);
this.CheckAndPromoteOperands(signatures, op.Text, ref left, ref right, op.Position);
}
Debug.Assert(op.Id == TokenId.Identifier, "op.id == TokenId.Identifier");
switch (op.Text)
{
case ExpressionConstants.KeywordEqual:
left = GenerateEqual(left, right);
break;
case ExpressionConstants.KeywordNotEqual:
left = GenerateNotEqual(left, right);
break;
case ExpressionConstants.KeywordGreaterThan:
left = GenerateGreaterThan(left, right);
break;
case ExpressionConstants.KeywordGreaterThanOrEqual:
left = GenerateGreaterThanEqual(left, right);
break;
case ExpressionConstants.KeywordLessThan:
left = GenerateLessThan(left, right);
break;
case ExpressionConstants.KeywordLessThanOrEqual:
left = GenerateLessThanEqual(left, right);
break;
}
}
this.RecurseLeave();
return left;
}
/// Handles +, -, & operators (& for string concat, not supported).
/// The parsed expression.
private Expression ParseAdditive()
{
this.RecurseEnter();
Expression left = this.ParseMultiplicative();
while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordAdd) ||
this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordSub))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseMultiplicative();
if (op.IdentifierIs(ExpressionConstants.KeywordAdd))
{
this.CheckAndPromoteOperands(typeof(OperationSignatures.IAddSignatures), op.Text, ref left, ref right, op.Position);
left = GenerateAdd(left, right);
}
else
{
Debug.Assert(ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub), "ExpressionParser.TokenIdentifierIs(op, ExpressionConstants.KeywordSub)");
this.CheckAndPromoteOperands(typeof(OperationSignatures.ISubtractSignatures), op.Text, ref left, ref right, op.Position);
left = GenerateSubtract(left, right);
}
}
this.RecurseLeave();
return left;
}
/// Handles mul, div, mod operators.
/// The parsed expression.
private Expression ParseMultiplicative()
{
this.RecurseEnter();
Expression left = this.ParseUnary();
while (this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordMultiply) ||
this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordDivide) ||
this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordModulo))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
Expression right = this.ParseUnary();
this.CheckAndPromoteOperands(typeof(OperationSignatures.IArithmeticSignatures), op.Text, ref left, ref right, op.Position);
if (op.IdentifierIs(ExpressionConstants.KeywordMultiply))
{
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.MultiplyExpression(left, right);
}
else
#endif
{
left = Expression.Multiply(left, right);
}
}
else if (op.IdentifierIs(ExpressionConstants.KeywordDivide))
{
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.DivideExpression(left, right);
}
else
#endif
{
left = Expression.Divide(left, right);
}
}
else
{
Debug.Assert(op.IdentifierIs(ExpressionConstants.KeywordModulo), "op.IdentifierIs(ExpressionConstants.KeywordModulo)");
#if ASTORIA_OPEN_OBJECT
if (left.Type == typeof(object) || right.Type == typeof(object))
{
left = LateBoundMethods.ModuloExpression(left, right);
}
else
#endif
{
left = Expression.Modulo(left, right);
}
}
}
this.RecurseLeave();
return left;
}
/// Handles -, not unary operators.
/// The parsed expression.
private Expression ParseUnary()
{
this.RecurseEnter();
if (this.CurrentToken.Id == TokenId.Minus || this.CurrentToken.IdentifierIs(ExpressionConstants.KeywordNot))
{
Token op = this.CurrentToken;
this.lexer.NextToken();
if (op.Id == TokenId.Minus && (ExpressionLexer.IsNumeric(this.CurrentToken.Id)))
{
Token numberLiteral = this.CurrentToken;
numberLiteral.Text = "-" + numberLiteral.Text;
numberLiteral.Position = op.Position;
this.CurrentToken = numberLiteral;
this.RecurseLeave();
return this.ParsePrimary();
}
Expression expr = this.ParseUnary();
if (op.Id == TokenId.Minus)
{
this.CheckAndPromoteOperand(typeof(OperationSignatures.INegationSignatures), op.Text, ref expr, op.Position);
#if ASTORIA_OPEN_OBJECT
if (expr.Type == typeof(object))
{
expr = LateBoundMethods.NegateExpression(expr);
}
else
#endif
{
expr = Expression.Negate(expr);
}
}
else
{
this.CheckAndPromoteOperand(typeof(OperationSignatures.INotSignatures), op.Text, ref expr, op.Position);
#if ASTORIA_OPEN_OBJECT
if (expr.Type == typeof(object))
{
expr = LateBoundMethods.NotExpression(expr);
}
else
#endif
if (expr.Type == typeof(bool) || expr.Type == typeof(Nullable))
{
// Expression.Not will take numerics and apply '~' to them, thus the extra check here.
expr = Expression.Not(expr);
}
else
{
throw ParseError(Strings.RequestQueryParser_NotDoesNotSupportType(expr.Type));
}
}
this.RecurseLeave();
return expr;
}
this.RecurseLeave();
return this.ParsePrimary();
}
/// Handles primary expressions.
/// The parsed expression.
private Expression ParsePrimary()
{
this.RecurseEnter();
Expression expr = this.ParsePrimaryStart();
while (true)
{
if (this.CurrentToken.Id == TokenId.Slash)
{
this.lexer.NextToken();
expr = this.ParseMemberAccess(expr);
}
else
{
break;
}
}
this.RecurseLeave();
return expr;
}
/// Handles the start of primary expressions.
/// The parsed expression.
private Expression ParsePrimaryStart()
{
switch (this.CurrentToken.Id)
{
case TokenId.BooleanLiteral:
return this.ParseTypedLiteral(typeof(bool), XmlConstants.EdmBooleanTypeName);
case TokenId.DateTimeLiteral:
return this.ParseTypedLiteral(typeof(DateTime), XmlConstants.EdmDateTimeTypeName);
case TokenId.DecimalLiteral:
return this.ParseTypedLiteral(typeof(decimal), XmlConstants.EdmDecimalTypeName);
case TokenId.NullLiteral:
return this.ParseNullLiteral();
case TokenId.Identifier:
return this.ParseIdentifier();
case TokenId.StringLiteral:
return this.ParseTypedLiteral(typeof(string), XmlConstants.EdmStringTypeName);
case TokenId.Int64Literal:
return this.ParseTypedLiteral(typeof(Int64), XmlConstants.EdmInt64TypeName);
case TokenId.IntegerLiteral:
return this.ParseTypedLiteral(typeof(Int32), XmlConstants.EdmInt32TypeName);
case TokenId.DoubleLiteral:
return this.ParseTypedLiteral(typeof(double), XmlConstants.EdmDoubleTypeName);
case TokenId.SingleLiteral:
return this.ParseTypedLiteral(typeof(Single), XmlConstants.EdmSingleTypeName);
case TokenId.GuidLiteral:
return this.ParseTypedLiteral(typeof(Guid), XmlConstants.EdmGuidTypeName);
case TokenId.BinaryLiteral:
return this.ParseTypedLiteral(typeof(byte[]), XmlConstants.EdmBinaryTypeName);
case TokenId.OpenParen:
return this.ParseParenExpression();
default:
throw ParseError(Strings.RequestQueryParser_ExpressionExpected(this.CurrentToken.Position));
}
}
/// Handles parenthesized expressions.
/// The parsed expression.
private Expression ParseParenExpression()
{
if (this.CurrentToken.Id != TokenId.OpenParen)
{
throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
Expression e = this.ParseExpression();
if (this.CurrentToken.Id != TokenId.CloseParen)
{
throw ParseError(Strings.RequestQueryParser_CloseParenOrOperatorExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
return e;
}
/// Handles identifiers.
/// The parsed expression.
private Expression ParseIdentifier()
{
this.ValidateToken(TokenId.Identifier);
bool identifierIsFunction = this.lexer.PeekNextToken().Id == TokenId.OpenParen;
if (identifierIsFunction)
{
return this.ParseIdentifierAsFunction();
}
else
{
return this.ParseMemberAccess(this.it);
}
}
/// Handles identifiers which have been recognized as functions.
/// The parsed expression.
private Expression ParseIdentifierAsFunction()
{
FunctionDescription[] functionDescriptions;
Token functionToken = this.CurrentToken;
if (!functions.TryGetValue(functionToken.Text, out functionDescriptions))
{
throw ParseError(Strings.RequestQueryParser_UnknownFunction(functionToken.Text, functionToken.Position));
}
this.lexer.NextToken();
Expression[] originalArguments = this.ParseArgumentList();
Expression[] arguments = this.nullPropagationRequired ? originalArguments : (Expression[])originalArguments.Clone();
FunctionDescription function = this.FindBestFunction(functionDescriptions, ref arguments);
if (function == null)
{
string message = Strings.RequestQueryParser_NoApplicableFunction(
functionToken.Text,
functionToken.Position,
FunctionDescription.BuildSignatureList(functionToken.Text, functionDescriptions));
throw ParseError(message);
}
// Special case for null propagation - we never strip nullability from expressions.
if (this.nullPropagationRequired && function.IsTypeCast)
{
Expression typeExpression = arguments[arguments.Length - 1];
Debug.Assert(typeExpression != null, "typeExpression != null -- otherwise function finding failed.");
Debug.Assert(typeExpression.Type == typeof(Type), "typeExpression.Type == typeof(Type) -- last argument is always type.");
Type castTargetType = (Type)((ConstantExpression)typeExpression).Value;
if (!TypeAllowsNull(castTargetType))
{
arguments[arguments.Length - 1] = Expression.Constant(typeof(Nullable<>).MakeGenericType(castTargetType));
}
}
Expression result = function.ConversionFunction(this.it, arguments);
if (this.nullPropagationRequired && !function.IsTypeCheck && !function.IsTypeCast)
{
Debug.Assert(
originalArguments != arguments,
"originalArguments != arguments -- arguments should have been cloned");
Debug.Assert(
originalArguments.Length == arguments.Length,
"originalArguments.Length == arguments.Length -- arguments should not be added/removed");
for (int i = 0; i < originalArguments.Length; i++)
{
result = this.ConsiderNullPropagation(originalArguments[i], result);
}
}
return result;
}
/// Handles boolean literals.
/// The parsed expression.
private Expression ParseBooleanLiteral()
{
Debug.Assert(this.CurrentToken.Id == TokenId.BooleanLiteral, "this.CurrentToken.Id == TokenId.BooleanLiteral");
string originalText = this.CurrentToken.Text;
this.lexer.NextToken();
if (originalText == ExpressionConstants.KeywordTrue)
{
return trueLiteral;
}
else
{
Debug.Assert(originalText == ExpressionConstants.KeywordFalse, "originalText == ExpressionConstants.KeywordFalse");
return falseLiteral;
}
}
/// Handles typed literals.
/// Expected type to be parsed.
/// Expected type name.
/// The constants expression produced by building the given literal.
private Expression ParseTypedLiteral(Type targetType, string targetTypeName)
{
object targetValue;
if (!WebConvert.TryKeyStringToPrimitive(this.CurrentToken.Text, targetType, out targetValue))
{
string message = Strings.RequestQueryParser_UnrecognizedLiteral(targetTypeName, this.CurrentToken.Text, this.CurrentToken.Position);
throw ParseError(message);
}
Expression result = this.CreateLiteral(targetValue, this.CurrentToken.Text);
this.lexer.NextToken();
return result;
}
/// Handles 'null' literals.
/// The parsed expression.
private Expression ParseNullLiteral()
{
Debug.Assert(this.CurrentToken.Id == TokenId.NullLiteral, "this.CurrentToken.Id == TokenId.NullLiteral");
this.lexer.NextToken();
return NullLiteral;
}
/// Handles member access.
/// Instance being accessed.
/// The parsed expression.
private Expression ParseMemberAccess(Expression instance)
{
Debug.Assert(instance != null, "instance != null");
Type type = instance.Type;
int errorPos = this.lexer.Position;
string id = this.CurrentToken.GetIdentifier();
this.lexer.NextToken();
// An open paren here would indicate calling a method in regular C# syntax.
ResourceProperty property = this.FindProperty(type, id);
if (property != null)
{
// We have a strongly-type property.
Expression result = this.ConsiderNullPropagation(instance, Expression.Property(instance, property.PropertyInfo));
if (property.ResourceContainer != null)
{
Expression filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer);
if (filter != null)
{
// We did null propagation for accessing the property, but we may also need
// to do null propagation on the property value itself (otherwise the interception
// lambda needs to check the argument for null, which is probably unexpected).
result = RequestQueryProcessor.ComposePropertyNavigation(
result, (LambdaExpression)filter, this.provider.NullPropagationRequired);
}
}
return result;
}
else
{
// Open types can have any members inside them
#if ASTORIA_OPEN_OBJECT
if (type == typeof(object) || OpenTypeAttribute.IsOpenType(type))
{
// object LateBoundMethods.GetValue(object, string)
Expression call =
Expression.Call(null /* instance */, LateBoundMethods.GetValueMethodInfo, instance, Expression.Constant(id));
return this.ConsiderNullPropagation(instance, call);
}
else
#endif
{
throw ParseError(Strings.RequestQueryParser_UnknownProperty(id, this.GetTypeName(type), errorPos));
}
}
}
/// Handles argument lists.
/// The parsed expressions.
private Expression[] ParseArgumentList()
{
if (this.CurrentToken.Id != TokenId.OpenParen)
{
throw ParseError(Strings.RequestQueryParser_OpenParenExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
Expression[] args = this.CurrentToken.Id != TokenId.CloseParen ? this.ParseArguments() : emptyExpressions;
if (this.CurrentToken.Id != TokenId.CloseParen)
{
throw ParseError(Strings.RequestQueryParser_CloseParenOrCommaExpected(this.CurrentToken.Position));
}
this.lexer.NextToken();
return args;
}
/// Handles comma-separated arguments.
/// The parsed expressions.
private Expression[] ParseArguments()
{
List argList = new List();
while (true)
{
argList.Add(this.ParseExpression());
if (this.CurrentToken.Id != TokenId.Comma)
{
break;
}
this.lexer.NextToken();
}
return argList.ToArray();
}
#endregion Parsing.
/// Creates a constant expression with the specified literal.
/// Constant value.
/// Value text.
/// The created expression.
private Expression CreateLiteral(object value, string text)
{
ConstantExpression expr = Expression.Constant(value);
this.literals.Add(expr, text);
return expr;
}
/// Finds the best fitting function for the specified arguments.
/// Functions to consider.
/// Arguments; if a best function is found, promoted arguments.
/// The best fitting function; null if none found or ambiguous.
private FunctionDescription FindBestFunction(FunctionDescription[] functions, ref Expression[] arguments)
{
Debug.Assert(functions != null, "functions != null");
List applicableFunctions = new List(functions.Length);
List applicablePromotedArguments = new List(functions.Length);
// Build a list of applicable functions (and cache their promoted arguments).
foreach (FunctionDescription candidate in functions)
{
if (candidate.ParameterTypes.Length != arguments.Length)
{
continue;
}
Expression[] promotedArguments = new Expression[arguments.Length];
bool argumentsMatch = true;
for (int i = 0; i < candidate.ParameterTypes.Length; i++)
{
promotedArguments[i] = this.PromoteExpression(arguments[i], candidate.ParameterTypes[i], true);
if (promotedArguments[i] == null)
{
argumentsMatch = false;
break;
}
}
if (argumentsMatch)
{
applicableFunctions.Add(candidate);
applicablePromotedArguments.Add(promotedArguments);
}
}
// Return the best applicable function.
if (applicableFunctions.Count == 0)
{
// No matching function.
return null;
}
else if (applicableFunctions.Count == 1)
{
arguments = applicablePromotedArguments[0];
return applicableFunctions[0];
}
else
{
// Find a single function which is better than all others.
int bestFunctionIndex = -1;
for (int i = 0; i < applicableFunctions.Count; i++)
{
bool betterThanAllOthers = true;
for (int j = 0; j < applicableFunctions.Count; j++)
{
if (i != j && IsBetterThan(arguments, applicableFunctions[j].ParameterTypes, applicableFunctions[i].ParameterTypes))
{
betterThanAllOthers = false;
break;
}
}
if (betterThanAllOthers)
{
if (bestFunctionIndex == -1)
{
bestFunctionIndex = i;
}
else
{
// Ambiguous.
return null;
}
}
}
if (bestFunctionIndex == -1)
{
return null;
}
arguments = applicablePromotedArguments[bestFunctionIndex];
return applicableFunctions[bestFunctionIndex];
}
}
/// Rewrites an expression to propagate null values if necessary.
/// Expression to check for null.
/// Expression to yield if does not yield null.
/// The possibly rewriteen .
private Expression ConsiderNullPropagation(Expression element, Expression notNullExpression)
{
if (!this.nullPropagationRequired || element == this.it || !TypeAllowsNull(element.Type))
{
return notNullExpression;
}
// Tiny optimization: remove the check on constants which are known not to be null.
// Otherwise every string literal propagates out, which is correct but unnecessarily messy.
if (element is ConstantExpression && element != NullLiteral)
{
return notNullExpression;
}
// ifTrue and ifFalse expressions must match exactly. We need to ensure that the 'false'
// side is nullable, and the 'true' side is a null of the correct type.
Expression test = Expression.Equal(element, Expression.Constant(null, element.Type));
Expression falseIf = notNullExpression;
if (!TypeAllowsNull(falseIf.Type))
{
falseIf = Expression.Convert(falseIf, typeof(Nullable<>).MakeGenericType(falseIf.Type));
}
Expression trueIf = Expression.Constant(null, falseIf.Type);
return Expression.Condition(test, trueIf, falseIf);
}
/// Checks that the operand (possibly promoted) is valid for the specified operation.
/// Type with signatures to match.
/// Name of operation for error reporting.
/// Expression for operand.
/// Position for error reporting.
private void CheckAndPromoteOperand(Type signatures, string operationName, ref Expression expr, int errorPos)
{
Debug.Assert(signatures != null, "signatures != null");
Debug.Assert(operationName != null, "operationName != null");
Expression[] args = new Expression[] { expr };
MethodBase method;
if (this.FindMethod(signatures, "F", args, out method) != 1)
{
throw ParseError(Strings.RequestQueryParser_IncompatibleOperand(operationName, this.GetTypeName(args[0].Type), errorPos));
}
expr = args[0];
}
/// Checks that the operands (possibly promoted) are valid for the specified operation.
/// Type with signatures to match.
/// Name of operation for error reporting.
/// Expression for left operand.
/// Expression for right operand.
/// Position for error reporting.
private void CheckAndPromoteOperands(Type signatures, string operationName, ref Expression left, ref Expression right, int errorPos)
{
Expression[] args = new Expression[] { left, right };
MethodBase method;
if (this.FindMethod(signatures, "F", args, out method) != 1)
{
throw this.IncompatibleOperandsError(operationName, left, right, errorPos);
}
left = args[0];
right = args[1];
}
/// Finds a property in the specified type with the given name.
/// Type in which to look for member.
/// Member name.
/// The ResourceProperty for the specified type.
private ResourceProperty FindProperty(Type type, string memberName)
{
// This could also be a FindPropertyOrField method.
Debug.Assert(type != null, "type != null");
Debug.Assert(memberName != null, "memberName != null");
ResourceProperty property = this.provider.TryResolvePropertyName(type, memberName);
if (property != null)
{
if (property.ResourceContainer != null)
{
this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, true /* singleResult */);
}
return property;
}
else
{
return null;
}
}
/// Finds the named method in the specifid type.
/// Type to look in.
/// Name of method to look for.
/// Arguments to method.
/// Best method found.
/// Number of matching methods.
private int FindMethod(Type type, string methodName, Expression[] args, out MethodBase method)
{
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Instance;
foreach (Type t in SelfAndBaseTypes(type))
{
MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterName, methodName);
int count = this.FindBestMethod(members.Cast(), args, out method);
if (count != 0)
{
return count;
}
}
method = null;
return 0;
}
/// Finds all applicable methods from the specified enumeration that match the arguments.
/// Enumerable object of candidate methods.
/// Argument expressions.
/// Methods that apply to the specified arguments.
private MethodData[] FindApplicableMethods(IEnumerable methods, Expression[] args)
{
List result = new List();
foreach (MethodBase method in methods)
{
MethodData methodData = new MethodData(method, method.GetParameters());
if (this.IsApplicable(methodData, args))
{
result.Add(methodData);
}
}
return result.ToArray();
}
/// Finds the best methods for the specified arguments given a candidate method enumeration.
/// Enumerable object for candidate methods.
/// Argument expressions to match.
/// Best matched method.
/// The number of "best match" methods.
private int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method)
{
MethodData[] applicable = this.FindApplicableMethods(methods, args);
if (applicable.Length > 1)
{
applicable = FindBestApplicableMethods(applicable, args);
}
int result = applicable.Length;
method = null;
if (applicable.Length == 1)
{
// If we started off with all non-OpenType expressions and end with all-OpenType
// expressions, we've been too aggresive - the transition from non-open-types
// to open types should initially happen only as a result of accessing open properties.
#if ASTORIA_OPEN_OBJECT
MethodData md = applicable[0];
bool originalArgsDefined = true;
bool promotedArgsOpen = true;
for (int i = 0; i < args.Length; i++)
{
originalArgsDefined = originalArgsDefined && !IsOpenPropertyExpression(args[i]);
promotedArgsOpen = promotedArgsOpen && md.Parameters[i].ParameterType == typeof(object);
args[i] = md.Args[i];
}
method = (originalArgsDefined && promotedArgsOpen) ? null : md.MethodBase;
result = (method == null) ? 0 : 1;
#else
for (int i = 0; i < args.Length; i++)
{
args[i] = applicable[0].Args[i];
}
method = applicable[0].MethodBase;
result = 1;
#endif
}
else if (applicable.Length > 1)
{
// We may have the case for operators (which C# doesn't) in which we have a nullable operand
// and a non-nullable operand. We choose to convert the one non-null operand to nullable in that
// case (the binary expression will lift to null).
if (args.Length == 2 && applicable.Length == 2 &&
GetNonNullableType(applicable[0].Parameters[0].ParameterType) ==
GetNonNullableType(applicable[1].Parameters[0].ParameterType))
{
MethodData nullableMethod =
TypeAllowsNull(applicable[0].Parameters[0].ParameterType) ?
applicable[0] :
applicable[1];
args[0] = nullableMethod.Args[0];
args[1] = nullableMethod.Args[1];
return this.FindBestMethod(methods, args, out method);
}
}
return result;
}
/// Gets a name that can be used for error messages.
/// Type to get name for.
/// The name of the specified .
private string GetTypeName(Type type)
{
Debug.Assert(type != null, "type != null");
return WebUtil.GetTypeName(this.provider, type);
}
/// Creates an exception indicated that two operands are incompatible.
/// Name of operation for operands.
/// Expression for left-hand operand.
/// Expression for right-hand operand.
/// Position for error.
/// A new .
private Exception IncompatibleOperandsError(string operationName, Expression left, Expression right, int pos)
{
string message = Strings.RequestQueryParser_IncompatibleOperands(
operationName,
this.GetTypeName(left.Type),
this.GetTypeName(right.Type),
pos);
return ParseError(message);
}
/// Checks whether the specified method is applicable given the argument expressions.
/// Method to check.
/// Argument expressions.
/// true if the method is applicable; false otherwise.
private bool IsApplicable(MethodData method, Expression[] args)
{
if (method.Parameters.Length != args.Length)
{
return false;
}
Expression[] promotedArgs = new Expression[args.Length];
for (int i = 0; i < args.Length; i++)
{
ParameterInfo pi = method.Parameters[i];
Debug.Assert(!pi.IsOut, "!pi.IsOut");
Expression promoted = this.PromoteExpression(args[i], pi.ParameterType, false);
if (promoted == null)
{
return false;
}
promotedArgs[i] = promoted;
}
method.Args = promotedArgs;
return true;
}
/// Promotes the specified expression to the given type if necessary.
/// Expression to promote.
/// Type to change expression to.
/// Whether an exact type is required; false implies a compatible type is OK.
/// Expression with the promoted type.
private Expression PromoteExpression(Expression expr, Type type, bool exact)
{
Debug.Assert(expr != null, "expr != null");
Debug.Assert(type != null, "type != null");
if (expr.Type == type)
{
return expr;
}
ConstantExpression ce = expr as ConstantExpression;
if (ce != null)
{
if (ce == NullLiteral)
{
if (TypeAllowsNull(type))
{
return Expression.Constant(null, type);
}
}
else
{
string text;
if (this.literals.TryGetValue(ce, out text))
{
Type target = GetNonNullableType(type);
object value = null;
if (ce.Type == typeof(string) && target == typeof(Type))
{
if (WebConvert.TryRemoveQuotes(ref text))
{
ResourceType resourceType = this.provider.TryResolveTypeName(text);
if (resourceType != null)
{
value = resourceType.Type;
}
}
}
else
{
switch (Type.GetTypeCode(ce.Type))
{
case TypeCode.Int32:
case TypeCode.Int64:
value = ParseNumber(text, target);
break;
case TypeCode.Double:
if (target == typeof(decimal))
{
value = ParseNumber(text, target);
}
break;
}
}
if (value != null)
{
return Expression.Constant(value, type);
}
}
}
}
if (IsCompatibleWith(expr.Type, type))
{
if (type.IsValueType || exact)
{
return Expression.Convert(expr, type);
}
return expr;
}
// Allow promotion from nullable to non-nullable by directly accessing underlying value.
if (IsNullableType(expr.Type) && type.IsValueType)
{
Expression valueAccessExpression = Expression.Property(expr, "Value");
valueAccessExpression = this.PromoteExpression(valueAccessExpression, type, exact);
return valueAccessExpression;
}
return null;
}
/// Checks that the current token has the specified identifier.
/// Identifier to check.
/// true if the current token is an identifier with the specified text.
private bool TokenIdentifierIs(string id)
{
return this.CurrentToken.IdentifierIs(id);
}
/// Validates the current token is of the specified kind.
/// Expected token kind.
private void ValidateToken(TokenId t)
{
if (this.CurrentToken.Id != t)
{
throw ParseError(Strings.RequestQueryParser_SyntaxError(this.CurrentToken.Position));
}
}
#region Recursion control.
/// Marks the fact that a recursive method was entered, and checks that the depth is allowed.
private void RecurseEnter()
{
this.recursionDepth++;
Debug.Assert(this.recursionDepth <= RecursionLimit, "this.recursionDepth <= recursionLimit");
if (this.recursionDepth == RecursionLimit)
{
throw DataServiceException.CreateDeepRecursion(RecursionLimit);
}
}
/// Marks the fact that a recursive method is leaving..
private void RecurseLeave()
{
this.recursionDepth--;
Debug.Assert(0 <= this.recursionDepth, "0 <= this.recursionDepth");
Debug.Assert(this.recursionDepth < RecursionLimit, "this.recursionDepth < recursionLimit");
}
#endregion Recursion control.
/// Use this class to encapsulate method information.
[DebuggerDisplay("MethodData {methodBase}")]
private class MethodData
{
#region Private fields.
/// Described method.
private readonly MethodBase methodBase;
/// Parameters for method.
private readonly ParameterInfo[] parameters;
/// Argument expressions.
private Expression[] args;
#endregion Private fields.
#region Constructors.
/// Initializes a new instance.
/// Described method
/// Parameters for method.
public MethodData(MethodBase method, ParameterInfo[] parameters)
{
this.methodBase = method;
this.parameters = parameters;
}
#endregion Constructors.
#region Properties.
/// Argument expressions.
public Expression[] Args
{
get { return this.args; }
set { this.args = value; }
}
/// Described method.
public MethodBase MethodBase
{
get { return this.methodBase; }
}
/// Parameters for method.
public ParameterInfo[] Parameters
{
get { return this.parameters; }
}
/// Enumeration of parameter types.
public IEnumerable ParameterTypes
{
get
{
foreach (ParameterInfo parameter in this.Parameters)
{
yield return parameter.ParameterType;
}
}
}
#endregion Properties.
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- FactoryId.cs
- ConfigurationStrings.cs
- TextEditor.cs
- SecurityProtocolCorrelationState.cs
- ListControlDataBindingHandler.cs
- HttpContextWrapper.cs
- SchemaImporterExtensionsSection.cs
- UnmanagedMemoryStreamWrapper.cs
- Baml2006KnownTypes.cs
- ToolboxItemCollection.cs
- DefaultMergeHelper.cs
- ProxyWebPartManagerDesigner.cs
- RankException.cs
- QueueProcessor.cs
- SafeWaitHandle.cs
- DataGridViewRowHeaderCell.cs
- ErrorProvider.cs
- brushes.cs
- SoapIgnoreAttribute.cs
- SQLInt16Storage.cs
- ResourcePermissionBase.cs
- DigitalSignature.cs
- CompensatableSequenceActivity.cs
- CompilerTypeWithParams.cs
- cookie.cs
- UICuesEvent.cs
- XPathDocumentBuilder.cs
- ParameterToken.cs
- SectionXmlInfo.cs
- ConnectionPoolManager.cs
- DataGridColumnCollection.cs
- FilteredSchemaElementLookUpTable.cs
- ObservableDictionary.cs
- SendMailErrorEventArgs.cs
- SqlTriggerContext.cs
- MailWriter.cs
- CodeSnippetTypeMember.cs
- SqlExpressionNullability.cs
- ControlTemplate.cs
- WebPartHelpVerb.cs
- XmlSchemaAnnotation.cs
- _AcceptOverlappedAsyncResult.cs
- EditBehavior.cs
- VectorAnimation.cs
- XmlSchemaAll.cs
- TimerElapsedEvenArgs.cs
- FormClosingEvent.cs
- ObjectViewListener.cs
- OracleTimeSpan.cs
- SocketPermission.cs
- PolicyStatement.cs
- HMACSHA512.cs
- CqlBlock.cs
- NumberFormatInfo.cs
- SecurityIdentifierConverter.cs
- UnwrappedTypesXmlSerializerManager.cs
- assemblycache.cs
- TransactionManager.cs
- TraceData.cs
- ISAPIApplicationHost.cs
- IPeerNeighbor.cs
- HttpWebRequestElement.cs
- CompiledXpathExpr.cs
- Int32AnimationBase.cs
- DataFormats.cs
- WorkflowRuntimeEndpoint.cs
- InvalidCommandTreeException.cs
- PairComparer.cs
- XmlParserContext.cs
- LinqDataSourceDisposeEventArgs.cs
- BamlRecordHelper.cs
- SafeCoTaskMem.cs
- AuthenticationManager.cs
- HashHelpers.cs
- SerializableAttribute.cs
- FormsAuthenticationTicket.cs
- Focus.cs
- Encoding.cs
- HScrollProperties.cs
- XmlWrappingReader.cs
- ListBindingHelper.cs
- AddInActivator.cs
- ThreadExceptionDialog.cs
- FormsAuthenticationEventArgs.cs
- FileClassifier.cs
- CodeTypeOfExpression.cs
- ButtonBase.cs
- HtmlContainerControl.cs
- HttpCookiesSection.cs
- externdll.cs
- SynthesizerStateChangedEventArgs.cs
- DataGridBoolColumn.cs
- CalendarDateChangedEventArgs.cs
- XsltArgumentList.cs
- CapabilitiesAssignment.cs
- Timer.cs
- OutputCacheModule.cs
- ZipIOExtraFieldElement.cs
- IgnorePropertiesAttribute.cs
- DigitShape.cs