THRIFT-1037 Proposed changes to support Silverlight, Windows Phone and AsyncCTP v3
Patch: Damian Mehers & Jens Geyer

git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1211880 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/csharp/src/Collections/THashSet.cs b/lib/csharp/src/Collections/THashSet.cs
index 0ddea54..e2fc8b5 100644
--- a/lib/csharp/src/Collections/THashSet.cs
+++ b/lib/csharp/src/Collections/THashSet.cs
@@ -23,11 +23,13 @@
 
 namespace Thrift.Collections
 {
+#if !SILVERLIGHT
      [Serializable]
+#endif
 	public class THashSet<T> : ICollection<T>
 	{
-#if NET_2_0
-		TDictSet<T> set = new TDictSet<T>();
+#if NET_2_0 || SILVERLIGHT
+        TDictSet<T> set = new TDictSet<T>();
 #else
 		HashSet<T> set = new HashSet<T>();
 #endif
@@ -76,8 +78,8 @@
 			return set.Remove(item);
 		}
 
-#if NET_2_0
-		private class TDictSet<V> : ICollection<V>
+#if NET_2_0 || SILVERLIGHT
+        private class TDictSet<V> : ICollection<V>
 		{
 			Dictionary<V, TDictSet<V>> dict = new Dictionary<V, TDictSet<V>>();
 
diff --git a/lib/csharp/src/Properties/AssemblyInfo.WP7.cs b/lib/csharp/src/Properties/AssemblyInfo.WP7.cs
new file mode 100644
index 0000000..697d7aa
--- /dev/null
+++ b/lib/csharp/src/Properties/AssemblyInfo.WP7.cs
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Thrift.WP7")]
+[assembly: AssemblyDescription("C# bindings for the Apache Thrift RPC system")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("The Apache Software Foundation")]
+[assembly: AssemblyProduct("Thrift")]
+[assembly: AssemblyCopyright("The Apache Software Foundation")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+//@TODO where to put License information?
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components.  If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a343f89c-57dd-4fa8-a9c6-35391cd5f655")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.9.0.0")]
+[assembly: AssemblyFileVersion("0.9.0.0")]
diff --git a/lib/csharp/src/Protocol/TBinaryProtocol.cs b/lib/csharp/src/Protocol/TBinaryProtocol.cs
index 4b3980e..e6b69d6 100644
--- a/lib/csharp/src/Protocol/TBinaryProtocol.cs
+++ b/lib/csharp/src/Protocol/TBinaryProtocol.cs
@@ -201,7 +201,12 @@
 
 		public override void WriteDouble(double d)
 		{
+#if !SILVERLIGHT
 			WriteI64(BitConverter.DoubleToInt64Bits(d));
+#else
+            var bytes = BitConverter.GetBytes(d);
+            WriteI64(BitConverter.ToInt64(bytes, 0));
+#endif
 		}
 
 		public override void WriteBinary(byte[] b)
@@ -348,7 +353,13 @@
 
 		public override double ReadDouble()
 		{
+#if !SILVERLIGHT
 			return BitConverter.Int64BitsToDouble(ReadI64());
+#else
+            var value = ReadI64();
+            var bytes = BitConverter.GetBytes(value);
+            return BitConverter.ToDouble(bytes, 0);
+#endif
 		}
 
 		public void SetReadLength(int readLength)
@@ -382,7 +393,7 @@
 			CheckReadLength(size);
 			byte[] buf = new byte[size];
 			trans.ReadAll(buf, 0, size);
-			return Encoding.UTF8.GetString(buf);
+			return Encoding.UTF8.GetString(buf, 0, buf.Length);
 		}
 
 		private int ReadAll(byte[] buf, int off, int len)
