Merge pull request #995 from yjoly/master
[mono.git] / mcs / class / System.ServiceModel.Web / System.Runtime.Serialization.Json / TypeMap.cs
1 //
2 // TypeMap.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2008 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.ObjectModel;
32 using System.Globalization;
33 using System.IO;
34 using System.Linq;
35 using System.Reflection;
36 using System.Runtime.CompilerServices;
37 using System.Runtime.Serialization;
38 using System.Text;
39 using System.Xml;
40
41 namespace System.Runtime.Serialization.Json
42 {
43         class TypeMap
44         {
45                 static bool IsInvalidNCName (string name)
46                 {
47                         if (name == null || name.Length == 0)
48                                 return true;
49                         try {
50                                 XmlConvert.VerifyNCName (name);
51                         } catch (XmlException) {
52                                 return true;
53                         }
54                         return false;
55                 }
56
57                 public static TypeMap CreateTypeMap (Type type)
58                 {
59                         object [] atts = type.GetCustomAttributes (typeof (DataContractAttribute), true);
60                         if (atts.Length == 1)
61                                 return CreateTypeMap (type, (DataContractAttribute) atts [0]);
62
63                         atts = type.GetCustomAttributes (typeof (SerializableAttribute), false);
64                         if (atts.Length == 1)
65                                 return CreateTypeMap (type, null);
66
67                         if (IsPrimitiveType (type))
68                                 return null;
69
70                         return CreateDefaultTypeMap (type);
71                 }
72
73                 static bool IsPrimitiveType (Type type)
74                 {
75                         if (type.IsEnum)
76                                 return true;
77                         if (Type.GetTypeCode (type) != TypeCode.Object)
78                                 return true; // FIXME: it is likely hacky
79                         return false;
80                 }
81
82                 static TypeMap CreateDefaultTypeMap (Type type)
83                 {
84                         var l = new List<TypeMapMember> ();
85                         foreach (var fi in type.GetFields ())
86                                 if (!fi.IsStatic)
87                                         l.Add (new TypeMapField (fi, null));
88                         foreach (var pi in type.GetProperties ())
89                                 if (pi.CanRead && pi.CanWrite && !pi.GetGetMethod (true).IsStatic && pi.GetIndexParameters ().Length == 0)
90                                         l.Add (new TypeMapProperty (pi, null));
91                         l.Sort ((x, y) => x.Order != y.Order ? x.Order - y.Order : String.Compare (x.Name, y.Name, StringComparison.Ordinal));
92                         return new TypeMap (type, null, l.ToArray ());
93                 }
94
95                 internal static bool IsDictionary (Type type)
96                 {
97                         Type inter;
98                         inter = type.GetInterface ("System.Collections.IDictionary", false);
99                         if (inter != null
100                                 && type.GetMethod ("Add", new Type[] { typeof (object), typeof (object) }) != null)
101                                 return true;
102                         
103                         inter = type.GetInterface ("System.Collections.Generic.IDictionary`2", false);
104                         if (inter != null
105                                 && type.GetMethod ("Add", new Type[] { inter.GetGenericArguments() [0], 
106                                                                            inter.GetGenericArguments() [1] }) != null)
107                                 return true;
108                         return false;
109                 }
110
111                 internal static bool IsEnumerable (Type type)
112                 {
113                         if (type.IsGenericType && 
114                                 type.GetGenericTypeDefinition() == typeof (LinkedList<>))
115                                 return true;
116                         
117                         if (IsPrimitiveType (type) || IsDictionary (type))
118                                 return false;
119                         
120                         Type inter;
121                         inter = type.GetInterface ("System.Collections.Generic.IReadOnlyCollection`1", false);
122                         if (inter != null)
123                                 return true;
124                         
125                         inter = type.GetInterface ("System.Collections.IEnumerable", false);
126                         if (inter != null && type.GetMethod ("Add", new Type[] { typeof (object) }) != null)
127                                 return true;
128                         
129                         inter = type.GetInterface ("System.Collections.Generic.IEnumerable`1", false);
130                         if (inter != null && type.GetMethod ("Add", new Type[] { inter.GetGenericArguments() [0] }) != null)
131                                 return true;
132                         return false;
133                 }
134
135                 static TypeMap CreateTypeMap (Type type, DataContractAttribute dca)
136                 {
137                         if (dca != null && dca.Name != null && IsInvalidNCName (dca.Name))
138                                 throw new InvalidDataContractException (String.Format ("DataContractAttribute for type '{0}' has an invalid name", type));
139
140                         List<TypeMapMember> members = new List<TypeMapMember> ();
141
142                         foreach (FieldInfo fi in type.GetFields (binding_flags)) {
143                                 if (fi.GetCustomAttributes (typeof (CompilerGeneratedAttribute), false).Length > 0)
144                                         continue;
145                                 if (dca != null) {
146                                         object [] atts = fi.GetCustomAttributes (typeof (DataMemberAttribute), true);
147                                         if (atts.Length == 0)
148                                                 continue;
149                                         DataMemberAttribute dma = (DataMemberAttribute) atts [0];
150                                         members.Add (new TypeMapField (fi, dma));
151                                 } else {
152                                         if (fi.GetCustomAttributes (typeof (IgnoreDataMemberAttribute), false).Length > 0)
153                                                 continue;
154                                         members.Add (new TypeMapField (fi, null));
155                                 }
156                         }
157
158                         if (dca != null) {
159                                 foreach (PropertyInfo pi in type.GetProperties (binding_flags)) {
160                                         object [] atts = pi.GetCustomAttributes (typeof (DataMemberAttribute), true);
161                                         if (atts.Length == 0)
162                                                 continue;
163                                         if (pi.GetIndexParameters ().Length > 0)
164                                                 continue;
165                                         if (IsEnumerable (pi.PropertyType) || IsDictionary (pi.PropertyType)) {
166                                                 if (!pi.CanRead)
167                                                         throw new InvalidDataContractException (String.Format ("Property {0} must have a getter", pi));
168                                         }
169                                         else if (!pi.CanRead || !pi.CanWrite)
170                                                 throw new InvalidDataContractException (String.Format ("Non-collection property {0} must have both getter and setter", pi));
171                                         DataMemberAttribute dma = (DataMemberAttribute) atts [0];
172                                         members.Add (new TypeMapProperty (pi, dma));
173                                 }
174                         }
175
176                         members.Sort (delegate (TypeMapMember m1, TypeMapMember m2) { return m1.Order != m2.Order ? m1.Order - m2.Order : String.CompareOrdinal (m1.Name, m2.Name); });
177                         return new TypeMap (type, dca == null ? null : dca.Name, members.ToArray ());
178                 }
179
180                 Type type;
181                 string element;
182                 TypeMapMember [] members;
183
184                 static readonly Type [] deser_methods_args = new Type [] { typeof (StreamingContext) };
185                 const BindingFlags binding_flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
186
187                 public TypeMap (Type type, string element, TypeMapMember [] orderedMembers)
188                 {
189                         this.type = type;
190                         this.element = element;
191                         this.members = orderedMembers;
192
193                         foreach (var mi in type.GetMethods (binding_flags)) {
194                                 if (mi.GetCustomAttributes (typeof (OnDeserializingAttribute), false).Length > 0)
195                                         OnDeserializing = mi;
196                                 else if (mi.GetCustomAttributes (typeof (OnDeserializedAttribute), false).Length > 0)
197                                         OnDeserialized = mi;
198                                 else if (mi.GetCustomAttributes (typeof (OnSerializingAttribute), false).Length > 0)
199                                         OnSerializing = mi;
200                                 else if (mi.GetCustomAttributes (typeof (OnSerializedAttribute), false).Length > 0)
201                                         OnSerialized = mi;
202                         }
203                 }
204
205                 public MethodInfo OnDeserializing { get; set; }
206                 public MethodInfo OnDeserialized { get; set; }
207                 public MethodInfo OnSerializing { get; set; }
208                 public MethodInfo OnSerialized { get; set; }
209
210                 public virtual void Serialize (JsonSerializationWriter outputter, object graph, string type)
211                 {
212                         if (OnSerializing != null)
213                                 OnSerializing.Invoke (graph, new object [] {new StreamingContext (StreamingContextStates.All)});
214
215                         outputter.Writer.WriteAttributeString ("type", type);
216                         foreach (TypeMapMember member in members) {
217                                 object memberObj = member.GetMemberOf (graph);
218                                 // FIXME: consider EmitDefaultValue
219                                 outputter.Writer.WriteStartElement (member.Name);
220                                 outputter.WriteObjectContent (memberObj, false, false);
221                                 outputter.Writer.WriteEndElement ();
222                         }
223
224                         if (OnSerialized != null)
225                                 OnSerialized.Invoke (graph, new object [] {new StreamingContext (StreamingContextStates.All)});
226                 }
227
228                 internal static object CreateInstance (Type type)
229                 {
230                         if (TypeMap.IsDictionary (type)) {
231                                 if (type.IsGenericType)
232                                         return Activator.CreateInstance (typeof (Dictionary<,>).MakeGenericType (type.GetGenericArguments ()));
233                                 else
234                                         return new Hashtable ();
235                         } else if (TypeMap.IsEnumerable (type)) {
236                                 if (type.IsGenericType)
237                                         return Activator.CreateInstance (typeof (List<>).MakeGenericType (type.GetGenericArguments ()));
238                                 else
239                                         return new ArrayList ();
240                         }
241                         else
242                                 return FormatterServices.GetUninitializedObject (type);
243                 }
244
245                 public virtual object Deserialize (JsonSerializationReader jsr, object o)
246                 {
247                         XmlReader reader = jsr.Reader;
248                         bool isNull = reader.GetAttribute ("type") == "null";
249
250                         object ret = isNull ? null : CreateInstance (type);
251                         if (ret != null && OnDeserializing != null)
252                                 OnDeserializing.Invoke (ret, new object [] {new StreamingContext (StreamingContextStates.All)});
253                         Dictionary<TypeMapMember,bool> filled = new Dictionary<TypeMapMember,bool> ();
254
255                         reader.ReadStartElement ();
256                         for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
257                                 bool consumed = false;
258                                 for (int i = 0; i < members.Length; i++) {
259                                         TypeMapMember mm = members [i];
260                                         if (mm.Name == reader.LocalName && reader.NamespaceURI == String.Empty) {
261                                                 if (filled.ContainsKey (mm))
262                                                         throw new SerializationException (String.Format ("Object content '{0}' for '{1}' already appeared in the reader", reader.LocalName, type));
263                                                 mm.SetMemberValue (ret, jsr);
264                                                 filled [mm] = true;
265                                                 consumed = true;
266                                                 break;
267                                         }
268                                 }
269                                 if (!consumed)
270                                         reader.Skip ();
271                         }
272                         reader.ReadEndElement ();
273                         if (ret != null && OnDeserialized != null)
274                                 OnDeserialized.Invoke (ret, new object [] {new StreamingContext (StreamingContextStates.All)});
275                         return ret;
276                 }
277         }
278
279         abstract class TypeMapMember
280         {
281                 MemberInfo mi;
282                 DataMemberAttribute dma;
283
284                 protected TypeMapMember (MemberInfo mi, DataMemberAttribute dma)
285                 {
286                         this.mi = mi;
287                         this.dma = dma;
288                 }
289
290                 public string Name {
291                         get { return dma == null ? mi.Name : dma.Name ?? mi.Name; }
292                 }
293
294                 public bool EmitDefaultValue {
295                         get { return dma != null && dma.EmitDefaultValue; }
296                 }
297
298                 public bool IsRequired {
299                         get { return dma != null && dma.IsRequired; }
300                 }
301
302                 public int Order {
303                         get { return dma != null ? dma.Order : -1; }
304                 }
305
306                 public abstract Type Type { get; }
307
308                 public abstract object GetMemberOf (object owner);
309
310                 public abstract void SetMemberValue (object owner, JsonSerializationReader value);
311         }
312
313         class TypeMapField : TypeMapMember
314         {
315                 FieldInfo field;
316
317                 public TypeMapField (FieldInfo fi, DataMemberAttribute dma)
318                         : base (fi, dma)
319                 {
320                         this.field = fi;
321                 }
322
323                 public override Type Type {
324                         get { return field.FieldType; }
325                 }
326
327                 public override object GetMemberOf (object owner)
328                 {
329                         return field.GetValue (owner);
330                 }
331                 
332                 public override void SetMemberValue (object owner, JsonSerializationReader jsr)
333                 {
334                         field.SetValue (owner, jsr.ReadObject (this.Type));
335                 }
336         }
337
338         class TypeMapProperty : TypeMapMember
339         {
340                 PropertyInfo property;
341
342                 public TypeMapProperty (PropertyInfo pi, DataMemberAttribute dma)
343                         : base (pi, dma)
344                 {
345                         this.property = pi;
346                 }
347
348                 public override Type Type {
349                         get { return property.PropertyType; }
350                 }
351
352                 public override object GetMemberOf (object owner)
353                 {
354                         return property.GetValue (owner, null);
355                 }
356
357                 public override void SetMemberValue (object owner, JsonSerializationReader jsr)
358                 {
359                         var pSetter = this.property.GetSetMethod (true);
360                         if (pSetter != null) {
361                                 property.SetValue (owner, jsr.ReadObject (this.Type), null);
362                                 
363                         } else { // no setter
364                                 var oldValue = property.GetValue (owner, null);
365                                 try {
366                                         jsr.ReadObject (this.Type, oldValue);
367                                 } catch (MissingMethodException e) {
368                                         throw new InvalidDataContractException (string.Format ("No set method for property '{0}' "
369                                                 + "in type '{1}'.", this.property.Name, this.property.PropertyType.FullName), e);
370                                 }
371                         }
372                 }
373         }
374 }