Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / Span.cs
1 //---------------------------------------------------------------------
2 // <copyright file="Span.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Objects
11 {
12     using System.Collections.Generic;
13     using System.Data.Common.Internal;
14     using System.Diagnostics;
15     using System.Text;
16
17     /// <summary>
18     /// A collection of paths to determine which entities are spanned into a query.
19     /// </summary>
20     internal sealed class Span
21     {
22         private List<SpanPath> _spanList;
23         private string _cacheKey;
24
25         internal Span()
26         {
27             _spanList = new List<SpanPath>();
28         }
29
30         /// <summary>
31         /// The list of paths that should be spanned into the query
32         /// </summary>
33         internal List<SpanPath> SpanList
34         {
35             get { return _spanList; }
36         }
37
38         /// <summary>
39         /// Checks whether relationship span needs to be performed. Currently this is only when the query is
40         /// not using MergeOption.NoTracking.
41         /// </summary>
42         /// <param name="mergeOption"></param>
43         /// <returns>True if the query needs a relationship span rewrite</returns>
44         internal static bool RequiresRelationshipSpan(MergeOption mergeOption)
45         {
46             return (mergeOption != MergeOption.NoTracking);
47         }
48
49         /// <summary>
50         /// Includes the specified span path in the specified span instance and returns the updated span instance.
51         /// If <paramref name="spanToIncludeIn"/> is null, a new span instance is constructed and returned that contains
52         /// the specified include path.
53         /// </summary>
54         /// <param name="spanToIncludeIn">The span instance to which the include path should be added. May be null</param>
55         /// <param name="pathToInclude">The include path to add</param>
56         /// <returns>A non-null span instance that contains the specified include path in addition to any paths ut already contained</returns>
57         internal static Span IncludeIn(Span spanToIncludeIn, string pathToInclude)
58         {
59             if (null == spanToIncludeIn)
60             {
61                 spanToIncludeIn = new Span();
62             }
63
64             spanToIncludeIn.Include(pathToInclude);
65             return spanToIncludeIn;
66         }
67
68         /// <summary>
69         /// Returns a span instance that is the union of the two specified span instances.
70         /// If <paramref name="span1"/> and <paramref name="span2"/> are both <c>null</c>,
71         /// then <c>null</c> is returned.
72         /// If <paramref name="span1"/> or <paramref name="span2"/> is null, but the remaining argument is non-null,
73         /// then the non-null argument is returned.
74         /// If neither <paramref name="span1"/> nor <paramref name="span2"/> are null, a new span instance is returned
75         /// that contains the merged span paths from both.
76         /// </summary>
77         /// <param name="span1">The first span instance from which to include span paths; may be <c>null</c></param>
78         /// <param name="span2">The second span instance from which to include span paths; may be <c>null</c></param>
79         /// <returns>A span instance representing the union of the two arguments; may be <c>null</c> if both arguments are null</returns>
80         internal static Span CopyUnion(Span span1, Span span2)
81         {
82             if (null == span1)
83             {
84                 return span2;
85             }
86
87             if (null == span2)
88             {
89                 return span1;
90             }
91
92             Span retSpan = span1.Clone();
93             foreach (SpanPath path in span2.SpanList)
94             {
95                 retSpan.AddSpanPath(path);
96             }
97
98             return retSpan;
99         }
100
101         internal string GetCacheKey()
102         {
103             if (null == _cacheKey)
104             {
105                 if (_spanList.Count > 0)
106                 {
107                     // If there is only a single Include path with a single property,
108                     // then simply use the property name as the cache key rather than
109                     // creating any new strings.
110                     if (_spanList.Count == 1 &&
111                        _spanList[0].Navigations.Count == 1)
112                     {
113                         _cacheKey = _spanList[0].Navigations[0];
114                     }
115                     else
116                     {
117                         StringBuilder keyBuilder = new StringBuilder();
118                         for (int pathIdx = 0; pathIdx < _spanList.Count; pathIdx++)
119                         {
120                             if (pathIdx > 0)
121                             {
122                                 keyBuilder.Append(";");
123                             }
124
125                             SpanPath thisPath = _spanList[pathIdx];
126                             keyBuilder.Append(thisPath.Navigations[0]);
127                             for (int propIdx = 1; propIdx < thisPath.Navigations.Count; propIdx++)
128                             {
129                                 keyBuilder.Append(".");
130                                 keyBuilder.Append(thisPath.Navigations[propIdx]);
131                             }
132                         }
133
134                         _cacheKey = keyBuilder.ToString();
135                     }
136                 }
137             }
138
139             return _cacheKey;
140         }
141
142         /// <summary>
143         /// Adds a path to span into the query.
144         /// </summary>
145         /// <param name="path">The path to span</param>
146         public void Include(string path)
147         {
148             EntityUtil.CheckStringArgument(path, "path");
149             if (path.Trim().Length == 0)
150             {
151                 throw new ArgumentException(System.Data.Entity.Strings.ObjectQuery_Span_WhiteSpacePath, "path");
152             }
153             
154             SpanPath spanPath = new SpanPath(ParsePath(path));
155             AddSpanPath(spanPath);
156             _cacheKey = null;
157         }
158
159         /// <summary>
160         /// Creates a new Span with the same SpanPaths as this Span
161         /// </summary>
162         /// <returns></returns>
163         internal Span Clone()
164         {
165             Span newSpan = new Span();
166             newSpan.SpanList.AddRange(_spanList);
167             newSpan._cacheKey = this._cacheKey;
168
169             return newSpan;
170         }
171
172         /// <summary>
173         /// Adds the path if it does not already exist
174         /// </summary>
175         /// <param name="spanPath"></param>
176         internal void AddSpanPath(SpanPath spanPath)
177         {
178             if (ValidateSpanPath(spanPath))
179             {
180                 RemoveExistingSubPaths(spanPath);
181                 _spanList.Add(spanPath);
182             }
183         }
184
185         /// <summary>
186         /// Returns true if the path can be added
187         /// </summary>
188         /// <param name="spanPath"></param>
189         private bool ValidateSpanPath(SpanPath spanPath)
190         {
191
192             // Check for dupliacte entries
193             for (int i = 0; i < _spanList.Count; i++)
194             { 
195                 // make sure spanPath is not a sub-path of anything already in the list
196                 if (spanPath.IsSubPath(_spanList[i]))
197                 {
198                     return false;
199                 }
200             }
201             return true;
202         }
203
204         private void RemoveExistingSubPaths(SpanPath spanPath)
205         {
206             List<SpanPath> toDelete = new List<SpanPath>();
207             for (int i = 0; i < _spanList.Count; i++)
208             {
209                 // make sure spanPath is not a sub-path of anything already in the list
210                 if (_spanList[i].IsSubPath(spanPath))
211                 {
212                     toDelete.Add(_spanList[i]);
213                 }
214             }
215
216             foreach (SpanPath path in toDelete)
217             {
218                 _spanList.Remove(path);
219             }
220         }
221
222         /// <summary>
223         /// Storage for a span path
224         /// Currently this includes the list of navigation properties
225         /// </summary>
226         internal class SpanPath
227         {
228             public readonly List<string> Navigations;
229
230             public SpanPath(List<string> navigations)
231             {
232                 Navigations = navigations;
233             }
234
235             public bool IsSubPath(SpanPath rhs)
236             {
237                 // this is a subpath of rhs if it has fewer paths, and all the path element values are equal
238                 if (Navigations.Count > rhs.Navigations.Count)
239                 {
240                     return false;
241                 }
242
243                 for (int i = 0; i < Navigations.Count; i++)
244                 {
245                     if (!Navigations[i].Equals(rhs.Navigations[i], StringComparison.OrdinalIgnoreCase))
246                     {
247                         return false;
248                     }
249                 }
250
251                 return true;
252             }
253         }
254
255         private static List<string> ParsePath(string path)
256         {
257             List<string> navigations = MultipartIdentifier.ParseMultipartIdentifier(path, "[", "]", '.');
258
259             for (int i = navigations.Count - 1; i >= 0; i--)
260             {
261                 if (navigations[i] == null)
262                 {
263                     navigations.RemoveAt(i);
264                 }
265                 else if (navigations[i].Length == 0)
266                 {
267                     throw EntityUtil.SpanPathSyntaxError();
268                 }
269             }
270
271             Debug.Assert(navigations.Count > 0, "Empty path found");
272             return navigations;
273         }
274     
275     }
276 }