PageRenderTime 487ms CodeModel.GetById 42ms RepoModel.GetById 4ms app.codeStats 0ms

/Atlassian.Jira/Linq/JqlExpressionVisitor.cs

https://bitbucket.org/headspring/atlassian.net-sdk
C# | 358 lines | 292 code | 56 blank | 10 comment | 57 complexity | a96f9b96fbf1cff209a19709a12a2e56 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Linq.Expressions;
  6. using System.Reflection;
  7. namespace Atlassian.Jira.Linq
  8. {
  9. public class JqlExpressionVisitor: ExpressionVisitor, IJqlExpressionVisitor
  10. {
  11. private StringBuilder _jqlWhere;
  12. private StringBuilder _jqlOrderBy;
  13. private int? _maxResults;
  14. private int? _startAt;
  15. private bool _processCount;
  16. public string Jql
  17. {
  18. get
  19. {
  20. return _jqlWhere.ToString() + _jqlOrderBy.ToString();
  21. }
  22. }
  23. public int? MaxResults
  24. {
  25. get
  26. {
  27. return _maxResults;
  28. }
  29. }
  30. public bool ProcessCount
  31. {
  32. get
  33. {
  34. return _processCount;
  35. }
  36. }
  37. public int? StartAt
  38. {
  39. get
  40. {
  41. return _startAt;
  42. }
  43. }
  44. public JqlData Process(Expression expression)
  45. {
  46. expression = ExpressionEvaluator.PartialEval(expression);
  47. _jqlWhere = new StringBuilder();
  48. _jqlOrderBy = new StringBuilder();
  49. this._maxResults = null;
  50. this._processCount = false;
  51. this._startAt = 0;
  52. this.Visit(expression);
  53. return new JqlData { Expression = Jql, MaxResults = _maxResults, ProcessCount = _processCount };
  54. }
  55. private string GetFieldNameFromBinaryExpression(BinaryExpression expression)
  56. {
  57. PropertyInfo propertyInfo = null;
  58. if(TryGetPropertyInfoFromBinaryExpression(expression, out propertyInfo))
  59. {
  60. var attributes = propertyInfo.GetCustomAttributes(typeof(JqlFieldNameAttribute), true);
  61. if (attributes.Count() > 0)
  62. {
  63. return ((JqlFieldNameAttribute)attributes[0]).Name;
  64. }
  65. else
  66. {
  67. return propertyInfo.Name;
  68. }
  69. }
  70. var methodCallExpression = expression.Left as MethodCallExpression;
  71. if (methodCallExpression != null)
  72. {
  73. return String.Format("\"{0}\"", ((ConstantExpression)methodCallExpression.Arguments[0]).Value);
  74. }
  75. throw new NotSupportedException(String.Format(
  76. "Operator '{0}' can only be applied on properties and property indexers.",
  77. expression.NodeType));
  78. }
  79. private bool TryGetPropertyInfoFromBinaryExpression(BinaryExpression expression, out PropertyInfo propertyInfo)
  80. {
  81. var memberExpression = expression.Left as MemberExpression;
  82. if (memberExpression != null)
  83. {
  84. propertyInfo = memberExpression.Member as PropertyInfo;
  85. if (propertyInfo != null)
  86. {
  87. return true;
  88. }
  89. }
  90. propertyInfo = null;
  91. return false;
  92. }
  93. private object GetFieldValueFromBinaryExpression(BinaryExpression expression)
  94. {
  95. if (expression.Right.NodeType == ExpressionType.Constant)
  96. {
  97. return ((ConstantExpression)expression.Right).Value;
  98. }
  99. else if (expression.Right.NodeType == ExpressionType.New)
  100. {
  101. var newExpression = (NewExpression)expression.Right;
  102. var args = new List<object>();
  103. foreach (ConstantExpression e in newExpression.Arguments)
  104. {
  105. args.Add(e.Value);
  106. }
  107. return newExpression.Constructor.Invoke(args.ToArray());
  108. }
  109. throw new NotSupportedException(String.Format(
  110. "Operator '{0}' can only be used with constant values.",
  111. expression.NodeType));
  112. }
  113. private void ProcessGreaterAndLessThanOperator(BinaryExpression expression, string operatorString)
  114. {
  115. var fieldName = GetFieldNameFromBinaryExpression(expression);
  116. var value = GetFieldValueFromBinaryExpression(expression);
  117. // field
  118. _jqlWhere.Append(fieldName);
  119. // operator
  120. _jqlWhere.Append(String.Format(" {0} ", operatorString));
  121. // value
  122. ProcessConstant(value);
  123. }
  124. private void ProcessEqualityOperator(BinaryExpression expression, bool equal)
  125. {
  126. if (expression.Left is MemberExpression)
  127. {
  128. ProcessMemberEqualityOperator(expression, equal);
  129. }
  130. else if (expression.Left is MethodCallExpression)
  131. {
  132. ProcessIndexedMemberEqualityOperator(expression, equal);
  133. }
  134. }
  135. private void ProcessIndexedMemberEqualityOperator(BinaryExpression expression, bool equal)
  136. {
  137. var methodExpression = expression.Left as MethodCallExpression;
  138. var fieldName = GetFieldNameFromBinaryExpression(expression);
  139. var fieldValue = GetFieldValueFromBinaryExpression(expression);
  140. // field
  141. _jqlWhere.Append(fieldName);
  142. // operator
  143. var operatorString = String.Empty;
  144. if(typeof(string).Equals(fieldValue.GetType()))
  145. {
  146. operatorString = equal? JiraOperators.CONTAINS: JiraOperators.NOTCONTAINS;
  147. }
  148. else
  149. {
  150. operatorString = equal? JiraOperators.EQUALS: JiraOperators.NOTEQUALS;
  151. }
  152. _jqlWhere.Append(String.Format(" {0} ", operatorString));
  153. // value
  154. ProcessConstant(GetFieldValueFromBinaryExpression(expression));
  155. }
  156. private void ProcessMemberEqualityOperator(BinaryExpression expression, bool equal)
  157. {
  158. var field = GetFieldNameFromBinaryExpression(expression);
  159. var value = GetFieldValueFromBinaryExpression(expression);
  160. // field
  161. _jqlWhere.Append(field);
  162. // special cases for empty/null string
  163. if (value == null || value.Equals(""))
  164. {
  165. _jqlWhere.Append(" ");
  166. _jqlWhere.Append(equal? JiraOperators.IS : JiraOperators.ISNOT);
  167. _jqlWhere.Append(" ");
  168. _jqlWhere.Append(value == null ? "null" : "empty");
  169. return;
  170. }
  171. // operator
  172. var operatorString = String.Empty;
  173. PropertyInfo propertyInfo = null;
  174. if(TryGetPropertyInfoFromBinaryExpression(expression, out propertyInfo)
  175. && propertyInfo.GetCustomAttributes(typeof(JqlContainsEqualityAttribute), true).Count() > 0)
  176. {
  177. operatorString = equal? JiraOperators.CONTAINS: JiraOperators.NOTCONTAINS;
  178. }
  179. else
  180. {
  181. operatorString = equal? JiraOperators.EQUALS: JiraOperators.NOTEQUALS;
  182. }
  183. _jqlWhere.Append(String.Format(" {0} ", operatorString));
  184. // value
  185. ProcessConstant(value);
  186. }
  187. private void ProcessConstant(object value)
  188. {
  189. var valueType = value.GetType();
  190. if (valueType == typeof(String)
  191. || valueType == typeof(ComparableString))
  192. {
  193. _jqlWhere.Append(String.Format("\"{0}\"", value));
  194. }
  195. else if (valueType == typeof(DateTime))
  196. {
  197. _jqlWhere.Append(String.Format("\"{0}\"", ((DateTime)value).ToString("yyyy/MM/dd")));
  198. }
  199. else
  200. {
  201. _jqlWhere.Append(value);
  202. }
  203. }
  204. private void ProcessUnionOperator(BinaryExpression expression, string operatorString)
  205. {
  206. _jqlWhere.Append("(");
  207. Visit(expression.Left);
  208. _jqlWhere.Append(" " + operatorString + " ");
  209. Visit(expression.Right);
  210. _jqlWhere.Append(")");
  211. }
  212. protected override Expression VisitMethodCall(MethodCallExpression node)
  213. {
  214. if (node.Method.Name == "OrderBy"
  215. || node.Method.Name == "OrderByDescending"
  216. || node.Method.Name == "ThenBy"
  217. || node.Method.Name == "ThenByDescending")
  218. {
  219. ProcessOrderBy(node);
  220. }
  221. else if (node.Method.Name == "Take")
  222. {
  223. ProcessTake(node);
  224. }
  225. else if (node.Method.Name == "Skip")
  226. {
  227. ProcessSkip(node);
  228. }
  229. else if (node.Method.Name == "Count")
  230. {
  231. _processCount = true;
  232. }
  233. return base.VisitMethodCall(node) ;
  234. }
  235. private void ProcessTake(MethodCallExpression node)
  236. {
  237. _maxResults = int.Parse(((ConstantExpression)node.Arguments[1]).Value.ToString());
  238. }
  239. private void ProcessSkip(MethodCallExpression node)
  240. {
  241. _startAt = int.Parse(((ConstantExpression)node.Arguments[1]).Value.ToString());
  242. }
  243. private void ProcessOrderBy(MethodCallExpression node)
  244. {
  245. var firstOrderBy = _jqlOrderBy.Length == 0;
  246. if (firstOrderBy)
  247. {
  248. _jqlOrderBy.Append(" order by ");
  249. }
  250. var member = ((LambdaExpression)((UnaryExpression)node.Arguments[1]).Operand).Body as MemberExpression;
  251. if (member != null)
  252. {
  253. if (firstOrderBy)
  254. {
  255. _jqlOrderBy.Append(member.Member.Name);
  256. }
  257. else
  258. {
  259. _jqlOrderBy.Insert(10, member.Member.Name + ", ");
  260. }
  261. }
  262. if (node.Method.Name == "OrderByDescending"
  263. || node.Method.Name == "ThenByDescending")
  264. {
  265. _jqlOrderBy.Append(" desc");
  266. }
  267. }
  268. protected override Expression VisitBinary(BinaryExpression node)
  269. {
  270. switch (node.NodeType)
  271. {
  272. case ExpressionType.GreaterThan:
  273. ProcessGreaterAndLessThanOperator(node, JiraOperators.GREATERTHAN);
  274. break;
  275. case ExpressionType.GreaterThanOrEqual:
  276. ProcessGreaterAndLessThanOperator(node, JiraOperators.GREATERTHANOREQUALS);
  277. break;
  278. case ExpressionType.LessThan:
  279. ProcessGreaterAndLessThanOperator(node, JiraOperators.LESSTHAN);
  280. break;
  281. case ExpressionType.LessThanOrEqual:
  282. ProcessGreaterAndLessThanOperator(node, JiraOperators.LESSTHANOREQUALS);
  283. break;
  284. case ExpressionType.Equal:
  285. ProcessEqualityOperator(node, true);
  286. break;
  287. case ExpressionType.NotEqual:
  288. ProcessEqualityOperator(node, false);
  289. break;
  290. case ExpressionType.AndAlso:
  291. ProcessUnionOperator(node, JiraOperators.AND);
  292. break;
  293. case ExpressionType.OrElse:
  294. ProcessUnionOperator(node, JiraOperators.OR);
  295. break;
  296. default:
  297. throw new NotSupportedException(String.Format("Expression type '{0}' is not supported.", node.NodeType));
  298. }
  299. return node;
  300. }
  301. }
  302. }