diff --git a/lib/csharp/src/Protocol/TJSONProtocol.cs b/lib/csharp/src/Protocol/TJSONProtocol.cs
index a35fcd3..ed6970e 100644
--- a/lib/csharp/src/Protocol/TJSONProtocol.cs
+++ b/lib/csharp/src/Protocol/TJSONProtocol.cs
@@ -841,7 +841,7 @@
 			if (reader.Peek() == QUOTE[0])
 			{
 				byte[] arr = ReadJSONString(true);
-				double dub = Double.Parse(utf8Encoding.GetString(arr), CultureInfo.InvariantCulture);
+				double dub = Double.Parse(utf8Encoding.GetString(arr,0,arr.Length), CultureInfo.InvariantCulture);
 
 				if (!context.EscapeNumbers() && !Double.IsNaN(dub) &&
 					!Double.IsInfinity(dub))
@@ -938,7 +938,8 @@
 											 "Message contained bad version.");
 			}
 
-			message.Name = utf8Encoding.GetString(ReadJSONString(false));
+            var buf = ReadJSONString(false);
+			message.Name = utf8Encoding.GetString(buf,0,buf.Length);
 			message.Type = (TMessageType)ReadJSONInteger();
 			message.SeqID = (int)ReadJSONInteger();
 			return message;
@@ -1059,7 +1060,8 @@
 
 		public override String ReadString()
 		{
-			return utf8Encoding.GetString(ReadJSONString(false));
+            var buf = ReadJSONString(false);
+			return utf8Encoding.GetString(buf,0,buf.Length);
 		}
 
 		public override byte[] ReadBinary()
diff --git a/lib/csharp/src/Protocol/TProtocol.cs b/lib/csharp/src/Protocol/TProtocol.cs
index 4f723dd..b6884c9 100644
--- a/lib/csharp/src/Protocol/TProtocol.cs
+++ b/lib/csharp/src/Protocol/TProtocol.cs
@@ -84,8 +84,9 @@
 		public abstract long ReadI64();
 		public abstract double ReadDouble();
 		public virtual string ReadString() {
-		       return Encoding.UTF8.GetString(ReadBinary());
-		}
+            var buf = ReadBinary();
+            return Encoding.UTF8.GetString(buf, 0, buf.Length);
+        }
 		public abstract byte[] ReadBinary();
 	}
 }
