Fix bugs in sizing TableLayoutPanel (Xamarin bug 18638)
[mono.git] / mcs / class / System.ComponentModel.DataAnnotations / System.ComponentModel.DataAnnotations / ValidationAttribute.cs
1 //
2 // ValidationAttribute.cs
3 //
4 // Authors:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Marek Habersack <mhabersack@novell.com>
7 //
8 // Copyright (C) 2008-2010 Novell Inc. http://novell.com
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31 using System;
32 using System.ComponentModel;
33 using System.Reflection;
34 using System.Globalization;
35
36 namespace System.ComponentModel.DataAnnotations
37 {
38         public abstract class ValidationAttribute : Attribute
39         {
40                 const string DEFAULT_ERROR_MESSAGE = "The field {0} is invalid.";
41 #if NET_4_0
42                 object nestedCallLock = new object ();
43                 bool nestedCall;
44 #else
45                 string errorMessageResourceName;
46                 string errorMessageString;
47                 Type errorMessageResourceType;
48 #endif
49                 string errorMessage;
50                 string fallbackErrorMessage;
51                 Func <string> errorMessageAccessor;
52                 
53                 protected ValidationAttribute ()
54                 {
55                 }
56
57                 protected ValidationAttribute (Func<string> errorMessageAccessor)
58                 {
59                         this.errorMessageAccessor = errorMessageAccessor;
60                 }
61
62                 protected ValidationAttribute (string errorMessage)
63                 {
64                         fallbackErrorMessage = errorMessage;
65                 }
66
67                 public virtual string FormatErrorMessage (string name)
68                 {
69                         string format = ErrorMessageString;
70                         if (String.IsNullOrEmpty (format))
71                                 return String.Empty;
72
73                         return String.Format (ErrorMessageString, name);
74                 }
75 #if NET_4_0
76                 public string ErrorMessage {
77                         get { return errorMessage; }
78                         set {
79                                 errorMessage = value;
80                                 if (errorMessage != null)
81                                         errorMessageAccessor = null;
82                         }
83                 }
84                 public string ErrorMessageResourceName { get; set; }
85                 public Type ErrorMessageResourceType { get; set; }
86 #else
87                 public string ErrorMessage {
88                         get { return errorMessage; }
89
90                         set {
91 #if !NET_4_0
92                                 if (errorMessage != null)
93                                         throw new InvalidOperationException ("This property can be set only once.");
94 #endif
95                                 if (String.IsNullOrEmpty (value))
96                                         throw new ArgumentException ("Value cannot be null or empty.", "value");
97
98                                 if (errorMessageResourceName != null || errorMessageResourceType != null)
99                                         throw new InvalidOperationException ("This property cannot be set because the attribute is already in the resource mode.");
100                                 
101                                 errorMessage = value;
102                         }
103                 }
104
105                 public string ErrorMessageResourceName {
106                         get { return errorMessageResourceName; }
107                         
108                         set {
109                                 if (errorMessageResourceName != null)
110                                         throw new InvalidOperationException ("This property can be set only once.");
111
112                                 if (String.IsNullOrEmpty (value))
113                                         throw new ArgumentException ("Value cannot be null or empty.", "value");
114
115                                 errorMessageResourceName = value;
116                                 if (errorMessageResourceType != null)
117                                         errorMessageString = GetStringFromResourceAccessor ();
118                         }
119                 }
120
121                 public Type ErrorMessageResourceType {
122                         get { return errorMessageResourceType; }
123                         set {
124                                 errorMessageResourceType = value;
125                                 if (!String.IsNullOrEmpty (errorMessageResourceName))
126                                         errorMessageString = GetStringFromResourceAccessor ();
127                         }
128                 }
129 #endif          
130                 protected string ErrorMessageString {
131                         get { return GetStringFromResourceAccessor (); }
132                 }
133
134 #if NET_4_5
135                 // See: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute.requiresvalidationcontext.aspx
136                 public virtual bool RequiresValidationContext { get { return false; } }
137 #endif
138
139 #if NET_4_0
140                 NotImplementedException NestedNIEX ()
141                 {
142                         return new NotImplementedException ("IsValid(object value) has not been implemented by this class.  The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context).");
143                 }
144                 
145                 //
146                 // This is the weirdest (to be gentle) idea ever... The IsValid (object) overload
147                 // throws the NIEX when it is called from the default IsValid (object,
148                 // ValidationContext) overload, but not when directly. And the reverse situation is
149                 // true as well. That means, the calls detect the "nested" calls and that we need to
150                 // protect the nestedCall flag... ugh
151                 //
152                 public virtual bool IsValid (object value)
153                 {
154                         lock (nestedCallLock) {
155                                 if (nestedCall)
156                                         throw NestedNIEX ();
157                                 try {
158                                         nestedCall = true;
159                                         return IsValid (value, null) == ValidationResult.Success;
160                                 } finally {
161                                         nestedCall = false;
162                                 }
163                         }
164                 }
165
166                 protected virtual ValidationResult IsValid (object value, ValidationContext validationContext)
167                 {
168                         lock (nestedCallLock) {
169                                 if (nestedCall)
170                                         throw NestedNIEX ();
171                                 
172                                 try {
173                                         nestedCall = true;
174                                         if (!IsValid (value)) {
175                                                 // .NET emulation
176                                                 if (validationContext == null)
177                                                         throw new NullReferenceException (".NET emulation.");
178                                                 string memberName = validationContext.MemberName;
179                                                 return new ValidationResult (FormatErrorMessage (validationContext.DisplayName), memberName != null ? new string[] { memberName } : new string[] {});
180                                         }
181                                 } finally {
182                                         nestedCall = false;
183                                 }
184                         }
185
186                         return ValidationResult.Success;
187                 }
188 #else
189                 public abstract bool IsValid (object value);
190 #endif
191
192 #if NET_4_0
193                 public ValidationResult GetValidationResult (object value, ValidationContext validationContext)
194                 {
195                         if (validationContext == null)
196                                 throw new ArgumentNullException ("validationContext");
197
198                         ValidationResult ret = IsValid (value, validationContext);
199                         if (ret != null && String.IsNullOrEmpty (ret.ErrorMessage))
200                                 ret.ErrorMessage = FormatErrorMessage (validationContext.DisplayName);
201                                 
202                         return ret;
203                 }
204 #endif
205                 string GetStringFromResourceAccessor ()
206                 {
207                         string resourceName = ErrorMessageResourceName;
208                         Type resourceType = ErrorMessageResourceType;
209                         string errorMessage = ErrorMessage;
210
211                         if (resourceName != null && errorMessage != null)
212                                 throw new InvalidOperationException ("Either ErrorMessage or ErrorMessageResourceName must be set, but not both.");
213                         
214                         if (resourceType == null ^ resourceName == null)
215                                 throw new InvalidOperationException ("Both ErrorMessageResourceType and ErrorMessageResourceName must be set on this attribute.");
216
217                         
218                         
219                         if (resourceType != null) {
220                                 PropertyInfo pi = resourceType.GetProperty (resourceName, BindingFlags.Public | BindingFlags.Static);
221                                 if (pi == null || !pi.CanRead)
222                                         throw new InvalidOperationException (
223                                                 String.Format ("Resource type '{0}' does not have an accessible static property named '{1}'.",
224                                                                resourceType, resourceName)
225                                         );
226
227                                 if (pi.PropertyType != typeof (string))
228                                         throw new InvalidOperationException (
229                                                 String.Format ("The property '{0}' on resource type '{1}' is not a string type.",
230                                                                resourceName, resourceType)
231                                         );
232                                 
233                                 return pi.GetValue (null, null) as string;
234                         }
235                         
236                         if (errorMessage == null) {
237                                 if (errorMessageAccessor != null)
238                                         return errorMessageAccessor ();
239                                 
240                                 if (fallbackErrorMessage != null)
241                                         return fallbackErrorMessage;
242                                 else
243                                         return DEFAULT_ERROR_MESSAGE;
244                         }
245                         
246                         return errorMessage;
247                 }
248 #if NET_4_0
249                 public void Validate (object value, ValidationContext validationContext)
250                 {
251                         if (validationContext == null)
252                                 throw new ArgumentNullException ("validationContext");
253
254                         ValidationResult result = IsValid (value, validationContext);
255                         if (result != null) {
256                                 string message = result.ErrorMessage;
257                                 if (message == null)
258                                         message = FormatErrorMessage (validationContext.DisplayName);
259                                 
260                                 throw new ValidationException (message, this, value);
261                         }
262                 }
263 #endif
264                 public void Validate (object value, string name)
265                 {
266                         if (!IsValid (value))
267                                 throw new ValidationException (FormatErrorMessage (name), this, value);
268                 }
269         }
270 }