2010-01-08 Atsushi Enomoto <atsushi@ximian.com>
[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.Serialization;
37 using System.Text;
38 using System.Xml;
39
40 namespace System.Runtime.Serialization.Json
41 {
42         class TypeMap
43         {
44                 static bool IsInvalidNCName (string name)
45                 {
46                         if (name == null || name.Length == 0)
47                                 return true;
48                         try {
49                                 XmlConvert.VerifyNCName (name);
50                         } catch (XmlException) {
51                                 return true;
52                         }
53                         return false;
54                 }
55
56                 public static TypeMap CreateTypeMap (Type type)
57                 {
58                         object [] atts = type.GetCustomAttributes (typeof (DataContractAttribute), true);
59                         if (atts.Length == 1)
60                                 return CreateTypeMap (type, (DataContractAttribute) atts [0]);
61
62                         atts = type.GetCustomAttributes (typeof (SerializableAttribute), false);
63                         if (atts.Length == 1)
64                                 return CreateTypeMap (type, null);
65
66                         if (IsPrimitiveType (type))
67                                 return null;
68
69                         return CreateDefaultTypeMap (type);
70                 }
71
72                 static bool IsPrimitiveType (Type type)
73                 {
74                         if (type.IsEnum)
75                                 return true;
76                         if (Type.GetTypeCode (type) != TypeCode.Object)
77                                 return true; // FIXME: it is likely hacky
78                         return false;
79                 }
80
81                 static TypeMap CreateDefaultTypeMap (Type type)
82                 {
83                         var l = new List<TypeMapMember> ();
84                         foreach (var fi in type.GetFields ())
85                                 l.Add (new TypeMapField (fi, null));
86                         foreach (var pi in type.GetProperties ())
87                                 if (pi.CanRead && pi.CanWrite)
88                                         l.Add (new TypeMapProperty (pi, null));
89                         l.Sort ((x, y) => x.Order != y.Order ? x.Order - y.Order : String.Compare (x.Name, y.Name, StringComparison.Ordinal));
90                         return new TypeMap (type, null, l.ToArray ());
91                 }
92
93                 static bool IsCollection (Type type)
94                 {
95                         if (type.GetInterface ("System.Collections.IList", false) != null)
96                                 return true;
97                         if (type.GetInterface ("System.Collections.Generic.IList`1", false) != null)
98                                 return true;
99                         if (type.GetInterface ("System.Collections.Generic.ICollection`1", false) != null)
100                                 return true;
101                         return false;
102                 }
103
104                 static TypeMap CreateTypeMap (Type type, DataContractAttribute dca)
105                 {
106                         if (dca != null && dca.Name != null && IsInvalidNCName (dca.Name))
107                                 throw new InvalidDataContractException (String.Format ("DataContractAttribute for type '{0}' has an invalid name", type));
108
109                         List<TypeMapMember> members = new List<TypeMapMember> ();
110
111                         foreach (FieldInfo fi in type.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
112                                 if (dca != null) {
113                                         object [] atts = fi.GetCustomAttributes (typeof (DataMemberAttribute), true);
114                                         if (atts.Length == 0)
115                                                 continue;
116                                         DataMemberAttribute dma = (DataMemberAttribute) atts [0];
117                                         members.Add (new TypeMapField (fi, dma));
118                                 } else {
119                                         if (fi.GetCustomAttributes (typeof (IgnoreDataMemberAttribute), false).Length > 0)
120                                                 continue;
121                                         members.Add (new TypeMapField (fi, null));
122                                 }
123                         }
124
125                         if (dca != null) {
126                                 foreach (PropertyInfo pi in type.GetProperties (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
127                                         object [] atts = pi.GetCustomAttributes (typeof (DataMemberAttribute), true);
128                                         if (atts.Length == 0)
129                                                 continue;
130                                         if (pi.GetIndexParameters ().Length > 0)
131                                                 continue;
132                                         if (IsCollection (pi.PropertyType)) {
133                                                 if (!pi.CanRead)
134                                                         throw new InvalidDataContractException (String.Format ("Property {0} must have a getter", pi));
135                                         }
136                                         else if (!pi.CanRead || !pi.CanWrite)
137                                                 throw new InvalidDataContractException (String.Format ("Non-collection property {0} must have both getter and setter", pi));
138                                         DataMemberAttribute dma = (DataMemberAttribute) atts [0];
139                                         members.Add (new TypeMapProperty (pi, dma));
140                                 }
141                         }
142
143                         members.Sort (delegate (TypeMapMember m1, TypeMapMember m2) { return m1.Order != m2.Order ? m1.Order - m2.Order : String.CompareOrdinal (m1.Name, m2.Name); });
144                         return new TypeMap (type, dca == null ? null : dca.Name, members.ToArray ());
145                 }
146
147                 Type type;
148                 string element;
149                 TypeMapMember [] members;
150
151                 public TypeMap (Type type, string element, TypeMapMember [] orderedMembers)
152                 {
153                         this.type = type;
154                         this.element = element;
155                         this.members = orderedMembers;
156                 }
157
158                 public void Serialize (JsonSerializationWriter outputter, object graph)
159                 {
160                         foreach (TypeMapMember member in members) {
161                                 object memberObj = member.GetMemberOf (graph);
162                                 // FIXME: consider EmitDefaultValue
163                                 outputter.Writer.WriteStartElement (member.Name);
164                                 outputter.WriteObjectContent (memberObj, false, false);
165                                 outputter.Writer.WriteEndElement ();
166                         }
167                 }
168
169                 public object Deserialize (JsonSerializationReader jsr)
170                 {
171                         XmlReader reader = jsr.Reader;
172                         object ret = FormatterServices.GetUninitializedObject (type);
173                         Dictionary<TypeMapMember,bool> filled = new Dictionary<TypeMapMember,bool> ();
174
175                         reader.ReadStartElement ();
176                         for (reader.MoveToContent (); reader.NodeType != XmlNodeType.EndElement; reader.MoveToContent ()) {
177                                 bool consumed = false;
178                                 for (int i = 0; i < members.Length; i++) {
179                                         TypeMapMember mm = members [i];
180                                         if (mm.Name == reader.LocalName && reader.NamespaceURI == String.Empty) {
181                                                 if (filled.ContainsKey (mm))
182                                                         throw new SerializationException (String.Format ("Object content '{0}' for '{1}' already appeared in the reader", reader.LocalName, type));
183                                                 mm.SetMemberValue (ret, jsr.ReadObject (mm.Type));
184                                                 filled [mm] = true;
185                                                 consumed = true;
186                                                 break;
187                                         }
188                                 }
189                                 if (!consumed)
190                                         reader.Skip ();
191                         }
192                         reader.ReadEndElement ();
193                         return ret;
194                 }
195         }
196
197         abstract class TypeMapMember
198         {
199                 MemberInfo mi;
200                 DataMemberAttribute dma;
201
202                 protected TypeMapMember (MemberInfo mi, DataMemberAttribute dma)
203                 {
204                         this.mi = mi;
205                         this.dma = dma;
206                 }
207
208                 public string Name {
209                         get { return dma == null ? mi.Name : dma.Name ?? mi.Name; }
210                 }
211
212                 public bool EmitDefaultValue {
213                         get { return dma != null && dma.EmitDefaultValue; }
214                 }
215
216                 public bool IsRequired {
217                         get { return dma != null && dma.IsRequired; }
218                 }
219
220                 public int Order {
221                         get { return dma != null ? dma.Order : -1; }
222                 }
223
224                 public abstract Type Type { get; }
225
226                 public abstract object GetMemberOf (object owner);
227
228                 public abstract void SetMemberValue (object owner, object value);
229         }
230
231         class TypeMapField : TypeMapMember
232         {
233                 FieldInfo field;
234
235                 public TypeMapField (FieldInfo fi, DataMemberAttribute dma)
236                         : base (fi, dma)
237                 {
238                         this.field = fi;
239                 }
240
241                 public override Type Type {
242                         get { return field.FieldType; }
243                 }
244
245                 public override object GetMemberOf (object owner)
246                 {
247                         return field.GetValue (owner);
248                 }
249
250                 public override void SetMemberValue (object owner, object value)
251                 {
252                         field.SetValue (owner, value);
253                 }
254         }
255
256         class TypeMapProperty : TypeMapMember
257         {
258                 PropertyInfo property;
259
260                 public TypeMapProperty (PropertyInfo pi, DataMemberAttribute dma)
261                         : base (pi, dma)
262                 {
263                         this.property = pi;
264                 }
265
266                 public override Type Type {
267                         get { return property.PropertyType; }
268                 }
269
270                 public override object GetMemberOf (object owner)
271                 {
272                         return property.GetValue (owner, null);
273                 }
274
275                 public override void SetMemberValue (object owner, object value)
276                 {
277                         property.SetValue (owner, value, null);
278                 }
279         }
280 }