diff --git a/lib/csharp/src/Thrift.WP7.csproj b/lib/csharp/src/Thrift.WP7.csproj
new file mode 100644
index 0000000..be38bc8
--- /dev/null
+++ b/lib/csharp/src/Thrift.WP7.csproj
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>10.0.20506</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{DF58BDB0-2457-4A52-9981-65A0E8B50833}</ProjectGuid>
+    <ProjectTypeGuids>{C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Thrift.WP7</RootNamespace>
+    <AssemblyName>Thrift.WP7</AssemblyName>
+    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+    <SilverlightVersion>$(TargetFrameworkVersion)</SilverlightVersion>
+    <TargetFrameworkProfile>WindowsPhone</TargetFrameworkProfile>
+    <TargetFrameworkIdentifier>Silverlight</TargetFrameworkIdentifier>
+    <SilverlightApplication>false</SilverlightApplication>
+    <ValidateXaml>true</ValidateXaml>
+    <ThrowErrorsInValidation>true</ThrowErrorsInValidation>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>Bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+    <NoStdLib>true</NoStdLib>
+    <NoConfig>true</NoConfig>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>Bin\Release</OutputPath>
+    <DefineConstants>TRACE;SILVERLIGHT;WINDOWS_PHONE</DefineConstants>
+    <NoStdLib>true</NoStdLib>
+    <NoConfig>true</NoConfig>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Phone" />
+    <Reference Include="Microsoft.Phone.Interop" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Servicemodel.Web" />
+    <Reference Include="System.Windows" />
+    <Reference Include="system" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml" />
+    <Reference Include="System.Net" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Collections\THashSet.cs" />
+    <Compile Include="Properties\AssemblyInfo.WP7.cs" />
+    <Compile Include="Protocol\TBase.cs" />
+    <Compile Include="Protocol\TBase64Utils.cs" />
+    <Compile Include="Protocol\TBinaryProtocol.cs" />
+    <Compile Include="Protocol\TField.cs" />
+    <Compile Include="Protocol\TJSONProtocol.cs" />
+    <Compile Include="Protocol\TList.cs" />
+    <Compile Include="Protocol\TMap.cs" />
+    <Compile Include="Protocol\TMessage.cs" />
+    <Compile Include="Protocol\TMessageType.cs" />
+    <Compile Include="Protocol\TProtocol.cs" />
+    <Compile Include="Protocol\TProtocolException.cs" />
+    <Compile Include="Protocol\TProtocolFactory.cs" />
+    <Compile Include="Protocol\TProtocolUtil.cs" />
+    <Compile Include="Protocol\TSet.cs" />
+    <Compile Include="Protocol\TStruct.cs" />
+    <Compile Include="Protocol\TType.cs" />
+    <Compile Include="TApplicationException.cs" />
+    <Compile Include="TProcessor.cs" />
+    <Compile Include="Transport\TFramedTransport.cs" />
+    <Compile Include="Transport\THttpClient.cs" />
+    <Compile Include="Transport\TStreamTransport.cs" />
+    <Compile Include="Transport\TTransport.cs" />
+    <Compile Include="Transport\TTransportException.cs" />
+    <Compile Include="Transport\TTransportFactory.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.$(TargetFrameworkProfile).Overrides.targets" />
+  <Import Project="$(MSBuildExtensionsPath)\Microsoft\Silverlight for Phone\$(TargetFrameworkVersion)\Microsoft.Silverlight.CSharp.targets" />
+  <ProjectExtensions />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
diff --git a/lib/csharp/src/Transport/THttpClient.cs b/lib/csharp/src/Transport/THttpClient.cs
index bf6ca4d..768b64e 100644
--- a/lib/csharp/src/Transport/THttpClient.cs
+++ b/lib/csharp/src/Transport/THttpClient.cs
@@ -23,6 +23,7 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Net;
+using System.Threading;
 
 namespace Thrift.Transport
 {
@@ -119,6 +120,7 @@
 			outputStream.Write(buf, off, len);
 		}
 
+#if !SILVERLIGHT
 		public override void Flush()
 		{
 			try 
@@ -153,11 +155,12 @@
 				throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx);
 			}
 		}
-
-		private HttpWebRequest CreateRequest()
+#endif
+        private HttpWebRequest CreateRequest()
 		{
 			HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri);
 
+#if !SILVERLIGHT
 			if (connectTimeout > 0)
 			{
 				connection.Timeout = connectTimeout;
@@ -166,23 +169,190 @@
 			{
 				connection.ReadWriteTimeout = readTimeout;
 			}
-
+#endif
 			// Make the request
 			connection.ContentType = "application/x-thrift";
 			connection.Accept = "application/x-thrift";
 			connection.UserAgent = "C#/THttpClient";
 			connection.Method = "POST";
+#if !SILVERLIGHT
 			connection.ProtocolVersion = HttpVersion.Version10;
+#endif
 
-			//add custom headers here
+            //add custom headers here
 			foreach (KeyValuePair<string, string> item in customHeaders)
 			{
+#if !SILVERLIGHT
 				connection.Headers.Add(item.Key, item.Value);
+#else
+                connection.Headers[item.Key] = item.Value;
+#endif
 			}
 
 			connection.Proxy = null;
 
-			return connection;
+            return connection;
 		}
