From 60643f023ffbb7a62740b18369b1e7200318ec21 Mon Sep 17 00:00:00 2001 From: Kazuki Oikawa Date: Sun, 10 Apr 2011 12:52:14 +0900 Subject: [PATCH] csharp: add ObjectPacker --- csharp/msgpack.tests/ObjectPackerTests.cs | 95 ++++++++++ csharp/msgpack.tests/msgpack.tests.csproj | 1 + csharp/msgpack/MsgPackReader.cs | 9 +- csharp/msgpack/MsgPackWriter.cs | 8 +- csharp/msgpack/ObjectPacker.cs | 220 ++++++++++++++++++++++ csharp/msgpack/ReflectionCache.cs | 44 +++++ csharp/msgpack/ReflectionCacheEntry.cs | 28 +++ csharp/msgpack/msgpack.csproj | 5 +- 8 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 csharp/msgpack.tests/ObjectPackerTests.cs create mode 100644 csharp/msgpack/ObjectPacker.cs create mode 100644 csharp/msgpack/ReflectionCache.cs create mode 100644 csharp/msgpack/ReflectionCacheEntry.cs diff --git a/csharp/msgpack.tests/ObjectPackerTests.cs b/csharp/msgpack.tests/ObjectPackerTests.cs new file mode 100644 index 00000000..b52e87c9 --- /dev/null +++ b/csharp/msgpack.tests/ObjectPackerTests.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace msgpack.tests +{ + [TestFixture] + public class ObjectPackerTests + { + public static void Main () + { + ObjectPackerTests tests = new ObjectPackerTests (); + tests.TestA (); + } + + [Test] + public void TestA () + { + ObjectPacker packer = new ObjectPacker (); + TestA_Class obj0 = new TestA_Class (); + TestA_Class obj1 = packer.Unpack (packer.Pack (obj0)); + obj0.Check (obj1); + } + + [Test] + public void TestB () + { + ObjectPacker packer = new ObjectPacker (); + Dictionary dic = new Dictionary (); + Random rnd = new Random (); + int size = rnd.Next () & 0xff; + for (int i = 0; i < size; i ++) + dic[rnd.Next()] = rnd.Next (); + Dictionary dic_ = packer.Unpack> (packer.Pack (dic)); + Assert.AreEqual (dic, dic_); + } + + class TestA_Class + { + public bool a; + public byte b; + public sbyte c; + public short d; + public ushort e; + public int f; + public uint g; + public long h; + public ulong i; + public float j; + public double k; + public int[] l; + public string m; + + public TestA_Class () + { + Random rnd = new Random (); + a = rnd.NextDouble () < 0.5; + b = (byte)rnd.Next (); + c = (sbyte)rnd.Next (); + d = (short)rnd.Next (); + e = (ushort)rnd.Next (); + f = (int)rnd.Next (); + g = (uint)rnd.Next (); + h = (long)rnd.Next (); + i = (ulong)rnd.Next (); + j = (float)rnd.NextDouble (); + k = (double)rnd.NextDouble (); + l = new int[rnd.Next () & 0xff]; + for (int z = 0; z < l.Length; z ++) + l[z] = rnd.Next (); + + byte[] buf = new byte[rnd.Next() & 0xff]; + rnd.NextBytes (buf); + m = Convert.ToBase64String (buf); + } + + public void Check (TestA_Class other) + { + Assert.AreEqual (this.a, other.a); + Assert.AreEqual (this.b, other.b); + Assert.AreEqual (this.c, other.c); + Assert.AreEqual (this.d, other.d); + Assert.AreEqual (this.e, other.e); + Assert.AreEqual (this.f, other.f); + Assert.AreEqual (this.g, other.g); + Assert.AreEqual (this.h, other.h); + Assert.AreEqual (this.i, other.i); + Assert.AreEqual (this.j, other.j); + Assert.AreEqual (this.k, other.k); + Assert.AreEqual (this.l, other.l); + Assert.AreEqual (this.m, other.m); + } + } + } +} diff --git a/csharp/msgpack.tests/msgpack.tests.csproj b/csharp/msgpack.tests/msgpack.tests.csproj index 5d27a80a..15ceda98 100644 --- a/csharp/msgpack.tests/msgpack.tests.csproj +++ b/csharp/msgpack.tests/msgpack.tests.csproj @@ -44,6 +44,7 @@ + diff --git a/csharp/msgpack/MsgPackReader.cs b/csharp/msgpack/MsgPackReader.cs index 7f281324..1a338e7f 100644 --- a/csharp/msgpack/MsgPackReader.cs +++ b/csharp/msgpack/MsgPackReader.cs @@ -37,6 +37,11 @@ namespace msgpack this.Type == TypePrefixes.Int32; } + public bool IsBoolean () + { + return this.Type == TypePrefixes.True || this.Type == TypePrefixes.False; + } + public bool IsSigned64 () { return this.Type == TypePrefixes.Int64; @@ -44,7 +49,8 @@ namespace msgpack public bool IsUnsigned () { - return this.Type == TypePrefixes.UInt8 || + return this.Type == TypePrefixes.PositiveFixNum || + this.Type == TypePrefixes.UInt8 || this.Type == TypePrefixes.UInt16 || this.Type == TypePrefixes.UInt32; } @@ -132,6 +138,7 @@ namespace msgpack break; case TypePrefixes.PositiveFixNum: ValueSigned = x & 0x7f; + ValueUnsigned = (uint)ValueSigned; break; case TypePrefixes.UInt8: x = _strm.ReadByte (); diff --git a/csharp/msgpack/MsgPackWriter.cs b/csharp/msgpack/MsgPackWriter.cs index 8db861f5..abda9c01 100644 --- a/csharp/msgpack/MsgPackWriter.cs +++ b/csharp/msgpack/MsgPackWriter.cs @@ -15,7 +15,7 @@ namespace msgpack _strm = strm; } - void Write (byte x) + public void Write (byte x) { if (x < 128) { _strm.WriteByte (x); @@ -27,7 +27,7 @@ namespace msgpack } } - void Write (ushort x) + public void Write (ushort x) { if (x < 0x100) { Write ((byte)x); @@ -74,7 +74,7 @@ namespace msgpack } } - void Write (sbyte x) + public void Write (sbyte x) { if (x >= -32 && x <= -1) { _strm.WriteByte ((byte)(0xe0 | (byte)x)); @@ -88,7 +88,7 @@ namespace msgpack } } - void Write (short x) + public void Write (short x) { if (x >= sbyte.MinValue && x <= sbyte.MaxValue) { Write ((sbyte)x); diff --git a/csharp/msgpack/ObjectPacker.cs b/csharp/msgpack/ObjectPacker.cs new file mode 100644 index 00000000..c3529959 --- /dev/null +++ b/csharp/msgpack/ObjectPacker.cs @@ -0,0 +1,220 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; + +namespace msgpack +{ + public class ObjectPacker + { + byte[] _buf = new byte[64]; + Encoding _encoding = Encoding.UTF8; + static Dictionary PackerMapping; + static Dictionary UnpackerMapping; + + delegate void PackDelegate (ObjectPacker packer, MsgPackWriter writer, object o); + delegate object UnpackDelegate (ObjectPacker packer, MsgPackReader reader); + + static ObjectPacker () + { + PackerMapping = new Dictionary (); + UnpackerMapping = new Dictionary (); + + PackerMapping.Add (typeof (string), StringPacker); + UnpackerMapping.Add (typeof (string), StringUnpacker); + } + + public byte[] Pack (object o) + { + using (MemoryStream ms = new MemoryStream ()) { + Pack (ms, o); + return ms.ToArray (); + } + } + + public void Pack (Stream strm, object o) + { + if (o != null && o.GetType ().IsPrimitive) + throw new NotSupportedException (); + MsgPackWriter writer = new MsgPackWriter (strm); + Pack (writer, o); + } + + void Pack (MsgPackWriter writer, object o) + { + if (o == null) { + writer.WriteNil (); + return; + } + + Type t = o.GetType (); + if (t.IsPrimitive) { + if (t.Equals (typeof (int))) writer.Write ((int)o); + else if (t.Equals (typeof (uint))) writer.Write ((uint)o); + else if (t.Equals (typeof (float))) writer.Write ((float)o); + else if (t.Equals (typeof (double))) writer.Write ((double)o); + else if (t.Equals (typeof (long))) writer.Write ((long)o); + else if (t.Equals (typeof (ulong))) writer.Write ((ulong)o); + else if (t.Equals (typeof (bool))) writer.Write ((bool)o); + else if (t.Equals (typeof (byte))) writer.Write ((byte)o); + else if (t.Equals (typeof (sbyte))) writer.Write ((sbyte)o); + else if (t.Equals (typeof (short))) writer.Write ((short)o); + else if (t.Equals (typeof (ushort)) || t.Equals (typeof (char))) writer.Write ((ushort)o); + else throw new NotSupportedException (); + return; + } + + PackDelegate packer; + if (PackerMapping.TryGetValue (t, out packer)) { + packer (this, writer, o); + return; + } + + if (t.IsArray) { + Array ary = (Array)o; + writer.WriteArrayHeader (ary.Length); + for (int i = 0; i < ary.Length; i ++) + Pack (writer, ary.GetValue (i)); + return; + } + + ReflectionCacheEntry entry = ReflectionCache.Lookup (t); + writer.WriteMapHeader (entry.FieldMap.Count); + foreach (KeyValuePair pair in entry.FieldMap) { + writer.Write (pair.Key); + object v = pair.Value.GetValue (o); + if (pair.Value.FieldType.IsInterface && v != null) { + writer.WriteArrayHeader (2); + writer.Write (v.GetType().FullName); + } + Pack (writer, v); + } + } + + public T Unpack (byte[] buf) + { + return Unpack (buf, 0, buf.Length); + } + + public T Unpack (byte[] buf, int offset, int size) + { + using (MemoryStream ms = new MemoryStream (buf, offset, size)) { + return Unpack (ms); + } + } + + public T Unpack (Stream strm) + { + if (typeof (T).IsPrimitive) + throw new NotSupportedException (); + MsgPackReader reader = new MsgPackReader (strm); + return (T)Unpack (reader, typeof (T)); + } + + object Unpack (MsgPackReader reader, Type t) + { + if (t.IsPrimitive) { + if (!reader.Read ()) throw new FormatException (); + if (t.Equals (typeof (int)) && reader.IsSigned ()) return reader.ValueSigned; + else if (t.Equals (typeof (uint)) && reader.IsUnsigned ()) return reader.ValueUnsigned; + else if (t.Equals (typeof (float)) && reader.Type == TypePrefixes.Float) return reader.ValueFloat; + else if (t.Equals (typeof (double)) && reader.Type == TypePrefixes.Double) return reader.ValueDouble; + else if (t.Equals (typeof (long))) { + if (reader.IsSigned64 ()) + return reader.ValueSigned64; + if (reader.IsSigned ()) + return (long)reader.ValueSigned; + } else if (t.Equals (typeof (ulong))) { + if (reader.IsUnsigned64 ()) + return reader.ValueUnsigned64; + if (reader.IsUnsigned ()) + return (ulong)reader.ValueUnsigned; + } else if (t.Equals (typeof (bool)) && reader.IsBoolean ()) return (reader.Type == TypePrefixes.True); + else if (t.Equals (typeof (byte)) && reader.IsUnsigned ()) return (byte)reader.ValueUnsigned; + else if (t.Equals (typeof (sbyte)) && reader.IsSigned ()) return (sbyte)reader.ValueSigned; + else if (t.Equals (typeof (short)) && reader.IsSigned ()) return (short)reader.ValueSigned; + else if (t.Equals (typeof (ushort)) && reader.IsUnsigned ()) return (ushort)reader.ValueUnsigned; + else if (t.Equals (typeof (char)) && reader.IsUnsigned ()) return (char)reader.ValueUnsigned; + else throw new NotSupportedException (); + } + + UnpackDelegate unpacker; + if (UnpackerMapping.TryGetValue (t, out unpacker)) + return unpacker (this, reader); + + if (t.IsArray) { + if (!reader.Read () || !reader.IsArray ()) + throw new FormatException (); + Type et = t.GetElementType (); + Array ary = Array.CreateInstance (et, (int)reader.Length); + for (int i = 0; i < ary.Length; i ++) + ary.SetValue (Unpack (reader, et), i); + return ary; + } + + if (!reader.Read ()) + throw new FormatException (); + if (reader.Type == TypePrefixes.Nil) + return null; + if (t.IsInterface) { + if (reader.Type != TypePrefixes.FixArray && reader.Length != 2) + throw new FormatException (); + if (!reader.Read () || !reader.IsRaw ()) + throw new FormatException (); + CheckBufferSize ((int)reader.Length); + reader.ReadValueRaw (_buf, 0, (int)reader.Length); + t = Type.GetType (Encoding.UTF8.GetString (_buf, 0, (int)reader.Length)); + if (!reader.Read () || reader.Type == TypePrefixes.Nil) + throw new FormatException (); + } + if (!reader.IsMap ()) + throw new FormatException (); + + object o = FormatterServices.GetUninitializedObject (t); + ReflectionCacheEntry entry = ReflectionCache.Lookup (t); + int members = (int)reader.Length; + for (int i = 0; i < members; i ++) { + if (!reader.Read () || !reader.IsRaw ()) + throw new FormatException (); + CheckBufferSize ((int)reader.Length); + reader.ReadValueRaw (_buf, 0, (int)reader.Length); + string name = Encoding.UTF8.GetString (_buf, 0, (int)reader.Length); + FieldInfo f; + if (!entry.FieldMap.TryGetValue (name, out f)) + throw new FormatException (); + f.SetValue (o, Unpack (reader, f.FieldType)); + } + + IDeserializationCallback callback = o as IDeserializationCallback; + if (callback != null) + callback.OnDeserialization (this); + return o; + } + + void CheckBufferSize (int size) + { + if (_buf.Length < size) + Array.Resize (ref _buf, size); + } + + static void StringPacker (ObjectPacker packer, MsgPackWriter writer, object o) + { + writer.Write (Encoding.UTF8.GetBytes ((string)o)); + } + + static object StringUnpacker (ObjectPacker packer, MsgPackReader reader) + { + if (!reader.Read ()) + throw new FormatException (); + if (reader.Type == TypePrefixes.Nil) + return null; + if (!reader.IsRaw ()) + throw new FormatException (); + packer.CheckBufferSize ((int)reader.Length); + reader.ReadValueRaw (packer._buf, 0, (int)reader.Length); + return Encoding.UTF8.GetString (packer._buf, 0, (int)reader.Length); + } + } +} diff --git a/csharp/msgpack/ReflectionCache.cs b/csharp/msgpack/ReflectionCache.cs new file mode 100644 index 00000000..d7f97264 --- /dev/null +++ b/csharp/msgpack/ReflectionCache.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace msgpack +{ + public static class ReflectionCache + { + static Dictionary _cache; + + static ReflectionCache () + { + _cache = new Dictionary (); + } + + public static ReflectionCacheEntry Lookup (Type type) + { + ReflectionCacheEntry entry; + lock (_cache) { + if (_cache.TryGetValue (type, out entry)) + return entry; + } + + entry = new ReflectionCacheEntry (type); + lock (_cache) { + _cache[type] = entry; + } + return entry; + } + + public static void RemoveCache (Type type) + { + lock (_cache) { + _cache.Remove (type); + } + } + + public static void Clear () + { + lock (_cache) { + _cache.Clear (); + } + } + } +} diff --git a/csharp/msgpack/ReflectionCacheEntry.cs b/csharp/msgpack/ReflectionCacheEntry.cs new file mode 100644 index 00000000..9a27f70b --- /dev/null +++ b/csharp/msgpack/ReflectionCacheEntry.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace msgpack +{ + public class ReflectionCacheEntry + { + const BindingFlags FieldBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.SetField; + + public ReflectionCacheEntry (Type t) + { + FieldInfo[] fields = t.GetFields (FieldBindingFlags); + IDictionary map = new Dictionary (fields.Length); + for (int i = 0; i < fields.Length; i ++) { + FieldInfo f = fields[i]; + string name = f.Name; + int pos; + if (name[0] == '<' && (pos = name.IndexOf ('>')) > 1) + name = name.Substring (1, pos - 1); // Auto-Property (\<.+\>) + map[name] = f; + } + FieldMap = map; + } + + public IDictionary FieldMap { get; private set; } + } +} diff --git a/csharp/msgpack/msgpack.csproj b/csharp/msgpack/msgpack.csproj index 237becc8..1a778ae8 100644 --- a/csharp/msgpack/msgpack.csproj +++ b/csharp/msgpack/msgpack.csproj @@ -1,4 +1,4 @@ - + Debug @@ -40,6 +40,9 @@ + + +