Merging more server support and exception fixes for Cocoa

Summary: Submitted by Andrew McGeachie.

Reviewed By: mcslee


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665281 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/cocoa/TBinaryProtocol.h b/lib/cocoa/TBinaryProtocol.h
index ac8bc57..2c56740 100644
--- a/lib/cocoa/TBinaryProtocol.h
+++ b/lib/cocoa/TBinaryProtocol.h
@@ -1,5 +1,7 @@
 #import "TProtocol.h"
 #import "TTransport.h"
+#import "TProtocolFactory.h"
+
 
 @interface TBinaryProtocol : NSObject <TProtocol> {
   id <TTransport> mTransport;
@@ -14,3 +16,13 @@
              strictWrite: (BOOL) strictWrite;
 
 @end;
+
+
+@interface TBinaryProtocolFactory : NSObject <TProtocolFactory> {
+}
+
++ (TBinaryProtocolFactory *) sharedFactory;
+
+- (TBinaryProtocol *) newProtocolOnTransport: (id <TTransport>) transport;
+
+@end
\ No newline at end of file
diff --git a/lib/cocoa/TBinaryProtocol.m b/lib/cocoa/TBinaryProtocol.m
index ba7808c..19db55f 100644
--- a/lib/cocoa/TBinaryProtocol.m
+++ b/lib/cocoa/TBinaryProtocol.m
@@ -5,6 +5,26 @@
 int32_t VERSION_MASK = 0xffff0000;
 
 
+static TBinaryProtocolFactory * gSharedFactory = nil;
+
+@implementation TBinaryProtocolFactory 
+
++ (TBinaryProtocolFactory *) sharedFactory {
+  if (gSharedFactory == nil) {
+    gSharedFactory = [[TBinaryProtocolFactory alloc] init];
+  }
+  
+  return gSharedFactory;
+}
+
+- (TBinaryProtocol *) newProtocolOnTransport: (id <TTransport>) transport {
+  return [[[TBinaryProtocol alloc] initWithTransport: transport] autorelease];
+}
+
+@end
+
+
+
 @implementation TBinaryProtocol
 
 - (id) initWithTransport: (id <TTransport>) transport
@@ -54,7 +74,8 @@
   if (size < 0) {
     int version = size & VERSION_MASK;
     if (version != VERSION_1) {
-      @throw [TProtocolException exceptionWithName: @"Bad version in readMessageBegin"];
+      @throw [TProtocolException exceptionWithName: @"TProtocolException"
+                                 reason: @"Bad version in readMessageBegin"];
     }
     if (type != NULL) {
       *type = version & 0x00FF;
@@ -69,7 +90,8 @@
     }
   } else {
     if (mStrictRead) {
-      @throw [TProtocolException exceptionWithName: @"Missing version in readMessageBegin, old client?"];
+      @throw [TProtocolException exceptionWithName: @"TProtocolException"
+                                 reason: @"Missing version in readMessageBegin, old client?"];
     }
     NSString * messageName = [self readStringBody: size];
     if (name != NULL) {
@@ -194,8 +216,8 @@
   uint8_t * buff = malloc(size);
   if (buff == NULL) {
     @throw [TProtocolException 
-             exceptionWithName: @"Out of memory" 
-             reason: [NSString stringWithFormat: @"Unable to allocate %d bytes trying to read binary data.",
+             exceptionWithName: @"TProtocolException"
+             reason: [NSString stringWithFormat: @"Out of memory.  Unable to allocate %d bytes trying to read binary data.",
                                size]];
   }
   [mTransport readAll: buff offset: 0 length: size];
@@ -348,7 +370,8 @@
     [self writeI32: length];
     [mTransport write: (uint8_t *) utf8Bytes offset: 0 length: length];
   } else {
-    // instead of crashing when we get null, let's write out a zero length string
+    // instead of crashing when we get null, let's write out a zero
+    // length string
     [self writeI32: 0];
   }
 }
diff --git a/lib/cocoa/TException.m b/lib/cocoa/TException.m
index 38d00ca..33ed7b5 100644
--- a/lib/cocoa/TException.m
+++ b/lib/cocoa/TException.m
@@ -30,4 +30,16 @@
 }
 
 
+- (NSString *) description
+{
+  NSMutableString * result = [NSMutableString stringWithString: [self name]];
+  [result appendFormat: @": %@", [self reason]];
+  if ([self userInfo] != nil) {
+    [result appendFormat: @"\n  userInfo = %@", [self userInfo]];
+  }
+  
+  return result;
+}
+
+
 @end
diff --git a/lib/cocoa/THTTPClient.m b/lib/cocoa/THTTPClient.m
index 096770d..45c0b80 100644
--- a/lib/cocoa/THTTPClient.m
+++ b/lib/cocoa/THTTPClient.m
@@ -39,6 +39,7 @@
   [mURL release];
   [mRequest release];
   [mRequestData release];
+  [mResponseData release];
   [super dealloc];
 }
 