-	}
+
+#if SILVERLIGHT
+        public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
+        {
+            // Extract request and reset buffer
+            var data = outputStream.ToArray();
+
+            //requestBuffer_ = new MemoryStream();
+
+            try
+            {
+                // Create connection object
+                var flushAsyncResult = new FlushAsyncResult(callback, state);
+                flushAsyncResult.Connection = CreateRequest();
+
+                flushAsyncResult.Data = data;
+
+
+                flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult);
+                return flushAsyncResult;
+
+            }
+            catch (IOException iox)
+            {
+                throw new TTransportException(iox.ToString());
+            }
+        }
+
+        public override void EndFlush(IAsyncResult asyncResult)
+        {
+            try
+            {
+                var flushAsyncResult = (FlushAsyncResult) asyncResult;
+
+                if (!flushAsyncResult.IsCompleted)
+                {
+                    var waitHandle = flushAsyncResult.AsyncWaitHandle;
+                    waitHandle.WaitOne();  // blocking INFINITEly
+                    waitHandle.Close();
+                }
+
+                if (flushAsyncResult.AsyncException != null)
+                {
+                    throw flushAsyncResult.AsyncException;
+                }
+            } finally
+            {
+                outputStream = new MemoryStream();
+            }
+
+        }
+
+
+        private void GetRequestStreamCallback(IAsyncResult asynchronousResult)
+        {
+            var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
+            try
+            {
+                var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult);
+                reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length);
+                reqStream.Flush();
+                reqStream.Close();
+
+                // Start the asynchronous operation to get the response
+                flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult);
+            }
+            catch (Exception exception)
+            {
+                flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
+                flushAsyncResult.UpdateStatusToComplete();
+                flushAsyncResult.NotifyCallbackWhenAvailable();
+            }
+        }
+
+        private void GetResponseCallback(IAsyncResult asynchronousResult)
+        {
+            var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState;
+            try
+            {
+                inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream();
+            }
+            catch (Exception exception)
+            {
+                flushAsyncResult.AsyncException = new TTransportException(exception.ToString());
+            }
+            flushAsyncResult.UpdateStatusToComplete();
+            flushAsyncResult.NotifyCallbackWhenAvailable();
+        }
+
+        // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
+        class FlushAsyncResult : IAsyncResult
+        {
+            private volatile Boolean _isCompleted;
+            private ManualResetEvent _evt;
+            private readonly AsyncCallback _cbMethod;
+            private readonly Object _state;
+
+            public FlushAsyncResult(AsyncCallback cbMethod, Object state)
+            {
+                _cbMethod = cbMethod;
+                _state = state;
+            }
+
+            internal byte[] Data { get; set; }
+            internal HttpWebRequest Connection { get; set; }
+            internal TTransportException AsyncException { get; set; }
+
+            public object AsyncState
+            {
+                get { return _state; }
+            }
+            public WaitHandle AsyncWaitHandle
+            {
+                get { return GetEvtHandle(); }
+            }
+            public bool CompletedSynchronously
+            {
+                get { return false; }
+            }
+            public bool IsCompleted
+            {
+                get { return _isCompleted; }
+            }
+            private readonly Object _locker = new Object();
+            private ManualResetEvent GetEvtHandle()
+            {
+                lock (_locker)
+                {
+                    if (_evt == null)
+                    {
+                        _evt = new ManualResetEvent(false);
+                    }
+                    if (_isCompleted)
+                    {
+                        _evt.Set();
+                    }
+                }
+                return _evt;
+            }
+            internal void UpdateStatusToComplete()
+            {
+                _isCompleted = true; //1. set _iscompleted to true 
+                lock (_locker)
+                {
+                    if (_evt != null)
+                    {
+                        _evt.Set(); //2. set the event, when it exists 
+                    }
+                }
+            }
+
+            internal void NotifyCallbackWhenAvailable()
+            {
+                if (_cbMethod != null)
+                {
+                    _cbMethod(this);
+                }
+            }
+        }
+
+#endif
+    }
 }
diff --git a/lib/csharp/src/Transport/TTransport.cs b/lib/csharp/src/Transport/TTransport.cs
index cecde87..520ba46 100644
--- a/lib/csharp/src/Transport/TTransport.cs
+++ b/lib/csharp/src/Transport/TTransport.cs
@@ -71,5 +71,14 @@
 		public virtual void Flush()
 		{
 		}
-	}
+        
+        public virtual IAsyncResult BeginFlush(AsyncCallback callback, object state)
+        {
+            return null;
+        }
+
+        public virtual void EndFlush(IAsyncResult asyncResult)
+        {
+        }
+    }
 }