Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Common / CommandTrees / ExpressionBuilder / Internal / EnumerableValidator.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EnumerableValidator.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Common.CommandTrees.ExpressionBuilder.Internal
11 {
12     using System.Collections.Generic;
13     using System.Data.Common.Utils;
14     using System.Diagnostics;
15
16     /// <summary>
17     /// Validates an input enumerable argument with a specific element type,
18     /// converting each input element into an instance of a specific output element type,
19     /// then producing a final result of another specific type.
20     /// </summary>
21     /// <typeparam name="TElementIn">The element type of the input enumerable</typeparam>
22     /// <typeparam name="TElementOut">The element type that input elements are converted to</typeparam>
23     /// <typeparam name="TResult">The type of the final result</typeparam>
24     internal sealed class EnumerableValidator<TElementIn, TElementOut, TResult>
25     {
26         private readonly string argumentName;
27         private readonly IEnumerable<TElementIn> target;
28
29         internal EnumerableValidator(IEnumerable<TElementIn> argument, string argumentName)
30         {
31             this.argumentName = argumentName;
32             this.target = argument;
33         }
34
35         private bool allowEmpty;
36         private int expectedElementCount = -1;
37         private Func<TElementIn, int, TElementOut> map;
38         private Func<List<TElementOut>, TResult> collect;
39         private Func<TElementIn, int, string> deriveName;
40         
41         /// <summary>
42         /// Gets or sets a value that determines whether an exception is thrown if the enumerable argument is empty.
43         /// </summary>
44         /// <remarks>
45         /// AllowEmpty is ignored if <see cref="ExpectedElementCount"/> is set.
46         /// If ExpectedElementCount is set to zero, an empty collection will not cause an exception to be thrown,
47         /// even if AllowEmpty is set to <c>false</c>.
48         /// </remarks>
49         public bool AllowEmpty { get { return this.allowEmpty; } set { this.allowEmpty = value; } }
50
51         /// <summary>
52         /// Gets or set a value that determines the number of elements expected in the enumerable argument.
53         /// A value of <c>-1</c> indicates that any number of elements is permitted, including zero.
54         /// Use <see cref="AllowEmpty"/> to disallow an empty list when ExpectedElementCount is set to -1.
55         /// </summary>
56         public int ExpectedElementCount { get { return this.expectedElementCount; } set { this.expectedElementCount = value; } }
57         
58         /// <summary>
59         /// Gets or sets the function used to convert an element from the enumerable argument into an instance of
60         /// the desired output element type. The position of the input element is also specified as an argument to this function.
61         /// </summary>
62         public Func<TElementIn, int, TElementOut> ConvertElement { get { return this.map; } set { this.map = value; } }
63
64         /// <summary>
65         /// Gets or sets the function used to create the output collection from a list of converted enumerable elements.
66         /// </summary>
67         public Func<List<TElementOut>, TResult> CreateResult { get { return this.collect; } set { this.collect = value; } }
68
69         /// <summary>
70         /// Gets or sets an optional function that can retrieve the name of an element from the enumerable argument.
71         /// If this function is set, duplicate input element names will result in an exception. Null or empty names will
72         /// not result in an exception. If specified, this function will be called after <see cref="ConvertElement"/>.
73         /// </summary>
74         public Func<TElementIn, int, string> GetName { get { return this.deriveName; } set { this.deriveName = value; } }
75
76         /// <summary>
77         /// Validates the input enumerable, converting each input element and producing the final instance of <typeparamref name="TResult"/> as a result.
78         /// </summary>
79         /// <returns>The instance of <typeparamref name="TResult"/> produced by calling the <see cref="CreateResult"/> function 
80         /// on the list of elements produced by calling the <see cref="ConvertElement"/> function on each element of the input enumerable.</returns>
81         /// <exception cref="ArgumentNullException">If the input enumerable itself is null</exception>
82         /// <exception cref="ArgumentNullException">If <typeparamref name="TElementIn"/> is a nullable type and any element of the input enumerable is null.</exception>
83         /// <exception cref="ArgumentException">If <see cref="ExpectedElementCount"/> is set and the actual number of input elements is not equal to this value.</exception>
84         /// <exception cref="ArgumentException">If <see cref="ExpectedElementCount"/> is -1, <see cref="AllowEmpty"/> is set to <c>false</c> and the input enumerable is empty.</exception>
85         /// <exception cref="ArgumentException">If <see cref="GetName"/> is set and a duplicate name is derived for more than one input element.</exception>
86         /// <remarks>Other exceptions may be thrown by the <see cref="ConvertElement"/> and <see cref="CreateResult"/> functions, and by the <see cref="GetName"/> function, if specified.</remarks>
87         internal TResult Validate()
88         {
89             return EnumerableValidator<TElementIn, TElementOut, TResult>.Validate(this.target,
90                                                                                   this.argumentName,
91                                                                                   this.ExpectedElementCount,
92                                                                                   this.AllowEmpty,
93                                                                                   this.ConvertElement,
94                                                                                   this.CreateResult,
95                                                                                   this.GetName);
96         }
97
98         private static TResult Validate(IEnumerable<TElementIn> argument,
99                                         string argumentName,
100                                         int expectedElementCount,
101                                         bool allowEmpty,
102                                         Func<TElementIn, int, TElementOut> map,
103                                         Func<List<TElementOut>, TResult> collect,
104                                         Func<TElementIn, int, string> deriveName)
105         {
106             Debug.Assert(map != null, "Set EnumerableValidator.ConvertElement before calling validate");
107             Debug.Assert(collect != null, "Set EnumerableValidator.CreateResult before calling validate");
108
109             EntityUtil.CheckArgumentNull(argument, argumentName);
110
111             bool checkNull = (default(TElementIn) == null);
112             bool checkCount = (expectedElementCount != -1);
113             Dictionary<string, int> nameIndex = null;
114             if (deriveName != null)
115             {
116                 nameIndex = new Dictionary<string, int>();
117             }
118
119             int pos = 0;
120             List<TElementOut> validatedElements = new List<TElementOut>();
121             foreach (TElementIn elementIn in argument)
122             {
123                 // More elements in 'arguments' than expected?
124                 if (checkCount && pos == expectedElementCount)
125                 {
126                     throw EntityUtil.Argument(System.Data.Entity.Strings.Cqt_ExpressionList_IncorrectElementCount, argumentName);
127                 }
128
129                 if (checkNull && elementIn == null)
130                 {
131                     // Don't call FormatIndex unless an exception is actually being thrown
132                     throw EntityUtil.ArgumentNull(StringUtil.FormatIndex(argumentName, pos));
133                 }
134
135                 TElementOut elementOut = map(elementIn, pos);
136                 validatedElements.Add(elementOut);
137
138                 if (deriveName != null)
139                 {
140                     string name = deriveName(elementIn, pos);
141                     Debug.Assert(name != null, "GetName should not produce null");
142                     int foundIndex = -1;
143                     if (nameIndex.TryGetValue(name, out foundIndex))
144                     {
145                         throw EntityUtil.Argument(
146                             System.Data.Entity.Strings.Cqt_Util_CheckListDuplicateName(foundIndex, pos, name),
147                             StringUtil.FormatIndex(argumentName, pos)
148                         );
149                     }
150                     nameIndex[name] = pos;
151                 }
152
153                 pos++;
154             }
155
156             // If an expected count was specified, the actual count must match
157             if (checkCount)
158             {
159                 if (pos != expectedElementCount)
160                 {
161                     throw EntityUtil.Argument(System.Data.Entity.Strings.Cqt_ExpressionList_IncorrectElementCount, argumentName);
162                 }
163             }
164             else
165             {
166                 // No expected count was specified, simply verify empty vs. non-empty.
167                 if (0 == pos && !allowEmpty)
168                 {
169                     throw EntityUtil.Argument(System.Data.Entity.Strings.Cqt_Util_CheckListEmptyInvalid, argumentName);
170                 }
171             }
172
173             return collect(validatedElements);
174         }
175     }
176 }