Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Serialization / Models.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="Models.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>                                                                
6 //------------------------------------------------------------------------------
7
8 namespace System.Xml.Serialization {
9
10     using System;
11     using System.Reflection;
12     using System.Collections;
13     using System.Diagnostics;
14
15     // These classes define the abstract serialization model, e.g. the rules for WHAT is serialized.  
16     // The answer of HOW the values are serialized is answered by a particular reflection importer 
17     // by looking for a particular set of custom attributes specific to the serialization format
18     // and building an appropriate set of accessors/mappings.
19
20     internal class ModelScope {
21         TypeScope typeScope;
22         Hashtable models = new Hashtable();
23         Hashtable arrayModels = new Hashtable();
24
25         internal ModelScope(TypeScope typeScope) {
26             this.typeScope = typeScope;
27         }
28
29         internal TypeScope TypeScope {
30             get { return typeScope; }
31         }
32
33         internal TypeModel GetTypeModel(Type type) {
34             return GetTypeModel(type, true);
35         }
36
37         internal TypeModel GetTypeModel(Type type, bool directReference) {
38             TypeModel model = (TypeModel)models[type];
39             if (model != null) return model;
40             TypeDesc typeDesc = typeScope.GetTypeDesc(type, null, directReference);
41
42             switch (typeDesc.Kind) {
43                 case TypeKind.Enum:
44                     model = new EnumModel(type, typeDesc, this);
45                     break;
46                 case TypeKind.Primitive:
47                     model = new PrimitiveModel(type, typeDesc, this);
48                     break;
49                 case TypeKind.Array:
50                 case TypeKind.Collection:
51                 case TypeKind.Enumerable:
52                     model = new ArrayModel(type, typeDesc, this);
53                     break;
54                 case TypeKind.Root:
55                 case TypeKind.Class:
56                 case TypeKind.Struct:
57                     model = new StructModel(type, typeDesc, this);
58                     break;
59                 default:
60                     if (!typeDesc.IsSpecial) throw new NotSupportedException(Res.GetString(Res.XmlUnsupportedTypeKind, type.FullName));
61                     model = new SpecialModel(type, typeDesc, this);
62                     break;
63             }
64
65             models.Add(type, model);
66             return model;
67         }
68
69         internal ArrayModel GetArrayModel(Type type) {
70             TypeModel model = (TypeModel)arrayModels[type];
71             if (model == null) {
72                 model = GetTypeModel(type);
73                 if (!(model is ArrayModel)) {
74                     TypeDesc typeDesc = typeScope.GetArrayTypeDesc(type);
75                     model = new ArrayModel(type, typeDesc, this);
76                 }
77                 arrayModels.Add(type, model);
78             }
79             return (ArrayModel)model;
80         }
81     }
82
83     internal abstract class TypeModel {
84         TypeDesc typeDesc;
85         Type type;
86         ModelScope scope;
87
88         protected TypeModel(Type type, TypeDesc typeDesc, ModelScope scope) {
89             this.scope = scope;
90             this.type = type;
91             this.typeDesc = typeDesc;
92         }
93
94         internal Type Type {
95             get { return type; }
96         }
97
98         internal ModelScope ModelScope {
99             get { return scope; }
100         }
101
102         internal TypeDesc TypeDesc {
103             get { return typeDesc; }
104         }
105     }
106
107     internal class ArrayModel : TypeModel {
108         internal ArrayModel(Type type, TypeDesc typeDesc, ModelScope scope) : base(type, typeDesc, scope) { }
109
110         internal TypeModel Element {
111             get { return ModelScope.GetTypeModel(TypeScope.GetArrayElementType(Type, null)); }
112         }
113     }
114
115     internal class PrimitiveModel : TypeModel {
116         internal PrimitiveModel(Type type, TypeDesc typeDesc, ModelScope scope) : base(type, typeDesc, scope) { }
117     }
118
119     internal class SpecialModel : TypeModel {
120         internal SpecialModel(Type type, TypeDesc typeDesc, ModelScope scope) : base(type, typeDesc, scope) { }
121     }
122
123     internal class StructModel : TypeModel {
124
125         internal StructModel(Type type, TypeDesc typeDesc, ModelScope scope) : base(type, typeDesc, scope) { }
126
127         internal MemberInfo[] GetMemberInfos() {
128             // we use to return Type.GetMembers() here, the members were returned in a different order: fields first, properties last
129             // Current System.Reflection code returns members in oposite order: properties first, then fields.
130             // This code make sure that returns members in the Everett order.
131             MemberInfo[] members = Type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
132             MemberInfo[] fieldsAndProps = new MemberInfo[members.Length];
133
134             int cMember = 0;
135             // first copy all non-property members over
136             for (int i = 0; i < members.Length; i++) {
137                 if ((members[i].MemberType & MemberTypes.Property) == 0) {
138                     fieldsAndProps[cMember++] = members[i];
139                 }
140             }
141             // now copy all property members over
142             for (int i = 0; i < members.Length; i++) {
143                 if ((members[i].MemberType & MemberTypes.Property) != 0) {
144                     fieldsAndProps[cMember++] = members[i];
145                 }
146             }
147             return fieldsAndProps;
148         }
149
150         internal FieldModel GetFieldModel(MemberInfo memberInfo) {
151             FieldModel model = null;
152             if (memberInfo is FieldInfo)
153                 model = GetFieldModel((FieldInfo)memberInfo);
154             else if (memberInfo is PropertyInfo)
155                 model = GetPropertyModel((PropertyInfo)memberInfo);
156             if (model != null) {
157                 if (model.ReadOnly && model.FieldTypeDesc.Kind != TypeKind.Collection && model.FieldTypeDesc.Kind != TypeKind.Enumerable)
158                     return null;
159             }
160             return model;
161         }
162
163         void CheckSupportedMember(TypeDesc typeDesc, MemberInfo member, Type type) {
164             if (typeDesc == null)
165                 return;
166             if (typeDesc.IsUnsupported) {
167                 if (typeDesc.Exception == null) {
168                     typeDesc.Exception = new NotSupportedException(Res.GetString(Res.XmlSerializerUnsupportedType, typeDesc.FullName));
169                 }
170                 throw new InvalidOperationException(Res.GetString(Res.XmlSerializerUnsupportedMember, member.DeclaringType.FullName + "." + member.Name, type.FullName), typeDesc.Exception);
171
172             }
173             CheckSupportedMember(typeDesc.BaseTypeDesc, member, type);
174             CheckSupportedMember(typeDesc.ArrayElementTypeDesc, member, type);
175         }
176
177         FieldModel GetFieldModel(FieldInfo fieldInfo) {
178             if (fieldInfo.IsStatic) return null;
179             if (fieldInfo.DeclaringType != Type) return null;
180
181             TypeDesc typeDesc = ModelScope.TypeScope.GetTypeDesc(fieldInfo.FieldType, fieldInfo, true, false);
182             if (fieldInfo.IsInitOnly && typeDesc.Kind != TypeKind.Collection && typeDesc.Kind != TypeKind.Enumerable)
183                 return null;
184
185             CheckSupportedMember(typeDesc, fieldInfo, fieldInfo.FieldType);
186             return new FieldModel(fieldInfo, fieldInfo.FieldType, typeDesc);
187         }
188
189         FieldModel GetPropertyModel(PropertyInfo propertyInfo) {
190             if (propertyInfo.DeclaringType != Type) return null;
191             if (CheckPropertyRead(propertyInfo)) {
192                 TypeDesc typeDesc = ModelScope.TypeScope.GetTypeDesc(propertyInfo.PropertyType, propertyInfo, true, false);
193                 // Fix for CSDMain 100492, please contact arssrvlt if you need to change this line
194                 if (!propertyInfo.CanWrite && typeDesc.Kind != TypeKind.Collection && typeDesc.Kind != TypeKind.Enumerable)
195                     return null;
196                 CheckSupportedMember(typeDesc, propertyInfo, propertyInfo.PropertyType);
197                 return new FieldModel(propertyInfo, propertyInfo.PropertyType, typeDesc);
198             }
199             return null;
200         }
201
202         //CheckProperty
203         internal static bool CheckPropertyRead(PropertyInfo propertyInfo) {
204             if (!propertyInfo.CanRead) return false;
205
206             MethodInfo getMethod = propertyInfo.GetGetMethod();
207             if (getMethod.IsStatic) return false;
208             ParameterInfo[] parameters = getMethod.GetParameters();
209             if (parameters.Length > 0) return false;
210             return true;
211         }
212     }
213
214     internal enum SpecifiedAccessor {
215         None,
216         ReadOnly,
217         ReadWrite,
218     }
219
220     internal class FieldModel {
221         SpecifiedAccessor checkSpecified = SpecifiedAccessor.None;
222         MemberInfo memberInfo;
223         MemberInfo checkSpecifiedMemberInfo;
224         MethodInfo checkShouldPersistMethodInfo;
225         bool checkShouldPersist;
226         bool readOnly = false;
227         bool isProperty = false;
228         Type fieldType;
229         string name;
230         TypeDesc fieldTypeDesc;
231
232         internal FieldModel(string name, Type fieldType, TypeDesc fieldTypeDesc, bool checkSpecified, bool checkShouldPersist) :
233             this(name, fieldType, fieldTypeDesc, checkSpecified, checkShouldPersist, false) {
234         }
235         internal FieldModel(string name, Type fieldType, TypeDesc fieldTypeDesc, bool checkSpecified, bool checkShouldPersist, bool readOnly) {
236             this.fieldTypeDesc = fieldTypeDesc;
237             this.name = name;
238             this.fieldType = fieldType;
239             this.checkSpecified = checkSpecified ? SpecifiedAccessor.ReadWrite : SpecifiedAccessor.None;
240             this.checkShouldPersist = checkShouldPersist;
241             this.readOnly = readOnly;
242         }
243
244         internal FieldModel(MemberInfo memberInfo, Type fieldType, TypeDesc fieldTypeDesc) {
245             this.name = memberInfo.Name;
246             this.fieldType = fieldType;
247             this.fieldTypeDesc = fieldTypeDesc;
248             this.memberInfo = memberInfo;
249             this.checkShouldPersistMethodInfo = memberInfo.DeclaringType.GetMethod("ShouldSerialize" + memberInfo.Name, new Type[0]);
250             this.checkShouldPersist = this.checkShouldPersistMethodInfo != null;
251
252             FieldInfo specifiedField = memberInfo.DeclaringType.GetField(memberInfo.Name + "Specified");
253             if (specifiedField != null) {
254                 if (specifiedField.FieldType != typeof(bool)) {
255                     throw new InvalidOperationException(Res.GetString(Res.XmlInvalidSpecifiedType, specifiedField.Name, specifiedField.FieldType.FullName, typeof(bool).FullName));
256                 }
257                 this.checkSpecified = specifiedField.IsInitOnly ? SpecifiedAccessor.ReadOnly : SpecifiedAccessor.ReadWrite;
258                 this.checkSpecifiedMemberInfo = specifiedField;
259             }
260             else {
261                 PropertyInfo specifiedProperty = memberInfo.DeclaringType.GetProperty(memberInfo.Name + "Specified");
262                 if (specifiedProperty != null) {
263                     if (StructModel.CheckPropertyRead(specifiedProperty)) {
264                         this.checkSpecified = specifiedProperty.CanWrite ? SpecifiedAccessor.ReadWrite : SpecifiedAccessor.ReadOnly;
265                         this.checkSpecifiedMemberInfo = specifiedProperty;
266                     }
267                     if (this.checkSpecified != SpecifiedAccessor.None && specifiedProperty.PropertyType != typeof(bool)) {
268                         throw new InvalidOperationException(Res.GetString(Res.XmlInvalidSpecifiedType, specifiedProperty.Name, specifiedProperty.PropertyType.FullName, typeof(bool).FullName));
269                     }
270                 }
271             }
272             if (memberInfo is PropertyInfo) {
273                 readOnly = !((PropertyInfo)memberInfo).CanWrite;
274                 isProperty = true;
275             }
276             else if (memberInfo is FieldInfo) {
277                 readOnly = ((FieldInfo)memberInfo).IsInitOnly;
278             }
279         }
280
281         internal string Name {
282             get { return name; }
283         }
284
285         internal Type FieldType {
286             get { return fieldType; }
287         }
288
289         internal TypeDesc FieldTypeDesc {
290             get { return fieldTypeDesc; }
291         }
292
293         internal bool CheckShouldPersist {
294             get { return checkShouldPersist; }
295         }
296
297         internal SpecifiedAccessor CheckSpecified {
298             get { return checkSpecified; }
299         }
300
301         internal MemberInfo MemberInfo {
302             get { return memberInfo; }
303         }
304         internal MemberInfo CheckSpecifiedMemberInfo {
305             get { return checkSpecifiedMemberInfo; }
306         }
307         internal MethodInfo CheckShouldPersistMethodInfo {
308             get { return checkShouldPersistMethodInfo; }
309         }
310
311         internal bool ReadOnly {
312             get { return readOnly; }
313         }
314
315         internal bool IsProperty {
316             get { return isProperty; }
317         }
318     }
319
320     internal class ConstantModel {
321         FieldInfo fieldInfo;
322         long value;
323
324         internal ConstantModel(FieldInfo fieldInfo, long value) {
325             this.fieldInfo = fieldInfo;
326             this.value = value;
327         }
328
329         internal string Name {
330             get { return fieldInfo.Name; }
331         }
332
333         internal long Value {
334             get { return value; }
335         }
336
337         internal FieldInfo FieldInfo {
338             get { return fieldInfo; }
339         }
340     }
341
342     internal class EnumModel : TypeModel {
343         ConstantModel[] constants;
344
345         internal EnumModel(Type type, TypeDesc typeDesc, ModelScope scope) : base(type, typeDesc, scope) { }
346
347         internal ConstantModel[] Constants {
348             get {
349                 if (constants == null) {
350                     ArrayList list = new ArrayList();
351                     FieldInfo[] fields = Type.GetFields();
352                     for (int i = 0; i < fields.Length; i++) {
353                         FieldInfo field = fields[i];
354                         ConstantModel constant = GetConstantModel(field);
355                         if (constant != null) list.Add(constant);
356                     }
357                     constants = (ConstantModel[])list.ToArray(typeof(ConstantModel));
358                 }
359                 return constants;
360             }
361
362         }
363
364         ConstantModel GetConstantModel(FieldInfo fieldInfo) {
365             if (fieldInfo.IsSpecialName) return null;
366             return new ConstantModel(fieldInfo, ((IConvertible)fieldInfo.GetValue(null)).ToInt64(null));
367         }
368     }
369 }
370