/src/JsonFx/Model/ModelWalker.cs
C# | 430 lines | 321 code | 56 blank | 53 comment | 48 complexity | 59dfc6e1c0009b60497b4f834ba1d538 MD5 | raw file
1#region License
2/*---------------------------------------------------------------------------------*\
3
4 Distributed under the terms of an MIT-style license:
5
6 The MIT License
7
8 Copyright (c) 2006-2010 Stephen M. McKamey
9
10 Permission is hereby granted, free of charge, to any person obtaining a copy
11 of this software and associated documentation files (the "Software"), to deal
12 in the Software without restriction, including without limitation the rights
13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 copies of the Software, and to permit persons to whom the Software is
15 furnished to do so, subject to the following conditions:
16
17 The above copyright notice and this permission notice shall be included in
18 all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 THE SOFTWARE.
27
28\*---------------------------------------------------------------------------------*/
29#endregion License
30
31using System;
32using System.Collections;
33using System.Collections.Generic;
34
35using JsonFx.CodeGen;
36using JsonFx.Serialization;
37using JsonFx.Serialization.Filters;
38using JsonFx.Serialization.GraphCycles;
39using JsonFx.Serialization.Resolvers;
40
41namespace JsonFx.Model
42{
43 /// <summary>
44 /// Generates a sequence of tokens from an object graph
45 /// </summary>
46 public class ModelWalker : IObjectWalker<ModelTokenType>
47 {
48 #region Fields
49
50 private readonly DataWriterSettings Settings;
51 private readonly IEnumerable<IDataFilter<ModelTokenType>> Filters;
52
53 #endregion Fields
54
55 #region Init
56
57 /// <summary>
58 /// Ctor
59 /// </summary>
60 /// <param name="settings"></param>
61 public ModelWalker(DataWriterSettings settings)
62 {
63 if (settings == null)
64 {
65 throw new ArgumentNullException("settings");
66 }
67 this.Settings = settings;
68
69 var filters = new List<IDataFilter<ModelTokenType>>();
70 if (settings.Filters != null)
71 {
72 foreach (var filter in settings.Filters)
73 {
74 if (filter != null)
75 {
76 filters.Add(filter);
77 }
78 }
79 }
80 this.Filters = filters;
81 }
82
83 #endregion Init
84
85 #region IObjectWalker<T> Methods
86
87 /// <summary>
88 /// Generates a sequence of tokens representing the value
89 /// </summary>
90 /// <param name="value"></param>
91 /// <returns></returns>
92 public IEnumerable<Token<ModelTokenType>> GetTokens(object value)
93 {
94 ICycleDetector detector;
95 switch (this.Settings.GraphCycles)
96 {
97 case GraphCycleType.MaxDepth:
98 {
99 detector = new DepthCounter(this.Settings.MaxDepth);
100 break;
101 }
102 default:
103 {
104 detector = new ReferenceSet();
105 break;
106 }
107 }
108
109 List<Token<ModelTokenType>> tokens = new List<Token<ModelTokenType>>();
110 this.GetTokens(tokens, detector, value);
111 return tokens;
112 }
113
114 #endregion IObjectWalker<T> Methods
115
116 #region Walker Methods
117
118 private void GetTokens(List<Token<ModelTokenType>> tokens, ICycleDetector detector, object value)
119 {
120 if (value == null)
121 {
122 tokens.Add(ModelGrammar.TokenNull);
123 return;
124 }
125
126 // test for cycles
127 if (detector.Add(value))
128 {
129 switch (this.Settings.GraphCycles)
130 {
131 case GraphCycleType.Reference:
132 {
133 // no need to remove value as was duplicate reference
134 throw new GraphCycleException(GraphCycleType.Reference, "Graph cycle detected: repeated references");
135 }
136 case GraphCycleType.MaxDepth:
137 {
138 throw new GraphCycleException(GraphCycleType.MaxDepth, "Graph cycle potentially detected: maximum depth exceeded");
139 }
140 default:
141 case GraphCycleType.Ignore:
142 {
143 // no need to remove value as was duplicate reference
144 // replace cycle with null
145 tokens.Add(ModelGrammar.TokenNull);
146 return;
147 }
148 }
149 }
150
151 try
152 {
153 foreach (var filter in this.Filters)
154 {
155 IEnumerable<Token<ModelTokenType>> filterResult;
156 if (filter.TryWrite(this.Settings, value, out filterResult))
157 {
158 // found a successful match
159 tokens.AddRange(filterResult);
160 return;
161 }
162 }
163
164 Type type = value.GetType();
165
166 // must test enumerations before other value types
167 if (type.IsEnum)
168 {
169 tokens.Add(ModelGrammar.TokenPrimitive((Enum)value));
170 return;
171 }
172
173 // Type.GetTypeCode() allows us to more efficiently switch type
174 switch (Type.GetTypeCode(type))
175 {
176 case TypeCode.Boolean:
177 {
178 tokens.Add(true.Equals(value) ? ModelGrammar.TokenTrue : ModelGrammar.TokenFalse);
179 return;
180 }
181 case TypeCode.Byte:
182 case TypeCode.Decimal:
183 case TypeCode.Int16:
184 case TypeCode.Int32:
185 case TypeCode.Int64:
186 case TypeCode.SByte:
187 case TypeCode.UInt16:
188 case TypeCode.UInt32:
189 case TypeCode.UInt64:
190 {
191 tokens.Add(ModelGrammar.TokenPrimitive((ValueType)value));
192 return;
193 }
194 case TypeCode.Double:
195 {
196 double doubleVal = (double)value;
197
198 if (Double.IsNaN(doubleVal))
199 {
200 tokens.Add(ModelGrammar.TokenNaN);
201 }
202 else if (Double.IsPositiveInfinity(doubleVal))
203 {
204 tokens.Add(ModelGrammar.TokenPositiveInfinity);
205 }
206 else if (Double.IsNegativeInfinity(doubleVal))
207 {
208 tokens.Add(ModelGrammar.TokenNegativeInfinity);
209 }
210 else
211 {
212 tokens.Add(ModelGrammar.TokenPrimitive(doubleVal));
213 }
214 return;
215 }
216 case TypeCode.Single:
217 {
218 float floatVal = (float)value;
219
220 if (Single.IsNaN(floatVal))
221 {
222 // use the Double equivalent
223 tokens.Add(ModelGrammar.TokenNaN);
224 }
225 else if (Single.IsPositiveInfinity(floatVal))
226 {
227 // use the Double equivalent
228 tokens.Add(ModelGrammar.TokenPositiveInfinity);
229 }
230 else if (Single.IsNegativeInfinity(floatVal))
231 {
232 // use the Double equivalent
233 tokens.Add(ModelGrammar.TokenNegativeInfinity);
234 }
235 else
236 {
237 tokens.Add(ModelGrammar.TokenPrimitive(floatVal));
238 }
239 return;
240 }
241 case TypeCode.Char:
242 case TypeCode.DateTime:
243 case TypeCode.String:
244 {
245 tokens.Add(ModelGrammar.TokenPrimitive(value));
246 return;
247 }
248 case TypeCode.DBNull:
249 case TypeCode.Empty:
250 {
251 tokens.Add(ModelGrammar.TokenNull);
252 return;
253 }
254 }
255
256 if (value is IEnumerable)
257 {
258 this.GetArrayTokens(tokens, detector, (IEnumerable)value);
259 return;
260 }
261
262 if (value is Guid || value is Uri || value is Version)
263 {
264 tokens.Add(ModelGrammar.TokenPrimitive(value));
265 return;
266 }
267
268 if (value is TimeSpan)
269 {
270 tokens.Add(ModelGrammar.TokenPrimitive((TimeSpan)value));
271 return;
272 }
273
274#if NET40 && !WINDOWS_PHONE
275 if (value is System.Dynamic.DynamicObject)
276 {
277 // TODO: expand to all IDynamicMetaObjectProvider?
278 this.GetObjectTokens(tokens, detector, type, (System.Dynamic.DynamicObject)value);
279 return;
280 }
281#endif
282
283 // all other structs and classes
284 this.GetObjectTokens(tokens, detector, type, value);
285 }
286 finally
287 {
288 detector.Remove(value);
289 }
290 }
291
292 private void GetArrayTokens(List<Token<ModelTokenType>> tokens, ICycleDetector detector, IEnumerable value)
293 {
294 DataName typeName = this.GetTypeName(value);
295 IEnumerator enumerator = value.GetEnumerator();
296
297 if (enumerator is IEnumerator<KeyValuePair<string, object>>)
298 {
299 this.GetObjectTokens(tokens, detector, typeName, (IEnumerator<KeyValuePair<string, object>>)enumerator);
300 return;
301 }
302
303 if (enumerator is IDictionaryEnumerator)
304 {
305 this.GetObjectTokens(tokens, detector, typeName, (IDictionaryEnumerator)enumerator);
306 return;
307 }
308
309 tokens.Add(ModelGrammar.TokenArrayBegin(typeName));
310
311 while (enumerator.MoveNext())
312 {
313 this.GetTokens(tokens, detector, enumerator.Current);
314 }
315
316 tokens.Add(ModelGrammar.TokenArrayEnd);
317 }
318
319 private void GetObjectTokens(List<Token<ModelTokenType>> tokens, ICycleDetector detector, DataName typeName, IDictionaryEnumerator enumerator)
320 {
321 tokens.Add(ModelGrammar.TokenObjectBegin(typeName));
322
323 while (enumerator.MoveNext())
324 {
325 tokens.Add(ModelGrammar.TokenProperty(enumerator.Key));
326 this.GetTokens(tokens, detector, enumerator.Value);
327 }
328
329 tokens.Add(ModelGrammar.TokenObjectEnd);
330 }
331
332 private void GetObjectTokens(List<Token<ModelTokenType>> tokens, ICycleDetector detector, DataName typeName, IEnumerator<KeyValuePair<string, object>> enumerator)
333 {
334 tokens.Add(ModelGrammar.TokenObjectBegin(typeName));
335
336 while (enumerator.MoveNext())
337 {
338 KeyValuePair<string, object> pair = enumerator.Current;
339 tokens.Add(ModelGrammar.TokenProperty(pair.Key));
340 this.GetTokens(tokens, detector, pair.Value);
341 }
342
343 tokens.Add(ModelGrammar.TokenObjectEnd);
344 }
345
346#if NET40 && !WINDOWS_PHONE
347 private void GetObjectTokens(List<Token<ModelTokenType>> tokens, ICycleDetector detector, Type type, System.Dynamic.DynamicObject value)
348 {
349 DataName typeName = this.GetTypeName(value);
350 tokens.Add(ModelGrammar.TokenObjectBegin(typeName));
351
352 foreach (var memberName in value.GetDynamicMemberNames())
353 {
354 object propertyValue;
355 if (!value.TryGetMember(new DynamicGetter(memberName), out propertyValue))
356 {
357 continue;
358 }
359
360 tokens.Add(ModelGrammar.TokenProperty(memberName));
361 this.GetTokens(tokens, detector, propertyValue);
362 }
363
364 tokens.Add(ModelGrammar.TokenObjectEnd);
365 }
366#endif
367
368 private void GetObjectTokens(List<Token<ModelTokenType>> tokens, ICycleDetector detector, Type type, object value)
369 {
370 DataName typeName = this.GetTypeName(value);
371 tokens.Add(ModelGrammar.TokenObjectBegin(typeName));
372
373 IDictionary<string, MemberMap> maps = this.Settings.Resolver.LoadMaps(type);
374 if (maps == null)
375 {
376 // TODO: verify no other valid situations here
377 tokens.Add(ModelGrammar.TokenObjectEnd);
378 return;
379 }
380
381 // allow the resolver to optionally sort the members
382 IEnumerable<MemberMap> members = this.Settings.Resolver.SortMembers(maps.Values);
383
384 foreach (var map in members)
385 {
386 if (map.IsAlternate ||
387 map.Getter == null)
388 {
389 continue;
390 }
391
392 object propertyValue = map.Getter(value);
393 if (map.IsIgnored != null &&
394 map.IsIgnored(value, propertyValue))
395 {
396 continue;
397 }
398
399 tokens.Add(ModelGrammar.TokenProperty(map.DataName));
400 this.GetTokens(tokens, detector, propertyValue);
401 }
402
403 tokens.Add(ModelGrammar.TokenObjectEnd);
404 }
405
406 #endregion Walker Methods
407
408 #region Utility Methods
409
410 private DataName GetTypeName(object value)
411 {
412 IEnumerable<DataName> typeNames = this.Settings.Resolver.LoadTypeName((value != null) ? value.GetType() : null);
413
414 if (typeNames != null)
415 {
416 foreach (DataName n in typeNames)
417 {
418 if (!n.IsEmpty)
419 {
420 return n;
421 }
422 }
423 }
424
425 return DataName.Empty;
426 }
427
428 #endregion Utility Methods
429 }
430}