New tests.
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / TypeDescriptorHelper.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\r
5  * This software is subject to the Microsoft Public License (Ms-PL). \r
6  * A copy of the license can be found in the license.htm file included \r
7  * in this distribution.\r
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc {\r
14     using System;\r
15     using System.Collections.Generic;\r
16     using System.ComponentModel;\r
17     using System.ComponentModel.DataAnnotations;\r
18     using System.Globalization;\r
19     using System.Linq;\r
20     using System.Reflection;\r
21     using System.Web.Mvc.Resources;\r
22 \r
23     // TODO: Remove this class in MVC 3\r
24     //\r
25     // We brought in a private copy of the AssociatedMetadataTypeTypeDescriptionProvider\r
26     // from .NET 4, because it provides several bug fixes and perf improvements. If we're\r
27     // running on .NET < 4, we'll use our private copy.\r
28 \r
29     internal static class TypeDescriptorHelper {\r
30 \r
31         private static Func<Type, ICustomTypeDescriptor> _typeDescriptorFactory = GetTypeDescriptorFactory();\r
32 \r
33         private static Func<Type, ICustomTypeDescriptor> GetTypeDescriptorFactory() {\r
34             if (Environment.Version.Major < 4) {\r
35                 return type => new _AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);\r
36             }\r
37 \r
38             return type => new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);\r
39         }\r
40 \r
41         public static ICustomTypeDescriptor Get(Type type) {\r
42             return _typeDescriptorFactory(type);\r
43         }\r
44 \r
45         // Private copies of the .NET 4 AssociatedMetadataType classes\r
46 \r
47         private class _AssociatedMetadataTypeTypeDescriptionProvider : TypeDescriptionProvider {\r
48             public _AssociatedMetadataTypeTypeDescriptionProvider(Type type)\r
49                 : base(TypeDescriptor.GetProvider(type)) {\r
50             }\r
51 \r
52             public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {\r
53                 ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance);\r
54                 return new _AssociatedMetadataTypeTypeDescriptor(baseDescriptor, objectType);\r
55             }\r
56         }\r
57 \r
58         private class _AssociatedMetadataTypeTypeDescriptor : CustomTypeDescriptor {\r
59             private Type AssociatedMetadataType {\r
60                 get;\r
61                 set;\r
62             }\r
63 \r
64             public _AssociatedMetadataTypeTypeDescriptor(ICustomTypeDescriptor parent, Type type)\r
65                 : base(parent) {\r
66                 AssociatedMetadataType = TypeDescriptorCache.GetAssociatedMetadataType(type);\r
67                 if (AssociatedMetadataType != null) {\r
68                     TypeDescriptorCache.ValidateMetadataType(type, AssociatedMetadataType);\r
69                 }\r
70             }\r
71 \r
72             public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) {\r
73                 return GetPropertiesWithMetadata(base.GetProperties(attributes));\r
74             }\r
75 \r
76             public override PropertyDescriptorCollection GetProperties() {\r
77                 return GetPropertiesWithMetadata(base.GetProperties());\r
78             }\r
79 \r
80             private PropertyDescriptorCollection GetPropertiesWithMetadata(PropertyDescriptorCollection originalCollection) {\r
81                 if (AssociatedMetadataType == null) {\r
82                     return originalCollection;\r
83                 }\r
84 \r
85                 bool customDescriptorsCreated = false;\r
86                 List<PropertyDescriptor> tempPropertyDescriptors = new List<PropertyDescriptor>();\r
87                 foreach (PropertyDescriptor propDescriptor in originalCollection) {\r
88                     Attribute[] newMetadata = TypeDescriptorCache.GetAssociatedMetadata(AssociatedMetadataType, propDescriptor.Name);\r
89                     PropertyDescriptor descriptor = propDescriptor;\r
90                     if (newMetadata.Length > 0) {\r
91                         // Create a metadata descriptor that wraps the property descriptor\r
92                         descriptor = new _MetadataPropertyDescriptorWrapper(propDescriptor, newMetadata);\r
93                         customDescriptorsCreated = true;\r
94                     }\r
95 \r
96                     tempPropertyDescriptors.Add(descriptor);\r
97                 }\r
98 \r
99                 if (customDescriptorsCreated) {\r
100                     return new PropertyDescriptorCollection(tempPropertyDescriptors.ToArray(), true);\r
101                 }\r
102                 return originalCollection;\r
103             }\r
104 \r
105             public override AttributeCollection GetAttributes() {\r
106                 // Since normal TD behavior is to return cached attribute instances on subsequent\r
107                 // calls to GetAttributes, we must be sure below to use the TD APIs to get both\r
108                 // the base and associated attributes\r
109                 AttributeCollection attributes = base.GetAttributes();\r
110                 if (AssociatedMetadataType != null) {\r
111                     // Note that the use of TypeDescriptor.GetAttributes here opens up the possibility of\r
112                     // infinite recursion, in the corner case of two Types referencing each other as\r
113                     // metadata types (or a longer cycle)\r
114                     Attribute[] newAttributes = TypeDescriptor.GetAttributes(AssociatedMetadataType).OfType<Attribute>().ToArray();\r
115                     attributes = AttributeCollection.FromExisting(attributes, newAttributes);\r
116                 }\r
117                 return attributes;\r
118             }\r
119 \r
120             private static class TypeDescriptorCache {\r
121                 private static readonly Attribute[] emptyAttributes = new Attribute[0];\r
122 \r
123                 // Stores the associated metadata type for a type\r
124                 private static readonly Dictionary<Type, Type> _metadataTypeCache = new Dictionary<Type, Type>();\r
125 \r
126                 // For a type and a property name stores the attributes for that property name.\r
127                 private static readonly Dictionary<Tuple<Type, string>, Attribute[]> _typeMemberCache = new Dictionary<Tuple<Type, string>, Attribute[]>();\r
128 \r
129                 // Stores whether or not a type and associated metadata type has been checked for validity\r
130                 private static readonly Dictionary<Tuple<Type, Type>, bool> _validatedMetadataTypeCache = new Dictionary<Tuple<Type, Type>, bool>();\r
131 \r
132                 public static void ValidateMetadataType(Type type, Type associatedType) {\r
133                     Tuple<Type, Type> typeTuple = new Tuple<Type, Type>(type, associatedType);\r
134 \r
135                     lock (_validatedMetadataTypeCache) {\r
136                         if (!_validatedMetadataTypeCache.ContainsKey(typeTuple)) {\r
137                             CheckAssociatedMetadataType(type, associatedType);\r
138                             _validatedMetadataTypeCache.Add(typeTuple, true);\r
139                         }\r
140                     }\r
141                 }\r
142 \r
143                 public static Type GetAssociatedMetadataType(Type type) {\r
144                     Type associatedMetadataType = null;\r
145                     lock (_metadataTypeCache) {\r
146                         if (_metadataTypeCache.TryGetValue(type, out associatedMetadataType)) {\r
147                             return associatedMetadataType;\r
148                         }\r
149                     }\r
150 \r
151                     // Try association attribute\r
152                     MetadataTypeAttribute attribute = (MetadataTypeAttribute)Attribute.GetCustomAttribute(type, typeof(MetadataTypeAttribute));\r
153                     if (attribute != null) {\r
154                         associatedMetadataType = attribute.MetadataClassType;\r
155                     }\r
156 \r
157                     lock (_metadataTypeCache) {\r
158                         _metadataTypeCache[type] = associatedMetadataType; \r
159                     }\r
160 \r
161                     return associatedMetadataType;\r
162                 }\r
163 \r
164                 private static void CheckAssociatedMetadataType(Type mainType, Type associatedMetadataType) {\r
165                     // Only properties from main type\r
166                     HashSet<string> mainTypeMemberNames = new HashSet<string>(mainType.GetProperties().Select(p => p.Name));\r
167 \r
168                     // Properties and fields from buddy type\r
169                     var buddyFields = associatedMetadataType.GetFields().Select(f => f.Name);\r
170                     var buddyProperties = associatedMetadataType.GetProperties().Select(p => p.Name);\r
171                     HashSet<string> buddyTypeMembers = new HashSet<string>(buddyFields.Concat(buddyProperties), StringComparer.Ordinal);\r
172 \r
173                     // Buddy members should be a subset of the main type's members\r
174                     if (!buddyTypeMembers.IsSubsetOf(mainTypeMemberNames)) {\r
175                         // Reduce the buddy members to the set not contained in the main members\r
176                         buddyTypeMembers.ExceptWith(mainTypeMemberNames);\r
177 \r
178                         throw new InvalidOperationException(String.Format(\r
179                             CultureInfo.CurrentCulture,\r
180                             MvcResources.PrivateAssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties,\r
181                             mainType.FullName,\r
182                             String.Join(", ", buddyTypeMembers.ToArray())));\r
183                     }\r
184                 }\r
185 \r
186                 public static Attribute[] GetAssociatedMetadata(Type type, string memberName) {\r
187                     var memberTuple = new Tuple<Type, string>(type, memberName);\r
188                     Attribute[] attributes;\r
189                     lock (_typeMemberCache) {\r
190                         if (_typeMemberCache.TryGetValue(memberTuple, out attributes)) {\r
191                             return attributes;\r
192                         }\r
193                     }\r
194 \r
195                     // Allow fields and properties\r
196                     MemberTypes allowedMemberTypes = MemberTypes.Property | MemberTypes.Field;\r
197                     // Only public static/instance members\r
198                     BindingFlags searchFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;\r
199                     // Try to find a matching member on type\r
200                     MemberInfo matchingMember = type.GetMember(memberName, allowedMemberTypes, searchFlags).FirstOrDefault();\r
201                     if (matchingMember != null) {\r
202                         attributes = Attribute.GetCustomAttributes(matchingMember, true /* inherit */);\r
203                     }\r
204                     else {\r
205                         attributes = emptyAttributes;\r
206                     }\r
207 \r
208                     lock (_typeMemberCache) {\r
209                         _typeMemberCache[memberTuple] = attributes;\r
210                     }\r
211                     return attributes;\r
212                 }\r
213 \r
214                 private class Tuple<T1, T2> {\r
215                     public T1 Item1 { get; set; }\r
216                     public T2 Item2 { get; set; }\r
217 \r
218                     public Tuple(T1 item1, T2 item2) {\r
219                         Item1 = item1;\r
220                         Item2 = item2;\r
221                     }\r
222 \r
223                     public override int GetHashCode() {\r
224                         int h1 = Item1.GetHashCode();\r
225                         int h2 = Item2.GetHashCode();\r
226                         return ((h1 << 5) + h1) ^ h2;\r
227                     }\r
228 \r
229                     public override bool Equals(object obj) {\r
230                         var other = obj as Tuple<T1, T2>;\r
231                         if (other != null) {\r
232                             return other.Item1.Equals(Item1) && other.Item2.Equals(Item2);\r
233                         }\r
234                         return false;\r
235                     }\r
236                 }\r
237             }\r
238         }\r
239 \r
240         private class _MetadataPropertyDescriptorWrapper : PropertyDescriptor {\r
241             private PropertyDescriptor _descriptor;\r
242             private bool _isReadOnly;\r
243 \r
244             public _MetadataPropertyDescriptorWrapper(PropertyDescriptor descriptor, Attribute[] newAttributes)\r
245                 : base(descriptor, newAttributes) {\r
246                 _descriptor = descriptor;\r
247                 var readOnlyAttribute = newAttributes.OfType<ReadOnlyAttribute>().FirstOrDefault();\r
248                 _isReadOnly = (readOnlyAttribute != null ? readOnlyAttribute.IsReadOnly : false);\r
249             }\r
250 \r
251             public override void AddValueChanged(object component, EventHandler handler) { _descriptor.AddValueChanged(component, handler); }\r
252 \r
253             public override bool CanResetValue(object component) { return _descriptor.CanResetValue(component); }\r
254 \r
255             public override Type ComponentType { get { return _descriptor.ComponentType; } }\r
256 \r
257             public override object GetValue(object component) { return _descriptor.GetValue(component); }\r
258 \r
259             public override bool IsReadOnly {\r
260                 get {\r
261                     // Dev10 Bug 594083\r
262                     // It's not enough to call the wrapped _descriptor because it does not know anything about\r
263                     // new attributes passed into the constructor of this class.\r
264                     return _isReadOnly || _descriptor.IsReadOnly;\r
265                 }\r
266             }\r
267 \r
268             public override Type PropertyType { get { return _descriptor.PropertyType; } }\r
269 \r
270             public override void RemoveValueChanged(object component, EventHandler handler) { _descriptor.RemoveValueChanged(component, handler); }\r
271 \r
272             public override void ResetValue(object component) { _descriptor.ResetValue(component); }\r
273 \r
274             public override void SetValue(object component, object value) { _descriptor.SetValue(component, value); }\r
275 \r
276             public override bool ShouldSerializeValue(object component) { return _descriptor.ShouldSerializeValue(component); }\r
277 \r
278             public override bool SupportsChangeEvents { get { return _descriptor.SupportsChangeEvents; } }\r
279         }\r
280     \r
281     }\r
282 }\r