1 //
2 // http://code.google.com/p/servicestack/wiki/TypeSerializer
3 // ServiceStack.Text: .NET C# POCO Type Text Serializer.
4 //
5 // Authors:
6 //   Demis Bellot (demis.bellot@gmail.com)
7 //
8 // Copyright 2011 Liquidbit Ltd.
9 //
10 // Licensed under the same terms of ServiceStack: new BSD license.
11 //
12 
13 using System;
14 using System.Collections;
15 using System.Collections.Generic;
16 using System.IO;
17 using System.Reflection;
18 using System.Threading;
19 using ServiceStack.Text.Json;
20 
21 namespace ServiceStack.Text.Common
22 {
WriteMapDelegate( TextWriter writer, object oMap, WriteObjectDelegate writeKeyFn, WriteObjectDelegate writeValueFn)23 	internal delegate void WriteMapDelegate(
24 		TextWriter writer,
25 		object oMap,
26 		WriteObjectDelegate writeKeyFn,
27 		WriteObjectDelegate writeValueFn);
28 
29 	internal static class WriteDictionary<TSerializer>
30 		where TSerializer : ITypeSerializer
31 	{
32 		private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer<TSerializer>();
33 
34 		internal class MapKey
35 		{
36 			internal Type KeyType;
37 			internal Type ValueType;
38 
MapKey(Type keyType, Type valueType)39 			public MapKey(Type keyType, Type valueType)
40 			{
41 				KeyType = keyType;
42 				ValueType = valueType;
43 			}
44 
Equals(MapKey other)45 			public bool Equals(MapKey other)
46 			{
47 				if (ReferenceEquals(null, other)) return false;
48 				if (ReferenceEquals(this, other)) return true;
49 				return Equals(other.KeyType, KeyType) && Equals(other.ValueType, ValueType);
50 			}
51 
Equals(object obj)52 			public override bool Equals(object obj)
53 			{
54 				if (ReferenceEquals(null, obj)) return false;
55 				if (ReferenceEquals(this, obj)) return true;
56 				if (obj.GetType() != typeof(MapKey)) return false;
57 				return Equals((MapKey)obj);
58 			}
59 
GetHashCode()60 			public override int GetHashCode()
61 			{
62 				unchecked
63 				{
64 					return ((KeyType != null ? KeyType.GetHashCode() : 0) * 397) ^ (ValueType != null ? ValueType.GetHashCode() : 0);
65 				}
66 			}
67 		}
68 
69 		static Dictionary<MapKey, WriteMapDelegate> CacheFns = new Dictionary<MapKey, WriteMapDelegate>();
70 
71 		public static Action<TextWriter, object, WriteObjectDelegate, WriteObjectDelegate>
GetWriteGenericDictionary(Type keyType, Type valueType)72 			GetWriteGenericDictionary(Type keyType, Type valueType)
73 		{
74 			WriteMapDelegate writeFn;
75             var mapKey = new MapKey(keyType, valueType);
76             if (CacheFns.TryGetValue(mapKey, out writeFn)) return writeFn.Invoke;
77 
78             var genericType = typeof(ToStringDictionaryMethods<,,>).MakeGenericType(keyType, valueType, typeof(TSerializer));
79             var mi = genericType.GetMethod("WriteIDictionary", BindingFlags.Static | BindingFlags.Public);
80             writeFn = (WriteMapDelegate)Delegate.CreateDelegate(typeof(WriteMapDelegate), mi);
81 
82             Dictionary<MapKey, WriteMapDelegate> snapshot, newCache;
83             do
84             {
85                 snapshot = CacheFns;
86                 newCache = new Dictionary<MapKey, WriteMapDelegate>(CacheFns);
87                 newCache[mapKey] = writeFn;
88 
89             } while (!ReferenceEquals(
90                 Interlocked.CompareExchange(ref CacheFns, newCache, snapshot), snapshot));
91 
92             return writeFn.Invoke;
93 		}
94 
WriteIDictionary(TextWriter writer, object oMap)95 		public static void WriteIDictionary(TextWriter writer, object oMap)
96 		{
97 			WriteObjectDelegate writeKeyFn = null;
98 			WriteObjectDelegate writeValueFn = null;
99 
100 			writer.Write(JsWriter.MapStartChar);
101 			var encodeMapKey = false;
102 
103 			var map = (IDictionary)oMap;
104 			var ranOnce = false;
105 			foreach (var key in map.Keys)
106 			{
107 				var dictionaryValue = map[key];
108 
109                 var isNull = (dictionaryValue == null);
110                 if (isNull && !JsConfig.IncludeNullValues) continue;
111 
112 				if (writeKeyFn == null)
113 				{
114 					var keyType = key.GetType();
115 					writeKeyFn = Serializer.GetWriteFn(keyType);
116 					encodeMapKey = Serializer.GetTypeInfo(keyType).EncodeMapKey;
117 				}
118 
119 				if (writeValueFn == null)
120 					writeValueFn = Serializer.GetWriteFn(dictionaryValue.GetType());
121 
122 				JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce);
123 
124 				JsState.WritingKeyCount++;
125 				JsState.IsWritingValue = false;
126 
127 				if (encodeMapKey)
128 				{
129 					JsState.IsWritingValue = true; //prevent ""null""
130 					writer.Write(JsWriter.QuoteChar);
131 					writeKeyFn(writer, key);
132 					writer.Write(JsWriter.QuoteChar);
133 				}
134 				else
135 				{
136 					writeKeyFn(writer, key);
137 				}
138 
139 				JsState.WritingKeyCount--;
140 
141 				writer.Write(JsWriter.MapKeySeperator);
142 
143                 if (isNull)
144                 {
145                     writer.Write(JsonUtils.Null);
146                 }
147                 else
148                 {
149                     JsState.IsWritingValue = true;
150                     writeValueFn(writer, dictionaryValue);
151                     JsState.IsWritingValue = false;
152                 }
153 			}
154 
155 			writer.Write(JsWriter.MapEndChar);
156 		}
157 	}
158 
159 	internal static class ToStringDictionaryMethods<TKey, TValue, TSerializer>
160 		where TSerializer : ITypeSerializer
161 	{
162 		private static readonly ITypeSerializer Serializer = JsWriter.GetTypeSerializer<TSerializer>();
163 
WriteIDictionary( TextWriter writer, object oMap, WriteObjectDelegate writeKeyFn, WriteObjectDelegate writeValueFn)164 		public static void WriteIDictionary(
165 			TextWriter writer,
166 			object oMap,
167 			WriteObjectDelegate writeKeyFn,
168 			WriteObjectDelegate writeValueFn)
169 		{
170 			if (writer == null) return; //AOT
171 			WriteGenericIDictionary(writer, (IDictionary<TKey, TValue>)oMap, writeKeyFn, writeValueFn);
172 		}
173 
WriteGenericIDictionary( TextWriter writer, IDictionary<TKey, TValue> map, WriteObjectDelegate writeKeyFn, WriteObjectDelegate writeValueFn)174 		public static void WriteGenericIDictionary(
175 			TextWriter writer,
176 			IDictionary<TKey, TValue> map,
177 			WriteObjectDelegate writeKeyFn,
178 			WriteObjectDelegate writeValueFn)
179 		{
180 		    if (map == null)
181 		    {
182 		        writer.Write(JsonUtils.Null);
183                 return;
184 		    }
185 			writer.Write(JsWriter.MapStartChar);
186 
187 			var encodeMapKey = Serializer.GetTypeInfo(typeof(TKey)).EncodeMapKey;
188 
189 			var ranOnce = false;
190 			foreach (var kvp in map)
191 			{
192                 var isNull = (kvp.Value == null);
193                 if (isNull && !JsConfig.IncludeNullValues) continue;
194 
195 				JsWriter.WriteItemSeperatorIfRanOnce(writer, ref ranOnce);
196 
197 				JsState.WritingKeyCount++;
198                 JsState.IsWritingValue = false;
199 
200 				if (encodeMapKey)
201 				{
202 					JsState.IsWritingValue = true; //prevent ""null""
203 					writer.Write(JsWriter.QuoteChar);
204 					writeKeyFn(writer, kvp.Key);
205 					writer.Write(JsWriter.QuoteChar);
206 				}
207 				else
208 				{
209 					writeKeyFn(writer, kvp.Key);
210 				}
211 
212 				JsState.WritingKeyCount--;
213 
214 				writer.Write(JsWriter.MapKeySeperator);
215 
216                 if (isNull)
217                 {
218                     writer.Write(JsonUtils.Null);
219                 }
220                 else
221                 {
222                     JsState.IsWritingValue = true;
223                     writeValueFn(writer, kvp.Value);
224                     JsState.IsWritingValue = false;
225                 }
226 			}
227 
228 			writer.Write(JsWriter.MapEndChar);
229 		}
230 	}
231 }