@@ -75,19 +76,20 @@
   [mRequestData setLength: 0];
 
   if (responseData == nil) {
-    @throw [TTransportException exceptionWithName: @"Could not make HTTP request"
-                                reason: @"unknown"
+    @throw [TTransportException exceptionWithName: @"TTransportException"
+                                reason: @"Could not make HTTP request"
                                 error: error];
   }
   if (![response isKindOfClass: [NSHTTPURLResponse class]]) {
-    @throw [TTransportException exceptionWithName: @"Unexpected NSURLResponse type"];
+    @throw [TTransportException exceptionWithName: @"TTransportException"
+                                           reason: @"Unexpected NSURLResponse type"];
   }
 
   NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *) response;
   if ([httpResponse statusCode] != 200) {
-    @throw [TTransportException exceptionWithName: 
-                                  [NSString stringWithFormat: @"Bad response from HTTP server: %d", 
-                                            [httpResponse statusCode]]];
+    @throw [TTransportException exceptionWithName: @"TTransportException"
+                                           reason: [NSString stringWithFormat: @"Bad response from HTTP server: %d", 
+                                                    [httpResponse statusCode]]];
   }
                                 
   // phew!
diff --git a/lib/cocoa/TNSFileHandleTransport.h b/lib/cocoa/TNSFileHandleTransport.h
new file mode 100644
index 0000000..fc03ce3
--- /dev/null
+++ b/lib/cocoa/TNSFileHandleTransport.h
@@ -0,0 +1,16 @@
+
+#import <Cocoa/Cocoa.h>
+#import "TTransport.h"
+
+@interface TNSFileHandleTransport : NSObject <TTransport> {
+  NSFileHandle * mInputFileHandle;
+  NSFileHandle * mOutputFileHandle;
+}
+
+- (id) initWithFileHandle: (NSFileHandle *) fileHandle;
+
+- (id) initWithInputFileHandle: (NSFileHandle *) inputFileHandle
+              outputFileHandle: (NSFileHandle *) outputFileHandle;
+
+
+@end
diff --git a/lib/cocoa/TNSFileHandleTransport.m b/lib/cocoa/TNSFileHandleTransport.m
new file mode 100644
index 0000000..7ad1ba7
--- /dev/null
+++ b/lib/cocoa/TNSFileHandleTransport.m
@@ -0,0 +1,68 @@
+
+#import "TNSFileHandleTransport.h"
+
+
+@implementation TNSFileHandleTransport
+
+- (id) initWithFileHandle: (NSFileHandle *) fileHandle
+{
+  return [self initWithInputFileHandle: fileHandle
+                      outputFileHandle: fileHandle];
+}
+
+
+- (id) initWithInputFileHandle: (NSFileHandle *) inputFileHandle
+              outputFileHandle: (NSFileHandle *) outputFileHandle
+{
+  self = [super init];
+  
+  mInputFileHandle = [inputFileHandle retain];
+  mOutputFileHandle = [outputFileHandle retain];
+  
+  return self;
+}
+
+
+- (void) dealloc {
+  [mInputFileHandle release];
+  [mOutputFileHandle release];
+  [super dealloc];
+}
+
+
+- (int) readAll: (uint8_t *) buf offset: (int) off length: (int) len
+{
+  int got = 0;
+  while (got < len) {
+    NSData * d = [mInputFileHandle readDataOfLength: len-got];
+    if ([d length] == 0) {
+      @throw [NSException exceptionWithName: @"TTransportException"
+                                     reason: @"Cannot read. No more data."
+                                   userInfo: nil];
+    }
+    [d getBytes: buf+got];
+    got += [d length];
+  }
+  return got;
+}
+
+
+- (void) write: (uint8_t *) data offset: (unsigned int) offset length: (unsigned int) length
+{
+  NSData * dataObject = [[NSData alloc] initWithBytesNoCopy: data+offset 
+                                                     length: length
+                                               freeWhenDone: NO];
+ 
+  [mOutputFileHandle writeData: dataObject];
+
+  
+  [dataObject release];
+}
+  
+
+- (void) flush
+{
+  [mOutputFileHandle synchronizeFile];  // ?
+}
+
+@end
diff --git a/lib/cocoa/TProcessor.h b/lib/cocoa/TProcessor.h
new file mode 100644
index 0000000..01b2a61
--- /dev/null
+++ b/lib/cocoa/TProcessor.h
@@ -0,0 +1,9 @@
+#import <Cocoa/Cocoa.h>
+
+
+@protocol TProcessor <NSObject>
+
+- (BOOL) processOnInputProtocol: (id <TProtocol>) inProtocol
+                 outputProtocol: (id <TProtocol>) outProtocol;
+
+@end
diff --git a/lib/cocoa/TProtocolFactory.h b/lib/cocoa/TProtocolFactory.h
new file mode 100644
index 0000000..06d62c8
--- /dev/null
+++ b/lib/cocoa/TProtocolFactory.h
@@ -0,0 +1,10 @@
+#import <Cocoa/Cocoa.h>
+#import "TProtocol.h"
+#import "TTransport.h"
+
+
+@protocol TProtocolFactory <NSObject>
+
+- (id <TProtocol>) newProtocolOnTransport: (id <TTransport>) transport;
+
+@end
diff --git a/lib/cocoa/TSocketClient.h b/lib/cocoa/TSocketClient.h
new file mode 100644
index 0000000..8905ed4
--- /dev/null
+++ b/lib/cocoa/TSocketClient.h
@@ -0,0 +1,13 @@
+#import <Cocoa/Cocoa.h>
+#import "TNSStreamTransport.h"
+
+@interface TSocketClient : TNSStreamTransport {
+}
+
+- (id) initWithHostname: (NSString *) hostname
+                   port: (int) port;
+
+@end
+
+
+
diff --git a/lib/cocoa/TSocketClient.m b/lib/cocoa/TSocketClient.m
new file mode 100644
index 0000000..f3acc3b
--- /dev/null
+++ b/lib/cocoa/TSocketClient.m
@@ -0,0 +1,24 @@
+#import <Cocoa/Cocoa.h>
+#import "TSocketClient.h"
+
+@implementation TSocketClient
+
+- (id) initWithHostname: (NSString *) hostname
+                   port: (int) port
+{
+  NSInputStream * input = nil;
+  NSOutputStream * output = nil;
+  
+  [NSStream getStreamsToHost: [NSHost hostWithName: hostname]
+            port: port
+            inputStream: &input 
+            outputStream: &output];
+
+  return [super initWithInputStream: input outputStream: output];
+}
+
+
+@end
+
+
+
diff --git a/lib/cocoa/TSocketServer.h b/lib/cocoa/TSocketServer.h
new file mode 100644
index 0000000..4b8e8d0
--- /dev/null
+++ b/lib/cocoa/TSocketServer.h
@@ -0,0 +1,21 @@
+#import <Cocoa/Cocoa.h>
+#import "TProtocolFactory.h"
+#import "TProcessor.h"
+
+
+@interface TSocketServer : NSObject {
+  NSSocketPort * mServerSocket;
+  NSFileHandle * mSocketFileHandle;
+  id <TProtocolFactory> mInputProtocolFactory;
+  id <TProtocolFactory> mOutputProtocolFactory;  
+  id <TProcessor> mProcessor;
+}
+
+- (id) initWithPort: (int) port
+    protocolFactory: (id <TProtocolFactory>) protocolFactory
+          processor: (id <TProcessor>) processor;
+
+@end
+
+
+
diff --git a/lib/cocoa/TSocketServer.m b/lib/cocoa/TSocketServer.m
new file mode 100644
index 0000000..a65d017
--- /dev/null
+++ b/lib/cocoa/TSocketServer.m
@@ -0,0 +1,80 @@
+#import <Cocoa/Cocoa.h>
+#import "TSocketServer.h"
+#import "TNSFileHandleTransport.h"
+#import "TProtocol.h"
+
+
+@implementation TSocketServer
+
+- (id) initWithPort: (int) port
+    protocolFactory: (id <TProtocolFactory>) protocolFactory
+          processor: (id <TProcessor>) processor;
+{
+  self = [super init];
+
+  mInputProtocolFactory = [protocolFactory retain];
+  mOutputProtocolFactory = [protocolFactory retain];
+  mProcessor = [processor retain];
+  
+  // create a socket
+  mServerSocket = [[NSSocketPort alloc] initWithTCPPort: 8081];
+  // wrap it in a file handle so we can get messages from it
+  mSocketFileHandle = [[NSFileHandle alloc] initWithFileDescriptor: [mServerSocket socket]
+                                            closeOnDealloc: YES];
+
+  // register for notifications of accepted incoming connections
+  [[NSNotificationCenter defaultCenter] addObserver: self 
+                                        selector: @selector(connectionAccepted:) 
+                                        name: NSFileHandleConnectionAcceptedNotification
+                                        object: mSocketFileHandle];
+
+  // tell socket to listen
+  [mSocketFileHandle acceptConnectionInBackgroundAndNotify];
+  
+  return self;
+}
+
+
+- (void) dealloc {
+  [mInputProtocolFactory release];
+  [mOutputProtocolFactory release];
+  [mProcessor release];
+  [mSocketFileHandle release];
+  [mServerSocket release];
+  [super dealloc];
+}
+
+
+- (void) connentionAccepted: (NSNotification *) aNotification
+{
+  NSFileHandle * socket = [[aNotification userInfo] objectForKey: NSFileHandleNotificationFileHandleItem];
+  
+  // now that we have a client connected, spin off a thread to handle activity
+  [NSThread detachNewThreadSelector: @selector(handleClientConnection:)
+                           toTarget: self
+                         withObject: socket];
+  
+  [[aNotification object] acceptConnectionInBackgroundAndNotify];
+}
+
+
+- (void) handleClientConnection: (NSFileHandle *) clientSocket
+{
+  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
+  TNSFileHandleTransport * transport = [[TNSFileHandleTransport alloc] initWithFileHandle: clientSocket];
+
+  id <TProtocol> inProtocol = [mInputProtocolFactory newProtocolOnTransport: transport];
+  id <TProtocol> outProtocol = [mOutputProtocolFactory newProtocolOnTransport: transport];
+  
+  while ([mProcessor processOnInputProtocol: inProtocol outputProtocol: outProtocol]);
+  
+  [pool release];
+}
+
+
+
+@end
+
+
+