PageRenderTime 25ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/Main/Source/Crystalbyte.Equinox.Imap/Linq/ImapFetchQueryTranslator.cs

#
C# | 287 lines | 195 code | 49 blank | 43 comment | 14 complexity | 3d8a8b298c54e55be6db9b20fa58eadd MD5 | raw file
  1. #region Microsoft Public License (Ms-PL)
  2. // // Microsoft Public License (Ms-PL)
  3. // //
  4. // // This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software.
  5. // //
  6. // // 1. Definitions
  7. // //
  8. // // The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
  9. // //
  10. // // A "contribution" is the original software, or any additions or changes to the software.
  11. // //
  12. // // A "contributor" is any person that distributes its contribution under this license.
  13. // //
  14. // // "Licensed patents" are a contributor's patent claims that read directly on its contribution.
  15. // //
  16. // // 2. Grant of Rights
  17. // //
  18. // // (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
  19. // //
  20. // // (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
  21. // //
  22. // // 3. Conditions and Limitations
  23. // //
  24. // // (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
  25. // //
  26. // // (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
  27. // //
  28. // // (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
  29. // //
  30. // // (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
  31. // //
  32. // // (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
  33. #endregion
  34. using System;
  35. using System.Collections.Generic;
  36. using System.IO;
  37. using System.Linq;
  38. using System.Linq.Expressions;
  39. using Crystalbyte.Equinox.Imap.Processing;
  40. using Crystalbyte.Equinox.IO;
  41. using Crystalbyte.Equinox.Text;
  42. namespace Crystalbyte.Equinox.Imap.Linq
  43. {
  44. /// <summary>
  45. /// This class creates the fetch command string from a LINQ expression tree.
  46. /// In addition it generates a surrogate expression tree while traversing
  47. /// the original that will bind the responses to the given properties once all data has been received.
  48. /// </summary>
  49. internal sealed class ImapFetchQueryTranslator : ExpressionVisitor
  50. {
  51. private readonly List<string> _headerRequests = new List<string>();
  52. private readonly ResponseProcessor _processor = new ResponseProcessor();
  53. private readonly StringWriter _writer = new StringWriter();
  54. private bool _isSingleMemberAccess;
  55. public bool IsUid { get; set; }
  56. public bool UsePeek { get; set; }
  57. private void AppendFetchPrefix(string messageSet)
  58. {
  59. if (IsUid) {
  60. AppendWord(SearchCommands.Uid);
  61. }
  62. AppendWord("FETCH");
  63. AppendWord(messageSet);
  64. AppendChar('(');
  65. }
  66. private void AppendFetchPostfix()
  67. {
  68. _writer.TrimEnd(Characters.Space);
  69. AppendChar(')');
  70. }
  71. public string Translate(LambdaExpression expression, string messageSet, out ResponseProcessor info)
  72. {
  73. info = _processor;
  74. DetermineSelectionType(expression);
  75. AppendFetchPrefix(messageSet);
  76. AppendFetchBody(expression);
  77. AppendFetchPostfix();
  78. if (_isSingleMemberAccess) {
  79. CreateSingleMemberAccessSurrogate(expression);
  80. }
  81. return _writer.ToString();
  82. }
  83. private void CreateSingleMemberAccessSurrogate(LambdaExpression expression)
  84. {
  85. var returnType = expression.ReturnType;
  86. var identifier = _processor.Items.First().Identifier;
  87. var dataAccess = CreateSurrogateExpression(returnType, identifier);
  88. var lambda = Expression.Lambda(dataAccess, Expression.Parameter(typeof (IMessageQueryable)));
  89. _processor.Expression = lambda;
  90. }
  91. private void DetermineSelectionType(LambdaExpression expression)
  92. {
  93. _isSingleMemberAccess = expression.Body.NodeType != ExpressionType.MemberInit;
  94. }
  95. private void AppendFetchBody(Expression expression)
  96. {
  97. var surrogate = Visit(expression);
  98. StoreReformedExpression(surrogate);
  99. if (_headerRequests.Count > 0) {
  100. CompressAndAppendHeaders();
  101. }
  102. _writer.TrimEnd(Characters.Space);
  103. }
  104. private void StoreReformedExpression(Expression surrogate)
  105. {
  106. _processor.Expression = surrogate;
  107. }
  108. private void CompressAndAppendHeaders()
  109. {
  110. var headers = FormatHeaderFieldsCommand(_headerRequests);
  111. var envelope = FormatBodyCommand(headers, UsePeek);
  112. AppendWord(envelope);
  113. }
  114. protected override Expression VisitMethodCall(MethodCallExpression m)
  115. {
  116. if (m.Method.Name == MemberNames.Parts) {
  117. var constant = (ConstantExpression) m.Arguments[0];
  118. var dynamic = constant.Value as string;
  119. var item = CreateResponseItemFromFetchToken(dynamic);
  120. StoreResponseItem(item);
  121. if (!string.IsNullOrEmpty(dynamic)) {
  122. dynamic = dynamic.ToUpper();
  123. }
  124. var command = FormatBodyCommand(dynamic, UsePeek);
  125. AppendWord(command);
  126. }
  127. return base.VisitMethodCall(m);
  128. }
  129. private static ResponseItem CreateResponseItemFromFetchToken(string token)
  130. {
  131. token = token.ToUpper();
  132. var command = FormatBodyResponseIdentifier(token);
  133. var item = new ResponseItem {Identifier = command};
  134. if (token.EndsWith("HEADER") || token.EndsWith("MIME")) {
  135. item.SectionParser = new HeaderCollectionParser();
  136. } else {
  137. item.SectionParser = new TextParser();
  138. }
  139. item.SectionReader = new DynamicSectionReader(command);
  140. return item;
  141. }
  142. protected override MemberBinding VisitBinding(MemberBinding binding)
  143. {
  144. // we need to visit the original expression tree first in order to collect all
  145. // necessary informations
  146. base.VisitBinding(binding);
  147. // now we replace the original member binding for our
  148. // GetValues call to the ResponseCatalogue
  149. if (binding is MemberAssignment) {
  150. var assignment = (MemberAssignment) binding;
  151. var type = assignment.Expression.Type;
  152. var identifier = _processor.Items.Last().Identifier;
  153. var expression = CreateSurrogateExpression(type, identifier);
  154. var surrogate = assignment.Update(expression);
  155. return surrogate;
  156. }
  157. throw new NotSupportedException();
  158. }
  159. private Expression CreateSurrogateExpression(Type returnType, string identifier)
  160. {
  161. var methodInfo = typeof (ResponseCatalogue).GetMethod("GetValue");
  162. var expression = Expression.Convert(Expression.Call(Expression.Constant(_processor.Catalogue), methodInfo, Expression.Constant(identifier)), returnType);
  163. return expression;
  164. }
  165. protected override Expression VisitMemberAccess(MemberExpression m)
  166. {
  167. var fetchable = GetFetchableAttribute(m);
  168. if (fetchable.IsHeader) {
  169. // header commands will be merged to reduce net traffic
  170. StoreHeaderRequest(fetchable.Identifier);
  171. } else {
  172. // the sequence number must not be requested, it always comes whether we want or not.
  173. if (!fetchable.IsSequenceNumber) {
  174. // other commands can't be merged, therefor they can be added without delay.
  175. AppendWord(fetchable.Identifier);
  176. }
  177. }
  178. StoreResponseItem(fetchable);
  179. return base.VisitMemberAccess(m);
  180. }
  181. private void StoreHeaderRequest(string name)
  182. {
  183. _headerRequests.Add(name);
  184. }
  185. private void StoreResponseItem(ResponseItem item)
  186. {
  187. _processor.Items.Add(item);
  188. }
  189. private void StoreResponseItem(FetchableAttribute fetchable, string dynamicIdentifier = "")
  190. {
  191. var reader = Activator.CreateInstance(fetchable.ReaderType) as ISectionReader;
  192. var parser = Activator.CreateInstance(fetchable.ParserType) as ISectionParser;
  193. var identifier = string.IsNullOrEmpty(dynamicIdentifier) ? fetchable.Identifier : dynamicIdentifier;
  194. var item = new ResponseItem {Identifier = identifier, IsInline = fetchable.IsInline, SectionParser = parser, SectionReader = reader};
  195. StoreResponseItem(item);
  196. }
  197. private static FetchableAttribute GetFetchableAttribute(MemberExpression m)
  198. {
  199. var type = m.Expression.Type;
  200. var property = type.GetProperty(m.Member.Name);
  201. return (FetchableAttribute) (property.GetCustomAttributes(typeof (FetchableAttribute), true)[0]);
  202. }
  203. private void AppendChar(char text)
  204. {
  205. _writer.Write(text);
  206. }
  207. private void AppendText(string text)
  208. {
  209. _writer.Write(text);
  210. }
  211. private void AppendWord(string word)
  212. {
  213. AppendText(word);
  214. _writer.Write(Characters.Space);
  215. }
  216. private static string FormatBodyCommand(string value, bool usePeek)
  217. {
  218. return string.Format(usePeek ? "BODY.PEEK[{0}]" : "BODY[{0}]", value);
  219. }
  220. private static string FormatBodyResponseIdentifier(string value)
  221. {
  222. return string.Format("BODY[{0}]", value);
  223. }
  224. private static string FormatHeaderFieldsCommand(IEnumerable<string> headers)
  225. {
  226. using (var sw = new StringWriter()) {
  227. sw.Write("HEADER.FIELDS (");
  228. foreach (var header in headers) {
  229. sw.Write(header);
  230. sw.Write(Characters.Space);
  231. }
  232. sw.TrimEnd(Characters.Space);
  233. sw.Write(")");
  234. return sw.ToString();
  235. }
  236. }
  237. }
  238. }