Explorar o código

MacOSX shell integration code plus icons.

Mackie Messer %!s(int64=11) %!d(string=hai) anos
pai
achega
50d178feaa
Modificáronse 100 ficheiros con 18548 adicións e 0 borrados
  1. 10 0
      shell_integration/MacOSX/LiferayNativity.xcworkspace/contents.xcworkspacedata
  2. 20 0
      shell_integration/MacOSX/LiferayNativity.xcworkspace/xcuserdata/mackie.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
  3. 32 0
      shell_integration/MacOSX/LiferayNativityFinder/ContentManager.h
  4. 292 0
      shell_integration/MacOSX/LiferayNativityFinder/ContentManager.m
  5. 28 0
      shell_integration/MacOSX/LiferayNativityFinder/ContextMenuHandlers.h
  6. 106 0
      shell_integration/MacOSX/LiferayNativityFinder/ContextMenuHandlers.m
  7. 74 0
      shell_integration/MacOSX/LiferayNativityFinder/ContextMenuHandlers.m.unc
  8. BIN=BIN
      shell_integration/MacOSX/LiferayNativityFinder/English.lproj/.DS_Store
  9. 2 0
      shell_integration/MacOSX/LiferayNativityFinder/English.lproj/InfoPlist.strings
  10. BIN=BIN
      shell_integration/MacOSX/LiferayNativityFinder/Finder/.DS_Store
  11. 1016 0
      shell_integration/MacOSX/LiferayNativityFinder/Finder/Finder.h
  12. 22 0
      shell_integration/MacOSX/LiferayNativityFinder/FinderHook.h
  13. 125 0
      shell_integration/MacOSX/LiferayNativityFinder/FinderHook.m
  14. 963 0
      shell_integration/MacOSX/LiferayNativityFinder/GCDAsyncSocket.h
  15. 6969 0
      shell_integration/MacOSX/LiferayNativityFinder/GCDAsyncSocket.m
  16. 29 0
      shell_integration/MacOSX/LiferayNativityFinder/IconCache.h
  17. 115 0
      shell_integration/MacOSX/LiferayNativityFinder/IconCache.m
  18. 23 0
      shell_integration/MacOSX/LiferayNativityFinder/IconOverlayHandlers.h
  19. 140 0
      shell_integration/MacOSX/LiferayNativityFinder/IconOverlayHandlers.m
  20. 46 0
      shell_integration/MacOSX/LiferayNativityFinder/Info.plist
  21. 251 0
      shell_integration/MacOSX/LiferayNativityFinder/JSONKit.h
  22. 3065 0
      shell_integration/MacOSX/LiferayNativityFinder/JSONKit.m
  23. 339 0
      shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/project.pbxproj
  24. 7 0
      shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  25. 1542 0
      shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/svp.mode1v3
  26. 1899 0
      shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/svp.pbxuser
  27. 14 0
      shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/xcuserdata/mackie.xcuserdatad/xcschemes/xcschememanagement.plist
  28. 29 0
      shell_integration/MacOSX/LiferayNativityFinder/MenuManager.h
  29. 184 0
      shell_integration/MacOSX/LiferayNativityFinder/MenuManager.m
  30. 48 0
      shell_integration/MacOSX/LiferayNativityFinder/RequestManager.h
  31. 170 0
      shell_integration/MacOSX/LiferayNativityFinder/RequestManager.m
  32. 2 0
      shell_integration/MacOSX/LiferayNativityInjector/English.lproj/InfoPlist.strings
  33. 59 0
      shell_integration/MacOSX/LiferayNativityInjector/Info.plist
  34. 35 0
      shell_integration/MacOSX/LiferayNativityInjector/LNStandardVersionComparator.h
  35. 158 0
      shell_integration/MacOSX/LiferayNativityInjector/LNStandardVersionComparator.m
  36. 27 0
      shell_integration/MacOSX/LiferayNativityInjector/LNVersionComparisonProtocol.h
  37. 270 0
      shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.m
  38. 9 0
      shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.sdef
  39. 269 0
      shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/project.pbxproj
  40. 7 0
      shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  41. 73 0
      shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/xcshareddata/xcschemes/LiferayNativity.osax.xcscheme
  42. 14 0
      shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/xcuserdata/mackie.xcuserdatad/xcschemes/xcschememanagement.plist
  43. 25 0
      shell_integration/MacOSX/LiferayNativityInjector/license.txt
  44. 9 0
      shell_integration/MacOSX/check.scpt
  45. 11 0
      shell_integration/MacOSX/deploy.sh
  46. 6 0
      shell_integration/MacOSX/load.scpt
  47. 8 0
      shell_integration/MacOSX/loadPlugin.sh
  48. 6 0
      shell_integration/MacOSX/unload.scpt
  49. BIN=BIN
      shell_integration/icons/.DS_Store
  50. BIN=BIN
      shell_integration/icons/1024x1024/Error_1024.png
  51. BIN=BIN
      shell_integration/icons/1024x1024/Error_Shared_1024.png
  52. BIN=BIN
      shell_integration/icons/1024x1024/OK_1024.png
  53. BIN=BIN
      shell_integration/icons/1024x1024/OK_Shared_1024.png
  54. BIN=BIN
      shell_integration/icons/1024x1024/Sync_1024.png
  55. BIN=BIN
      shell_integration/icons/1024x1024/Sync_Shared_1024.png
  56. BIN=BIN
      shell_integration/icons/1024x1024/Warning_1024.png
  57. BIN=BIN
      shell_integration/icons/1024x1024/Warning_Shared_1024.png
  58. BIN=BIN
      shell_integration/icons/128x128/.DS_Store
  59. BIN=BIN
      shell_integration/icons/128x128/Error_128.png
  60. BIN=BIN
      shell_integration/icons/128x128/Error_Shared_128.png
  61. BIN=BIN
      shell_integration/icons/128x128/OK_128.png
  62. BIN=BIN
      shell_integration/icons/128x128/OK_Shared_128.png
  63. BIN=BIN
      shell_integration/icons/128x128/Sync_128.png
  64. BIN=BIN
      shell_integration/icons/128x128/Sync_Shared_128.png
  65. BIN=BIN
      shell_integration/icons/128x128/Warning_128.png
  66. BIN=BIN
      shell_integration/icons/128x128/Warning_Shared_128.png
  67. BIN=BIN
      shell_integration/icons/16x16/Error_16.png
  68. BIN=BIN
      shell_integration/icons/16x16/Error_Shared_16.png
  69. BIN=BIN
      shell_integration/icons/16x16/OK_16.png
  70. BIN=BIN
      shell_integration/icons/16x16/OK_Shared_16.png
  71. BIN=BIN
      shell_integration/icons/16x16/Sync_16.png
  72. BIN=BIN
      shell_integration/icons/16x16/Sync_Shared_16.png
  73. BIN=BIN
      shell_integration/icons/16x16/Warning_16.png
  74. BIN=BIN
      shell_integration/icons/16x16/Warning_Shared_16.png
  75. BIN=BIN
      shell_integration/icons/256x256/Error_256.png
  76. BIN=BIN
      shell_integration/icons/256x256/Error_Shared_256.png
  77. BIN=BIN
      shell_integration/icons/256x256/OK_256.png
  78. BIN=BIN
      shell_integration/icons/256x256/OK_Shared_256.png
  79. BIN=BIN
      shell_integration/icons/256x256/Sync_256.png
  80. BIN=BIN
      shell_integration/icons/256x256/Sync_Shared_256.png
  81. BIN=BIN
      shell_integration/icons/256x256/Warning_256.png
  82. BIN=BIN
      shell_integration/icons/256x256/Warning_Shared_256.png
  83. BIN=BIN
      shell_integration/icons/32x32/.DS_Store
  84. BIN=BIN
      shell_integration/icons/32x32/Error_32.png
  85. BIN=BIN
      shell_integration/icons/32x32/Error_Shared_32.png
  86. BIN=BIN
      shell_integration/icons/32x32/OK_32.png
  87. BIN=BIN
      shell_integration/icons/32x32/OK_Shared_32.png
  88. BIN=BIN
      shell_integration/icons/32x32/Sync_32.png
  89. BIN=BIN
      shell_integration/icons/32x32/Sync_Shared_32.png
  90. BIN=BIN
      shell_integration/icons/32x32/Warning_32.png
  91. BIN=BIN
      shell_integration/icons/32x32/Warning_Shared_32.png
  92. BIN=BIN
      shell_integration/icons/48x48/Error_48.png
  93. BIN=BIN
      shell_integration/icons/48x48/Error_Shared_48.png
  94. BIN=BIN
      shell_integration/icons/48x48/OK_48.png
  95. BIN=BIN
      shell_integration/icons/48x48/OK_Shared_48.png
  96. BIN=BIN
      shell_integration/icons/48x48/Sync_48.png
  97. BIN=BIN
      shell_integration/icons/48x48/Sync_Shared_48.png
  98. BIN=BIN
      shell_integration/icons/48x48/Warning_48.png
  99. BIN=BIN
      shell_integration/icons/48x48/Warning_Shared_48.png
  100. BIN=BIN
      shell_integration/icons/512x512/.DS_Store

+ 10 - 0
shell_integration/MacOSX/LiferayNativity.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:LiferayNativityFinder/LiferayNativityFinder.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:LiferayNativityInjector/LiferayNativityInjector.xcodeproj">
+   </FileRef>
+</Workspace>

+ 20 - 0
shell_integration/MacOSX/LiferayNativity.xcworkspace/xcuserdata/mackie.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Bucket
+   type = "0"
+   version = "1.0">
+   <FileBreakpoints>
+      <FileBreakpoint
+         shouldBeEnabled = "Yes"
+         ignoreCount = "0"
+         continueAfterRunningActions = "No"
+         filePath = "LiferayNativityFinder/ContentManager.m"
+         timestampString = "426161859.780288"
+         startingColumnNumber = "9223372036854775807"
+         endingColumnNumber = "9223372036854775807"
+         startingLineNumber = "90"
+         endingLineNumber = "90"
+         landmarkName = "-setResultForPath:result:"
+         landmarkType = "5">
+      </FileBreakpoint>
+   </FileBreakpoints>
+</Bucket>

+ 32 - 0
shell_integration/MacOSX/LiferayNativityFinder/ContentManager.h

@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface ContentManager : NSObject
+{
+	NSMutableDictionary* _fileNamesCache;
+	BOOL _fileIconsEnabled;
+}
+
++ (ContentManager*)sharedInstance;
+
+- (void)enableFileIcons:(BOOL)enable;
+- (NSNumber*)iconByPath:(NSString*)path;
+- (void)removeAllIcons;
+- (void)removeIcons:(NSArray*)paths;
+- (void)setIcons:(NSDictionary*)iconDictionary filterByFolder:(NSString*)filterFolder;
+- (void)setResultForPath:(NSString*)path result:(NSString*)result;
+
+@end

+ 292 - 0
shell_integration/MacOSX/LiferayNativityFinder/ContentManager.m

@@ -0,0 +1,292 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <AppKit/NSApplication.h>
+#import <AppKit/NSWindow.h>
+#import <objc/runtime.h>
+#import "ContentManager.h"
+#import "MenuManager.h"
+#import "RequestManager.h"
+#import "IconCache.h"
+
+static ContentManager* sharedInstance = nil;
+
+@implementation ContentManager
+- init
+{
+	self = [super init];
+
+	if (self)
+	{
+		_fileNamesCache = [[NSMutableDictionary alloc] init];
+		_fileIconsEnabled = TRUE;
+		
+		[[IconCache sharedInstance] registerIcon:@"/Users/mackie/owncloud.com/mirall/shell_integration/icons/128x128/OK_128.png"];
+		[[IconCache sharedInstance] registerIcon:@"/Users/mackie/owncloud.com/mirall/shell_integration/icons/128x128/Sync_128.png"];
+		[[IconCache sharedInstance] registerIcon:@"/Users/mackie/owncloud.com/mirall/shell_integration/icons/128x128/Warning_128.png"];
+		[[IconCache sharedInstance] registerIcon:@"/Users/mackie/owncloud.com/mirall/shell_integration/icons/128x128/Error_128.png"];
+		[[IconCache sharedInstance] registerIcon:@"/Users/mackie/owncloud.com/mirall/shell_integration/icons/128x128/OK_Shared_128.png"];
+		
+	}
+
+	return self;
+}
+
+- (void)dealloc
+{
+	[self removeAllIcons];
+	[_fileNamesCache release];
+	sharedInstance = nil;
+
+	[super dealloc];
+}
+
++ (ContentManager*)sharedInstance
+{
+	@synchronized(self)
+	{
+		if (sharedInstance == nil)
+		{
+			sharedInstance = [[self alloc] init];
+		}
+	}
+
+	return sharedInstance;
+}
+
+- (void)enableFileIcons:(BOOL)enable
+{
+	_fileIconsEnabled = enable;
+
+	[self repaintAllWindows];
+}
+
+- (void)setResultForPath:(NSString*)path result:(NSString*)result
+{
+	int res = 0; // NOP
+	if( [result isEqualToString:@"OK"] ) {
+		res = 1;
+	} else if( [result isEqualToString:@"NEED_SYNC"]) {
+		res = 2;
+	} else if( [result isEqualToString:@"IGNORE"]) {
+		res = 3;
+	} else if( [result isEqualToString:@"ERROR"]) {
+		res = 4;
+	}else if( [result isEqualToString:@"SHARED"]) {
+		res = 5;
+	}else if( [result isEqualToString:@"NOP"]) {
+		// Nothing.
+	} else {
+		NSLog(@"Unknown status code %@", result);
+	}
+	NSString* normalizedPath = [path decomposedStringWithCanonicalMapping];
+	[_fileNamesCache setObject:[NSNumber numberWithInt:res] forKey:normalizedPath];
+	NSLog(@"SET value %d", res);
+	
+	[self repaintAllWindows];
+}
+
+- (NSNumber*)iconByPath:(NSString*)path
+{
+	if (!_fileIconsEnabled)
+	{
+		NSLog(@"Icons are NOT ENABLED!");
+		// return nil;
+	}
+
+	NSString* normalizedPath = [path decomposedStringWithCanonicalMapping];
+
+	NSNumber* result = [_fileNamesCache objectForKey:normalizedPath];
+	NSLog(@"XXXXXXX Asking for icon for path %@ = %d",path, [result intValue]);
+	
+	if( result != nil ) {
+		NSLog(@"Found icon index %d", [result intValue]);
+		// there is a proper icon index
+	} else {
+		NSLog(@"Need to query for icon");
+		// start the async call
+		NSNumber *minusOne = -1;
+		[_fileNamesCache setObject:minusOne forKey:normalizedPath];
+		[[RequestManager sharedInstance] askForIcon:normalizedPath];
+		result = [NSNumber numberWithInt:0];
+	}
+    NSLog(@"iconByPath return value %d", [result intValue]);
+	
+	return result;
+}
+
+- (void)removeAllIcons
+{
+	[_fileNamesCache removeAllObjects];
+
+	[self repaintAllWindows];
+}
+
+- (void)removeIcons:(NSArray*)paths
+{
+	for (NSString* path in paths)
+	{
+		NSString* normalizedPath = [path decomposedStringWithCanonicalMapping];
+
+		[_fileNamesCache removeObjectForKey:normalizedPath];
+	}
+
+	[self repaintAllWindows];
+}
+
+- (void)repaintAllWindows
+{
+	NSArray* windows = [[NSApplication sharedApplication] windows];
+
+	for (int i = 0; i < [windows count]; i++)
+	{
+		NSWindow* window = [windows objectAtIndex:i];
+
+		if (![window isVisible])
+		{
+			continue;
+		}
+
+		MenuManager* menuManager = [MenuManager sharedInstance];
+		RequestManager* requestManager = [RequestManager sharedInstance];
+
+		if ([[window className] isEqualToString:@"TBrowserWindow"])
+		{
+			NSObject* browserWindowController = [window browserWindowController];
+
+			BOOL repaintWindow = YES;
+
+			NSString* filterFolder = [requestManager filterFolder];
+
+			if (filterFolder)
+			{
+				repaintWindow = NO;
+
+				struct TFENodeVector* targetPath;
+
+				if ([browserWindowController respondsToSelector:@selector(targetPath)])
+				{
+					// 10.7 & 10.8
+					targetPath = [browserWindowController targetPath];
+				}
+				else if ([browserWindowController respondsToSelector:@selector(activeContainer)])
+				{
+					// 10.9
+					targetPath = [[browserWindowController activeContainer] targetPath];
+				}
+				else
+				{
+					NSLog(@"LiferayNativityFinder: refreshing icon badges failed");
+
+					return;
+				}
+
+				NSArray* folderPaths = [menuManager pathsForNodes:targetPath];
+
+				for (NSString* folderPath in folderPaths)
+				{
+					if ([folderPath hasPrefix:filterFolder] || [filterFolder hasPrefix:folderPath])
+					{
+						repaintWindow = YES;
+
+						break;
+					}
+				}
+			}
+
+			if (repaintWindow)
+			{
+				if ([browserWindowController respondsToSelector:@selector(browserViewController)])
+				{
+					// 10.7 & 10.8
+					NSObject* browserViewController = [browserWindowController browserViewController];
+
+					NSObject* browserView = [browserViewController browserView];
+
+					dispatch_async(dispatch_get_main_queue(), ^{[browserView setNeedsDisplay:YES];});
+				}
+				else if ([browserWindowController respondsToSelector:@selector(activeBrowserViewController)])
+				{
+					// 10.9
+					NSObject* browserViewController = [browserWindowController activeBrowserViewController];
+
+					NSObject* browserView = [browserViewController browserView];
+
+					if ([browserView isKindOfClass:(id)objc_getClass("TListView")])
+					{
+						// List or Coverflow View
+						[self setNeedsDisplayForListView:browserView];
+					}
+					else
+					{
+						// Icon or Column View
+						dispatch_async(dispatch_get_main_queue(), ^{[browserView setNeedsDisplay:YES];});
+					}
+				}
+				else
+				{
+					NSLog(@"LiferayNativityFinder: refreshing icon badges failed");
+
+					return;
+				}
+			}
+		}
+	}
+}
+
+- (void)setIcons:(NSDictionary*)iconDictionary filterByFolder:(NSString*)filterFolder
+{
+	for (NSString* path in iconDictionary)
+	{
+		if (filterFolder && ![path hasPrefix:filterFolder])
+		{
+			continue;
+		}
+
+		NSString* normalizedPath = [path decomposedStringWithCanonicalMapping];
+		NSNumber* iconId = [iconDictionary objectForKey:path];
+
+		if ([iconId intValue] == -1)
+		{
+			[_fileNamesCache removeObjectForKey:normalizedPath];
+		}
+		else
+		{
+			[_fileNamesCache setObject:iconId forKey:normalizedPath];
+		}
+	}
+
+	[self repaintAllWindows];
+}
+
+- (void)setNeedsDisplayForListView:(NSView*)view
+{
+	NSArray* subviews = [view subviews];
+
+	for (int i = 0; i < [subviews count]; i++)
+	{
+		NSView* subview = [subviews objectAtIndex:i];
+
+		if ([subview isKindOfClass:(id)objc_getClass("TListRowView")])
+		{
+			[self setNeedsDisplayForListView:subview];
+		}
+		else if ([subview isKindOfClass:(id)objc_getClass("TListNameCellView")])
+		{
+			dispatch_async(dispatch_get_main_queue(), ^{[subview setNeedsDisplay:YES];});
+		}
+	}
+}
+
+@end

+ 28 - 0
shell_integration/MacOSX/LiferayNativityFinder/ContextMenuHandlers.h

@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface NSObject (ContextMenuHandlers)
+
+struct TFENodeVector;
+
++ (void)ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 browserController:(id)arg5 addPlugIns:(BOOL)arg6;
++ (void)ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6;
++ (void)ContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3;
+
+- (void)ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 browserController:(id)arg2 container:(BOOL)arg3;
+- (void)ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 windowController:(id)arg2 container:(BOOL)arg3;
+
+@end

+ 106 - 0
shell_integration/MacOSX/LiferayNativityFinder/ContextMenuHandlers.m

@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import "ContextMenuHandlers.h"
+#import "MenuManager.h"
+
+@implementation NSObject (ContextMenuHandlers)
+
++ (void)ContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3 // 10.7 & 10.8
+{
+	[self ContextMenuHandlers_addViewSpecificStuffToMenu:arg1 browserViewController:arg2 context:arg3];
+
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	if (menuManager.menuItems.count > 0)
+	{
+		[menuManager addItemsToMenu:arg1 forFiles:menuManager.menuItems];
+		[menuManager.menuItems removeAllObjects];
+	}
+}
+
++ (void)ContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 clickedView:(id)arg2 browserViewController:(id)arg3 context:(unsigned int)arg4 // 10.9
+{
+	[self ContextMenuHandlers_addViewSpecificStuffToMenu:arg1 clickedView:arg2 browserViewController:arg3 context:arg4];
+
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	if (menuManager.menuItems.count > 0)
+	{
+		[menuManager addItemsToMenu:arg1 forFiles:menuManager.menuItems];
+		[menuManager.menuItems removeAllObjects];
+	}
+}
+
++ (void)ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6   // 10.7
+{
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	menuManager.menuItems = (NSMutableArray*)[menuManager pathsForNodes:arg2];
+
+	[self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 windowController:arg5 addPlugIns:arg6];
+}
+
++ (void)ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 browserController:(id)arg5 addPlugIns:(BOOL)arg6  // 10.8
+{
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	menuManager.menuItems = (NSMutableArray*)[menuManager pathsForNodes:arg2];
+
+	[self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 browserController:arg5 addPlugIns:arg6];
+}
+
++ (void)ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 clickedView:(id)arg4 browserViewController:(id)arg5 addPlugIns:(BOOL)arg6  // 10.9
+{
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	menuManager.menuItems = (NSMutableArray*)[menuManager pathsForNodes:arg2];
+
+	[self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 clickedView:arg4 browserViewController:arg5 addPlugIns:arg6];
+}
+
+- (void)ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 windowController:(id)arg2 container:(BOOL)arg3   // 10.7
+{
+	[self ContextMenuHandlers_configureWithNodes:arg1 windowController:arg2 container:arg3];
+
+	TContextMenu* realSelf = (TContextMenu*)self;
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	NSArray* selectedItems = [menuManager pathsForNodes:arg1];
+	[menuManager addItemsToMenu:realSelf forFiles:selectedItems];
+}
+
+- (void)ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 browserController:(id)arg2 container:(BOOL)arg3  // 10.8
+{
+	[self ContextMenuHandlers_configureWithNodes:arg1 browserController:arg2 container:arg3];
+
+	TContextMenu* realSelf = (TContextMenu*)self;
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	NSArray* selectedItems = [menuManager pathsForNodes:arg1];
+	[menuManager addItemsToMenu:realSelf forFiles:selectedItems];
+}
+
+- (void)ContextMenuHandlers_configureFromMenuNeedsUpdate:(id)arg1 clickedView:(id)arg2 container:(BOOL)arg3 event:(id)arg4 selectedNodes:(const struct TFENodeVector *)arg5  // 10.9
+{
+	[self ContextMenuHandlers_configureFromMenuNeedsUpdate:arg1 clickedView:arg2 container:arg3 event:arg4 selectedNodes:arg5];  // 10.8
+
+	TContextMenu* realSelf = (TContextMenu*)self;
+	MenuManager* menuManager = [MenuManager sharedInstance];
+
+	NSArray* selectedItems = [menuManager pathsForNodes:arg5];
+	[menuManager addItemsToMenu:realSelf forFiles:selectedItems];
+}
+
+@end

+ 74 - 0
shell_integration/MacOSX/LiferayNativityFinder/ContextMenuHandlers.m.unc

@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import "ContextMenuHandlers.h"
+#import "Finder/Finder.h"
+#import "MenuManager.h"
+
+@implementation NSObject (ContextMenuHandlers)
+
++ (void) ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6  // Lion
+{
+	MenuManager* contextMenuUtils = [MenuManager sharedInstance];
+
+	contextMenuUtils.menuItems = (NSMutableArray*)[contextMenuUtils pathsForNodes:arg2];
+
+	[self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 windowController:arg5 addPlugIns:arg6];
+}
+
++ (void) ContextMenuHandlers_handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector*)arg2 event:(id)arg3 view:(id)arg4 browserController:(id)arg5 addPlugIns:(BOOL)arg6 // Mountain Lion
+{
+	MenuManager* contextMenuUtils = [MenuManager sharedInstance];
+
+	contextMenuUtils.menuItems = (NSMutableArray*)[contextMenuUtils pathsForNodes:arg2];
+
+	[self ContextMenuHandlers_handleContextMenuCommon:arg1 nodes:arg2 event:arg3 view:arg4 browserController:arg5 addPlugIns:arg6];
+}
+
++ (void) ContextMenuHandlers_addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3
+{
+	[self ContextMenuHandlers_addViewSpecificStuffToMenu:arg1 browserViewController:arg2 context:arg3];
+
+	if ([MenuManager sharedInstance].menuItems.count > 0)
+	{
+		MenuManager* contextMenuUtils = [MenuManager sharedInstance];
+		[contextMenuUtils addItemsToMenu:arg1 forPaths:contextMenuUtils.menuItems];
+		[contextMenuUtils.menuItems removeAllObjects];
+	}
+}
+
+- (void) ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 windowController:(id)arg2 container:(BOOL)arg3  // Lion
+{
+	[self ContextMenuHandlers_configureWithNodes:arg1 windowController:arg2 container:arg3];
+
+	TContextMenu* realSelf = (TContextMenu*)self;
+	MenuManager* contextMenuUtils = [MenuManager sharedInstance];
+
+	NSArray* selectedItems = [contextMenuUtils pathsForNodes:arg1];
+	[contextMenuUtils addItemsToMenu:realSelf forPaths:selectedItems];
+}
+
+- (void) ContextMenuHandlers_configureWithNodes:(const struct TFENodeVector*)arg1 browserController:(id)arg2 container:(BOOL)arg3 // Mountain Lion
+{
+	[self ContextMenuHandlers_configureWithNodes:arg1 browserController:arg2 container:arg3];
+
+	TContextMenu* realSelf = (TContextMenu*)self;
+	MenuManager* contextMenuUtils = [MenuManager sharedInstance];
+
+	NSArray* selectedItems = [contextMenuUtils pathsForNodes:arg1];
+	[contextMenuUtils addItemsToMenu:realSelf forPaths:selectedItems];
+}
+
+
+@end

BIN=BIN
shell_integration/MacOSX/LiferayNativityFinder/English.lproj/.DS_Store


+ 2 - 0
shell_integration/MacOSX/LiferayNativityFinder/English.lproj/InfoPlist.strings

@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+

BIN=BIN
shell_integration/MacOSX/LiferayNativityFinder/Finder/.DS_Store


+ 1016 - 0
shell_integration/MacOSX/LiferayNativityFinder/Finder/Finder.h

@@ -0,0 +1,1016 @@
+//
+//  Finder.h
+//
+//  Created by snow on 9/30/10.
+//  Copyright 2010 Canvastudio Les Nie. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import <Quartz/Quartz.h>
+
+struct _NSPoint {
+    float x;
+    float y;
+};
+
+struct _NSSize {
+    float width;
+    float height;
+};
+
+struct _NSRect {
+    struct _NSPoint origin;
+    struct _NSSize size;
+};
+
+typedef struct {
+    struct TFENode *_M_start;
+    struct TFENode *_M_finish;
+    struct TFENode *_M_end_of_storage;
+} _Vector_impl_6bc0f568;
+
+struct TFENodeVector {
+    _Vector_impl_6bc0f568 _M_impl;
+};
+
+@class TListViewController, TTableViewShrinkToFitController;
+
+@interface TListView : NSOutlineView
+{
+    TListViewController *_controller;
+    BOOL _itemHitOnMouseDown;
+    TTableViewShrinkToFitController *_stfController;
+}
+
+- (void)dealloc;
+- (void)setDelegate:(id)arg1;
+- (BOOL)shouldDelayWindowOrderingForEvent:(id)arg1;
+- (BOOL)acceptsFirstResponder;
+- (id)columnWithStringIdentifier:(id)arg1;
+- (struct CGRect)_dropHighlightBackgroundRectForRow:(long long)arg1;
+- (void)drawRow:(long long)arg1 clipRect:(struct CGRect)arg2;
+- (BOOL)clickedOnMoreButton:(id)arg1;
+- (BOOL)handleUnicodeTextInput:(id)arg1;
+- (BOOL)acceptsFirstMouse:(id)arg1;
+- (unsigned long long)hitTestForEvent:(id)arg1 row:(long long)arg2;
+- (id)menuForEvent:(id)arg1;
+- (BOOL)_onlyDragOnContent;
+- (BOOL)commonMouseDownAndEarlyReturn:(id)arg1 controller:(id)arg2;
+- (void)commonPostMouseDown:(id)arg1 controller:(id)arg2;
+- (BOOL)_typeSelectInterpretKeyEvent:(id)arg1;
+- (void)mouseDown:(id)arg1;
+- (void)drawRect:(struct CGRect)arg1;
+- (BOOL)_wantsLiveResizeToUseCachedImage;
+- (id)inputContext;
+- (void)keyDown:(id)arg1;
+- (void)expandItem:(id)arg1 expandChildren:(BOOL)arg2;
+- (void)collapseItem:(id)arg1 collapseChildren:(BOOL)arg2;
+- (void)selectRowIndexes:(id)arg1 byExtendingSelection:(BOOL)arg2;
+- (void)editColumn:(long long)arg1 row:(long long)arg2 withEvent:(id)arg3 select:(BOOL)arg4;
+- (id)preparedCellAtColumn:(long long)arg1 row:(long long)arg2;
+- (void)startEditingWithNode:(const struct TFENode *)arg1;
+- (void)stopEditing:(BOOL)arg1;
+- (struct CGRect)maxSTFEditorFrameFromTitleFrame:(struct CGRect)arg1;
+- (void)updateSTFEditorLocation;
+- (BOOL)shrinkToFitTextViewAboutToOpen;
+- (void)shrinkToFitTextViewEditingComplete:(id)arg1;
+- (void)shrinkToFitTextViewAboutToClose;
+- (id)stfEditorController;
+@property(readonly, nonatomic) TListViewController *controller; // @synthesize controller=_controller;
+
+@end
+
+typedef struct {
+    unsigned int selected:1;
+    unsigned int focus:1;
+    unsigned int twoLines:1;
+    unsigned int label:3;
+} CDStruct_b8373011;
+typedef struct {
+    struct CGRect _field1;
+    unsigned long long _field2;
+    struct CGRect _field3[2];
+    unsigned long long _field4;
+} CDStruct_51b97681;
+
+@protocol IKImageProxy <NSObject>
+- (void)bind;
+- (void)unbind;
+- (BOOL)isBinded;
+- (int)proxyDataFormat;
+- (id)proxyData;
+- (void)disconnect;
+- (void)connect:(id)fp8;
+- (id)image;
+- (id)thumbnailWithSize:(struct _NSSize)fp8 antialiased:(BOOL)fp16 qualityRequested:(int)fp20 qualityProduced:(int *)fp24;
+- (BOOL)isVectorial;
+- (struct _NSSize)proxySize;
+- (void)lockForThreadedOperation;
+- (void)unlockForThreadedOperation;
+- (BOOL)isLockedForThreadedOperation;
+@end
+
+
+@interface IKImageWrapper : NSObject
+{
+    NSString *_path;
+    NSData *_dataRepresentation;
+    NSBitmapImageRep *_bitmapRepresentation;
+    id <IKImageProxy> _imageProxy;
+    union {
+        struct CGImage *_cgImage;
+        CIImage *_ciImage;
+        struct CGImageSource *_cgImageSource;
+        NSImage *_nsImage;
+    } _volatileRep;
+    unsigned short _volatileRepresentation;
+    unsigned int _exifOrientation:3;
+    unsigned int _generatedWithIconServices:1;
+    unsigned int _underlyingDataAreVolatile:1;
+    unsigned int _isReference:1;
+    struct _NSSize _cachedSize;
+    NSDictionary *_info;
+}
+
++ (id)imageWithPath:(id)fp8;
++ (id)imageWithNSImage:(id)fp8;
++ (id)imageWithCGImage:(struct CGImage *)fp8;
++ (id)imageWithCGImageSource:(struct CGImageSource *)fp8;
++ (id)imageWithData:(id)fp8;
++ (id)imageWithPasteboard:(id)fp8;
++ (id)imageWithNSBitmapImageRep:(id)fp8;
++ (id)emptyImage;
++ (id)imageWithSize:(struct _NSSize)fp8;
++ (id)imageWithImageProxy:(id)fp8;
++ (id)imageWithObject:(id)fp8;
+- (void)dealloc;
+- (void)finalize;
+- (id)initWithPath:(id)fp8;
+- (id)initWithCGImage:(struct CGImage *)fp8;
+- (id)initWithCGImageSource:(struct CGImageSource *)fp8;
+- (id)initWithNSImage:(id)fp8;
+- (id)initEmptyImage;
+- (id)initWithPasteboard:(id)fp8;
+- (id)initWithSize:(struct _NSSize)fp8;
+- (id)initWithData:(id)fp8;
+- (id)initWithNSBitmapImageRep:(id)fp8;
+- (id)initWithImageProxy:(id)fp8;
+- (id)initWithOpenGLID:(unsigned int)fp8 size:(struct _NSSize)fp12 offset:(struct _NSPoint)fp20 premultiplied:(BOOL)fp28 deleteWhenDone:(BOOL)fp32;
+- (int)volatileRepresentation;
+- (void)setVolatileRepresentation:(int)fp8;
+- (void)releaseVolatileImageRep;
+- (unsigned short)flags;
+- (void)setFlags:(unsigned short)fp8;
+- (BOOL)wasGeneratedWithIconServices;
+- (void)setWasGeneratedWithIconServices:(BOOL)fp8;
+- (BOOL)underlyingDataAreVolatile;
+- (void)setUnderlyingDataAreVolatile:(BOOL)fp8;
+- (struct CGImage *)_cgImage;
+- (id)_nsImage;
+- (struct CGImage *)cgImage;
+- (id)nsImage:(BOOL)fp8;
+- (id)nsImage;
+- (struct CGImageSource *)cgImageSourceRef:(BOOL)fp8;
+- (void)setCGImageSource:(struct CGImageSource *)fp8;
+- (void)setCGImage:(struct CGImage *)fp8;
+- (void)setNSImage:(id)fp8;
+- (id)copy;
+- (void)setIsReference:(BOOL)fp8;
+- (void)integrateReferenceInstance:(id)fp8;
+- (void)referenceWillDie;
+- (id)referenceInstance;
+- (id)_thumbnailWithSize:(struct _NSSize)fp8 antialiased:(BOOL)fp16 qualityRequested:(int)fp20 qualityProduced:(int *)fp24;
+- (id)thumbnailWithSize:(struct _NSSize)fp8 antialiased:(BOOL)fp16 qualityRequested:(int)fp20 qualityProduced:(int *)fp24;
+- (struct _NSSize)cachedSize;
+- (id)_sizeOfNSImage:(id)fp8;
+- (struct _NSSize)_size;
+- (struct _NSSize)size;
+- (void)setSize:(struct _NSSize)fp8;
+- (void)setSizeWithoutSavingContent:(struct _NSSize)fp8;
+- (BOOL)isVectorial;
+- (BOOL)isValid;
+- (BOOL)isEmpty;
+- (BOOL)hasAlpha;
+- (id)animatedGifsCache;
+- (BOOL)isAnimatedGifs;
+- (int)imageFrameCount;
+- (int)loopCount;
+- (float)nextFrameDelayAtIndex:(int)fp8;
+- (struct CGImage *)imageAtFrameIndex:(int)fp8;
+- (id)GIFRepresentation;
+- (id)TIFFRepresentation;
+- (id)TIFFRepresentationUsingCompression:(unsigned int)fp8 factor:(float)fp12;
+- (id)IK_JPEGRepresentationWithCompressionFactor:(float)fp8;
+- (id)imagePath;
+- (id)_dataRepresentationFromBitmapRepresentation:(id)fp8;
+- (id)_createBitmapImageRepFromCGRepresentation;
+- (id)dataRepresentationFromCGRepresentationWithCompressionFactor:(float)fp8;
+- (id)dataRepresentation;
+- (id)imageProxy;
+- (void)setImageProxy:(id)fp8;
+- (void)setPath:(id)fp8;
+- (void)setDataRepresentation:(id)fp8;
+- (void)drawInRect:(NSRect)fp8 fromRect:(NSRect)fp24 alpha:(float)fp40;
+- (void)lockFocus;
+- (void)unlockFocus;
+- (void)saveAsTIFFAtPath:(id)fp8;
+- (void)saveAsJPGAtPath:(id)fp8;
+- (id)writeToFileWithAutomaticFormat:(id)fp8;
+- (BOOL)hasDataRepresentation;
+- (BOOL)hasBitmapRepresentation;
+- (id)bitmapRepresentation;
+- (void)setBitmapRepresentation:(id)fp8;
+- (struct CGContext *)cgContext;
+- (void)mapIntoVRAM;
+- (void)mapRepresentationIntoRAM:(int)fp8;
+- (BOOL)mappedIntoRAM;
+- (BOOL)mappedAndDecompressedIntoRAM;
+- (BOOL)mappedIntoVRAM;
+- (void)freeImageCache;
+- (void)bindCGCache;
+- (BOOL)hasCGCache;
+- (BOOL)hasVolatileCache;
+- (BOOL)hasRAMCache;
+- (void)freeRAMCache;
+- (void)freeVRAMCache;
+- (void)freeCache;
+- (BOOL)textureIsPacked;
+- (unsigned int)openGLTextureID;
+- (void)deleteTextureInCurrentContext;
+- (void)setOpenGLTextureID:(unsigned int)fp8 withGLContext:(id)fp12;
+- (unsigned int)generateNewGLTextureID;
+- (struct _NSPoint)openGLTextureOffset;
+- (void)setOpenGLTextureOffset:(struct _NSPoint)fp8;
+- (BOOL)openGLTextureIsPremultiplied;
+- (void)setOpenGLTextureIsPremultiplied:(BOOL)fp8;
+- (void)setValue:(id)fp8 forKey:(id)fp12;
+- (id)valueForKey:(id)fp8;
+- (id)_tryToCreateCGImageRepFromNonCGFile:(id)fp8;
+- (id)description;
+
+@end
+
+
+@class IKMipmapItem;
+
+@interface IKMipmapImage : NSObject
+{
+    IKMipmapItem *_mipmaps[4];
+    IKMipmapItem *_originalMipmap;
+    IKMipmapItem *_customMipmap;
+    unsigned long _version;
+    struct _NSSize _originalImageSizeCache;
+    unsigned int _originalImageIsInvalid:1;
+    unsigned int _dirty:1;
+    unsigned int _mark:1;
+    unsigned int _isReference:1;
+}
+
+- (void)_mipmapCommonInit;
+- (id)init;
+- (id)initWithMipmapSizes:(id)fp8 VMUsagePolicy:(id)fp12;
+- (void)_cleanUp;
+- (void)dealloc;
+- (void)finalize;
+- (BOOL)isDirty;
+- (void)setDirty:(BOOL)fp8;
+- (void)setIsReference:(BOOL)fp8;
+- (void)setOriginalImageIsInvalid:(BOOL)fp8;
+- (unsigned long)version;
+- (void)setVersion:(unsigned long)fp8;
+- (BOOL)marked;
+- (void)mark;
+- (void)clearMark;
+- (id)temporaryItem;
+- (id)originalItem;
+- (id)mipmapItemAtIndex:(int)fp8;
+- (int)indexOfMipmapItem:(id)fp8;
+- (int)highestMipmapItemIndex;
+- (id)highestMipmapItem;
+- (float)originalAspectRatio;
+- (BOOL)originalImageIsInvalid;
+- (void)checkAndMarkMipmapAsInvalid;
+- (void)invalidateOriginalImageSizeCache;
+- (struct _NSSize)originalImageSize;
+- (void)setOriginalImageSizeCache:(struct _NSSize)fp8;
+- (struct _NSSize)originalImageSizeCache;
+- (id)image;
+- (void)setImage:(id)fp8;
+- (void)setImageWithoutInvalidate:(id)fp8;
+- (id *)mipmapItems;
+- (id)validMipmapItems;
+- (BOOL)atLeastOneMipmapItemIsValid;
+- (BOOL)allMipmapItemsAreValid;
+- (BOOL)customMipmapIsValidAndMatchSize:(struct _NSSize)fp8 andQuality:(int)fp16;
+- (BOOL)validateMipmap:(id)fp8 withModel:(id)fp12 withQuality:(int)fp16;
+- (BOOL)validateMipmap:(id)fp8 withQuality:(int)fp12;
+- (BOOL)validateMipmapAtIndex:(int)fp8 withQuality:(int)fp12;
+- (int)bestMipmapIndexToValidateForSize:(int)fp8;
+- (id)mipmapWithSize:(int)fp8;
+- (void)setImage:(id)fp8 forMipmapSize:(int)fp12;
+- (id)fastMipmapItemForSize:(int)fp8 forOpenGL:(BOOL)fp12 useMinimumQualityThreshold:(BOOL)fp16;
+- (id)_fastMipmapItemForSize:(struct _NSSize)fp8 forOpenGL:(BOOL)fp16 useMinimumQualityThreshold:(BOOL)fp20;
+- (id)fastMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16;
+- (id)fastestMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16;
+- (void)_cacheMipmapSize:(struct _NSSize)fp8 fromModel:(id)fp16;
+- (id)nicestMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20;
+- (BOOL)shouldUseOriginalImageToCacheNiceImageWithSize:(struct _NSSize)fp8;
+- (id)niceMipmapItemForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20;
+- (id)fastMipmapItemWithExactSize:(struct _NSSize)fp8;
+- (id)nicestImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20;
+- (id)niceImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16 cacheIt:(BOOL)fp20;
+- (id)fastImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16;
+- (id)fastestImageForSize:(struct _NSSize)fp8 forGLRendering:(BOOL)fp16;
+- (id)lockMipmapAtIndex:(int)fp8;
+- (void)unlockMipmapItem:(id)fp8;
+- (BOOL)preloadMipmapsWithQuality:(int)fp8;
+- (BOOL)containsMipmapItem:(id)fp8;
+- (void)freeAllCaches;
+- (void)freeTemporaryCache;
+- (void)freeExpendedRepresentationCaches;
+- (void)freeOriginalImageCache;
+- (void)invalidateMipMaps;
+- (void)setMipmapSizes:(id)fp8;
+- (void)setMipmapVMUsagePolicy:(id)fp8;
+- (id)referenceInstance;
+- (void)integrateReferenceInstance:(id)fp8;
+- (void)referenceWillDie;
+
+@end
+
+
+@interface IKMipmapItem : NSObject
+{
+    IKMipmapImage *_parent;
+    IKImageWrapper *_image;
+    int _mipmapSize;
+    unsigned int _vmUsagePolicy:8;
+    unsigned int _thumbnailQuality:8;
+    unsigned int _isReference:1;
+}
+
+- (id)init;
+- (void)dealloc;
+- (id)description;
+- (id)parent;
+- (void)setParent:(id)fp8;
+- (BOOL)loaded;
+- (void)mapIntoVRAM;
+- (BOOL)unload;
+- (BOOL)isValid;
+- (int)thumbnailQuality;
+- (void)setThumbnailQuality:(int)fp8;
+- (id)__image;
+- (id)image;
+- (void)setImage:(id)fp8;
+- (void)setMipmapSize:(int)fp8;
+- (void)invalidate;
+- (int)mipmapSize;
+- (void)__setDictionaryRepresentation:(id)fp8;
+- (BOOL)setAsMipmapOfImage:(id)fp8 withSize:(struct _NSSize)fp12 antialiased:(BOOL)fp20 quality:(int)fp24;
+- (BOOL)setAsMipmapOfImage:(id)fp8 aspectRatio:(float)fp12 antialiased:(BOOL)fp16 quality:(int)fp20;
+- (int)vmUsagePolicy;
+- (void)setVmUsagePolicy:(int)fp8;
+- (void)setIsReference:(BOOL)fp8;
+- (void)setAsReferenceOf:(id)fp8;
+- (void)integrateReferenceInstance:(id)fp8 replaceImage:(BOOL)fp12;
+- (void)referenceWillDie;
+
+@end
+
+
+@interface IKImageCell : NSObject
+{
+    id _parent;
+    id _cellSource;
+    id _proxy;
+    unsigned int _dataSourceIndex;
+    unsigned int _mipmapDBIndex;
+    IKMipmapImage *_mipmapImage;
+    unsigned int _datasourceIsVectorial:1;
+    float _alpha;
+    NSMutableDictionary *_properties;
+}
+
++ (id)_IKBuildImageWrapperForType:(id)fp8 withObject:(id)fp12 withOwner:(id)fp16;
+- (id)init;
+- (struct _NSRect)imageFrame;
+- (void)invalidate;
+- (void)validate;
+- (void)mipmapImageChanged;
+- (void)validateMipmapDBIndex;
+- (id)mipmapDB;
+- (unsigned int)mipmapDBIndex;
+- (void)setCacheDBIndex:(unsigned int)fp8;
+- (void)parentWillDie:(id)fp8;
+- (void)dealloc;
+- (void)finalize;
+- (void)setParent:(id)fp8;
+- (id)parent;
+- (void)setDataSource:(id)fp8;
+- (id)dataSource;
+- (unsigned int)dataSourceIndex;
+- (void)setDataSourceIndex:(unsigned int)fp8;
+- (id)mipmapImage;
+- (void)setMipmapImage:(id)fp8;
+- (float)alpha;
+- (void)setAlpha:(float)fp8;
+- (BOOL)isAnIcon;
+- (BOOL)_representationTypeCanBePlayed:(id)fp8;
+- (void)removeObjectForKey:(id)fp8;
+- (void)setObject:(id)fp8 forKey:(id)fp12;
+- (id)objectForKey:(id)fp8;
+- (void)checkMipmapVersion;
+- (id)dataSourcePath;
+
+@end
+
+//@class IKImageWrapper;
+
+@interface TIconViewCell : IKImageBrowserCell
+{
+    IKImageWrapper *_titleImage;
+    BOOL _twoLine;
+    CDStruct_b8373011 _titleSettings;
+}
+
++ (struct CGSize)cellSizeForIconSize:(double)arg1 labelOnBottom:(BOOL)arg2 gridSpacing:(double)arg3 titleAttrs:(id)arg4 subTitleAttrs:(id)arg5;
+- (id)init;
+- (void)dealloc;
+- (double)iconSize;
+- (BOOL)labelOnBottom;
+- (BOOL)showPreview;
+- (BOOL)showItemInfo;
+- (double)endCapWidth;
+- (struct CGRect)frame;
+- (struct CGRect)imageFrame;
+- (double)titleOffset;
+- (double)maxTitleWidth;
+- (struct CGRect)titleFrame;
+- (struct CGRect)subtitleFrame;
+- (int)heightOfInfoSpace;
+- (id)subString:(id)arg1 atIndex:(unsigned long long)arg2 attributes:(id)arg3 lineBreakMode:(unsigned long long)arg4;
+- (struct CGRect)placeSubString:(id)arg1 atIndex:(unsigned long long)arg2 fromFrame:(struct CGRect)arg3 bounds:(struct CGRect)arg4 attributes:(id)arg5 lineBreakMode:(unsigned long long)arg6 position:(BOOL)arg7;
+- (CDStruct_51b97681)calculateTextMetrics:(id)arg1 attributes:(id)arg2;
+- (void)drawLabel:(struct CGContext *)arg1 fillRect:(struct CGRect)arg2 bounds:(struct CGRect)arg3 firstLine:(struct CGRect)arg4 secondLine:(struct CGRect)arg5 actualLineCount:(unsigned long long)arg6 selected:(BOOL)arg7 labelValue:(short)arg8 justification:(short)arg9 inset:(double)arg10 radius:(double)arg11;
+- (id)constructTitleImage;
+- (CDStruct_b8373011)currentTitleImageSettings;
+- (BOOL)titleImageStillValid:(CDStruct_b8373011)arg1;
+- (id)titleImage;
+- (void)invalidate;
+- (void)drawTitle;
+
+@end
+
+@protocol TShrinkToFitDelegateProtocol
+- (BOOL)shrinkToFitTextViewAboutToOpen;
+- (void)shrinkToFitTextViewEditingComplete:(id)arg1;
+- (void)shrinkToFitTextViewAboutToClose;
+@end
+
+@class TCocoaShrinkToFitController, TIconViewController;
+
+@interface TIconView : IKImageBrowserView <TShrinkToFitDelegateProtocol>
+{
+    TIconViewController *_controller;
+    TCocoaShrinkToFitController *_stfController;
+    BOOL _startEditingOnMouseUp;
+    BOOL _viewIsReloadingData;
+    BOOL _isDrawingInDragImage;
+    unsigned long long _editedCellIndex;
+    unsigned long long _selectionCountBeforeReloadingData;
+}
+
+- (id)initWithFrame:(struct CGRect)arg1 controller:(id)arg2;
+- (void)setDelegate:(id)arg1;
+- (id)_viewIdentifier;
+- (void)viewDidMoveToWindow;
+- (BOOL)shouldPreserveVisibleRangeWhileZooming;
+- (void)reloadData;
+- (BOOL)isReloadingData;
+- (unsigned long long)selectionCountBeforeReloadingData;
+- (BOOL)respondsToSelector:(SEL)arg1;
+- (void)draggingExited:(id)arg1;
+- (void)dragImage:(id)arg1 at:(struct CGPoint)arg2 offset:(struct CGSize)arg3 event:(id)arg4 pasteboard:(id)arg5 source:(id)arg6 slideBack:(BOOL)arg7;
+- (void)scrollSTFEditorIntoView;
+- (void)updateSTFEditorLocation;
+- (void)scrollWheel:(id)arg1;
+- (void)browserDidScroll;
+- (void)mouseDown:(id)arg1;
+- (void)drawRect:(struct CGRect)arg1;
+- (void)rightMouseDown:(id)arg1;
+- (void)mouseDragged:(id)arg1;
+- (void)mouseUp:(id)arg1;
+- (BOOL)_typeSelectInterpretKeyEvent:(id)arg1;
+- (id)inputContext;
+- (void)keyDown:(id)arg1;
+- (int)defaultHeightOfInfoSpaceWithCurrentViewOptions;
+- (BOOL)isDragImageOpaque;
+- (int)nextIndexInGridLayoutWithDirectionKey:(unsigned short)arg1 currentIndex:(long long)arg2;
+- (void)startEditingWithNode:(const struct TFENode *)arg1 afterDelay:(BOOL)arg2;
+- (void)stopEditing:(BOOL)arg1;
+- (unsigned long long)editingIndex;
+- (struct CGRect)maxSTFEditorFrameForCellAtIndex:(unsigned long long)arg1;
+- (BOOL)editCellTitleAtIndex:(unsigned long long)arg1 withEvent:(id)arg2 select:(BOOL)arg3;
+- (BOOL)shrinkToFitTextViewAboutToOpen;
+- (void)shrinkToFitTextViewEditingComplete:(id)arg1;
+- (void)shrinkToFitTextViewAboutToClose;
+- (BOOL)hasFocus;
+- (id)draggedImageWithEvent:(id)arg1 countBadge:(int)arg2 hotPoint:(struct CGPoint *)arg3;
+@property(readonly, retain, nonatomic) TIconViewController *controller; // @synthesize controller=_controller;
+
+@end
+
+@class TPropertyIconController;
+
+@interface TIconImageView : NSImageView
+{
+    TPropertyIconController *_controller;
+}
+
+- (id)initWithFrame:(struct CGRect)arg1;
+- (id)initWithCoder:(id)arg1;
+- (void)initCommon;
+- (void)drawRect:(struct CGRect)arg1;
+- (void)setImage:(id)arg1;
+- (BOOL)canChangeIcon;
+- (BOOL)validateCopy:(id)arg1;
+- (void)copy:(id)arg1;
+- (BOOL)validateCut:(id)arg1;
+- (void)cut:(id)arg1;
+- (BOOL)validateDelete:(id)arg1;
+- (void)delete:(id)arg1;
+- (BOOL)validatePaste:(id)arg1;
+- (void)paste:(id)arg1;
+- (BOOL)validateUndo:(id)arg1;
+- (void)undo:(id)arg1;
+- (BOOL)validateRedo:(id)arg1;
+- (void)redo:(id)arg1;
+- (BOOL)validateMenuItem:(id)arg1;
+- (unsigned long long)draggingEntered:(id)arg1;
+- (void)concludeDragOperation:(id)arg1;
+- (void)draggingEnded:(id)arg1;
+@property TPropertyIconController *controller;  //@synthesize controller=_controller;
+
+@end
+
+@interface FINode : NSObject
+{
+    
+}
+
++ (id)nodeWithFENode:(const struct TFENode *)arg1;
++ (struct TFENode)asFENode:(id)arg1;
+- (struct TFENode)feNode;
+- (struct TFENode)feNodeFollowingAliasChainSynchronously;
+- (struct TFENode)feNodeFollowingAliasChainAsyncWithTarget:(id)arg1 okToLogin:(BOOL)arg2 tryToFixIfBroken:(BOOL)arg3;
+- (BOOL)nodeIs:(unsigned long long)arg1;
+- (id)name;
+- (id)fullPath;
+- (id)kind;
+- (id)kindWithoutPlatform;
+- (id)copyMDAttribute:(struct __CFString *)arg1;
+- (id)typeIdentifier;
+- (short)labelValue;
+- (id)icon;
+- (BOOL)isDimmed;
+- (id)modificationDate;
+- (id)creationDate;
+- (id)lastOpenedDate;
+- (long long)fileSize;
+- (id)size:(BOOL)arg1;
+- (id)label;
+- (id)version;
+- (id)comments;
+- (id)authorName;
+- (id)serverUserName;
+- (BOOL)supportsScreenSharing;
+- (BOOL)supportsFileSharing;
+- (int)serverConnectionState;
+- (BOOL)isSharedServer;
+- (BOOL)isODSNode;
+- (BOOL)isMountedSharePoint;
+- (BOOL)isIDiskNode;
+- (BOOL)isVolume;
+- (BOOL)volumeIsEjectableOrUnmountable;
+- (void)connectToSharedServerAs;
+- (void)askToUseODS;
+- (void)disconnectShare;
+- (void)launchScreenSharingApp;
+- (id)url;
+- (long long)fileSizeSync;
+- (BOOL)isExtensionHidden;
+- (BOOL)containsLocalizations;
+- (BOOL)containsPlugins;
+- (BOOL)isAlias;
+- (BOOL)isMDQueryHit;
+- (BOOL)isResolved;
+- (BOOL)isApplication;
+- (BOOL)isContainer;
+- (BOOL)isPackage;
+- (BOOL)isVirtual;
+- (BOOL)isQueryHit;
+- (unsigned long long)nodeIs64:(unsigned long long)arg1;
+
+@end
+
+
+@interface TViewController : NSViewController
+{
+}
+
+- (id)initWithCoder:(id)arg1;
+- (id)initWithNibName:(id)arg1 bundle:(id)arg2;
+- (void)initCommon;
+- (void)loadView;
+
+@end
+
+@class IPropertyValueExtractor, NSObject, TLayoutBinder;
+
+@interface IPropertyValueController : TViewController
+{
+    NSObject *_value;
+    TLayoutBinder *_layoutBinder;
+    double _viewHeight;
+    IPropertyValueExtractor *_valueExtractor;
+    BOOL _shouldBeVisible;
+    BOOL _shouldBeEnabled;
+}
+
++ (id)propertyValueController;
++ (id)propertyValueControllerWithValueExtractor:(id)arg1;
+- (id)initWithValueExtractor:(id)arg1;
+- (void)initCommon;
+- (void)dealloc;
+- (id)defaultValue;
+- (void)setView:(id)arg1;
+@property(retain) IPropertyValueExtractor *valueExtractor; // @synthesize valueExtractor=_valueExtractor;
+- (void)updateWithNodes:(const struct TFENodeVector *)arg1;
+@property BOOL shouldBeVisible; // @synthesize shouldBeVisible=_shouldBeVisible;
+- (id)extractValueFromNodes:(const struct TFENodeVector *)arg1;
+- (BOOL)needsUpdateForProperty:(unsigned int)arg1;
+- (BOOL)isApplicableToNodes:(const struct TFENodeVector *)arg1;
+- (void)flush;
+- (BOOL)canModifyNodes:(const struct TFENodeVector *)arg1;
+- (BOOL)adjustSize:(BOOL)arg1;
+- (void)handleNodesGoingAway:(const struct TFENodeVector *)arg1;
+- (void)handleNodeMDAttributesChanged:(const struct TFENode *)arg1 attributes:(id)arg2 isDisplayAttributes:(BOOL)arg3;
+@property BOOL shouldBeEnabled; // @synthesize shouldBeEnabled=_shouldBeEnabled;
+@property(readonly, retain) TLayoutBinder *layoutBinder; // @synthesize layoutBinder=_layoutBinder;
+@property(retain) NSObject *value; // @synthesize value=_value;
+
+@end
+
+
+@interface TPropertyImageViewController : IPropertyValueController
+{
+}
+
+@end
+
+
+
+@interface TPropertyIconController : TPropertyImageViewController
+{
+    struct TFENodeVector _nodes;
+    BOOL _nodesHaveSameIcon;
+    BOOL _nodesHaveCustomIcon;
+    BOOL _nodesCanChangeIcon;
+}
+
+- (void)initCommon;
+- (void)updateWithNodes:(const struct TFENodeVector *)arg1;
+- (BOOL)canModifyNodes:(const struct TFENodeVector *)arg1;
+- (BOOL)validateCopy:(id)arg1;
+- (void)copy:(id)arg1;
+- (BOOL)validateCut:(id)arg1;
+- (void)cut:(id)arg1;
+- (BOOL)validateDelete:(id)arg1;
+- (void)delete:(id)arg1;
+- (BOOL)validatePaste:(id)arg1;
+- (void)paste:(id)arg1;
+- (void)concludeDragOperation:(id)arg1;
+
+@end
+
+struct TFENode {
+    struct OpaqueNodeRef *fNodeRef;
+};
+
+
+@class TViewOptionsWindowController;
+
+@interface TFileBasedImageView : NSImageView
+{
+    TViewOptionsWindowController *_controller;
+    struct TFENode _imageNode;
+}
+
+@property struct TFENode *imageNode; // @dynamic imageNode;
+- (void)mouseDown:(id)arg1;
+- (BOOL)performDragOperation:(id)arg1;
+
+@end
+
+@interface TTextCell : NSTextFieldCell
+{
+    double _leftMargin;
+    double _rightMargin;
+    BOOL _drawGrayTextWhenDisabled;
+}
+
+- (id)init;
+- (id)initTextCell:(id)arg1;
+- (id)initWithCoder:(id)arg1;
+- (void)initializeTextCell;
+- (struct CGSize)cellSizeForBounds:(struct CGRect)arg1;
+- (struct CGRect)titleRectForBounds:(struct CGRect)arg1;
+- (void)drawInteriorWithFrame:(struct CGRect)arg1 inView:(id)arg2;
+- (void)drawWithExpansionFrame:(struct CGRect)arg1 inView:(id)arg2;
+- (unsigned long long)hitTestForEvent:(id)arg1 inRect:(struct CGRect)arg2 ofView:(id)arg3;
+@property BOOL drawGrayTextWhenDisabled; // @synthesize drawGrayTextWhenDisabled=_drawGrayTextWhenDisabled;
+@property double rightMargin; // @synthesize rightMargin=_rightMargin;
+@property double leftMargin; // @synthesize leftMargin=_leftMargin;
+
+@end
+
+struct TIconRef {
+    //struct TRef fIconRef;
+};
+
+@interface TIconAndTextCell : TTextCell
+{
+    struct TIconRef _icon;
+    struct CGSize _iconSize;
+    double _iconToTextSpacing;
+    BOOL _showIcon;
+}
+
+- (void)initializeTextCell;
+- (id)copyWithZone:(struct _NSZone *)arg1;
+- (void)setIcon:(const struct TIconRef *)arg1;
+@property(readonly) struct TIconRef *icon;
+- (struct CGRect)titleRectForBounds:(struct CGRect)arg1;
+- (struct CGRect)imageRectForBounds:(struct CGRect)arg1;
+- (struct CGSize)cellSizeForBounds:(struct CGRect)arg1;
+- (void)drawIconWithFrame:(struct CGRect)arg1;
+- (void)drawInteriorWithFrame:(struct CGRect)arg1 inView:(id)arg2;
+- (unsigned long long)hitTestForEvent:(id)arg1 inRect:(struct CGRect)arg2 ofView:(id)arg3;
+@property BOOL showIcon; // @synthesize showIcon=_showIcon;
+@property double iconToTextSpacing; // @synthesize iconToTextSpacing=_iconToTextSpacing;
+@property struct CGSize iconSize; // @synthesize iconSize=_iconSize;
+
+@end
+
+@interface TNodeIconAndNameCell : TIconAndTextCell
+{
+    struct TFENode _node;
+}
+
+- (id)copyWithZone:(struct _NSZone *)arg1;
+- (const struct TFENode *)node;
+- (void)setNode:(const struct TFENode *)arg1;
+- (id)accessibilityAttributeNames;
+- (id)accessibilityAttributeValue:(id)arg1;
+- (BOOL)accessibilityIsAttributeSettable:(id)arg1;
+
+@end
+
+@class NSImage, NSView;
+
+@interface TListViewIconAndTextCell : TNodeIconAndNameCell
+{
+    NSImage *_thumbnail;
+    NSView *_view;
+}
+
+- (void)initializeTextCell;
+- (void)dealloc;
+- (id)copyWithZone:(struct _NSZone *)arg1;
+- (void)drawIconWithFrame:(struct CGRect)arg1;
+- (id)controller;
+- (id)accessibilityActionNames;
+- (id)accessibilityActionDescription:(id)arg1;
+- (void)accessibilityPerformAction:(id)arg1;
+@property NSView *view; // @synthesize view=_view;
+@property(retain) NSImage *thumbnail; // @synthesize thumbnail=_thumbnail;
+
+@end
+
+@interface IKImageFlowView : NSOpenGLView
+{
+    id _dataSource;
+    id _dragDestinationDelegate;
+    id _delegate;
+    void *_reserved;
+}
+
++ (id)pixelFormat;
++ (BOOL)flowViewIsSupportedByCurrentHardware;
++ (void)initialize;
++ (void)setImportAnimationStyle:(unsigned int)fp8;
+- (void)_setDefaultTextAttributes;
+- (void)_ikCommonInit;
+- (id)initWithFrame:(struct _NSRect)fp8;
+- (void)dealloc;
+- (void)finalize;
+- (void)setValue:(id)fp8 forUndefinedKey:(id)fp12;
+- (id)valueForUndefinedKey:(id)fp8;
+- (id)allocateNewCell;
+- (void)dataSourceDidChange;
+- (void)_reloadCellDataAtIndex:(int)fp8;
+- (void)reloadCellDataAtIndex:(int)fp8;
+- (void)reloadAllCellsData;
+- (void)reloadData;
+- (id)loadCellAtIndex:(int)fp8;
+- (void)didStabilize;
+- (BOOL)isAnimating;
+- (void)setAnimationsMask:(unsigned int)fp8;
+- (unsigned int)animationsMask;
+- (void)_cellFinishedImportAnimation:(id)fp8;
+- (BOOL)itemAtIndexIsLoaded:(unsigned int)fp8;
+- (void)keyWindowChanged:(id)fp8;
+- (void)setSelectedIndex:(unsigned int)fp8;
+- (BOOL)hitTestWithImage:(id)fp8 x:(float)fp12 y:(float)fp16;
+- (unsigned int)cellIndexAtLocation:(struct _NSPoint)fp8;
+- (void)_adjustScroller;
+- (void)resetCursorRects;
+- (void)frameDidChange:(id)fp8;
+- (void)invalidateLayout;
+- (float)offset;
+- (int)cellIndexAtPosition:(float)fp8;
+- (int)heightOfInfoSpace;
+- (int)countOfVisibleCellsOnEachSide;
+- (struct _NSRange)rangeOfVisibleIndexes;
+- (struct _NSRange)rangeOfVisibleIndexesAtSelection;
+- (id)visibleCellIndexesAtSelection;
+- (id)visibleCellIndexes;
+- (void)flipCellsWithOldSelectedIndex:(unsigned int)fp8 newSelectedIndex:(unsigned int)fp12;
+- (void)flowLayout:(struct _NSRange)fp8;
+- (void)zoomOnSelectedLayerLayout:(struct _NSRange)fp8;
+- (void)updateLayoutInRange:(struct _NSRange)fp8;
+- (void)updateLayout;
+- (struct _NSRect)titleFrame;
+- (struct _NSRect)subtitleFrame;
+- (struct _NSRect)splitterFrame;
+- (double)_viewAspectRatio;
+- (double)_zScreen;
+- (struct _NSSize)imageRenderedSize;
+- (struct _NSRect)selectedImageFrame;
+- (double)_computeCameraDZ;
+- (double)cameraDZ;
+- (double)_computeCameraDY;
+- (double)cameraDY;
+- (float)convertPixelUnitTo3DUnit:(float)fp8;
+- (double)alignOnPixelValue;
+- (BOOL)updatesCGSurfaceOnDrawRect;
+- (void)setUpdatesCGSurfaceOnDrawRect:(BOOL)fp8;
+- (BOOL)showSplitter;
+- (void)setShowSplitter:(BOOL)fp8;
+- (id)delegate;
+- (void)setDelegate:(id)fp8;
+- (id)dataSource;
+- (void)setDataSource:(id)fp8;
+- (void)setZoomOnSelectedLayer:(BOOL)fp8;
+- (BOOL)zoomOnSelectedLayer;
+- (unsigned int)itemsCount;
+- (id)cells;
+- (unsigned int)selectedIndex;
+- (unsigned int)focusedIndex;
+- (id)backgroundColor;
+- (void)_setBackgroundColorWithRed:(float)fp8 green:(float)fp12 blue:(float)fp16 alpha:(float)fp20;
+- (BOOL)backgroundIsLight;
+- (BOOL)backgroundIsBlack;
+- (BOOL)_convertColor:(id)fp8 toRed:(float *)fp12 green:(float *)fp16 blue:(float *)fp20 alpha:(float *)fp24;
+- (void)_getBackgroundRed:(float *)fp8 green:(float *)fp12 blue:(float *)fp16 alpha:(float *)fp20;
+- (void)setBackgroundColor:(id)fp8;
+- (id)cellBackgroundColor;
+- (void)setCellBackgroundColor:(id)fp8;
+- (id)cellBorderColor;
+- (void)setCellBorderColor:(id)fp8;
+- (float)imageAspectRatio;
+- (void)setImageAspectRatio:(float)fp8;
+- (float)scaleFactor;
+- (id)cacheManager;
+- (BOOL)cellsAlignOnBaseline;
+- (void)setCellsAlignOnBaseline:(BOOL)fp8;
+- (void)startInlinePreview;
+- (void)stopInlinePreview;
+- (void)inlinePreviewDidRenderImage:(void *)fp8;
+- (id)thumbnailImageAtIndex:(int)fp8;
+- (id)previewImageAtIndex:(int)fp8;
+- (void)initRenderingContext;
+- (void *)fogShader;
+- (void)renewGState;
+- (void)setHidden:(BOOL)fp8;
+- (id)renderer;
+- (void)_setAutoscalesBoundsToPixelUnits:(BOOL)fp8;
+- (void)setCacheManager:(id)fp8;
+- (id)imageFlowContext;
+- (void)setImageFlowContext:(id)fp8;
+- (void)__ikSetupGLContext:(id)fp8;
+- (id)openGLContext;
+- (void)setOpenGLContext:(id)fp8;
+- (void)_cacheWasFlushed:(id)fp8;
+- (float)fogAtLocation:(float)fp8;
+- (struct _NSRect)clampedBounds;
+- (struct _NSRect)clampedFrame;
+- (void)drawVisibleCells:(struct _NSRect)fp8;
+- (void)drawBackground;
+- (void)drawTitle;
+- (BOOL)installViewport;
+- (void)setupGLState;
+- (void)installPerspetiveViewportForPicking:(BOOL)fp8 location:(struct _NSPoint)fp12;
+- (void)drawFocusRing;
+- (BOOL)drawWithCurrentRendererInRect:(struct _NSRect)fp8;
+- (void)__copyPixels:(void *)fp8 withSize:(struct _NSSize)fp12 toCurrentFocusedViewAtPoint:(struct _NSPoint)fp20;
+- (void)__copyGLToCurrentFocusedView;
+- (BOOL)_createPBuffer;
+- (void)_deletePBUffer;
+- (BOOL)_installPBuffer;
+- (void)_copyPBufferToCGSurface;
+- (void)drawRect:(struct _NSRect)fp8;
+
+@end
+
+@interface TFlowView : IKImageFlowView
+{
+}
+
+- (id)_viewIdentifier;
+- (BOOL)acceptsFirstMouse:(id)arg1;
+- (BOOL)acceptsFirstResponder;
+- (void)dragImage:(id)arg1 at:(struct CGPoint)arg2 offset:(struct CGSize)arg3 event:(id)arg4 pasteboard:(id)arg5 source:(id)arg6 slideBack:(BOOL)arg7;
+- (BOOL)isDragImageOpaque;
+- (void)mouseDown:(id)arg1;
+- (void)reloadData;
+- (void)resetCursorRects;
+- (BOOL)shouldDelayWindowOrderingForEvent:(id)arg1;
+
+@end
+
+@interface TContextMenu : NSMenu
+{
+}
+
++ (id)contextMenuWithDelegate:(id)arg1;
++ (void)clearContextMenuState;
++ (BOOL)allowContextualMenuForEvent:(id)arg1;
++ (void)contextMenuClickedOnNodes:(const struct TFENodeVector *)arg1 event:(id)arg2 view:(id)arg3 windowController:(id)arg4;
++ (void)contextMenuClickedOnContainer:(const struct TFENode *)arg1 event:(id)arg2 view:(id)arg3 windowController:(id)arg4;
++ (void)populateActionMenu:(id)arg1 forWindowController:(id)arg2;
+- (id)initWithTitle:(id)arg1;
+- (id)initWithObject:(id)arg1 nodes:(const struct TFENodeVector *)arg2 event:(id)arg3;
+- (id)initWithDelegate:(id)arg1;
+- (void)dealloc;
+- (void)configureWithNodes:(const struct TFENodeVector *)arg1 windowController:(id)arg2 container:(BOOL)arg3;
+- (void)configureForSidebarWithNode:(const struct TFENode *)arg1 windowController:(id)arg2 constrained:(BOOL)arg3 data:(id)arg4;
+- (void)configureForPathbarWithNode:(const struct TFENode *)arg1 windowController:(id)arg2;
+- (void)menuDidCompleteInteraction:(id)arg1;
+
+@end
+
+@interface TContextMenu (Private)
++ (void)addViewSpecificStuffToMenu:(id)arg1 browserViewController:(id)arg2 context:(unsigned int)arg3;
++ (void)buildContextMenu:(id)arg1 forContext:(unsigned int)arg2 target:(id)arg3 maxItems:(unsigned long long)arg4 addServices:(BOOL)arg5;
++ (void)handleContextMenuCommon:(unsigned int)arg1 nodes:(const struct TFENodeVector *)arg2 event:(id)arg3 view:(id)arg4 windowController:(id)arg5 addPlugIns:(BOOL)arg6;
+@end
+
+
+
+@interface TDimmableIconImageView : NSImageView
+{
+}
+
+- (void)drawRect:(struct CGRect)arg1;
+
+@end
+
+@interface TListRowView : NSTableRowView
+{
+    struct TFENode _node;
+    TListViewController *_listViewController;
+//    struct TNSRef<TListRowSelectionView *> _selectionView;
+    _Bool _isDropTarget;
+}
+
+@property(nonatomic) _Bool isDropTarget; // @synthesize isDropTarget=_isDropTarget;
+@property(nonatomic) TListViewController *listViewController; // @synthesize listViewController=_listViewController;
+@property(nonatomic) struct TFENode node; // @synthesize node=_node;
+- (void)openNode;
+- (void)setSelected:(BOOL)arg1;
+- (void)updateCellSelectedStateAppearance;
+- (void)layout;
+- (void)setNeedsLayout:(BOOL)arg1;
+- (struct CGRect)selectionFrame;
+- (void)updateLayer;
+- (_Bool)isRowAfterSelected;
+- (_Bool)isRowBeforeSelected;
+- (long long)selectionHighlightStyle;
+- (void)forceDisclosureTriangleBackgroundStyle;
+- (id)disclosureTriangleButton;
+- (void)dealloc;
+- (id)initWithFrame:(struct CGRect)arg1;
+
+@end

+ 22 - 0
shell_integration/MacOSX/LiferayNativityFinder/FinderHook.h

@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface FinderHook : NSObject
+
++ (void)hookClassMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector;
++ (void)hookMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector;
+
+@end

+ 125 - 0
shell_integration/MacOSX/LiferayNativityFinder/FinderHook.m

@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import "ContentManager.h"
+#import "FinderHook.h"
+#import "IconCache.h"
+#import "objc/objc-class.h"
+#import "RequestManager.h"
+
+static BOOL installed = NO;
+
+@implementation FinderHook
+
++ (void)hookClassMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector
+{
+	Class hookedClass = NSClassFromString(className);
+	Method oldMethod = class_getClassMethod(hookedClass, oldSelector);
+	Method newMethod = class_getClassMethod(hookedClass, newSelector);
+
+	method_exchangeImplementations(newMethod, oldMethod);
+}
+
++ (void)hookMethod:(SEL)oldSelector inClass:(NSString*)className toCallToTheNewMethod:(SEL)newSelector
+{
+	Class hookedClass = NSClassFromString(className);
+	Method oldMethod = class_getInstanceMethod(hookedClass, oldSelector);
+	Method newMethod = class_getInstanceMethod(hookedClass, newSelector);
+
+	method_exchangeImplementations(newMethod, oldMethod);
+}
+
++ (void)install
+{
+	if (installed)
+	{
+		NSLog(@"LiferayNativityFinder: already installed");
+
+		return;
+	}
+
+	NSLog(@"LiferayNativityFinder: installing ownCloud Shell extension");
+
+	[RequestManager sharedInstance];
+
+	// Icons
+	[self hookMethod:@selector(drawImage:) inClass:@"IKImageBrowserCell" toCallToTheNewMethod:@selector(IconOverlayHandlers_IKImageBrowserCell_drawImage:)]; // 10.7 & 10.8 & 10.9 (Icon View arrange by name)
+
+	[self hookMethod:@selector(drawImage:) inClass:@"IKFinderReflectiveIconCell" toCallToTheNewMethod:@selector(IconOverlayHandlers_IKFinderReflectiveIconCell_drawImage:)]; // 10.7 & 10.8 & 10.9 (Icon View arrange by everything else)
+
+	[self hookMethod:@selector(drawIconWithFrame:) inClass:@"TListViewIconAndTextCell" toCallToTheNewMethod:@selector(IconOverlayHandlers_drawIconWithFrame:)]; // 10.7 & 10.8 & 10.9 Column View
+
+	[self hookMethod:@selector(drawRect:) inClass:@"TDimmableIconImageView" toCallToTheNewMethod:@selector(IconOverlayHandlers_drawRect:)]; // 10.9 (List and Coverflow Views)
+
+	// Context Menus
+	[self hookClassMethod:@selector(addViewSpecificStuffToMenu:browserViewController:context:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_addViewSpecificStuffToMenu:browserViewController:context:)]; // 10.7 & 10.8
+
+	[self hookClassMethod:@selector(addViewSpecificStuffToMenu:clickedView:browserViewController:context:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_addViewSpecificStuffToMenu:clickedView:browserViewController:context:)]; // 10.9
+
+	[self hookClassMethod:@selector(handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:)]; // 10.7
+
+	[self hookClassMethod:@selector(handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:)]; // 10.8
+
+	[self hookClassMethod:@selector(handleContextMenuCommon:nodes:event:clickedView:browserViewController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_handleContextMenuCommon:nodes:event:clickedView:browserViewController:addPlugIns:)]; // 10.9
+
+	[self hookMethod:@selector(configureWithNodes:windowController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_configureWithNodes:windowController:container:)]; // 10.7
+
+	[self hookMethod:@selector(configureWithNodes:browserController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_configureWithNodes:browserController:container:)]; // 10.8
+
+	[self hookMethod:@selector(configureFromMenuNeedsUpdate:clickedView:container:event:selectedNodes:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(ContextMenuHandlers_configureFromMenuNeedsUpdate:clickedView:container:event:selectedNodes:)]; // 10.9
+
+	installed = YES;
+
+    NSLog(@"LiferayNativityFinder: installed");
+}
+
++ (void)uninstall
+{
+	if (!installed)
+	{
+		NSLog(@"LiferayNativityFinder: not installed");
+
+		return;
+	}
+
+	NSLog(@"LiferayNativityFinder: uninstalling");
+
+	[[ContentManager sharedInstance] dealloc];
+
+	[[IconCache sharedInstance] dealloc];
+
+	[[RequestManager sharedInstance] dealloc];
+
+	// Icons
+	[self hookMethod:@selector(IconOverlayHandlers_drawImage:) inClass:@"TIconViewCell" toCallToTheNewMethod:@selector(drawImage:)]; // 10.7 & 10.8 & 10.9
+
+	[self hookMethod:@selector(IconOverlayHandlers_drawIconWithFrame:) inClass:@"TListViewIconAndTextCell" toCallToTheNewMethod:@selector(drawIconWithFrame:)]; // 10.7 & 10.8 & 10.9
+
+	// Context Menus
+	[self hookClassMethod:@selector(ContextMenuHandlers_addViewSpecificStuffToMenu:browserViewController:context:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(addViewSpecificStuffToMenu:browserViewController:context:)]; // 10.7 & 10.8
+
+	[self hookClassMethod:@selector(ContextMenuHandlers_handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(handleContextMenuCommon:nodes:event:view:windowController:addPlugIns:)]; // 10.7
+
+	[self hookMethod:@selector(ContextMenuHandlers_configureWithNodes:windowController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(configureWithNodes:windowController:container:)]; // 10.7
+
+	[self hookClassMethod:@selector(ContextMenuHandlers_handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(handleContextMenuCommon:nodes:event:view:browserController:addPlugIns:)]; // 10.8
+
+	[self hookMethod:@selector(ContextMenuHandlers_configureWithNodes:browserController:container:) inClass:@"TContextMenu" toCallToTheNewMethod:@selector(configureWithNodes:browserController:container:)]; // 10.8
+
+	installed = NO;
+
+    NSLog(@"LiferayNativityFinder: uninstalled");
+}
+
+@end

+ 963 - 0
shell_integration/MacOSX/LiferayNativityFinder/GCDAsyncSocket.h

@@ -0,0 +1,963 @@
+//  
+//  GCDAsyncSocket.h
+//  
+//  This class is in the public domain.
+//  Originally created by Robbie Hanson in Q3 2010.
+//  Updated and maintained by Deusty LLC and the Mac development community.
+//  
+//  http://code.google.com/p/cocoaasyncsocket/
+//
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+#import <dispatch/dispatch.h>
+
+@class GCDAsyncReadPacket;
+@class GCDAsyncWritePacket;
+
+extern NSString *const GCDAsyncSocketException;
+extern NSString *const GCDAsyncSocketErrorDomain;
+
+#if !TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketSSLCipherSuites;
+extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
+#endif
+
+enum GCDAsyncSocketError
+{
+	GCDAsyncSocketNoError = 0,           // Never used
+	GCDAsyncSocketBadConfigError,        // Invalid configuration
+	GCDAsyncSocketBadParamError,         // Invalid parameter was passed
+	GCDAsyncSocketConnectTimeoutError,   // A connect operation timed out
+	GCDAsyncSocketReadTimeoutError,      // A read operation timed out
+	GCDAsyncSocketWriteTimeoutError,     // A write operation timed out
+	GCDAsyncSocketReadMaxedOutError,     // Reached set maxLength without completing
+	GCDAsyncSocketClosedError,           // The remote peer closed the connection
+	GCDAsyncSocketOtherError,            // Description provided in userInfo
+};
+typedef enum GCDAsyncSocketError GCDAsyncSocketError;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface GCDAsyncSocket : NSObject
+{
+	uint32_t flags;
+	uint16_t config;
+	
+	id delegate;
+	dispatch_queue_t delegateQueue;
+	
+	int socket4FD;
+	int socket6FD;
+	int connectIndex;
+	NSData * connectInterface4;
+	NSData * connectInterface6;
+	
+	dispatch_queue_t socketQueue;
+	
+	dispatch_source_t accept4Source;
+	dispatch_source_t accept6Source;
+	dispatch_source_t connectTimer;
+	dispatch_source_t readSource;
+	dispatch_source_t writeSource;
+	dispatch_source_t readTimer;
+	dispatch_source_t writeTimer;
+	
+	NSMutableArray *readQueue;
+	NSMutableArray *writeQueue;
+	
+	GCDAsyncReadPacket *currentRead;
+	GCDAsyncWritePacket *currentWrite;
+	
+	unsigned long socketFDBytesAvailable;
+	
+	NSMutableData *partialReadBuffer;
+		
+#if TARGET_OS_IPHONE
+	CFStreamClientContext streamContext;
+	CFReadStreamRef readStream;
+	CFWriteStreamRef writeStream;
+#else
+	SSLContextRef sslContext;
+	NSMutableData *sslReadBuffer;
+	size_t sslWriteCachedLength;
+#endif
+	
+	id userData;
+}
+
+/**
+ * GCDAsyncSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ * 
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ * 
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
+ * 
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (id)init;
+- (id)initWithSocketQueue:(dispatch_queue_t)sq;
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq;
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;
+
+#pragma mark Configuration
+
+- (id)delegate;
+- (void)setDelegate:(id)delegate;
+- (void)synchronouslySetDelegate:(id)delegate;
+
+- (dispatch_queue_t)delegateQueue;
+- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue;
+
+- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr;
+- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+
+/**
+ * Traditionally sockets are not closed until the conversation is over.
+ * However, it is technically possible for the remote enpoint to close its write stream.
+ * Our socket would then be notified that there is no more data to be read,
+ * but our socket would still be writeable and the remote endpoint could continue to receive our data.
+ * 
+ * The argument for this confusing functionality stems from the idea that a client could shut down its
+ * write stream after sending a request to the server, thus notifying the server there are to be no further requests.
+ * In practice, however, this technique did little to help server developers.
+ * 
+ * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
+ * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
+ * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
+ * Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
+ * 
+ * In addition to the technical challenges and confusion, many high level socket/stream API's provide
+ * no support for dealing with the problem. If the read stream is closed, the API immediately declares the
+ * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
+ * It might sound like poor design at first, but in fact it simplifies development.
+ * 
+ * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
+ * Thus it actually makes sense to close the socket at this point.
+ * And in fact this is what most networking developers want and expect to happen.
+ * However, if you are writing a server that interacts with a plethora of clients,
+ * you might encounter a client that uses the discouraged technique of shutting down its write stream.
+ * If this is the case, you can set this property to NO,
+ * and make use of the socketDidCloseReadStream delegate method.
+ * 
+ * The default value is YES.
+**/
+- (BOOL)autoDisconnectOnClosedReadStream;
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ * 
+ * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols,
+ * and can simulataneously accept incoming connections on either protocol.
+ * 
+ * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen.
+ * By default, the preferred protocol is IPv4, but may be configured as desired.
+**/
+- (BOOL)isIPv4Enabled;
+- (void)setIPv4Enabled:(BOOL)flag;
+
+- (BOOL)isIPv6Enabled;
+- (void)setIPv6Enabled:(BOOL)flag;
+
+- (BOOL)isIPv4PreferredOverIPv6;
+- (void)setPreferIPv4OverIPv6:(BOOL)flag;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally by socket in any way.
+**/
+- (id)userData;
+- (void)setUserData:(id)arbitraryUserData;
+
+#pragma mark Accepting
+
+/**
+ * Tells the socket to begin listening and accepting connections on the given port.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ * 
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+**/
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * This method is the same as acceptOnPort:error: with the
+ * additional option of specifying which interface to listen on.
+ * 
+ * For example, you could specify that the socket should only accept connections over ethernet,
+ * and not other interfaces such as wifi.
+ * 
+ * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept connections from the local machine.
+ * 
+ * You can see the list of interfaces via the command line utility "ifconfig",
+ * or programmatically via the getifaddrs() function.
+ * 
+ * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
+**/
+- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects to the given host and port.
+ * 
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error:
+ * and uses the default interface, and no timeout.
+**/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host and port with an optional timeout.
+ * 
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface.
+**/
+- (BOOL)connectToHost:(NSString *)host
+               onPort:(uint16_t)port
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host & port, via the optional interface, with an optional timeout.
+ * 
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * The host may also be the special strings "localhost" or "loopback" to specify connecting
+ * to a service on the local machine.
+ * 
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ * 
+ * To not time out use a negative time interval.
+ * 
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ * 
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ * 
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ * 
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToHost:(NSString *)host
+               onPort:(uint16_t)port
+         viaInterface:(NSString *)interface
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ * 
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ * 
+ * This method invokes connectToAdd
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+/**
+ * This method is the same as connectToAddress:error: with an additional timeout option.
+ * To not time out use a negative time interval, or simply use the connectToAddress:error: method.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, using the specified interface and timeout.
+ * 
+ * The address is specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ * 
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ * 
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ * 
+ * The timeout is optional. To not time out use a negative time interval.
+ * 
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ * 
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ * 
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ * 
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr
+            viaInterface:(NSString *)interface
+             withTimeout:(NSTimeInterval)timeout
+                   error:(NSError **)errPtr;
+
+#pragma mark Disconnecting
+
+/**
+ * Disconnects immediately (synchronously). Any pending reads or writes are dropped.
+ * 
+ * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method
+ * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods).
+ * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns.
+ * 
+ * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method)
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket release];
+ * 
+ * If you plan on disconnecting the socket, and then immediately asking it to connect again,
+ * you'll likely want to do so like this:
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket setDelegate:self];
+ * [asyncSocket connect...];
+**/
+- (void)disconnect;
+
+/**
+ * Disconnects after all pending reads have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending writes.
+**/
+- (void)disconnectAfterReading;
+
+/**
+ * Disconnects after all pending writes have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending reads.
+**/
+- (void)disconnectAfterWriting;
+
+/**
+ * Disconnects after all pending reads and writes have completed.
+ * After calling this, the read and write methods will do nothing.
+**/
+- (void)disconnectAfterReadingAndWriting;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns whether the socket is disconnected or connected.
+ * 
+ * A disconnected socket may be recycled.
+ * That is, it can used again for connecting or listening.
+ * 
+ * If a socket is in the process of connecting, it may be neither disconnected nor connected.
+**/
+- (BOOL)isDisconnected;
+- (BOOL)isConnected;
+
+/**
+ * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected.
+ * The host will be an IP address.
+**/
+- (NSString *)connectedHost;
+- (uint16_t)connectedPort;
+
+- (NSString *)localHost;
+- (uint16_t)localPort;
+
+/**
+ * Returns the local or remote address to which this socket is connected,
+ * specified as a sockaddr structure wrapped in a NSData object.
+ * 
+ * See also the connectedHost, connectedPort, localHost and localPort methods.
+**/
+- (NSData *)connectedAddress;
+- (NSData *)localAddress;
+
+/**
+ * Returns whether the socket is IPv4 or IPv6.
+ * An accepting socket may be both.
+**/
+- (BOOL)isIPv4;
+- (BOOL)isIPv6;
+
+/**
+ * Returns whether or not the socket has been secured via SSL/TLS.
+ * 
+ * See also the startTLS method.
+**/
+- (BOOL)isSecure;
+
+#pragma mark Reading
+
+// The readData and writeData methods won't block (they are asynchronous).
+// 
+// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue.
+// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue.
+// 
+// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.)
+// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method
+// is called to optionally allow you to extend the timeout.
+// Upon a timeout, the "socket:didDisconnectWithError:" method is called
+// 
+// The tag is for your convenience.
+// You can use it as an array index, step number, state id, pointer, etc.
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, the socket will create a buffer for you.
+ * 
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+					 buffer:(NSMutableData *)buffer
+			   bufferOffset:(NSUInteger)offset
+						tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * A maximum of length bytes will be read.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ * If maxLength is zero, no length restriction is enforced.
+ * 
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer  via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+                     buffer:(NSMutableData *)buffer
+               bufferOffset:(NSUInteger)offset
+                  maxLength:(NSUInteger)length
+                        tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * 
+ * If the length is 0, this method does nothing and the delegate is not called.
+**/
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ * 
+ * If the length is 0, this method does nothing and the delegate is not called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataToLength:(NSUInteger)length
+             withTimeout:(NSTimeInterval)timeout
+                  buffer:(NSMutableData *)buffer
+            bufferOffset:(NSUInteger)offset
+                     tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * 
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ * 
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+                   tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * 
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ * 
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If you pass a maxLength parameter that is less than the length of the data parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer if nil, a buffer will automatically be created for you.
+ * 
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ * 
+ * If you pass a maxLength parameter that is less than the length of the data (separator) parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+             maxLength:(NSUInteger)length
+                   tag:(long)tag;
+
+#pragma mark Writing
+
+/**
+ * Writes data to the socket, and calls the delegate when finished.
+ * 
+ * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
+ * If the timeout value is negative, the write operation will not use a timeout.
+ * 
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method
+ * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed.
+ * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it.
+ * This is for performance reasons. Often times, if NSMutableData is passed, it is because
+ * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+#pragma mark Security
+
+/**
+ * Secures the connection using SSL/TLS.
+ * 
+ * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes
+ * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing
+ * the upgrade to TLS at the same time, without having to wait for the write to finish.
+ * Any reads or writes scheduled after this method is called will occur over the secured connection.
+ * 
+ * The possible keys and values for the TLS settings are well documented.
+ * Some possible keys are:
+ * - kCFStreamSSLLevel
+ * - kCFStreamSSLAllowsExpiredCertificates
+ * - kCFStreamSSLAllowsExpiredRoots
+ * - kCFStreamSSLAllowsAnyRoot
+ * - kCFStreamSSLValidatesCertificateChain
+ * - kCFStreamSSLPeerName
+ * - kCFStreamSSLCertificates
+ * - kCFStreamSSLIsServer
+ * 
+ * Please refer to Apple's documentation for associated values, as well as other possible keys.
+ * 
+ * If you pass in nil or an empty dictionary, the default settings will be used.
+ * 
+ * The default settings will check to make sure the remote party's certificate is signed by a
+ * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired.
+ * However it will not verify the name on the certificate unless you
+ * give it a name to verify against via the kCFStreamSSLPeerName key.
+ * The security implications of this are important to understand.
+ * Imagine you are attempting to create a secure connection to MySecureServer.com,
+ * but your socket gets directed to MaliciousServer.com because of a hacked DNS server.
+ * If you simply use the default settings, and MaliciousServer.com has a valid certificate,
+ * the default settings will not detect any problems since the certificate is valid.
+ * To properly secure your connection in this particular scenario you
+ * should set the kCFStreamSSLPeerName property to "MySecureServer.com".
+ * If you do not know the peer name of the remote host in advance (for example, you're not sure
+ * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the
+ * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured.
+ * The X509Certificate class is part of the CocoaAsyncSocket open source project.
+ **/
+- (void)startTLS:(NSDictionary *)tlsSettings;
+
+#pragma mark Advanced
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ * 
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ * 
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ * 
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ * 
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ * 
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's file descriptor(s).
+ * If the socket is a server socket (is accepting incoming connections),
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's internal CFReadStream/CFWriteStream.
+ * 
+ * These streams are only used as workarounds for specific iOS shortcomings:
+ * 
+ * - Apple has decided to keep the SecureTransport framework private is iOS.
+ *   This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it.
+ *   Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream,
+ *   instead of the preferred and faster and more powerful SecureTransport.
+ * 
+ * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded,
+ *   Apple only bothers to notify us via the CFStream API.
+ *   The faster and more powerful GCD API isn't notified properly in this case.
+ * 
+ * See also: (BOOL)enableBackgroundingOnSocket
+**/
+- (CFReadStreamRef)readStream;
+- (CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ * 
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * 
+ * Returns YES if successful, NO otherwise.
+ * 
+ * Note: Apple does not officially support backgrounding server sockets.
+ * That is, if your socket is accepting incoming connections, Apple does not officially support
+ * allowing iOS applications to accept incoming connections while an app is backgrounded.
+ * 
+ * Example usage:
+ * 
+ * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
+ * {
+ *     [asyncSocket performBlock:^{
+ *         [asyncSocket enableBackgroundingOnSocket];
+ *     }];
+ * }
+**/
+- (BOOL)enableBackgroundingOnSocket;
+
+#else
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket.
+**/
+- (SSLContextRef)sslContext;
+
+#endif
+
+#pragma mark Utilities
+
+/**
+ * Extracting host and port information from raw address data.
+**/
++ (NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address;
+
+/**
+ * A few common line separators, for use with the readDataToData:... methods.
+**/
++ (NSData *)CRLFData;   // 0x0D0A
++ (NSData *)CRData;     // 0x0D
++ (NSData *)LFData;     // 0x0A
++ (NSData *)ZeroData;   // 0x00
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol GCDAsyncSocketDelegate
+@optional
+
+/**
+ * This method is called immediately prior to socket:didAcceptNewSocket:.
+ * It optionally allows a listening socket to specify the socketQueue for a new accepted socket.
+ * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue.
+ * 
+ * Since you cannot autorelease a dispatch_queue,
+ * this method uses the "new" prefix in its name to specify that the returned queue has been retained.
+ * 
+ * Thus you could do something like this in the implementation:
+ * return dispatch_queue_create("MyQueue", NULL);
+ * 
+ * If you are placing multiple sockets on the same queue,
+ * then care should be taken to increment the retain count each time this method is invoked.
+ * 
+ * For example, your implementation might look something like this:
+ * dispatch_retain(myExistingQueue);
+ * return myExistingQueue;
+**/
+- (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket accepts a connection.
+ * Another socket is automatically spawned to handle it.
+ * 
+ * You must retain the newSocket if you wish to handle the connection.
+ * Otherwise the newSocket instance will be released and the spawned connection will be closed.
+ * 
+ * By default the new socket will have the same delegate and delegateQueue.
+ * You may, of course, change this at any time.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
+
+/**
+ * Called when a socket has completed reading the requested data into memory.
+ * Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
+
+/**
+ * Called when a socket has read in data, but has not yet completed the read.
+ * This would occur if using readToData: or readToLength: methods.
+ * It may be used to for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called when a socket has completed writing the requested data. Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag;
+
+/**
+ * Called when a socket has written some data, but has not yet completed the entire write.
+ * It may be used to for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called if a read operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
+ * 
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been read so far for the read operation.
+ * 
+ * Note that this method may be called multiple times for a single read if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
+                                                                 elapsed:(NSTimeInterval)elapsed
+                                                               bytesDone:(NSUInteger)length;
+
+/**
+ * Called if a write operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
+ * 
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been written so far for the write operation.
+ * 
+ * Note that this method may be called multiple times for a single write if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
+                                                                  elapsed:(NSTimeInterval)elapsed
+                                                                bytesDone:(NSUInteger)length;
+
+/**
+ * Conditionally called if the read stream closes, but the write stream may still be writeable.
+ * 
+ * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO.
+ * See the discussion on the autoDisconnectOnClosedReadStream method for more information.
+**/
+- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket disconnects with or without error.
+ * 
+ * If you call the disconnect method, and the socket wasn't already disconnected,
+ * this delegate method will be called before the disconnect method returns.
+**/
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err;
+
+/**
+ * Called after the socket has successfully completed SSL/TLS negotiation.
+ * This method is not called unless you use the provided startTLS method.
+ * 
+ * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close,
+ * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code.
+**/
+- (void)socketDidSecure:(GCDAsyncSocket *)sock;
+
+@end

+ 6969 - 0
shell_integration/MacOSX/LiferayNativityFinder/GCDAsyncSocket.m

@@ -0,0 +1,6969 @@
+//
+//  GCDAsyncSocket.m
+//  
+//  This class is in the public domain.
+//  Originally created by Robbie Hanson in Q4 2010.
+//  Updated and maintained by Deusty LLC and the Mac development community.
+//
+//  http://code.google.com/p/cocoaasyncsocket/
+//
+
+#import "GCDAsyncSocket.h"
+
+#if TARGET_OS_IPHONE
+  #import <CFNetwork/CFNetwork.h>
+#endif
+
+#import <arpa/inet.h>
+#import <fcntl.h>
+#import <ifaddrs.h>
+#import <netdb.h>
+#import <netinet/in.h>
+#import <net/if.h>
+#import <sys/socket.h>
+#import <sys/types.h>
+#import <sys/ioctl.h>
+#import <sys/poll.h>
+#import <sys/uio.h>
+#import <unistd.h>
+
+
+#if 0
+
+// Logging Enabled - See log level below
+
+// Logging uses the CocoaLumberjack framework (which is also GCD based).
+// http://code.google.com/p/cocoalumberjack/
+// 
+// It allows us to do a lot of logging without significantly slowing down the code.
+#import "DDLog.h"
+
+#define LogAsync   YES
+#define LogContext 65535
+
+#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+#define LogC(flg, frmt, ...)    LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+
+#define LogError(frmt, ...)     LogObjc(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogWarn(frmt, ...)      LogObjc(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogInfo(frmt, ...)      LogObjc(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogVerbose(frmt, ...)   LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogCError(frmt, ...)    LogC(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCWarn(frmt, ...)     LogC(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCInfo(frmt, ...)     LogC(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCVerbose(frmt, ...)  LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogTrace()              LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
+#define LogCTrace()             LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
+
+// Log levels : off, error, warn, info, verbose
+static const int logLevel = LOG_LEVEL_VERBOSE;
+
+#else
+
+// Logging Disabled
+
+#define LogError(frmt, ...)     {}
+#define LogWarn(frmt, ...)      {}
+#define LogInfo(frmt, ...)      {}
+#define LogVerbose(frmt, ...)   {}
+
+#define LogCError(frmt, ...)    {}
+#define LogCWarn(frmt, ...)     {}
+#define LogCInfo(frmt, ...)     {}
+#define LogCVerbose(frmt, ...)  {}
+
+#define LogTrace()              {}
+#define LogCTrace(frmt, ...)    {}
+
+#endif
+
+/**
+ * Seeing a return statements within an inner block
+ * can sometimes be mistaken for a return point of the enclosing method.
+ * This makes inline blocks a bit easier to read.
+**/
+#define return_from_block  return
+
+/**
+ * A socket file descriptor is really just an integer.
+ * It represents the index of the socket within the kernel.
+ * This makes invalid file descriptor comparisons easier to read.
+**/
+#define SOCKET_NULL -1
+
+
+NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
+NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
+
+#if !TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
+NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
+#endif
+
+enum GCDAsyncSocketFlags
+{
+	kSocketStarted                 = 1 <<  0,  // If set, socket has been started (accepting/connecting)
+	kConnected                     = 1 <<  1,  // If set, the socket is connected
+	kForbidReadsWrites             = 1 <<  2,  // If set, no new reads or writes are allowed
+	kReadsPaused                   = 1 <<  3,  // If set, reads are paused due to possible timeout
+	kWritesPaused                  = 1 <<  4,  // If set, writes are paused due to possible timeout
+	kDisconnectAfterReads          = 1 <<  5,  // If set, disconnect after no more reads are queued
+	kDisconnectAfterWrites         = 1 <<  6,  // If set, disconnect after no more writes are queued
+	kSocketCanAcceptBytes          = 1 <<  7,  // If set, we know socket can accept bytes. If unset, it's unknown.
+	kReadSourceSuspended           = 1 <<  8,  // If set, the read source is suspended
+	kWriteSourceSuspended          = 1 <<  9,  // If set, the write source is suspended
+	kQueuedTLS                     = 1 << 10,  // If set, we've queued an upgrade to TLS
+	kStartingReadTLS               = 1 << 11,  // If set, we're waiting for TLS negotiation to complete
+	kStartingWriteTLS              = 1 << 12,  // If set, we're waiting for TLS negotiation to complete
+	kSocketSecure                  = 1 << 13,  // If set, socket is using secure communication via SSL/TLS
+	kSocketHasReadEOF              = 1 << 14,  // If set, we have read EOF from socket
+	kReadStreamClosed              = 1 << 15,  // If set, we've read EOF plus prebuffer has been drained
+#if TARGET_OS_IPHONE
+	kAddedStreamListener           = 1 << 16,  // If set, CFStreams have been added to listener thread
+	kSecureSocketHasBytesAvailable = 1 << 17,  // If set, CFReadStream has notified us of bytes available
+#endif
+};
+
+enum GCDAsyncSocketConfig
+{
+	kIPv4Disabled              = 1 << 0,  // If set, IPv4 is disabled
+	kIPv6Disabled              = 1 << 1,  // If set, IPv6 is disabled
+	kPreferIPv6                = 1 << 2,  // If set, IPv6 is preferred over IPv4
+	kAllowHalfDuplexConnection = 1 << 3,  // If set, the socket will stay open even if the read stream closes
+};
+
+#if TARGET_OS_IPHONE
+  static NSThread *listenerThread;  // Used for CFStreams
+#endif
+
+@interface GCDAsyncSocket (Private)
+
+// Accepting
+- (BOOL)doAccept:(int)socketFD;
+
+// Connecting
+- (void)startConnectTimeout:(NSTimeInterval)timeout;
+- (void)endConnectTimeout;
+- (void)doConnectTimeout;
+- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port;
+- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
+- (void)lookup:(int)aConnectIndex didFail:(NSError *)error;
+- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr;
+- (void)didConnect:(int)aConnectIndex;
+- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error;
+
+// Disconnect
+- (void)closeWithError:(NSError *)error;
+- (void)close;
+- (void)maybeClose;
+
+// Errors
+- (NSError *)badConfigError:(NSString *)msg;
+- (NSError *)badParamError:(NSString *)msg;
+- (NSError *)gaiError:(int)gai_error;
+- (NSError *)errnoError;
+- (NSError *)errnoErrorWithReason:(NSString *)reason;
+- (NSError *)connectTimeoutError;
+- (NSError *)otherError:(NSString *)msg;
+
+// Diagnostics
+- (NSString *)connectedHost4;
+- (NSString *)connectedHost6;
+- (uint16_t)connectedPort4;
+- (uint16_t)connectedPort6;
+- (NSString *)localHost4;
+- (NSString *)localHost6;
+- (uint16_t)localPort4;
+- (uint16_t)localPort6;
+- (NSString *)connectedHostFromSocket4:(int)socketFD;
+- (NSString *)connectedHostFromSocket6:(int)socketFD;
+- (uint16_t)connectedPortFromSocket4:(int)socketFD;
+- (uint16_t)connectedPortFromSocket6:(int)socketFD;
+- (NSString *)localHostFromSocket4:(int)socketFD;
+- (NSString *)localHostFromSocket6:(int)socketFD;
+- (uint16_t)localPortFromSocket4:(int)socketFD;
+- (uint16_t)localPortFromSocket6:(int)socketFD;
+
+// Utilities
+- (void)getInterfaceAddress4:(NSMutableData **)addr4Ptr
+                    address6:(NSMutableData **)addr6Ptr
+             fromDescription:(NSString *)interfaceDescription
+                        port:(uint16_t)port;
+- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD;
+- (void)suspendReadSource;
+- (void)resumeReadSource;
+- (void)suspendWriteSource;
+- (void)resumeWriteSource;
+
+// Reading
+- (void)maybeDequeueRead;
+- (void)flushSSLBuffers;
+- (void)doReadData;
+- (void)doReadEOF;
+- (void)completeCurrentRead;
+- (void)endCurrentRead;
+- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout;
+- (void)doReadTimeout;
+- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
+
+// Writing
+- (void)maybeDequeueWrite;
+- (void)doWriteData;
+- (void)completeCurrentWrite;
+- (void)endCurrentWrite;
+- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout;
+- (void)doWriteTimeout;
+- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
+
+// Security
+- (void)maybeStartTLS;
+#if !TARGET_OS_IPHONE
+- (void)continueSSLHandshake;
+#endif
+
+// CFStream
+#if TARGET_OS_IPHONE
++ (void)startListenerThreadIfNeeded;
+- (BOOL)createReadAndWriteStream;
+- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite;
+- (BOOL)addStreamsToRunLoop;
+- (BOOL)openStreams;
+- (void)removeStreamsFromRunLoop;
+#endif
+
+// Class Methods
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncReadPacket encompasses the instructions for any given read.
+ * The content of a read packet allows the code to determine if we're:
+ *  - reading to a certain length
+ *  - reading to a certain separator
+ *  - or simply reading the first chunk of available data
+**/
+@interface GCDAsyncReadPacket : NSObject
+{
+  @public
+	NSMutableData *buffer;
+	NSUInteger startOffset;
+	NSUInteger bytesDone;
+	NSUInteger maxLength;
+	NSTimeInterval timeout;
+	NSUInteger readLength;
+	NSData *term;
+	BOOL bufferOwner;
+	NSUInteger originalBufferLength;
+	long tag;
+}
+- (id)initWithData:(NSMutableData *)d
+       startOffset:(NSUInteger)s
+         maxLength:(NSUInteger)m
+           timeout:(NSTimeInterval)t
+        readLength:(NSUInteger)l
+        terminator:(NSData *)e
+               tag:(long)i;
+
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;
+
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr;
+
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
+
+@end
+
+@implementation GCDAsyncReadPacket
+
+- (id)initWithData:(NSMutableData *)d
+       startOffset:(NSUInteger)s
+         maxLength:(NSUInteger)m
+           timeout:(NSTimeInterval)t
+        readLength:(NSUInteger)l
+        terminator:(NSData *)e
+               tag:(long)i
+{
+	if((self = [super init]))
+	{
+		bytesDone = 0;
+		maxLength = m;
+		timeout = t;
+		readLength = l;
+		term = [e copy];
+		tag = i;
+		
+		if (d)
+		{
+			buffer = [d retain];
+			startOffset = s;
+			bufferOwner = NO;
+			originalBufferLength = [d length];
+		}
+		else
+		{
+			if (readLength > 0)
+				buffer = [[NSMutableData alloc] initWithLength:readLength];
+			else
+				buffer = [[NSMutableData alloc] initWithLength:0];
+			
+			startOffset = 0;
+			bufferOwner = YES;
+			originalBufferLength = 0;
+		}
+	}
+	return self;
+}
+
+/**
+ * Increases the length of the buffer (if needed) to ensure a read of the given size will fit.
+**/
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead
+{
+	NSUInteger buffSize = [buffer length];
+	NSUInteger buffUsed = startOffset + bytesDone;
+	
+	NSUInteger buffSpace = buffSize - buffUsed;
+	
+	if (bytesToRead > buffSpace)
+	{
+		NSUInteger buffInc = bytesToRead - buffSpace;
+		
+		[buffer increaseLengthBy:buffInc];
+	}
+}
+
+/**
+ * This method is used when we do NOT know how much data is available to be read from the socket.
+ * This method returns the default value unless it exceeds the specified readLength or maxLength.
+ * 
+ * Furthermore, the shouldPreBuffer decision is based upon the packet type,
+ * and whether the returned value would fit in the current buffer without requiring a resize of the buffer.
+**/
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+	NSUInteger result;
+	
+	if (readLength > 0)
+	{
+		// Read a specific length of data
+		
+		result = MIN(defaultValue, (readLength - bytesDone));
+		
+		// There is no need to prebuffer since we know exactly how much data we need to read.
+		// Even if the buffer isn't currently big enough to fit this amount of data,
+		// it would have to be resized eventually anyway.
+		
+		if (shouldPreBufferPtr)
+			*shouldPreBufferPtr = NO;
+	}
+	else
+	{
+		// Either reading until we find a specified terminator,
+		// or we're simply reading all available data.
+		// 
+		// In other words, one of:
+		// 
+		// - readDataToData packet
+		// - readDataWithTimeout packet
+		
+		if (maxLength > 0)
+			result =  MIN(defaultValue, (maxLength - bytesDone));
+		else
+			result = defaultValue;
+		
+		// Since we don't know the size of the read in advance,
+		// the shouldPreBuffer decision is based upon whether the returned value would fit
+		// in the current buffer without requiring a resize of the buffer.
+		// 
+		// This is because, in all likelyhood, the amount read from the socket will be less than the default value.
+		// Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead.
+		
+		if (shouldPreBufferPtr)
+		{
+			NSUInteger buffSize = [buffer length];
+			NSUInteger buffUsed = startOffset + bytesDone;
+			
+			NSUInteger buffSpace = buffSize - buffUsed;
+			
+			if (buffSpace >= result)
+				*shouldPreBufferPtr = NO;
+			else
+				*shouldPreBufferPtr = YES;
+		}
+	}
+	
+	return result;
+}
+
+/**
+ * For read packets without a set terminator, returns the amount of data
+ * that can be read without exceeding the readLength or maxLength.
+ * 
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ * 
+ * The given hint MUST be greater than zero.
+**/
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable
+{
+	NSAssert(term == nil, @"This method does not apply to term reads");
+	NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+	
+	if (readLength > 0)
+	{
+		// Read a specific length of data
+		
+		return MIN(bytesAvailable, (readLength - bytesDone));
+		
+		// No need to avoid resizing the buffer.
+		// If the user provided their own buffer,
+		// and told us to read a certain length of data that exceeds the size of the buffer,
+		// then it is clear that our code will resize the buffer during the read operation.
+		// 
+		// This method does not actually do any resizing.
+		// The resizing will happen elsewhere if needed.
+	}
+	else
+	{
+		// Read all available data
+		
+		NSUInteger result = bytesAvailable;
+		
+		if (maxLength > 0)
+		{
+			result = MIN(result, (maxLength - bytesDone));
+		}
+		
+		// No need to avoid resizing the buffer.
+		// If the user provided their own buffer,
+		// and told us to read all available data without giving us a maxLength,
+		// then it is clear that our code might resize the buffer during the read operation.
+		// 
+		// This method does not actually do any resizing.
+		// The resizing will happen elsewhere if needed.
+		
+		return result;
+	}
+}
+
+/**
+ * For read packets with a set terminator, returns the amount of data
+ * that can be read without exceeding the maxLength.
+ * 
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ * 
+ * To optimize memory allocations, mem copies, and mem moves
+ * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first,
+ * or if the data can be read directly into the read packet's buffer.
+**/
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+	NSAssert(term != nil, @"This method does not apply to non-term reads");
+	NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+	
+	
+	NSUInteger result = bytesAvailable;
+	
+	if (maxLength > 0)
+	{
+		result = MIN(result, (maxLength - bytesDone));
+	}
+	
+	// Should the data be read into the read packet's buffer, or into a pre-buffer first?
+	// 
+	// One would imagine the preferred option is the faster one.
+	// So which one is faster?
+	// 
+	// Reading directly into the packet's buffer requires:
+	// 1. Possibly resizing packet buffer (malloc/realloc)
+	// 2. Filling buffer (read)
+	// 3. Searching for term (memcmp)
+	// 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy)
+	// 
+	// Reading into prebuffer first:
+	// 1. Possibly resizing prebuffer (malloc/realloc)
+	// 2. Filling buffer (read)
+	// 3. Searching for term (memcmp)
+	// 4. Copying underflow into packet buffer (malloc/realloc, memcpy)
+	// 5. Removing underflow from prebuffer (memmove)
+	// 
+	// Comparing the performance of the two we can see that reading
+	// data into the prebuffer first is slower due to the extra memove.
+	// 
+	// However:
+	// The implementation of NSMutableData is open source via core foundation's CFMutableData.
+	// Decreasing the length of a mutable data object doesn't cause a realloc.
+	// In other words, the capacity of a mutable data object can grow, but doesn't shrink.
+	// 
+	// This means the prebuffer will rarely need a realloc.
+	// The packet buffer, on the other hand, may often need a realloc.
+	// This is especially true if we are the buffer owner.
+	// Furthermore, if we are constantly realloc'ing the packet buffer,
+	// and then moving the overflow into the prebuffer,
+	// then we're consistently over-allocating memory for each term read.
+	// And now we get into a bit of a tradeoff between speed and memory utilization.
+	// 
+	// The end result is that the two perform very similarly.
+	// And we can answer the original question very simply by another means.
+	// 
+	// If we can read all the data directly into the packet's buffer without resizing it first,
+	// then we do so. Otherwise we use the prebuffer.
+	
+	if (shouldPreBufferPtr)
+	{
+		NSUInteger buffSize = [buffer length];
+		NSUInteger buffUsed = startOffset + bytesDone;
+		
+		if ((buffSize - buffUsed) >= result)
+			*shouldPreBufferPtr = NO;
+		else
+			*shouldPreBufferPtr = YES;
+	}
+	
+	return result;
+}
+
+/**
+ * For read packets with a set terminator,
+ * returns the amount of data that can be read from the given preBuffer,
+ * without going over a terminator or the maxLength.
+ * 
+ * It is assumed the terminator has not already been read.
+**/
+- (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr
+{
+	NSAssert(term != nil, @"This method does not apply to non-term reads");
+	NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!");
+	
+	// We know that the terminator, as a whole, doesn't exist in our own buffer.
+	// But it is possible that a portion of it exists in our buffer.
+	// So we're going to look for the terminator starting with a portion of our own buffer.
+	// 
+	// Example:
+	// 
+	// term length      = 3 bytes
+	// bytesDone        = 5 bytes
+	// preBuffer length = 5 bytes
+	// 
+	// If we append the preBuffer to our buffer,
+	// it would look like this:
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// ---------------------
+	// 
+	// So we start our search here:
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// -------^-^-^---------
+	// 
+	// And move forwards...
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// ---------^-^-^-------
+	// 
+	// Until we find the terminator or reach the end.
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// ---------------^-^-^-
+	
+	BOOL found = NO;
+	
+	NSUInteger termLength = [term length];
+	NSUInteger preBufferLength = [preBuffer length];
+	
+	if ((bytesDone + preBufferLength) < termLength)
+	{
+		// Not enough data for a full term sequence yet
+		return preBufferLength;
+	}
+	
+	NSUInteger maxPreBufferLength;
+	if (maxLength > 0) {
+		maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
+		
+		// Note: maxLength >= termLength
+	}
+	else {
+		maxPreBufferLength = preBufferLength;
+	}
+	
+	uint8_t seq[termLength];
+	const void *termBuf = [term bytes];
+	
+	NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
+	uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
+	
+	NSUInteger preLen = termLength - bufLen;
+	const uint8_t *pre = [preBuffer bytes];
+	
+	NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
+	
+	NSUInteger result = preBufferLength;
+	
+	NSUInteger i;
+	for (i = 0; i < loopCount; i++)
+	{
+		if (bufLen > 0)
+		{
+			// Combining bytes from buffer and preBuffer
+			
+			memcpy(seq, buf, bufLen);
+			memcpy(seq + bufLen, pre, preLen);
+			
+			if (memcmp(seq, termBuf, termLength) == 0)
+			{
+				result = preLen;
+				found = YES;
+				break;
+			}
+			
+			buf++;
+			bufLen--;
+			preLen++;
+		}
+		else
+		{
+			// Comparing directly from preBuffer
+			
+			if (memcmp(pre, termBuf, termLength) == 0)
+			{
+				NSUInteger preOffset = pre - (const uint8_t *)[preBuffer bytes]; // pointer arithmetic
+				
+				result = preOffset + termLength;
+				found = YES;
+				break;
+			}
+			
+			pre++;
+		}
+	}
+	
+	// There is no need to avoid resizing the buffer in this particular situation.
+	
+	if (foundPtr) *foundPtr = found;
+	return result;
+}
+
+/**
+ * For read packets with a set terminator, scans the packet buffer for the term.
+ * It is assumed the terminator had not been fully read prior to the new bytes.
+ * 
+ * If the term is found, the number of excess bytes after the term are returned.
+ * If the term is not found, this method will return -1.
+ * 
+ * Note: A return value of zero means the term was found at the very end.
+ * 
+ * Prerequisites:
+ * The given number of bytes have been added to the end of our buffer.
+ * Our bytesDone variable has NOT been changed due to the prebuffered bytes.
+**/
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes
+{
+	NSAssert(term != nil, @"This method does not apply to non-term reads");
+	
+	// The implementation of this method is very similar to the above method.
+	// See the above method for a discussion of the algorithm used here.
+	
+	uint8_t *buff = [buffer mutableBytes];
+	NSUInteger buffLength = bytesDone + numBytes;
+	
+	const void *termBuff = [term bytes];
+	NSUInteger termLength = [term length];
+	
+	// Note: We are dealing with unsigned integers,
+	// so make sure the math doesn't go below zero.
+	
+	NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;
+	
+	while (i + termLength <= buffLength)
+	{
+		uint8_t *subBuffer = buff + startOffset + i;
+		
+		if (memcmp(subBuffer, termBuff, termLength) == 0)
+		{
+			return buffLength - (i + termLength);
+		}
+		
+		i++;
+	}
+	
+	return -1;
+}
+
+- (void)dealloc
+{
+	[buffer release];
+	[term release];
+	[super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncWritePacket encompasses the instructions for any given write.
+**/
+@interface GCDAsyncWritePacket : NSObject
+{
+  @public
+	NSData *buffer;
+	NSUInteger bytesDone;
+	long tag;
+	NSTimeInterval timeout;
+}
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
+@end
+
+@implementation GCDAsyncWritePacket
+
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+	if((self = [super init]))
+	{
+		buffer = [d retain]; // Retain not copy. For performance as documented in header file.
+		bytesDone = 0;
+		timeout = t;
+		tag = i;
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	[buffer release];
+	[super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
+ * This class my be altered to support more than just TLS in the future.
+**/
+@interface GCDAsyncSpecialPacket : NSObject
+{
+  @public
+	NSDictionary *tlsSettings;
+}
+- (id)initWithTLSSettings:(NSDictionary *)settings;
+@end
+
+@implementation GCDAsyncSpecialPacket
+
+- (id)initWithTLSSettings:(NSDictionary *)settings
+{
+	if((self = [super init]))
+	{
+		tlsSettings = [settings copy];
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	[tlsSettings release];
+	[super dealloc];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncSocket
+
+- (id)init
+{
+	return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (id)initWithSocketQueue:(dispatch_queue_t)sq
+{
+	return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+	return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+	if((self = [super init]))
+	{
+		delegate = aDelegate;
+		
+		if (dq)
+		{
+			dispatch_retain(dq);
+			delegateQueue = dq;
+		}
+		
+		socket4FD = SOCKET_NULL;
+		socket6FD = SOCKET_NULL;
+		connectIndex = 0;
+		
+		if (sq)
+		{
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			
+			dispatch_retain(sq);
+			socketQueue = sq;
+		}
+		else
+		{
+			socketQueue = dispatch_queue_create("GCDAsyncSocket", NULL);
+		}
+		
+		readQueue = [[NSMutableArray alloc] initWithCapacity:5];
+		currentRead = nil;
+		
+		writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
+		currentWrite = nil;
+		
+		partialReadBuffer = [[NSMutableData alloc] init];
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+	
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		[self closeWithError:nil];
+	}
+	else
+	{
+		dispatch_sync(socketQueue, ^{
+			[self closeWithError:nil];
+		});
+	}
+	
+	delegate = nil;
+	if (delegateQueue)
+		dispatch_release(delegateQueue);
+	delegateQueue = NULL;
+	
+	dispatch_release(socketQueue);
+	socketQueue = NULL;
+	
+	[readQueue release];
+	[writeQueue release];
+	
+	[partialReadBuffer release];
+	
+#if !TARGET_OS_IPHONE
+	[sslReadBuffer release];
+#endif
+	
+	[userData release];
+	
+	LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+	
+	[super dealloc];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return delegate;
+	}
+	else
+	{
+		__block id result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = delegate;
+		});
+		
+		return result;
+	}
+}
+
+- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		delegate = newDelegate;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegate:(id)newDelegate
+{
+	[self setDelegate:newDelegate synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id)newDelegate
+{
+	[self setDelegate:newDelegate synchronously:YES];
+}
+
+- (dispatch_queue_t)delegateQueue
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return delegateQueue;
+	}
+	else
+	{
+		__block dispatch_queue_t result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = delegateQueue;
+		});
+		
+		return result;
+	}
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		
+		if (delegateQueue)
+			dispatch_release(delegateQueue);
+		
+		if (newDelegateQueue)
+			dispatch_retain(newDelegateQueue);
+		
+		delegateQueue = newDelegateQueue;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		if (delegatePtr) *delegatePtr = delegate;
+		if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
+	}
+	else
+	{
+		__block id dPtr = NULL;
+		__block dispatch_queue_t dqPtr = NULL;
+		
+		dispatch_sync(socketQueue, ^{
+			dPtr = delegate;
+			dqPtr = delegateQueue;
+		});
+		
+		if (delegatePtr) *delegatePtr = dPtr;
+		if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
+	}
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		
+		delegate = newDelegate;
+		
+		if (delegateQueue)
+			dispatch_release(delegateQueue);
+		
+		if (newDelegateQueue)
+			dispatch_retain(newDelegateQueue);
+		
+		delegateQueue = newDelegateQueue;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (BOOL)autoDisconnectOnClosedReadStream
+{
+	// Note: YES means kAllowHalfDuplexConnection is OFF
+	
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return ((config & kAllowHalfDuplexConnection) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = ((config & kAllowHalfDuplexConnection) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
+{
+	// Note: YES means kAllowHalfDuplexConnection is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+			config &= ~kAllowHalfDuplexConnection;
+		else
+			config |= kAllowHalfDuplexConnection;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4Enabled
+{
+	// Note: YES means kIPv4Disabled is OFF
+	
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return ((config & kIPv4Disabled) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = ((config & kIPv4Disabled) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+	// Note: YES means kIPv4Disabled is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+			config &= ~kIPv4Disabled;
+		else
+			config |= kIPv4Disabled;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv6Enabled
+{
+	// Note: YES means kIPv6Disabled is OFF
+	
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return ((config & kIPv6Disabled) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = ((config & kIPv6Disabled) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+	// Note: YES means kIPv6Disabled is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+			config &= ~kIPv6Disabled;
+		else
+			config |= kIPv6Disabled;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4PreferredOverIPv6
+{
+	// Note: YES means kPreferIPv6 is OFF
+	
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return ((config & kPreferIPv6) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = ((config & kPreferIPv6) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setPreferIPv4OverIPv6:(BOOL)flag
+{
+	// Note: YES means kPreferIPv6 is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+			config &= ~kPreferIPv6;
+		else
+			config |= kPreferIPv6;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (id)userData
+{
+	__block id result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		result = [userData retain];
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return [result autorelease];
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+	dispatch_block_t block = ^{
+		
+		if (userData != arbitraryUserData)
+		{
+			[userData release];
+			userData = [arbitraryUserData retain];
+		}
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Accepting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
+{
+	return [self acceptOnInterface:nil port:port error:errPtr];
+}
+
+- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	// Just in-case interface parameter is immutable.
+	NSString *interface = [[inInterface copy] autorelease];
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	// CreateSocket Block
+	// This block will be invoked within the dispatch block below.
+	
+	int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+		
+		int socketFD = socket(domain, SOCK_STREAM, 0);
+		
+		if (socketFD == SOCKET_NULL)
+		{
+			NSString *reason = @"Error in socket() function";
+			err = [[self errnoErrorWithReason:reason] retain];
+			
+			return SOCKET_NULL;
+		}
+		
+		int status;
+		
+		// Set socket options
+		
+		status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+		if (status == -1)
+		{
+			NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+			err = [[self errnoErrorWithReason:reason] retain];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		int reuseOn = 1;
+		status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+		if (status == -1)
+		{
+			NSString *reason = @"Error enabling address reuse (setsockopt)";
+			err = [[self errnoErrorWithReason:reason] retain];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		// Bind socket
+		
+		status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+		if (status == -1)
+		{
+			NSString *reason = @"Error in bind() function";
+			err = [[self errnoErrorWithReason:reason] retain];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		// Listen
+		
+		status = listen(socketFD, 1024);
+		if (status == -1)
+		{
+			NSString *reason = @"Error in listen() function";
+			err = [[self errnoErrorWithReason:reason] retain];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		return socketFD;
+	};
+	
+	// Create dispatch block and run on socketQueue
+	
+	dispatch_block_t block = ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		if (delegate == nil) // Must have delegate set
+		{
+			NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+			err = [[self badConfigError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		if (delegateQueue == NULL) // Must have delegate queue set
+		{
+			NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+			err = [[self badConfigError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+		BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+		
+		if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+		{
+			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+			err = [[self badConfigError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		if (![self isDisconnected]) // Must be disconnected
+		{
+			NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+			err = [[self badConfigError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		// Clear queues (spurious read/write requests post disconnect)
+		[readQueue removeAllObjects];
+		[writeQueue removeAllObjects];
+		
+		// Resolve interface from description
+		
+		NSMutableData *interface4 = nil;
+		NSMutableData *interface6 = nil;
+		
+		[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port];
+		
+		if ((interface4 == nil) && (interface6 == nil))
+		{
+			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		if (isIPv4Disabled && (interface6 == nil))
+		{
+			NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		if (isIPv6Disabled && (interface4 == nil))
+		{
+			NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
+		BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);
+		
+		// Create sockets, configure, bind, and listen
+		
+		if (enableIPv4)
+		{
+			LogVerbose(@"Creating IPv4 socket");
+			socket4FD = createSocket(AF_INET, interface4);
+			
+			if (socket4FD == SOCKET_NULL)
+			{
+				[pool drain];
+				return_from_block;
+			}
+		}
+		
+		if (enableIPv6)
+		{
+			LogVerbose(@"Creating IPv6 socket");
+			
+			if (enableIPv4 && (port == 0))
+			{
+				// No specific port was specified, so we allowed the OS to pick an available port for us.
+				// Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket.
+				
+				struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
+				addr6->sin6_port = htons([self localPort4]);
+			}
+			
+			socket6FD = createSocket(AF_INET6, interface6);
+			
+			if (socket6FD == SOCKET_NULL)
+			{
+				if (socket4FD != SOCKET_NULL)
+				{
+					close(socket4FD);
+				}
+				
+				[pool drain];
+				return_from_block;
+			}
+		}
+		
+		// Create accept sources
+		
+		if (enableIPv4)
+		{
+			accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
+			
+			int socketFD = socket4FD;
+			dispatch_source_t acceptSource = accept4Source;
+			
+			dispatch_source_set_event_handler(accept4Source, ^{
+				NSAutoreleasePool *eventPool = [[NSAutoreleasePool alloc] init];
+				
+				LogVerbose(@"event4Block");
+				
+				unsigned long i = 0;
+				unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+				
+				LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+				
+				while ([self doAccept:socketFD] && (++i < numPendingConnections));
+				
+				[eventPool drain];
+			});
+			
+			dispatch_source_set_cancel_handler(accept4Source, ^{
+				
+				LogVerbose(@"dispatch_release(accept4Source)");
+				dispatch_release(acceptSource);
+				
+				LogVerbose(@"close(socket4FD)");
+				close(socketFD);
+			});
+			
+			LogVerbose(@"dispatch_resume(accept4Source)");
+			dispatch_resume(accept4Source);
+		}
+		
+		if (enableIPv6)
+		{
+			accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
+			
+			int socketFD = socket6FD;
+			dispatch_source_t acceptSource = accept6Source;
+			
+			dispatch_source_set_event_handler(accept6Source, ^{
+				NSAutoreleasePool *eventPool = [[NSAutoreleasePool alloc] init];
+				
+				LogVerbose(@"event6Block");
+				
+				unsigned long i = 0;
+				unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+				
+				LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+				
+				while ([self doAccept:socketFD] && (++i < numPendingConnections));
+				
+				[eventPool drain];
+			});
+			
+			dispatch_source_set_cancel_handler(accept6Source, ^{
+				
+				LogVerbose(@"dispatch_release(accept6Source)");
+				dispatch_release(acceptSource);
+				
+				LogVerbose(@"close(socket6FD)");
+				close(socketFD);
+			});
+			
+			LogVerbose(@"dispatch_resume(accept6Source)");
+			dispatch_resume(accept6Source);
+		}
+		
+		flags |= kSocketStarted;
+		
+		result = YES;
+		[pool drain];
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		LogInfo(@"Error in accept: %@", err);
+		
+		if (errPtr)
+			*errPtr = [err autorelease];
+		else
+			[err release];
+	}
+	
+	return result;
+}
+
+- (BOOL)doAccept:(int)parentSocketFD
+{
+	LogTrace();
+	
+	BOOL isIPv4;
+	int childSocketFD;
+	NSData *childSocketAddress;
+	
+	if (parentSocketFD == socket4FD)
+	{
+		isIPv4 = YES;
+		
+		struct sockaddr_in addr;
+		socklen_t addrLen = sizeof(addr);
+		
+		childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+		
+		if (childSocketFD == -1)
+		{
+			LogWarn(@"Accept failed with error: %@", [self errnoError]);
+			return NO;
+		}
+		
+		childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+	}
+	else // if (parentSocketFD == socket6FD)
+	{
+		isIPv4 = NO;
+		
+		struct sockaddr_in6 addr;
+		socklen_t addrLen = sizeof(addr);
+		
+		childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+		
+		if (childSocketFD == -1)
+		{
+			LogWarn(@"Accept failed with error: %@", [self errnoError]);
+			return NO;
+		}
+		
+		childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+	}
+	
+	// Enable non-blocking IO on the socket
+	
+	int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK);
+	if (result == -1)
+	{
+		LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)");
+		return NO;
+	}
+	
+	// Prevent SIGPIPE signals
+	
+	int nosigpipe = 1;
+	setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+	
+	// Notify delegate
+	
+	if (delegateQueue)
+	{
+		id theDelegate = delegate;
+		
+		dispatch_async(delegateQueue, ^{
+			NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+			
+			// Query delegate for custom socket queue
+			
+			dispatch_queue_t childSocketQueue = NULL;
+			
+			if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
+			{
+				childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
+				                                                              onSocket:self];
+			}
+			
+			// Create GCDAsyncSocket instance for accepted socket
+			
+			GCDAsyncSocket *acceptedSocket = [[GCDAsyncSocket alloc] initWithDelegate:delegate
+			                                                            delegateQueue:delegateQueue
+			                                                              socketQueue:childSocketQueue];
+			
+			if (isIPv4)
+				acceptedSocket->socket4FD = childSocketFD;
+			else
+				acceptedSocket->socket6FD = childSocketFD;
+			
+			acceptedSocket->flags = (kSocketStarted | kConnected);
+			
+			// Setup read and write sources for accepted socket
+			
+			dispatch_async(acceptedSocket->socketQueue, ^{
+				NSAutoreleasePool *socketPool = [[NSAutoreleasePool alloc] init];
+				
+				[acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
+				
+				[socketPool drain];
+			});
+			
+			// Notify delegate
+			
+			if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)])
+			{
+				[theDelegate socket:self didAcceptNewSocket:acceptedSocket];
+			}
+			
+			// Release the socket queue returned from the delegate (it was retained by acceptedSocket)
+			if (childSocketQueue)
+				dispatch_release(childSocketQueue);
+			
+			// Release the accepted socket (it should have been retained by the delegate)
+			[acceptedSocket release];
+			
+			[delegatePool drain];
+		});
+	}
+	
+	return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a connection attempt.
+ * It is shared between the connectToHost and connectToAddress methods.
+ * 
+**/
+- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
+{
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	if (delegate == nil) // Must have delegate set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (delegateQueue == NULL) // Must have delegate queue set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (![self isDisconnected]) // Must be disconnected
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (interface)
+	{
+		NSMutableData *interface4 = nil;
+		NSMutableData *interface6 = nil;
+		
+		[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
+		
+		if ((interface4 == nil) && (interface6 == nil))
+		{
+			if (errPtr)
+			{
+				NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+				*errPtr = [self badParamError:msg];
+			}
+			return NO;
+		}
+		
+		if (isIPv4Disabled && (interface6 == nil))
+		{
+			if (errPtr)
+			{
+				NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+				*errPtr = [self badParamError:msg];
+			}
+			return NO;
+		}
+		
+		if (isIPv6Disabled && (interface4 == nil))
+		{
+			if (errPtr)
+			{
+				NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+				*errPtr = [self badParamError:msg];
+			}
+			return NO;
+		}
+		
+		connectInterface4 = [interface4 retain];
+		connectInterface6 = [interface6 retain];
+	}
+	
+	// Clear queues (spurious read/write requests post disconnect)
+	[readQueue removeAllObjects];
+	[writeQueue removeAllObjects];
+	
+	return YES;
+}
+
+- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
+{
+	return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)host
+               onPort:(uint16_t)port
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr
+{
+	return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)inHost
+               onPort:(uint16_t)port
+         viaInterface:(NSString *)inInterface
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	// Just in case immutable objects were passed
+	NSString *host = [[inHost copy] autorelease];
+	NSString *interface = [[inInterface copy] autorelease];
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		// Check for problems with host parameter
+		
+		if ([host length] == 0)
+		{
+			NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		// Run through standard pre-connect checks
+		
+		if (![self preConnectWithInterface:interface error:&err])
+		{
+			[err retain];
+			[pool drain];
+			return_from_block;
+		}
+		
+		// We've made it past all the checks.
+		// It's time to start the connection process.
+		
+		flags |= kSocketStarted;
+		
+		LogVerbose(@"Dispatching DNS lookup...");
+		
+		// It's possible that the given host parameter is actually a NSMutableString.
+		// So we want to copy it now, within this block that will be executed synchronously.
+		// This way the asynchronous lookup block below doesn't have to worry about it changing.
+		
+		int aConnectIndex = connectIndex;
+		NSString *hostCpy = [[host copy] autorelease];
+		
+		dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+		dispatch_async(globalConcurrentQueue, ^{
+			NSAutoreleasePool *lookupPool = [[NSAutoreleasePool alloc] init];
+			
+			[self lookup:aConnectIndex host:hostCpy port:port];
+			
+			[lookupPool drain];
+		});
+		
+		[self startConnectTimeout:timeout];
+		
+		result = YES;
+		[pool drain];
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		if (errPtr)
+			*errPtr = [err autorelease];
+		else
+			[err release];
+	}
+	
+	return result;
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+	return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
+{
+	return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)inRemoteAddr
+            viaInterface:(NSString *)inInterface
+             withTimeout:(NSTimeInterval)timeout
+                   error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	// Just in case immutable objects were passed
+	NSData *remoteAddr = [[inRemoteAddr copy] autorelease];
+	NSString *interface = [[inInterface copy] autorelease];
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		// Check for problems with remoteAddr parameter
+		
+		NSData *address4 = nil;
+		NSData *address6 = nil;
+		
+		if ([remoteAddr length] >= sizeof(struct sockaddr))
+		{
+			const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes];
+			
+			if (sockaddr->sa_family == AF_INET)
+			{
+				if ([remoteAddr length] == sizeof(struct sockaddr_in))
+				{
+					address4 = remoteAddr;
+				}
+			}
+			else if (sockaddr->sa_family == AF_INET6)
+			{
+				if ([remoteAddr length] == sizeof(struct sockaddr_in6))
+				{
+					address6 = remoteAddr;
+				}
+			}
+		}
+		
+		if ((address4 == nil) && (address6 == nil))
+		{
+			NSString *msg = @"A valid IPv4 or IPv6 address was not given";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+		BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+		
+		if (isIPv4Disabled && (address4 != nil))
+		{
+			NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		if (isIPv6Disabled && (address6 != nil))
+		{
+			NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
+			err = [[self badParamError:msg] retain];
+			
+			[pool drain];
+			return_from_block;
+		}
+		
+		// Run through standard pre-connect checks
+		
+		if (![self preConnectWithInterface:interface error:&err])
+		{
+			[err retain];
+			[pool drain];
+			return_from_block;
+		}
+		
+		// We've made it past all the checks.
+		// It's time to start the connection process.
+		
+		if (![self connectWithAddress4:address4 address6:address6 error:&err])
+		{
+			[err retain];
+			[pool drain];
+			return_from_block;
+		}
+		
+		flags |= kSocketStarted;
+		
+		[self startConnectTimeout:timeout];
+		
+		result = YES;
+		[pool drain];
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		if (errPtr)
+			*errPtr = [err autorelease];
+		else
+			[err release];
+	}
+	
+	return result;
+}
+
+- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port
+{
+	LogTrace();
+	
+	// This method is executed on a global concurrent queue.
+	// It posts the results back to the socket queue.
+	// The lookupIndex is used to ignore the results if the connect operation was cancelled or timed out.
+	
+	NSError *error = nil;
+	
+	NSData *address4 = nil;
+	NSData *address6 = nil;
+	
+	
+	if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+	{
+		// Use LOOPBACK address
+		struct sockaddr_in nativeAddr;
+		nativeAddr.sin_len         = sizeof(struct sockaddr_in);
+		nativeAddr.sin_family      = AF_INET;
+		nativeAddr.sin_port        = htons(port);
+		nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+		memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.sin_zero));
+		
+		struct sockaddr_in6 nativeAddr6;
+		nativeAddr6.sin6_len       = sizeof(struct sockaddr_in6);
+		nativeAddr6.sin6_family    = AF_INET6;
+		nativeAddr6.sin6_port      = htons(port);
+		nativeAddr6.sin6_flowinfo  = 0;
+		nativeAddr6.sin6_addr      = in6addr_loopback;
+		nativeAddr6.sin6_scope_id  = 0;
+		
+		// Wrap the native address structures
+		address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+		address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+	}
+	else
+	{
+		NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+		
+		struct addrinfo hints, *res, *res0;
+		
+		memset(&hints, 0, sizeof(hints));
+		hints.ai_family   = PF_UNSPEC;
+		hints.ai_socktype = SOCK_STREAM;
+		hints.ai_protocol = IPPROTO_TCP;
+		
+		int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+		
+		if (gai_error)
+		{
+			error = [self gaiError:gai_error];
+		}
+		else
+		{
+			for(res = res0; res; res = res->ai_next)
+			{
+				if ((address4 == nil) && (res->ai_family == AF_INET))
+				{
+					// Found IPv4 address
+					// Wrap the native address structure
+					address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+				}
+				else if ((address6 == nil) && (res->ai_family == AF_INET6))
+				{
+					// Found IPv6 address
+					// Wrap the native address structure
+					address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+				}
+			}
+			freeaddrinfo(res0);
+			
+			if ((address4 == nil) && (address6 == nil))
+			{
+				error = [self gaiError:EAI_FAIL];
+			}
+		}
+	}
+	
+	if (error)
+	{
+		dispatch_async(socketQueue, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			[self lookup:aConnectIndex didFail:error];
+			[pool drain];
+		});
+	}
+	else
+	{
+		dispatch_async(socketQueue, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			[self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6];
+			[pool drain];
+		});
+	}
+}
+
+- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	NSAssert(address4 || address6, @"Expected at least one valid address");
+	
+	if (aConnectIndex != connectIndex)
+	{
+		LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	// Check for problems
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && (address6 == nil))
+	{
+		NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
+		
+		[self closeWithError:[self otherError:msg]];
+		return;
+	}
+	
+	if (isIPv6Disabled && (address4 == nil))
+	{
+		NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
+		
+		[self closeWithError:[self otherError:msg]];
+		return;
+	}
+	
+	// Start the normal connection process
+	
+	NSError *err = nil;
+	if (![self connectWithAddress4:address4 address6:address6 error:&err])
+	{
+		[self closeWithError:err];
+	}
+}
+
+/**
+ * This method is called if the DNS lookup fails.
+ * This method is executed on the socketQueue.
+ * 
+ * Since the DNS lookup executed synchronously on a global concurrent queue,
+ * the original connection request may have already been cancelled or timed-out by the time this method is invoked.
+ * The lookupIndex tells us whether the lookup is still valid or not.
+**/
+- (void)lookup:(int)aConnectIndex didFail:(NSError *)error
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	
+	if (aConnectIndex != connectIndex)
+	{
+		LogInfo(@"Ignoring lookup:didFail: - already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	[self endConnectTimeout];
+	[self closeWithError:error];
+}
+
+- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
+	LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
+	
+	// Determine socket type
+	
+	BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
+	
+	BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
+	
+	// Create the socket
+	
+	int socketFD;
+	NSData *address;
+	NSData *connectInterface;
+	
+	if (useIPv6)
+	{
+		LogVerbose(@"Creating IPv6 socket");
+		
+		socket6FD = socket(AF_INET6, SOCK_STREAM, 0);
+		
+		socketFD = socket6FD;
+		address = address6;
+		connectInterface = connectInterface6;
+	}
+	else
+	{
+		LogVerbose(@"Creating IPv4 socket");
+		
+		socket4FD = socket(AF_INET, SOCK_STREAM, 0);
+		
+		socketFD = socket4FD;
+		address = address4;
+		connectInterface = connectInterface4;
+	}
+	
+	if (socketFD == SOCKET_NULL)
+	{
+		if (errPtr)
+			*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
+		
+		return NO;
+	}
+	
+	// Bind the socket to the desired interface (if needed)
+	
+	if (connectInterface)
+	{
+		LogVerbose(@"Binding socket...");
+		
+		if ([[self class] portFromAddress:connectInterface] > 0)
+		{
+			// Since we're going to be binding to a specific port,
+			// we should turn on reuseaddr to allow us to override sockets in time_wait.
+			
+			int reuseOn = 1;
+			setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+		}
+		
+		const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
+		
+		int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
+		if (result != 0)
+		{
+			if (errPtr)
+				*errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
+			
+			return NO;
+		}
+	}
+	
+	// Start the connection process in a background queue
+	
+	int aConnectIndex = connectIndex;
+	
+	dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+	dispatch_async(globalConcurrentQueue, ^{
+		
+		int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
+		if (result == 0)
+		{
+			dispatch_async(socketQueue, ^{
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				[self didConnect:aConnectIndex];
+				[pool drain];
+			});
+		}
+		else
+		{
+			NSError *error = [self errnoErrorWithReason:@"Error in connect() function"];
+			
+			dispatch_async(socketQueue, ^{
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				[self didNotConnect:aConnectIndex error:error];
+				[pool drain];
+			});
+		}
+	});
+	
+	LogVerbose(@"Connecting...");
+	
+	return YES;
+}
+
+- (void)didConnect:(int)aConnectIndex
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	
+	if (aConnectIndex != connectIndex)
+	{
+		LogInfo(@"Ignoring didConnect, already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	flags |= kConnected;
+	
+	[self endConnectTimeout];
+	
+	#if TARGET_OS_IPHONE
+	// The endConnectTimeout method executed above incremented the connectIndex.
+	aConnectIndex = connectIndex;
+	#endif
+	
+	// Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
+	// 
+	// Note:
+	// There may be configuration options that must be set by the delegate before opening the streams.
+	// The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
+	// 
+	// Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
+	// This gives the delegate time to properly configure the streams if needed.
+	
+	dispatch_block_t SetupStreamsPart1 = ^{
+		#if TARGET_OS_IPHONE
+		
+		if (![self createReadAndWriteStream])
+		{
+			[self closeWithError:[self otherError:@"Error creating CFStreams"]];
+			return;
+		}
+		
+		if (![self registerForStreamCallbacksIncludingReadWrite:NO])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+			return;
+		}
+		
+		#endif
+	};
+	dispatch_block_t SetupStreamsPart2 = ^{
+		#if TARGET_OS_IPHONE
+		
+		if (aConnectIndex != connectIndex)
+		{
+			// The socket has been disconnected.
+			return;
+		}
+		
+		if (![self addStreamsToRunLoop])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+			return;
+		}
+		
+		if (![self openStreams])
+		{
+			[self closeWithError:[self otherError:@"Error creating CFStreams"]];
+			return;
+		}
+		
+		#endif
+	};
+	
+	// Notify delegate
+	
+	NSString *host = [self connectedHost];
+	uint16_t port = [self connectedPort];
+	
+	if (delegateQueue && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
+	{
+		SetupStreamsPart1();
+		
+		id theDelegate = delegate;
+		
+		dispatch_async(delegateQueue, ^{
+			NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+			
+			[theDelegate socket:self didConnectToHost:host port:port];
+			
+			dispatch_async(socketQueue, ^{
+				NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
+				
+				SetupStreamsPart2();
+				
+				[callbackPool drain];
+			});
+			
+			[delegatePool drain];
+		});
+	}
+	else
+	{
+		SetupStreamsPart1();
+		SetupStreamsPart2();
+	}
+		
+	// Get the connected socket
+	
+	int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD;
+	
+	// Enable non-blocking IO on the socket
+	
+	int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+	if (result == -1)
+	{
+		NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
+		[self closeWithError:[self otherError:errMsg]];
+		
+		return;
+	}
+	
+	// Prevent SIGPIPE signals
+	
+	int nosigpipe = 1;
+	setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+	
+	// Setup our read/write sources
+	
+	[self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
+	
+	// Dequeue any pending read/write requests
+	
+	[self maybeDequeueRead];
+	[self maybeDequeueWrite];
+}
+
+- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	
+	if (aConnectIndex != connectIndex)
+	{
+		LogInfo(@"Ignoring didNotConnect, already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	[self endConnectTimeout];
+	[self closeWithError:error];
+}
+
+- (void)startConnectTimeout:(NSTimeInterval)timeout
+{
+	if (timeout >= 0.0)
+	{
+		connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+		
+		dispatch_source_set_event_handler(connectTimer, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			
+			[self doConnectTimeout];
+			
+			[pool drain];
+		});
+		
+		dispatch_source_t theConnectTimer = connectTimer;
+		dispatch_source_set_cancel_handler(connectTimer, ^{
+			LogVerbose(@"dispatch_release(connectTimer)");
+			dispatch_release(theConnectTimer);
+		});
+		
+		dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
+		dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
+		
+		dispatch_resume(connectTimer);
+	}
+}
+
+- (void)endConnectTimeout
+{
+	LogTrace();
+	
+	if (connectTimer)
+	{
+		dispatch_source_cancel(connectTimer);
+		connectTimer = NULL;
+	}
+	
+	// Increment connectIndex.
+	// This will prevent us from processing results from any related background asynchronous operations.
+	// 
+	// Note: This should be called from close method even if connectTimer is NULL.
+	// This is because one might disconnect a socket prior to a successful connection which had no timeout.
+	
+	connectIndex++;
+	
+	if (connectInterface4)
+	{
+		[connectInterface4 release];
+		connectInterface4 = nil;
+	}
+	if (connectInterface6)
+	{
+		[connectInterface6 release];
+		connectInterface6 = nil;
+	}
+}
+
+- (void)doConnectTimeout
+{
+	LogTrace();
+	
+	[self endConnectTimeout];
+	[self closeWithError:[self connectTimeoutError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Disconnecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)closeWithError:(NSError *)error
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	
+	[self endConnectTimeout];
+	
+	if (currentRead != nil)  [self endCurrentRead];
+	if (currentWrite != nil) [self endCurrentWrite];
+	
+	[readQueue removeAllObjects];
+	[writeQueue removeAllObjects];
+	
+	[partialReadBuffer setLength:0];
+	
+	#if TARGET_OS_IPHONE
+	{
+		if (readStream || writeStream)
+		{
+			[self removeStreamsFromRunLoop];
+			
+			if (readStream)
+			{
+				CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
+				CFReadStreamClose(readStream);
+				CFRelease(readStream);
+				readStream = NULL;
+			}
+			if (writeStream)
+			{
+				CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
+				CFWriteStreamClose(writeStream);
+				CFRelease(writeStream);
+				writeStream = NULL;
+			}
+		}
+	}
+	#else
+	{
+		[sslReadBuffer setLength:0];
+		if (sslContext)
+		{
+			// Getting a linker error here about SSLDisposeContext?
+			// You need to add the Security Framework to your application.
+			
+			SSLDisposeContext(sslContext);
+			sslContext = NULL;
+		}
+	}
+	#endif
+	
+	// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+	// invoke the cancel handler if the dispatch source is paused.
+	// So we have to unpause the source if needed.
+	// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+	
+	if (accept4Source)
+	{
+		LogVerbose(@"dispatch_source_cancel(accept4Source)");
+		dispatch_source_cancel(accept4Source);
+		
+		// We never suspend accept4Source
+		
+		accept4Source = NULL;
+	}
+	
+	if (accept6Source)
+	{
+		LogVerbose(@"dispatch_source_cancel(accept6Source)");
+		dispatch_source_cancel(accept6Source);
+		
+		// We never suspend accept6Source
+		
+		accept6Source = NULL;
+	}
+	if (!readSource && !writeSource) {
+		LogVerbose(@"manually closing close");
+
+		if (socket4FD) {
+			close(socket4FD);
+		}
+
+		if (socket6FD) {
+			close(socket6FD);
+		}
+	}
+
+	if (readSource)
+	{
+		LogVerbose(@"dispatch_source_cancel(readSource)");
+		dispatch_source_cancel(readSource);
+		
+		[self resumeReadSource];
+		
+		readSource = NULL;
+	}
+	
+	if (writeSource)
+	{
+		LogVerbose(@"dispatch_source_cancel(writeSource)");
+		dispatch_source_cancel(writeSource);
+		
+		[self resumeWriteSource];
+		
+		writeSource = NULL;
+	}
+	
+	// The sockets will be closed by the cancel handlers of the corresponding source
+	
+	socket4FD = SOCKET_NULL;
+	socket6FD = SOCKET_NULL;
+	
+	// If the client has passed the connect/accept method, then the connection has at least begun.
+	// Notify delegate that it is now ending.
+	BOOL shouldCallDelegate = (flags & kSocketStarted);
+	
+	// Clear stored socket info and all flags (config remains as is)
+	socketFDBytesAvailable = 0;
+	flags = 0;
+	
+	if (shouldCallDelegate)
+	{
+		if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
+		{
+			id theDelegate = delegate;
+			
+			dispatch_async(delegateQueue, ^{
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				[theDelegate socketDidDisconnect:self withError:error];
+				
+				[pool drain];
+			});
+		}	
+	}
+}
+
+- (void)disconnect
+{
+	dispatch_block_t block = ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		if (flags & kSocketStarted)
+		{
+			[self closeWithError:nil];
+		}
+		
+		[pool drain];
+	};
+	
+	// Synchronous disconnection, as documented in the header file
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+}
+
+- (void)disconnectAfterReading
+{
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		if (flags & kSocketStarted)
+		{
+			flags |= (kForbidReadsWrites | kDisconnectAfterReads);
+			[self maybeClose];
+		}
+		
+		[pool drain];
+	});
+}
+
+- (void)disconnectAfterWriting
+{
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		if (flags & kSocketStarted)
+		{
+			flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
+			[self maybeClose];
+		}
+		
+		[pool drain];
+	});
+}
+
+- (void)disconnectAfterReadingAndWriting
+{
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		if (flags & kSocketStarted)
+		{
+			flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
+			[self maybeClose];
+		}
+		
+		[pool drain];
+	});
+}
+
+/**
+ * Closes the socket if possible.
+ * That is, if all writes have completed, and we're set to disconnect after writing,
+ * or if all reads have completed, and we're set to disconnect after reading.
+**/
+- (void)maybeClose
+{
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	BOOL shouldClose = NO;
+	
+	if (flags & kDisconnectAfterReads)
+	{
+		if (([readQueue count] == 0) && (currentRead == nil))
+		{
+			if (flags & kDisconnectAfterWrites)
+			{
+				if (([writeQueue count] == 0) && (currentWrite == nil))
+				{
+					shouldClose = YES;
+				}
+			}
+			else
+			{
+				shouldClose = YES;
+			}
+		}
+	}
+	else if (flags & kDisconnectAfterWrites)
+	{
+		if (([writeQueue count] == 0) && (currentWrite == nil))
+		{
+			shouldClose = YES;
+		}
+	}
+	
+	if (shouldClose)
+	{
+		[self closeWithError:nil];
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
+}
+
+- (NSError *)gaiError:(int)gai_error
+{
+	NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errnoErrorWithReason:(NSString *)reason
+{
+	NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey,
+	                                                                    reason, NSLocalizedFailureReasonErrorKey, nil];
+	
+	return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+	NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)sslError:(OSStatus)ssl_error
+{
+	NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h";
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey];
+	
+	return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo];
+}
+
+- (NSError *)connectTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Attempt to connect to host timed out", nil);
+	
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket maxed out error.
+**/
+- (NSError *)readMaxedOutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError",
+														 @"GCDAsyncSocket", [NSBundle mainBundle],
+														 @"Read operation reached set maximum length", nil);
+	
+	NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)readTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Read operation timed out", nil);
+	
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)writeTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Write operation timed out", nil);
+	
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
+}
+
+- (NSError *)connectionClosedError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Socket closed by remote peer", nil);
+	
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isDisconnected
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+		result = (flags & kSocketStarted) ? NO : YES;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isConnected
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+		result = (flags & kConnected) ? YES : NO;
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (NSString *)connectedHost
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self connectedHostFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self connectedHostFromSocket6:socket6FD];
+		
+		return nil;
+	}
+	else
+	{
+		__block NSString *result = nil;
+		
+		dispatch_sync(socketQueue, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			
+			if (socket4FD != SOCKET_NULL)
+				result = [[self connectedHostFromSocket4:socket4FD] retain];
+			else if (socket6FD != SOCKET_NULL)
+				result = [[self connectedHostFromSocket6:socket6FD] retain];
+			
+			[pool drain];
+		});
+		
+		return [result autorelease];
+	}
+}
+
+- (uint16_t)connectedPort
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self connectedPortFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self connectedPortFromSocket6:socket6FD];
+		
+		return 0;
+	}
+	else
+	{
+		__block uint16_t result = 0;
+		
+		dispatch_sync(socketQueue, ^{
+			// No need for autorelease pool
+			
+			if (socket4FD != SOCKET_NULL)
+				result = [self connectedPortFromSocket4:socket4FD];
+			else if (socket6FD != SOCKET_NULL)
+				result = [self connectedPortFromSocket6:socket6FD];
+		});
+		
+		return result;
+	}
+}
+
+- (NSString *)localHost
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self localHostFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self localHostFromSocket6:socket6FD];
+		
+		return nil;
+	}
+	else
+	{
+		__block NSString *result = nil;
+		
+		dispatch_sync(socketQueue, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			
+			if (socket4FD != SOCKET_NULL)
+				result = [[self localHostFromSocket4:socket4FD] retain];
+			else if (socket6FD != SOCKET_NULL)
+				result = [[self localHostFromSocket6:socket6FD] retain];
+			
+			[pool drain];
+		});
+		
+		return [result autorelease];
+	}
+}
+
+- (uint16_t)localPort
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self localPortFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self localPortFromSocket6:socket6FD];
+		
+		return 0;
+	}
+	else
+	{
+		__block uint16_t result = 0;
+		
+		dispatch_sync(socketQueue, ^{
+			// No need for autorelease pool
+			
+			if (socket4FD != SOCKET_NULL)
+				result = [self localPortFromSocket4:socket4FD];
+			else if (socket6FD != SOCKET_NULL)
+				result = [self localPortFromSocket6:socket6FD];
+		});
+		
+		return result;
+	}
+}
+
+- (NSString *)connectedHost4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self connectedHostFromSocket4:socket4FD];
+	
+	return nil;
+}
+
+- (NSString *)connectedHost6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self connectedHostFromSocket6:socket6FD];
+	
+	return nil;
+}
+
+- (uint16_t)connectedPort4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self connectedPortFromSocket4:socket4FD];
+	
+	return 0;
+}
+
+- (uint16_t)connectedPort6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self connectedPortFromSocket6:socket6FD];
+	
+	return 0;
+}
+
+- (NSString *)localHost4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self localHostFromSocket4:socket4FD];
+	
+	return nil;
+}
+
+- (NSString *)localHost6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self localHostFromSocket6:socket6FD];
+	
+	return nil;
+}
+
+- (uint16_t)localPort4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self localPortFromSocket4:socket4FD];
+	
+	return 0;
+}
+
+- (uint16_t)localPort6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self localPortFromSocket6:socket6FD];
+	
+	return 0;
+}
+
+- (NSString *)connectedHostFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)connectedHostFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)connectedPortFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)connectedPortFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSString *)localHostFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)localHostFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)localPortFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)localPortFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSData *)connectedAddress
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+		if (socket4FD != SOCKET_NULL)
+		{
+			struct sockaddr_in sockaddr4;
+			socklen_t sockaddr4len = sizeof(sockaddr4);
+			
+			if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+			}
+		}
+		
+		if (socket6FD != SOCKET_NULL)
+		{
+			struct sockaddr_in6 sockaddr6;
+			socklen_t sockaddr6len = sizeof(sockaddr6);
+			
+			if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+			}
+		}
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return [result autorelease];
+}
+
+- (NSData *)localAddress
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+		if (socket4FD != SOCKET_NULL)
+		{
+			struct sockaddr_in sockaddr4;
+			socklen_t sockaddr4len = sizeof(sockaddr4);
+			
+			if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+			}
+		}
+		
+		if (socket6FD != SOCKET_NULL)
+		{
+			struct sockaddr_in6 sockaddr6;
+			socklen_t sockaddr6len = sizeof(sockaddr6);
+			
+			if (getsockname(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+			}
+		}
+	};
+	
+	if (dispatch_get_current_queue() == socketQueue)
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return [result autorelease];
+}
+
+- (BOOL)isIPv4
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return (socket4FD != SOCKET_NULL);
+	}
+	else
+	{
+		__block BOOL result = NO;
+		
+		dispatch_sync(socketQueue, ^{
+			result = (socket4FD != SOCKET_NULL);
+		});
+		
+		return result;
+	}
+}
+
+- (BOOL)isIPv6
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return (socket6FD != SOCKET_NULL);
+	}
+	else
+	{
+		__block BOOL result = NO;
+		
+		dispatch_sync(socketQueue, ^{
+			result = (socket6FD != SOCKET_NULL);
+		});
+		
+		return result;
+	}
+}
+
+- (BOOL)isSecure
+{
+	if (dispatch_get_current_queue() == socketQueue)
+	{
+		return (flags & kSocketSecure) ? YES : NO;
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+			result = (flags & kSocketSecure) ? YES : NO;
+		});
+		
+		return result;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Finds the address of an interface description.
+ * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
+ * 
+ * The interface description may optionally contain a port number at the end, separated by a colon.
+ * If a non-zero port parameter is provided, any port number in the interface description is ignored.
+ * 
+ * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object.
+**/
+- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
+                    address6:(NSMutableData **)interfaceAddr6Ptr
+             fromDescription:(NSString *)interfaceDescription
+                        port:(uint16_t)port
+{
+	NSMutableData *addr4 = nil;
+	NSMutableData *addr6 = nil;
+	
+	NSString *interface = nil;
+	
+	NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
+	if ([components count] > 0)
+	{
+		NSString *temp = [components objectAtIndex:0];
+		if ([temp length] > 0)
+		{
+			interface = temp;
+		}
+	}
+	if ([components count] > 1 && port == 0)
+	{
+		long portL = strtol([[components objectAtIndex:1] UTF8String], NULL, 10);
+		
+		if (portL > 0 && portL <= UINT16_MAX)
+		{
+			port = (uint16_t)portL;
+		}
+	}
+	
+	if (interface == nil)
+	{
+		// ANY address
+		
+		struct sockaddr_in sockaddr4;
+		memset(&sockaddr4, 0, sizeof(sockaddr4));
+		
+		sockaddr4.sin_len         = sizeof(sockaddr4);
+		sockaddr4.sin_family      = AF_INET;
+		sockaddr4.sin_port        = htons(port);
+		sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
+		
+		struct sockaddr_in6 sockaddr6;
+		memset(&sockaddr6, 0, sizeof(sockaddr6));
+		
+		sockaddr6.sin6_len       = sizeof(sockaddr6);
+		sockaddr6.sin6_family    = AF_INET6;
+		sockaddr6.sin6_port      = htons(port);
+		sockaddr6.sin6_addr      = in6addr_any;
+		
+		addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+		addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+	}
+	else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
+	{
+		// LOOPBACK address
+		
+		struct sockaddr_in sockaddr4;
+		memset(&sockaddr4, 0, sizeof(sockaddr4));
+		
+		sockaddr4.sin_len         = sizeof(sockaddr4);
+		sockaddr4.sin_family      = AF_INET;
+		sockaddr4.sin_port        = htons(port);
+		sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+		
+		struct sockaddr_in6 sockaddr6;
+		memset(&sockaddr6, 0, sizeof(sockaddr6));
+		
+		sockaddr6.sin6_len       = sizeof(sockaddr6);
+		sockaddr6.sin6_family    = AF_INET6;
+		sockaddr6.sin6_port      = htons(port);
+		sockaddr6.sin6_addr      = in6addr_loopback;
+		
+		addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+		addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+	}
+	else
+	{
+		const char *iface = [interface UTF8String];
+		
+		struct ifaddrs *addrs;
+		const struct ifaddrs *cursor;
+		
+		if ((getifaddrs(&addrs) == 0))
+		{
+			cursor = addrs;
+			while (cursor != NULL)
+			{
+				if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
+				{
+					// IPv4
+					
+					struct sockaddr_in nativeAddr4;
+					memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
+					
+					if (strcmp(cursor->ifa_name, iface) == 0)
+					{
+						// Name match
+						
+						nativeAddr4.sin_port = htons(port);
+						
+						addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+					}
+					else
+					{
+						char ip[INET_ADDRSTRLEN];
+						
+						const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
+						
+						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+						{
+							// IP match
+							
+							nativeAddr4.sin_port = htons(port);
+							
+							addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+						}
+					}
+				}
+				else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
+				{
+					// IPv6
+					
+					struct sockaddr_in6 nativeAddr6;
+					memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
+					
+					if (strcmp(cursor->ifa_name, iface) == 0)
+					{
+						// Name match
+						
+						nativeAddr6.sin6_port = htons(port);
+						
+						addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+					}
+					else
+					{
+						char ip[INET6_ADDRSTRLEN];
+						
+						const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
+						
+						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+						{
+							// IP match
+							
+							nativeAddr6.sin6_port = htons(port);
+							
+							addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+						}
+					}
+				}
+				
+				cursor = cursor->ifa_next;
+			}
+			
+			freeifaddrs(addrs);
+		}
+	}
+	
+	if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
+	if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
+}
+
+- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
+{
+	readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
+	writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
+	
+	// Setup event handlers
+	
+	dispatch_source_set_event_handler(readSource, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		LogVerbose(@"readEventBlock");
+		
+		socketFDBytesAvailable = dispatch_source_get_data(readSource);
+		LogVerbose(@"socketFDBytesAvailable: %lu", socketFDBytesAvailable);
+		
+		if (socketFDBytesAvailable > 0)
+			[self doReadData];
+		else
+			[self doReadEOF];
+		
+		[pool drain];
+	});
+	
+	dispatch_source_set_event_handler(writeSource, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		LogVerbose(@"writeEventBlock");
+		
+		flags |= kSocketCanAcceptBytes;
+		[self doWriteData];
+		
+		[pool drain];
+	});
+	
+	// Setup cancel handlers
+	
+	__block int socketFDRefCount = 2;
+	
+	dispatch_source_t theReadSource = readSource;
+	dispatch_source_t theWriteSource = writeSource;
+	
+	dispatch_source_set_cancel_handler(readSource, ^{
+		
+		LogVerbose(@"readCancelBlock");
+		
+		LogVerbose(@"dispatch_release(readSource)");
+		dispatch_release(theReadSource);
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+		}
+	});
+	
+	dispatch_source_set_cancel_handler(writeSource, ^{
+		
+		LogVerbose(@"writeCancelBlock");
+		
+		LogVerbose(@"dispatch_release(writeSource)");
+		dispatch_release(theWriteSource);
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+		}
+	});
+	
+	// We will not be able to read until data arrives.
+	// But we should be able to write immediately.
+	
+	socketFDBytesAvailable = 0;
+	flags &= ~kReadSourceSuspended;
+	
+	LogVerbose(@"dispatch_resume(readSource)");
+	dispatch_resume(readSource);
+	
+	flags |= kSocketCanAcceptBytes;
+	flags |= kWriteSourceSuspended;
+}
+
+- (BOOL)usingCFStream
+{
+	#if TARGET_OS_IPHONE
+		
+		if (flags & kSocketSecure)
+		{
+			// Due to the fact that Apple doesn't give us the full power of SecureTransport on iOS,
+			// we are relegated to using the slower, less powerful, and RunLoop based CFStream API. :( Boo!
+			// 
+			// Thus we're not able to use the GCD read/write sources in this particular scenario.
+			
+			return YES;
+		}
+		
+	#endif
+	
+	return NO;
+}
+
+- (void)suspendReadSource
+{
+	if (!(flags & kReadSourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(readSource)");
+		
+		dispatch_suspend(readSource);
+		flags |= kReadSourceSuspended;
+	}
+}
+
+- (void)resumeReadSource
+{
+	if (flags & kReadSourceSuspended)
+	{
+		LogVerbose(@"dispatch_resume(readSource)");
+		
+		dispatch_resume(readSource);
+		flags &= ~kReadSourceSuspended;
+	}
+}
+
+- (void)suspendWriteSource
+{
+	if (!(flags & kWriteSourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(writeSource)");
+		
+		dispatch_suspend(writeSource);
+		flags |= kWriteSourceSuspended;
+	}
+}
+
+- (void)resumeWriteSource
+{
+	if (flags & kWriteSourceSuspended)
+	{
+		LogVerbose(@"dispatch_resume(writeSource)");
+		
+		dispatch_resume(writeSource);
+		flags &= ~kWriteSourceSuspended;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Reading
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	[self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+                     buffer:(NSMutableData *)buffer
+               bufferOffset:(NSUInteger)offset
+                        tag:(long)tag
+{
+	[self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+                     buffer:(NSMutableData *)buffer
+               bufferOffset:(NSUInteger)offset
+                  maxLength:(NSUInteger)length
+                        tag:(long)tag
+{
+	if (offset > [buffer length]) {
+		LogWarn(@"Cannot read: offset > [buffer length]");
+		return;
+	}
+	
+	GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+	                                                          startOffset:offset
+	                                                            maxLength:length
+	                                                              timeout:timeout
+	                                                           readLength:0
+	                                                           terminator:nil
+	                                                                  tag:tag];
+	
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		LogTrace();
+		
+		if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+		{
+			[readQueue addObject:packet];
+			[self maybeDequeueRead];
+		}
+		
+		[pool drain];
+	});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+	[packet release];
+}
+
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	[self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
+}
+
+- (void)readDataToLength:(NSUInteger)length
+             withTimeout:(NSTimeInterval)timeout
+                  buffer:(NSMutableData *)buffer
+            bufferOffset:(NSUInteger)offset
+                     tag:(long)tag
+{
+	if (length == 0) {
+		LogWarn(@"Cannot read: length == 0");
+		return;
+	}
+	if (offset > [buffer length]) {
+		LogWarn(@"Cannot read: offset > [buffer length]");
+		return;
+	}
+	
+	GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+	                                                          startOffset:offset
+	                                                            maxLength:0
+	                                                              timeout:timeout
+	                                                           readLength:length
+	                                                           terminator:nil
+	                                                                  tag:tag];
+	
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		LogTrace();
+		
+		if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+		{
+			[readQueue addObject:packet];
+			[self maybeDequeueRead];
+		}
+		
+		[pool drain];
+	});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+	[packet release];
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+                   tag:(long)tag
+{
+	[self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag
+{
+	[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+             maxLength:(NSUInteger)maxLength
+                   tag:(long)tag
+{
+	if ([data length] == 0) {
+		LogWarn(@"Cannot read: [data length] == 0");
+		return;
+	}
+	if (offset > [buffer length]) {
+		LogWarn(@"Cannot read: offset > [buffer length]");
+		return;
+	}
+	if (maxLength > 0 && maxLength < [data length]) {
+		LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]");
+		return;
+	}
+	
+	GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+	                                                          startOffset:offset
+	                                                            maxLength:maxLength
+	                                                              timeout:timeout
+	                                                           readLength:0
+	                                                           terminator:data
+	                                                                  tag:tag];
+	
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		LogTrace();
+		
+		if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+		{
+			[readQueue addObject:packet];
+			[self maybeDequeueRead];
+		}
+		
+		[pool drain];
+	});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+	[packet release];
+}
+
+/**
+ * This method starts a new read, if needed.
+ * 
+ * It is called when:
+ * - a user requests a read
+ * - after a read request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ * 
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueRead
+{
+	LogTrace();
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	// If we're not currently processing a read AND we have an available read stream
+	if ((currentRead == nil) && (flags & kConnected))
+	{
+		if ([readQueue count] > 0)
+		{
+			// Dequeue the next object in the write queue
+			currentRead = [[readQueue objectAtIndex:0] retain];
+			[readQueue removeObjectAtIndex:0];
+			
+			
+			if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
+			{
+				LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+				
+				// Attempt to start TLS
+				flags |= kStartingReadTLS;
+				
+				// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+				[self maybeStartTLS];
+			}
+			else
+			{
+				LogVerbose(@"Dequeued GCDAsyncReadPacket");
+				
+				// Setup read timer (if needed)
+				[self setupReadTimerWithTimeout:currentRead->timeout];
+				
+				// Immediately read, if possible
+				[self doReadData];
+			}
+		}
+		else if (flags & kDisconnectAfterReads)
+		{
+			if (flags & kDisconnectAfterWrites)
+			{
+				if (([writeQueue count] == 0) && (currentWrite == nil))
+				{
+					[self closeWithError:nil];
+				}
+			}
+			else
+			{
+				[self closeWithError:nil];
+			}
+		}
+		else if (flags & kSocketSecure)
+		{
+			[self flushSSLBuffers];
+			
+			// Edge case:
+			// 
+			// We just drained all data from the ssl buffers,
+			// and all known data from the socket (socketFDBytesAvailable).
+			// 
+			// If we didn't get any data from this process,
+			// then we may have reached the end of the TCP stream.
+			// 
+			// Be sure callbacks are enabled so we're notified about a disconnection.
+			
+			if ([partialReadBuffer length] == 0)
+			{
+				if ([self usingCFStream]) {
+					// Callbacks never disabled
+				}
+				else {
+					[self resumeReadSource];
+				}
+			}
+		}
+	}
+}
+
+- (void)flushSSLBuffers
+{
+	LogTrace();
+	
+	NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
+	
+	if ([partialReadBuffer length] > 0)
+	{
+		// Only flush the ssl buffers if the partialReadBuffer is empty.
+		// This is to avoid growing the partialReadBuffer inifinitely large.
+		
+		return;
+	}
+	
+#if TARGET_OS_IPHONE
+	
+	if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+	{
+		LogVerbose(@"%@ - Flushing ssl buffers into partialReadBuffer...", THIS_METHOD);
+		
+		CFIndex defaultBytesToRead = (1024 * 16);
+		
+		NSUInteger partialReadBufferOffset = [partialReadBuffer length];
+		[partialReadBuffer increaseLengthBy:defaultBytesToRead];
+		
+		uint8_t *buffer = [partialReadBuffer mutableBytes] + partialReadBufferOffset;
+		
+		CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
+		LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
+		
+		if (result <= 0)
+		{
+			[partialReadBuffer setLength:partialReadBufferOffset];
+		}
+		else
+		{
+			[partialReadBuffer setLength:(partialReadBufferOffset + result)];
+		}
+		
+		flags &= ~kSecureSocketHasBytesAvailable;
+	}
+	
+#else
+	
+	__block NSUInteger estimatedBytesAvailable = 0;
+	
+	dispatch_block_t updateEstimatedBytesAvailable = ^{
+		
+		// Figure out if there is any data available to be read
+		// 
+		// socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket
+		// [sslReadBuffer length] <- Number of encrypted bytes we've buffered from bsd socket
+		// sslInternalBufSize     <- Number od decrypted bytes SecureTransport has buffered
+		// 
+		// We call the variable "estimated" because we don't know how many decrypted bytes we'll get
+		// from the encrypted bytes in the sslReadBuffer.
+		// However, we do know this is an upper bound on the estimation.
+		
+		estimatedBytesAvailable = socketFDBytesAvailable + [sslReadBuffer length];
+		
+		size_t sslInternalBufSize = 0;
+		SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
+		
+		estimatedBytesAvailable += sslInternalBufSize;
+	};
+	
+	updateEstimatedBytesAvailable();
+	
+	if (estimatedBytesAvailable > 0)
+	{
+		LogVerbose(@"%@ - Flushing ssl buffers into partialReadBuffer...", THIS_METHOD);
+		
+		BOOL done = NO;
+		do
+		{
+			LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
+			
+			// Make room in the partialReadBuffer
+			
+			NSUInteger partialReadBufferOffset = [partialReadBuffer length];
+			[partialReadBuffer increaseLengthBy:estimatedBytesAvailable];
+			
+			uint8_t *buffer = (uint8_t *)[partialReadBuffer mutableBytes] + partialReadBufferOffset;
+			size_t bytesRead = 0;
+			
+			// Read data into partialReadBuffer
+			
+			OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
+			LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
+			
+			[partialReadBuffer setLength:(partialReadBufferOffset + bytesRead)];
+			LogVerbose(@"%@ - partialReadBuffer.length = %lu", THIS_METHOD, (unsigned long)[partialReadBuffer length]);
+			
+			if (result != noErr)
+			{
+				done = YES;
+			}
+			else
+			{
+				updateEstimatedBytesAvailable();
+			}
+			
+		} while (!done && estimatedBytesAvailable > 0);
+	}
+	
+#endif
+}
+
+- (void)doReadData
+{
+	LogTrace();
+	
+	// This method is called on the socketQueue.
+	// It might be called directly, or via the readSource when data is available to be read.
+	
+	if ((currentRead == nil) || (flags & kReadsPaused))
+	{
+		LogVerbose(@"No currentRead or kReadsPaused");
+		
+		// Unable to read at this time
+		
+		if (flags & kSocketSecure)
+		{
+			// Here's the situation:
+			// 
+			// We have an established secure connection.
+			// There may not be a currentRead, but there might be encrypted data sitting around for us.
+			// When the user does get around to issuing a read, that encrypted data will need to be decrypted.
+			// 
+			// So why make the user wait?
+			// We might as well get a head start on decrypting some data now.
+			// 
+			// The other reason we do this has to do with detecting a socket disconnection.
+			// The SSL/TLS protocol has it's own disconnection handshake.
+			// So when a secure socket is closed, a "goodbye" packet comes across the wire.
+			// We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
+			
+			[self flushSSLBuffers];
+		}
+		
+		if ([self usingCFStream])
+		{
+			// CFReadStream only fires once when there is available data.
+			// It won't fire again until we've invoked CFReadStreamRead.
+		}
+		else
+		{
+			// If the readSource is firing, we need to pause it
+			// or else it will continue to fire over and over again.
+			// 
+			// If the readSource is not firing,
+			// we want it to continue monitoring the socket.
+			
+			if (socketFDBytesAvailable > 0)
+			{
+				[self suspendReadSource];
+			}
+		}
+		return;
+	}
+	
+	BOOL hasBytesAvailable;
+	unsigned long estimatedBytesAvailable;
+	
+	#if TARGET_OS_IPHONE
+	{
+		if (flags & kSocketSecure)
+		{
+			// Relegated to using CFStream... :( Boo! Give us SecureTransport Apple!
+			
+			estimatedBytesAvailable = 0;
+			if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+				hasBytesAvailable = YES;
+			else
+				hasBytesAvailable = NO;
+		}
+		else
+		{
+			estimatedBytesAvailable = socketFDBytesAvailable;
+			hasBytesAvailable = (estimatedBytesAvailable > 0);
+			
+		}
+	}
+	#else
+	{	
+		estimatedBytesAvailable = socketFDBytesAvailable;
+		
+		if (flags & kSocketSecure)
+		{
+			// There are 2 buffers to be aware of here.
+			// 
+			// We are using SecureTransport, a TLS/SSL security layer which sits atop TCP.
+			// We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction.
+			// Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
+			// SecureTransport then decrypts the data, and finally returns the decrypted data back to us.
+			// 
+			// The first buffer is one we create.
+			// SecureTransport often requests small amounts of data.
+			// This has to do with the encypted packets that are coming across the TCP stream.
+			// But it's non-optimal to do a bunch of small reads from the BSD socket.
+			// So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
+			// and may store excess in the sslReadBuffer.
+			
+			estimatedBytesAvailable += [sslReadBuffer length];
+			
+			// The second buffer is within SecureTransport.
+			// As mentioned earlier, there are encrypted packets coming across the TCP stream.
+			// SecureTransport needs the entire packet to decrypt it.
+			// But if the entire packet produces X bytes of decrypted data,
+			// and we only asked SecureTransport for X/2 bytes of data,
+			// it must store the extra X/2 bytes of decrypted data for the next read.
+			// 
+			// The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
+			// From the documentation:
+			// 
+			// "This function does not block or cause any low-level read operations to occur."
+			
+			size_t sslInternalBufSize = 0;
+			SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
+			
+			estimatedBytesAvailable += sslInternalBufSize;
+		}
+	
+		hasBytesAvailable = (estimatedBytesAvailable > 0);
+	}
+	#endif
+	
+	if ((hasBytesAvailable == NO) && ([partialReadBuffer length] == 0))
+	{
+		LogVerbose(@"No data available to read...");
+		
+		// No data available to read.
+		
+		if (![self usingCFStream])
+		{
+			// Need to wait for readSource to fire and notify us of
+			// available data in the socket's internal read buffer.
+			
+			[self resumeReadSource];
+		}
+		return;
+	}
+	
+	if (flags & kStartingReadTLS)
+	{
+		LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+		
+		// The readQueue is waiting for SSL/TLS handshake to complete.
+		
+		if (flags & kStartingWriteTLS)
+		{
+			#if !TARGET_OS_IPHONE
+			{
+				// We are in the process of a SSL Handshake.
+				// We were waiting for incoming data which has just arrived.
+				
+				[self continueSSLHandshake];
+			}
+			#endif
+		}
+		else
+		{
+			// We are still waiting for the writeQueue to drain and start the SSL/TLS process.
+			// We now know data is available to read.
+			
+			if (![self usingCFStream])
+			{
+				// Suspend the read source or else it will continue to fire nonstop.
+				
+				[self suspendReadSource];
+			}
+		}
+		
+		return;
+	}
+	
+	BOOL done        = NO;  // Completed read operation
+	NSError *error   = nil; // Error occured
+	
+	NSUInteger totalBytesReadForCurrentRead = 0;
+	
+	// 
+	// STEP 1 - READ FROM PREBUFFER
+	// 
+	
+	NSUInteger partialReadBufferLength = [partialReadBuffer length];
+	
+	if (partialReadBufferLength > 0)
+	{
+		// There are 3 types of read packets:
+		// 
+		// 1) Read all available data.
+		// 2) Read a specific length of data.
+		// 3) Read up to a particular terminator.
+		
+		NSUInteger bytesToCopy;
+		
+		if (currentRead->term != nil)
+		{
+			// Read type #3 - read up to a terminator
+			
+			bytesToCopy = [currentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done];
+		}
+		else
+		{
+			// Read type #1 or #2
+			
+			bytesToCopy = [currentRead readLengthForNonTermWithHint:partialReadBufferLength];
+		}
+		
+		// Make sure we have enough room in the buffer for our read.
+		
+		[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+		
+		// Copy bytes from prebuffer into packet buffer
+		
+		uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
+		                                                                  currentRead->bytesDone;
+		
+		memcpy(buffer, [partialReadBuffer bytes], bytesToCopy);
+		
+		// Remove the copied bytes from the partial read buffer
+		[partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToCopy) withBytes:NULL length:0];
+		partialReadBufferLength -= bytesToCopy;
+		
+		LogVerbose(@"copied(%lu) partialReadBufferLength(%lu)", bytesToCopy, partialReadBufferLength);
+		
+		// Update totals
+		
+		currentRead->bytesDone += bytesToCopy;
+		totalBytesReadForCurrentRead += bytesToCopy;
+		
+		// Check to see if the read operation is done
+		
+		if (currentRead->readLength > 0)
+		{
+			// Read type #2 - read a specific length of data
+			
+			done = (currentRead->bytesDone == currentRead->readLength);
+		}
+		else if (currentRead->term != nil)
+		{
+			// Read type #3 - read up to a terminator
+			
+			// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
+			
+			if (!done && currentRead->maxLength > 0)
+			{
+				// We're not done and there's a set maxLength.
+				// Have we reached that maxLength yet?
+				
+				if (currentRead->bytesDone >= currentRead->maxLength)
+				{
+					error = [self readMaxedOutError];
+				}
+			}
+		}
+		else
+		{
+			// Read type #1 - read all available data
+			// 
+			// We're done as soon as
+			// - we've read all available data (in prebuffer and socket)
+			// - we've read the maxLength of read packet.
+			
+			done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
+		}
+		
+	}
+	
+	// 
+	// STEP 2 - READ FROM SOCKET
+	// 
+	
+	BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO;  // Nothing more to via socket (end of file)
+	BOOL waiting   = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
+	
+	if (!done && !error && !socketEOF && !waiting && hasBytesAvailable)
+	{
+		NSAssert((partialReadBufferLength == 0), @"Invalid logic");
+		
+		// There are 3 types of read packets:
+		// 
+		// 1) Read all available data.
+		// 2) Read a specific length of data.
+		// 3) Read up to a particular terminator.
+		
+		BOOL readIntoPartialReadBuffer = NO;
+		NSUInteger bytesToRead;
+		
+		if ([self usingCFStream])
+		{
+			// Since Apple has neglected to make SecureTransport available on iOS,
+			// we are relegated to using the slower, less powerful, RunLoop based CFStream API.
+			// 
+			// This API doesn't tell us how much data is available on the socket to be read.
+			// If we had that information we could optimize our memory allocations, and sys calls.
+			// 
+			// But alas...
+			// So we do it old school, and just read as much data from the socket as we can.
+			
+			NSUInteger defaultReadLength = (1024 * 32);
+			
+			bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+			                                        shouldPreBuffer:&readIntoPartialReadBuffer];
+		}
+		else
+		{
+			if (currentRead->term != nil)
+			{
+				// Read type #3 - read up to a terminator
+				
+				bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
+													 shouldPreBuffer:&readIntoPartialReadBuffer];
+			}
+			else
+			{
+				// Read type #1 or #2
+				
+				bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
+			}
+		}
+		
+		if (bytesToRead > SIZE_MAX) // NSUInteger may be bigger than size_t (read param 3)
+		{
+			bytesToRead = SIZE_MAX;
+		}
+		
+		// Make sure we have enough room in the buffer for our read.
+		// 
+		// We are either reading directly into the currentRead->buffer,
+		// or we're reading into the temporary partialReadBuffer.
+		
+		uint8_t *buffer;
+		
+		if (readIntoPartialReadBuffer)
+		{
+			if (bytesToRead > partialReadBufferLength)
+			{
+				[partialReadBuffer setLength:bytesToRead];
+			}
+			
+			buffer = [partialReadBuffer mutableBytes];
+		}
+		else
+		{
+			[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+			
+			buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset + currentRead->bytesDone;
+		}
+		
+		// Read data into buffer
+		
+		size_t bytesRead = 0;
+		
+		if (flags & kSocketSecure)
+		{
+			#if TARGET_OS_IPHONE
+			{	
+				CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
+				LogVerbose(@"CFReadStreamRead(): result = %i", (int)result);
+				
+				if (result < 0)
+				{
+					error = [NSMakeCollectable(CFReadStreamCopyError(readStream)) autorelease];
+					
+					if (readIntoPartialReadBuffer)
+						[partialReadBuffer setLength:0];
+				}
+				else if (result == 0)
+				{
+					socketEOF = YES;
+					
+					if (readIntoPartialReadBuffer)
+						[partialReadBuffer setLength:0];
+				}
+				else
+				{
+					waiting = YES;
+					bytesRead = (size_t)result;
+				}
+				
+				// We only know how many decrypted bytes were read.
+				// The actual number of bytes read was likely more due to the overhead of the encryption.
+				// So we reset our flag, and rely on the next callback to alert us of more data.
+				flags &= ~kSecureSocketHasBytesAvailable;
+			}	
+			#else
+			{	
+				// The documentation from Apple states:
+				// 
+				//     "a read operation might return errSSLWouldBlock,
+				//      indicating that less data than requested was actually transferred"
+				// 
+				// However, starting around 10.7, the function will sometimes return noErr,
+				// even if it didn't read as much data as requested. So we need to watch out for that.
+				
+				OSStatus result;
+				do
+				{
+					void *loop_buffer = buffer + bytesRead;
+					size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead;
+					size_t loop_bytesRead = 0;
+					
+					result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead);
+					LogVerbose(@"read from secure socket = %u", (unsigned)bytesRead);
+					
+					bytesRead += loop_bytesRead;
+					
+				} while ((result == noErr) && (bytesRead < bytesToRead));
+				
+				
+				if (result != noErr)
+				{
+					if (result == errSSLWouldBlock)
+						waiting = YES;
+					else
+						error = [self sslError:result];
+					
+					// It's possible that bytesRead > 0, yet the result is errSSLWouldBlock.
+					// This happens when the SSLRead function is able to read some data,
+					// but not the entire amount we requested.
+					
+					if (bytesRead <= 0)
+					{
+						bytesRead = 0;
+						
+						if (readIntoPartialReadBuffer)
+							[partialReadBuffer setLength:0];
+					}
+				}
+				
+				// Do not modify socketFDBytesAvailable.
+				// It will be updated via the SSLReadFunction().
+			}	
+			#endif
+		}
+		else
+		{
+			int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+			
+			ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
+			LogVerbose(@"read from socket = %i", (int)result);
+			
+			if (result < 0)
+			{
+				if (errno == EWOULDBLOCK)
+					waiting = YES;
+				else
+					error = [self errnoErrorWithReason:@"Error in read() function"];
+				
+				socketFDBytesAvailable = 0;
+				
+				if (readIntoPartialReadBuffer)
+					[partialReadBuffer setLength:0];
+			}
+			else if (result == 0)
+			{
+				socketEOF = YES;
+				socketFDBytesAvailable = 0;
+				
+				if (readIntoPartialReadBuffer)
+					[partialReadBuffer setLength:0];
+			}
+			else
+			{
+				bytesRead = result;
+				
+				if (socketFDBytesAvailable <= bytesRead)
+					socketFDBytesAvailable = 0;
+				else
+					socketFDBytesAvailable -= bytesRead;
+				
+				if (socketFDBytesAvailable == 0)
+				{
+					waiting = YES;
+				}
+			}
+		}
+		
+		if (bytesRead > 0)
+		{
+			// Check to see if the read operation is done
+			
+			if (currentRead->readLength > 0)
+			{
+				// Read type #2 - read a specific length of data
+				// 
+				// Note: We should never be using a prebuffer when we're reading a specific length of data.
+				
+				NSAssert(readIntoPartialReadBuffer == NO, @"Invalid logic");
+				
+				currentRead->bytesDone += bytesRead;
+				totalBytesReadForCurrentRead += bytesRead;
+				
+				done = (currentRead->bytesDone == currentRead->readLength);
+			}
+			else if (currentRead->term != nil)
+			{
+				// Read type #3 - read up to a terminator
+				
+				if (readIntoPartialReadBuffer)
+				{
+					// We just read a big chunk of data into the partialReadBuffer.
+					// Search for the terminating sequence.
+					// 
+					// Note: We are depending upon [partialReadBuffer length] to tell us how much data is
+					// available in the partialReadBuffer. So we need to be sure this matches how many bytes
+					// have actually been read into said buffer.
+					
+					[partialReadBuffer setLength:bytesRead];
+					
+					bytesToRead = [currentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done];
+					
+					// Ensure there's room on the read packet's buffer
+					
+					[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+					
+					// Copy bytes from prebuffer into read buffer
+					
+					uint8_t *preBuf = [partialReadBuffer mutableBytes];
+					uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+					                                                             + currentRead->bytesDone;
+					
+					memcpy(readBuf, preBuf, bytesToRead);
+					
+					// Remove the copied bytes from the prebuffer
+					[partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0];
+					
+					// Update totals
+					currentRead->bytesDone += bytesToRead;
+					totalBytesReadForCurrentRead += bytesToRead;
+					
+					// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above
+				}
+				else
+				{
+					// We just read a big chunk of data directly into the packet's buffer.
+					// We need to move any overflow into the prebuffer.
+					
+					NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
+					
+					if (overflow == 0)
+					{
+						// Perfect match!
+						// Every byte we read stays in the read buffer,
+						// and the last byte we read was the last byte of the term.
+						
+						currentRead->bytesDone += bytesRead;
+						totalBytesReadForCurrentRead += bytesRead;
+						done = YES;
+					}
+					else if (overflow > 0)
+					{
+						// The term was found within the data that we read,
+						// and there are extra bytes that extend past the end of the term.
+						// We need to move these excess bytes out of the read packet and into the prebuffer.
+						
+						NSInteger underflow = bytesRead - overflow;
+						
+						// Copy excess data into partialReadBuffer
+						void *overflowBuffer = buffer + currentRead->bytesDone + underflow;
+						
+						[partialReadBuffer appendBytes:overflowBuffer length:overflow];
+						
+						// Note: The completeCurrentRead method will trim the buffer for us.
+						
+						currentRead->bytesDone += underflow;
+						totalBytesReadForCurrentRead += underflow;
+						done = YES;
+					}
+					else
+					{
+						// The term was not found within the data that we read.
+						
+						currentRead->bytesDone += bytesRead;
+						totalBytesReadForCurrentRead += bytesRead;
+						done = NO;
+					}
+				}
+				
+				if (!done && currentRead->maxLength > 0)
+				{
+					// We're not done and there's a set maxLength.
+					// Have we reached that maxLength yet?
+					
+					if (currentRead->bytesDone >= currentRead->maxLength)
+					{
+						error = [self readMaxedOutError];
+					}
+				}
+			}
+			else
+			{
+				// Read type #1 - read all available data
+				
+				if (readIntoPartialReadBuffer)
+				{
+					// We just read a chunk of data into the partialReadBuffer.
+					// Copy the data into the read packet.
+					// 
+					// Recall that we didn't read directly into the packet's buffer to avoid
+					// over-allocating memory since we had no clue how much data was available to be read.
+					// 
+					// Note: We are depending upon [partialReadBuffer length] to tell us how much data is
+					// available in the partialReadBuffer. So we need to be sure this matches how many bytes
+					// have actually been read into said buffer.
+					
+					[partialReadBuffer setLength:bytesRead];
+					
+					// Ensure there's room on the read packet's buffer
+					
+					[currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
+					
+					// Copy bytes from prebuffer into read buffer
+					
+					uint8_t *preBuf = [partialReadBuffer mutableBytes];
+					uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+					                                                             + currentRead->bytesDone;
+					
+					memcpy(readBuf, preBuf, bytesRead);
+					
+					// Remove the copied bytes from the prebuffer
+					[partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesRead) withBytes:NULL length:0];
+					
+					// Update totals
+					currentRead->bytesDone += bytesRead;
+					totalBytesReadForCurrentRead += bytesRead;
+				}
+				else
+				{
+					currentRead->bytesDone += bytesRead;
+					totalBytesReadForCurrentRead += bytesRead;
+				}
+				
+				done = YES;
+			}
+			
+		} // if (bytesRead > 0)
+		
+	} // if (!done && !error && !socketEOF && !waiting && hasBytesAvailable)
+	
+	
+	if (!done && currentRead->readLength == 0 && currentRead->term == nil)
+	{
+		// Read type #1 - read all available data
+		// 
+		// We might arrive here if we read data from the prebuffer but not from the socket.
+		
+		done = (totalBytesReadForCurrentRead > 0);
+	}
+	
+	// Check to see if we're done, or if we've made progress
+	
+	if (done)
+	{
+		[self completeCurrentRead];
+		
+		if (!error && (!socketEOF || partialReadBufferLength > 0))
+		{
+			[self maybeDequeueRead];
+		}
+	}
+	else if (totalBytesReadForCurrentRead > 0)
+	{
+		// We're not done read type #2 or #3 yet, but we have read in some bytes
+		
+		if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
+		{
+			id theDelegate = delegate;
+			GCDAsyncReadPacket *theRead = currentRead;
+			
+			dispatch_async(delegateQueue, ^{
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				[theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theRead->tag];
+				
+				[pool drain];
+			});
+		}
+	}
+	
+	// Check for errors
+	
+	if (error)
+	{
+		[self closeWithError:error];
+	}
+	else if (socketEOF)
+	{
+		[self doReadEOF];
+	}
+	else if (waiting)
+	{
+		if (![self usingCFStream])
+		{
+			// Monitor the socket for readability (if we're not already doing so)
+			[self resumeReadSource];
+		}
+	}
+	
+	// Do not add any code here without first adding return statements in the error cases above.
+}
+
+- (void)doReadEOF
+{
+	LogTrace();
+	
+	// This method may be called more than once.
+	// If the EOF is read while there is still data in the partialReadBuffer,
+	// then this method may be called continually after invocations of doReadData to see if it's time to disconnect.
+	
+	flags |= kSocketHasReadEOF;
+	
+	if (flags & kSocketSecure)
+	{
+		// If the SSL layer has any buffered data, flush it into the partialReadBuffer now.
+		
+		[self flushSSLBuffers];
+	}
+	
+	BOOL shouldDisconnect;
+	NSError *error = nil;
+	
+	if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
+	{
+		// We received an EOF during or prior to startTLS.
+		// The SSL/TLS handshake is now impossible, so this is an unrecoverable situation.
+		
+		shouldDisconnect = YES;
+		
+		#if !TARGET_OS_IPHONE
+			error = [self sslError:errSSLClosedAbort];
+		#endif
+	}
+	else if (flags & kReadStreamClosed)
+	{
+		// The partialReadBuffer has already been drained.
+		// The config allows half-duplex connections.
+		// We've previously checked the socket, and it appeared writeable.
+		// So we marked the read stream as closed and notified the delegate.
+		// 
+		// As per the half-duplex contract, the socket will be closed when a write fails,
+		// or when the socket is manually closed.
+		
+		shouldDisconnect = NO;
+	}
+	else if ([partialReadBuffer length] > 0)
+	{
+		LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer");
+		
+		// Although we won't be able to read any more data from the socket,
+		// there is existing data that has been prebuffered that we can read.
+		
+		shouldDisconnect = NO;
+	}
+	else if (config & kAllowHalfDuplexConnection)
+	{
+		// We just received an EOF (end of file) from the socket's read stream.
+		// This means the remote end of the socket (the peer we're connected to)
+		// has explicitly stated that it will not be sending us any more data.
+		// 
+		// Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us)
+		
+		int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+		
+		struct pollfd pfd[1];
+		pfd[0].fd = socketFD;
+		pfd[0].events = POLLOUT;
+		pfd[0].revents = 0;
+		
+		poll(pfd, 1, 0);
+		
+		if (pfd[0].revents & POLLOUT)
+		{
+			// Socket appears to still be writeable
+			
+			shouldDisconnect = NO;
+			flags |= kReadStreamClosed;
+			
+			// Notify the delegate that we're going half-duplex
+			
+			if (delegateQueue && [delegate respondsToSelector:@selector(socketDidCloseReadStream:)])
+			{
+				id theDelegate = delegate;
+				
+				dispatch_async(delegateQueue, ^{
+					NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+					
+					[theDelegate socketDidCloseReadStream:self];
+					
+					[pool drain];
+				});
+			}
+		}
+		else
+		{
+			shouldDisconnect = YES;
+		}
+	}
+	else
+	{
+		shouldDisconnect = YES;
+	}
+	
+	
+	if (shouldDisconnect)
+	{
+		if (error == nil)
+		{
+			error = [self connectionClosedError];
+		}
+		[self closeWithError:error];
+	}
+	else
+	{
+		if (![self usingCFStream])
+		{
+			// Suspend the read source (if needed)
+			
+			[self suspendReadSource];
+		}
+	}
+}
+
+- (void)completeCurrentRead
+{
+	LogTrace();
+	
+	NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
+	
+	
+	NSData *result;
+	
+	if (currentRead->bufferOwner)
+	{
+		// We created the buffer on behalf of the user.
+		// Trim our buffer to be the proper size.
+		[currentRead->buffer setLength:currentRead->bytesDone];
+		
+		result = currentRead->buffer;
+	}
+	else
+	{
+		// We did NOT create the buffer.
+		// The buffer is owned by the caller.
+		// Only trim the buffer if we had to increase its size.
+		
+		if ([currentRead->buffer length] > currentRead->originalBufferLength)
+		{
+			NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
+			NSUInteger origSize = currentRead->originalBufferLength;
+			
+			NSUInteger buffSize = MAX(readSize, origSize);
+			
+			[currentRead->buffer setLength:buffSize];
+		}
+		
+		uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
+		
+		result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
+	}
+	
+	if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)])
+	{
+		id theDelegate = delegate;
+		GCDAsyncReadPacket *theRead = currentRead;
+		
+		dispatch_async(delegateQueue, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			
+			[theDelegate socket:self didReadData:result withTag:theRead->tag];
+			
+			[pool drain];
+		});
+	}
+	
+	[self endCurrentRead];
+}
+
+- (void)endCurrentRead
+{
+	if (readTimer)
+	{
+		dispatch_source_cancel(readTimer);
+		readTimer = NULL;
+	}
+	
+	[currentRead release];
+	currentRead = nil;
+}
+
+- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
+{
+	if (timeout >= 0.0)
+	{
+		readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+		
+		dispatch_source_set_event_handler(readTimer, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			[self doReadTimeout];
+			[pool drain];
+		});
+		
+		dispatch_source_t theReadTimer = readTimer;
+		dispatch_source_set_cancel_handler(readTimer, ^{
+			LogVerbose(@"dispatch_release(readTimer)");
+			dispatch_release(theReadTimer);
+		});
+		
+		dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
+		
+		dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+		dispatch_resume(readTimer);
+	}
+}
+
+- (void)doReadTimeout
+{
+	// This is a little bit tricky.
+	// Ideally we'd like to synchronously query the delegate about a timeout extension.
+	// But if we do so synchronously we risk a possible deadlock.
+	// So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+	
+	flags |= kReadsPaused;
+	
+	if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
+	{
+		id theDelegate = delegate;
+		GCDAsyncReadPacket *theRead = currentRead;
+		
+		dispatch_async(delegateQueue, ^{
+			NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+			
+			NSTimeInterval timeoutExtension = 0.0;
+			
+			timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
+			                                                             elapsed:theRead->timeout
+			                                                           bytesDone:theRead->bytesDone];
+			
+			dispatch_async(socketQueue, ^{
+				NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
+				
+				[self doReadTimeoutWithExtension:timeoutExtension];
+				
+				[callbackPool drain];
+			});
+			
+			[delegatePool drain];
+		});
+	}
+	else
+	{
+		[self doReadTimeoutWithExtension:0.0];
+	}
+}
+
+- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+	if (currentRead)
+	{
+		if (timeoutExtension > 0.0)
+		{
+			currentRead->timeout += timeoutExtension;
+			
+			// Reschedule the timer
+			dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC));
+			dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+			
+			// Unpause reads, and continue
+			flags &= ~kReadsPaused;
+			[self doReadData];
+		}
+		else
+		{
+			LogVerbose(@"ReadTimeout");
+			
+			[self closeWithError:[self readTimeoutError]];
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Writing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	if ([data length] == 0) return;
+	
+	GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
+	
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		LogTrace();
+		
+		if ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+		{
+			[writeQueue addObject:packet];
+			[self maybeDequeueWrite];
+		}
+		
+		[pool drain];
+	});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+	[packet release];
+}
+
+/**
+ * Conditionally starts a new write.
+ * 
+ * It is called when:
+ * - a user requests a write
+ * - after a write request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ * 
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueWrite
+{
+	LogTrace();
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	
+	// If we're not currently processing a write AND we have an available write stream
+	if ((currentWrite == nil) && (flags & kConnected))
+	{
+		if ([writeQueue count] > 0)
+		{
+			// Dequeue the next object in the write queue
+			currentWrite = [[writeQueue objectAtIndex:0] retain];
+			[writeQueue removeObjectAtIndex:0];
+			
+			
+			if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]])
+			{
+				LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+				
+				// Attempt to start TLS
+				flags |= kStartingWriteTLS;
+				
+				// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+				[self maybeStartTLS];
+			}
+			else
+			{
+				LogVerbose(@"Dequeued GCDAsyncWritePacket");
+				
+				// Setup write timer (if needed)
+				[self setupWriteTimerWithTimeout:currentWrite->timeout];
+				
+				// Immediately write, if possible
+				[self doWriteData];
+			}
+		}
+		else if (flags & kDisconnectAfterWrites)
+		{
+			if (flags & kDisconnectAfterReads)
+			{
+				if (([readQueue count] == 0) && (currentRead == nil))
+				{
+					[self closeWithError:nil];
+				}
+			}
+			else
+			{
+				[self closeWithError:nil];
+			}
+		}
+	}
+}
+
+- (void)doWriteData
+{
+	LogTrace();
+	
+	// This method is called by the writeSource via the socketQueue
+	
+	if ((currentWrite == nil) || (flags & kWritesPaused))
+	{
+		LogVerbose(@"No currentWrite or kWritesPaused");
+		
+		// Unable to write at this time
+		
+		if ([self usingCFStream])
+		{
+			// CFWriteStream only fires once when there is available data.
+			// It won't fire again until we've invoked CFWriteStreamWrite.
+		}
+		else
+		{
+			// If the writeSource is firing, we need to pause it
+			// or else it will continue to fire over and over again.
+			
+			if (flags & kSocketCanAcceptBytes)
+			{
+				[self suspendWriteSource];
+			}
+		}
+		return;
+	}
+	
+	if (!(flags & kSocketCanAcceptBytes))
+	{
+		LogVerbose(@"No space available to write...");
+		
+		// No space available to write.
+		
+		if (![self usingCFStream])
+		{
+			// Need to wait for writeSource to fire and notify us of
+			// available space in the socket's internal write buffer.
+			
+			[self resumeWriteSource];
+		}
+		return;
+	}
+	
+	if (flags & kStartingWriteTLS)
+	{
+		LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+		
+		// The writeQueue is waiting for SSL/TLS handshake to complete.
+		
+		if (flags & kStartingReadTLS)
+		{
+			#if !TARGET_OS_IPHONE
+			
+				// We are in the process of a SSL Handshake.
+				// We were waiting for available space in the socket's internal OS buffer to continue writing.
+			
+				[self continueSSLHandshake];
+			
+			#endif
+		}
+		else
+		{
+			// We are still waiting for the readQueue to drain and start the SSL/TLS process.
+			// We now know we can write to the socket.
+			
+			if (![self usingCFStream])
+			{
+				// Suspend the write source or else it will continue to fire nonstop.
+				
+				[self suspendWriteSource];
+			}
+		}
+		
+		return;
+	}
+	
+	// Note: This method is not called if theCurrentWrite is an GCDAsyncSpecialPacket (startTLS packet)
+	
+	BOOL waiting = NO;
+	NSError *error = nil;
+	size_t bytesWritten = 0;
+	
+	if (flags & kSocketSecure)
+	{
+		#if TARGET_OS_IPHONE
+			
+			const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+			
+			NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+			
+			if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+			{
+				bytesToWrite = SIZE_MAX;
+			}
+		
+			CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
+			LogVerbose(@"CFWriteStreamWrite(%lu) = %li", bytesToWrite, result);
+		
+			if (result < 0)
+			{
+				error = [NSMakeCollectable(CFWriteStreamCopyError(writeStream)) autorelease];
+			}
+			else
+			{
+				bytesWritten = (size_t)result;
+				
+				// We always set waiting to true in this scenario.
+				// CFStream may have altered our underlying socket to non-blocking.
+				// Thus if we attempt to write without a callback, we may end up blocking our queue.
+				waiting = YES;
+			}
+			
+		#else
+			
+			// We're going to use the SSLWrite function.
+			// 
+			// OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed)
+			// 
+			// Parameters:
+			// context     - An SSL session context reference.
+			// data        - A pointer to the buffer of data to write.
+			// dataLength  - The amount, in bytes, of data to write.
+			// processed   - On return, the length, in bytes, of the data actually written.
+			// 
+			// It sounds pretty straight-forward,
+			// but there are a few caveats you should be aware of.
+			// 
+			// The SSLWrite method operates in a non-obvious (and rather annoying) manner.
+			// According to the documentation:
+			// 
+			//   Because you may configure the underlying connection to operate in a non-blocking manner,
+			//   a write operation might return errSSLWouldBlock, indicating that less data than requested
+			//   was actually transferred. In this case, you should repeat the call to SSLWrite until some
+			//   other result is returned.
+			// 
+			// This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock,
+			// then the SSLWrite method returns (with the proper errSSLWouldBlock return value),
+			// but it sets bytesWritten to bytesToWrite !!
+			// 
+			// In other words, if the SSLWrite function doesn't completely write all the data we tell it to,
+			// then it doesn't tell us how many bytes were actually written.
+			// 
+			// You might be wondering:
+			// If the SSLWrite function doesn't tell us how many bytes were written,
+			// then how in the world are we supposed to update our parameters (buffer & bytesToWrite)
+			// for the next time we invoke SSLWrite?
+			// 
+			// The answer is that SSLWrite cached all the data we told it to write,
+			// and it will push out that data next time we call SSLWrite.
+			// If we call SSLWrite with new data, it will push out the cached data first, and then the new data.
+			// If we call SSLWrite with empty data, then it will simply push out the cached data.
+			// 
+			// For this purpose we're going to break large writes into a series of smaller writes.
+			// This allows us to report progress back to the delegate.
+			
+			OSStatus result;
+			
+			BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
+			BOOL hasNewDataToWrite = YES;
+			
+			if (hasCachedDataToWrite)
+			{
+				size_t processed = 0;
+				
+				result = SSLWrite(sslContext, NULL, 0, &processed);
+				
+				if (result == noErr)
+				{
+					bytesWritten = sslWriteCachedLength;
+					sslWriteCachedLength = 0;
+					
+					if (currentWrite->bytesDone == [currentWrite->buffer length])
+					{
+						// We've written all data for the current write.
+						hasNewDataToWrite = NO;
+					}
+				}
+				else
+				{
+					if (result == errSSLWouldBlock)
+					{
+						waiting = YES;
+					}
+					else
+					{
+						error = [self sslError:result];
+					}
+					
+					// Can't write any new data since we were unable to write the cached data.
+					hasNewDataToWrite = NO;
+				}
+			}
+			
+			if (hasNewDataToWrite)
+			{
+				const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone + bytesWritten;
+				
+				NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
+				
+				if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+				{
+					bytesToWrite = SIZE_MAX;
+				}
+				
+				size_t bytesRemaining = bytesToWrite;
+				
+				BOOL keepLooping = YES;
+				while (keepLooping)
+				{
+					size_t sslBytesToWrite = MIN(bytesRemaining, 32768);
+					size_t sslBytesWritten = 0;
+					
+					result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
+					
+					if (result == noErr)
+					{
+						buffer += sslBytesWritten;
+						bytesWritten += sslBytesWritten;
+						bytesRemaining -= sslBytesWritten;
+						
+						keepLooping = (bytesRemaining > 0);
+					}
+					else
+					{
+						if (result == errSSLWouldBlock)
+						{
+							waiting = YES;
+							sslWriteCachedLength = sslBytesToWrite;
+						}
+						else
+						{
+							error = [self sslError:result];
+						}
+						
+						keepLooping = NO;
+					}
+					
+				} // while (keepLooping)
+				
+			} // if (hasNewDataToWrite)
+		
+		#endif
+	}
+	else
+	{
+		int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+		
+		const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+		
+		NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+		
+		if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+		{
+			bytesToWrite = SIZE_MAX;
+		}
+		
+		ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
+		LogVerbose(@"wrote to socket = %zd", result);
+		
+		// Check results
+		if (result < 0)
+		{
+			if (errno == EWOULDBLOCK)
+			{
+				waiting = YES;
+			}
+			else
+			{
+				error = [self errnoErrorWithReason:@"Error in write() function"];
+			}
+		}
+		else
+		{
+			bytesWritten = result;
+		}
+	}
+	
+	// We're done with our writing.
+	// If we explictly ran into a situation where the socket told us there was no room in the buffer,
+	// then we immediately resume listening for notifications.
+	// 
+	// We must do this before we dequeue another write,
+	// as that may in turn invoke this method again.
+	// 
+	// Note that if CFStream is involved, it may have maliciously put our socket in blocking mode.
+	
+	if (waiting)
+	{
+		flags &= ~kSocketCanAcceptBytes;
+		
+		if (![self usingCFStream])
+		{
+			[self resumeWriteSource];
+		}
+	}
+	
+	// Check our results
+	
+	BOOL done = NO;
+	
+	if (bytesWritten > 0)
+	{
+		// Update total amount read for the current write
+		currentWrite->bytesDone += bytesWritten;
+		LogVerbose(@"currentWrite->bytesDone = %lu", currentWrite->bytesDone);
+		
+		// Is packet done?
+		done = (currentWrite->bytesDone == [currentWrite->buffer length]);
+	}
+	
+	if (done)
+	{
+		[self completeCurrentWrite];
+		
+		if (!error)
+		{
+			[self maybeDequeueWrite];
+		}
+	}
+	else
+	{
+		// We were unable to finish writing the data,
+		// so we're waiting for another callback to notify us of available space in the lower-level output buffer.
+		
+		if (!waiting & !error)
+		{
+			// This would be the case if our write was able to accept some data, but not all of it.
+			
+			flags &= ~kSocketCanAcceptBytes;
+			
+			if (![self usingCFStream])
+			{
+				[self resumeWriteSource];
+			}
+		}
+		
+		if (bytesWritten > 0)
+		{
+			// We're not done with the entire write, but we have written some bytes
+			
+			if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
+			{
+				id theDelegate = delegate;
+				GCDAsyncWritePacket *theWrite = currentWrite;
+				
+				dispatch_async(delegateQueue, ^{
+					NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+					
+					[theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWrite->tag];
+					
+					[pool drain];
+				});
+			}
+		}
+	}
+	
+	// Check for errors
+	
+	if (error)
+	{
+		[self closeWithError:[self errnoErrorWithReason:@"Error in write() function"]];
+	}
+	
+	// Do not add any code here without first adding a return statement in the error case above.
+}
+
+- (void)completeCurrentWrite
+{
+	LogTrace();
+	
+	NSAssert(currentWrite, @"Trying to complete current write when there is no current write.");
+	
+	
+	if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
+	{
+		id theDelegate = delegate;
+		GCDAsyncWritePacket *theWrite = currentWrite;
+		
+		dispatch_async(delegateQueue, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			
+			[theDelegate socket:self didWriteDataWithTag:theWrite->tag];
+			
+			[pool drain];
+		});
+	}
+	
+	[self endCurrentWrite];
+}
+
+- (void)endCurrentWrite
+{
+	if (writeTimer)
+	{
+		dispatch_source_cancel(writeTimer);
+		writeTimer = NULL;
+	}
+	
+	[currentWrite release];
+	currentWrite = nil;
+}
+
+- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout
+{
+	if (timeout >= 0.0)
+	{
+		writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+		
+		dispatch_source_set_event_handler(writeTimer, ^{
+			NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+			
+			[self doWriteTimeout];
+			
+			[pool drain];
+		});
+		
+		dispatch_source_t theWriteTimer = writeTimer;
+		dispatch_source_set_cancel_handler(writeTimer, ^{
+			LogVerbose(@"dispatch_release(writeTimer)");
+			dispatch_release(theWriteTimer);
+		});
+		
+		dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
+		
+		dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+		dispatch_resume(writeTimer);
+	}
+}
+
+- (void)doWriteTimeout
+{
+	// This is a little bit tricky.
+	// Ideally we'd like to synchronously query the delegate about a timeout extension.
+	// But if we do so synchronously we risk a possible deadlock.
+	// So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+	
+	flags |= kWritesPaused;
+	
+	if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
+	{
+		id theDelegate = delegate;
+		GCDAsyncWritePacket *theWrite = currentWrite;
+		
+		dispatch_async(delegateQueue, ^{
+			NSAutoreleasePool *delegatePool = [[NSAutoreleasePool alloc] init];
+			
+			NSTimeInterval timeoutExtension = 0.0;
+			
+			timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag
+			                                                              elapsed:theWrite->timeout
+			                                                            bytesDone:theWrite->bytesDone];
+			
+			dispatch_async(socketQueue, ^{
+				NSAutoreleasePool *callbackPool = [[NSAutoreleasePool alloc] init];
+				
+				[self doWriteTimeoutWithExtension:timeoutExtension];
+				
+				[callbackPool drain];
+			});
+			
+			[delegatePool drain];
+		});
+	}
+	else
+	{
+		[self doWriteTimeoutWithExtension:0.0];
+	}
+}
+
+- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+	if (currentWrite)
+	{
+		if (timeoutExtension > 0.0)
+		{
+			currentWrite->timeout += timeoutExtension;
+			
+			// Reschedule the timer
+			dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeoutExtension * NSEC_PER_SEC));
+			dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+			
+			// Unpause writes, and continue
+			flags &= ~kWritesPaused;
+			[self doWriteData];
+		}
+		else
+		{
+			LogVerbose(@"WriteTimeout");
+			
+			[self closeWithError:[self writeTimeoutError]];
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)startTLS:(NSDictionary *)tlsSettings
+{
+	LogTrace();
+	
+	if (tlsSettings == nil)
+    {
+        // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary,
+        // but causes problems if we later try to fetch the remote host's certificate.
+        // 
+        // To be exact, it causes the following to return NULL instead of the normal result:
+        // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates)
+        // 
+        // So we use an empty dictionary instead, which works perfectly.
+        
+        tlsSettings = [NSDictionary dictionary];
+    }
+	
+	GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
+	
+	dispatch_async(socketQueue, ^{
+		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+		
+		if ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites))
+		{
+			[readQueue addObject:packet];
+			[writeQueue addObject:packet];
+			
+			flags |= kQueuedTLS;
+			
+			[self maybeDequeueRead];
+			[self maybeDequeueWrite];
+		}
+		
+		[pool drain];
+	});
+	
+	[packet release];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security - Mac OS X
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if !TARGET_OS_IPHONE
+
+- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
+{
+	LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength);
+	
+	if ((socketFDBytesAvailable == 0) && ([sslReadBuffer length] == 0))
+	{
+		LogVerbose(@"%@ - No data available to read...", THIS_METHOD);
+		
+		// No data available to read.
+		// 
+		// Need to wait for readSource to fire and notify us of
+		// available data in the socket's internal read buffer.
+		
+		[self resumeReadSource];
+		
+		*bufferLength = 0;
+		return errSSLWouldBlock;
+	}
+	
+	size_t totalBytesRead = 0;
+	size_t totalBytesLeftToBeRead = *bufferLength;
+	
+	BOOL done = NO;
+	BOOL socketError = NO;
+	
+	// 
+	// STEP 1 : READ FROM SSL PRE BUFFER
+	// 
+	
+	NSUInteger sslReadBufferLength = [sslReadBuffer length];
+	
+	if (sslReadBufferLength > 0)
+	{
+		LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);
+		
+		size_t bytesToCopy;
+		if (sslReadBufferLength > totalBytesLeftToBeRead)
+			bytesToCopy = totalBytesLeftToBeRead;
+		else
+			bytesToCopy = (size_t)sslReadBufferLength;
+		
+		LogVerbose(@"%@: Copying %zu bytes from sslReadBuffer", THIS_METHOD, bytesToCopy);
+		
+		memcpy(buffer, [sslReadBuffer mutableBytes], bytesToCopy);
+		
+		[sslReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToCopy) withBytes:NULL length:0];
+		
+		LogVerbose(@"%@: sslReadBuffer.length = %lu", THIS_METHOD, (unsigned long)[sslReadBuffer length]);
+		
+		totalBytesRead += bytesToCopy;
+		totalBytesLeftToBeRead -= bytesToCopy;
+		
+		done = (totalBytesLeftToBeRead == 0);
+		
+		if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+	}
+	
+	// 
+	// STEP 2 : READ FROM SOCKET
+	// 
+	
+	if (!done && (socketFDBytesAvailable > 0))
+	{
+		LogVerbose(@"%@: Reading from socket...", THIS_METHOD);
+		
+		int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD;
+		
+		BOOL readIntoPreBuffer;
+		size_t bytesToRead;
+		uint8_t *buf;
+		
+		if (socketFDBytesAvailable > totalBytesLeftToBeRead)
+		{
+			// Read all available data from socket into sslReadBuffer.
+			// Then copy requested amount into dataBuffer.
+			
+			LogVerbose(@"%@: Reading into sslReadBuffer...", THIS_METHOD);
+			
+			if ([sslReadBuffer length] < socketFDBytesAvailable)
+			{
+				[sslReadBuffer setLength:socketFDBytesAvailable];
+			}
+			
+			readIntoPreBuffer = YES;
+			bytesToRead = (size_t)socketFDBytesAvailable;
+			buf = [sslReadBuffer mutableBytes];
+		}
+		else
+		{
+			// Read available data from socket directly into dataBuffer.
+			
+			LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD);
+			
+			readIntoPreBuffer = NO;
+			bytesToRead = totalBytesLeftToBeRead;
+			buf = (uint8_t *)buffer + totalBytesRead;
+		}
+		
+		ssize_t result = read(socketFD, buf, bytesToRead);
+		LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result);
+		
+		if (result < 0)
+		{
+			LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno);
+			
+			if (errno != EWOULDBLOCK)
+			{
+				socketError = YES;
+			}
+			
+			socketFDBytesAvailable = 0;
+			
+			if (readIntoPreBuffer)
+			{
+				[sslReadBuffer setLength:0];
+			}
+		}
+		else if (result == 0)
+		{
+			LogVerbose(@"%@: read EOF", THIS_METHOD);
+			
+			socketError = YES;
+			socketFDBytesAvailable = 0;
+			
+			if (readIntoPreBuffer)
+			{
+				[sslReadBuffer setLength:0];
+			}
+		}
+		else
+		{
+			size_t bytesReadFromSocket = result;
+			
+			if (socketFDBytesAvailable > bytesReadFromSocket)
+				socketFDBytesAvailable -= bytesReadFromSocket;
+			else
+				socketFDBytesAvailable = 0;
+			
+			if (readIntoPreBuffer)
+			{
+				size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
+				
+				LogVerbose(@"%@: Copying %zu bytes out of sslReadBuffer", THIS_METHOD, bytesToCopy);
+				
+				memcpy((uint8_t *)buffer + totalBytesRead, [sslReadBuffer bytes], bytesToCopy);
+				
+				[sslReadBuffer setLength:bytesReadFromSocket];
+				[sslReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToCopy) withBytes:NULL length:0];
+				
+				totalBytesRead += bytesToCopy;
+				totalBytesLeftToBeRead -= bytesToCopy;
+				
+				LogVerbose(@"%@: sslReadBuffer.length = %lu", THIS_METHOD, (unsigned long)[sslReadBuffer length]);
+			}
+			else
+			{
+				totalBytesRead += bytesReadFromSocket;
+				totalBytesLeftToBeRead -= bytesReadFromSocket;
+			}
+			
+			done = (totalBytesLeftToBeRead == 0);
+			
+			if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+		}
+	}
+	
+	*bufferLength = totalBytesRead;
+	
+	if (done)
+		return noErr;
+	
+	if (socketError)
+		return errSSLClosedAbort;
+	
+	return errSSLWouldBlock;
+}
+
+- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
+{
+	if (!(flags & kSocketCanAcceptBytes))
+	{
+		// Unable to write.
+		// 
+		// Need to wait for writeSource to fire and notify us of
+		// available space in the socket's internal write buffer.
+		
+		[self resumeWriteSource];
+		
+		*bufferLength = 0;
+		return errSSLWouldBlock;
+	}
+	
+	size_t bytesToWrite = *bufferLength;
+	size_t bytesWritten = 0;
+	
+	BOOL done = NO;
+	BOOL socketError = NO;
+	
+	int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+	
+	ssize_t result = write(socketFD, buffer, bytesToWrite);
+	
+	if (result < 0)
+	{
+		if (errno != EWOULDBLOCK)
+		{
+			socketError = YES;
+		}
+		
+		flags &= ~kSocketCanAcceptBytes;
+	}
+	else if (result == 0)
+	{
+		flags &= ~kSocketCanAcceptBytes;
+	}
+	else
+	{
+		bytesWritten = result;
+		
+		done = (bytesWritten == bytesToWrite);
+	}
+	
+	*bufferLength = bytesWritten;
+	
+	if (done)
+		return noErr;
+	
+	if (socketError)
+		return errSSLClosedAbort;
+	
+	return errSSLWouldBlock;
+}
+
+static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
+{
+	GCDAsyncSocket *asyncSocket = (GCDAsyncSocket *)connection;
+	
+	NSCAssert(dispatch_get_current_queue() == asyncSocket->socketQueue, @"What the deuce?");
+	
+	return [asyncSocket sslReadWithBuffer:data length:dataLength];
+}
+
+static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength)
+{
+	GCDAsyncSocket *asyncSocket = (GCDAsyncSocket *)connection;
+	
+	NSCAssert(dispatch_get_current_queue() == asyncSocket->socketQueue, @"What the deuce?");
+	
+	return [asyncSocket sslWriteWithBuffer:data length:dataLength];
+}
+
+- (void)maybeStartTLS
+{
+	LogTrace();
+	
+	// We can't start TLS until:
+	// - All queued reads prior to the user calling startTLS are complete
+	// - All queued writes prior to the user calling startTLS are complete
+	// 
+	// We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		LogVerbose(@"Starting TLS...");
+		
+		OSStatus status;
+		
+		GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+		NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+		
+		// Create SSLContext, and setup IO callbacks and connection ref
+		
+		BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue];
+		
+		status = SSLNewContext(isServer, &sslContext);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLNewContext"]];
+			return;
+		}
+		
+		status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
+			return;
+		}
+		
+		status = SSLSetConnection(sslContext, (SSLConnectionRef)self);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
+			return;
+		}
+		
+		// Configure SSLContext from given settings
+		// 
+		// Checklist:
+		// 1. kCFStreamSSLPeerName
+		// 2. kCFStreamSSLAllowsAnyRoot
+		// 3. kCFStreamSSLAllowsExpiredRoots
+		// 4. kCFStreamSSLValidatesCertificateChain
+		// 5. kCFStreamSSLAllowsExpiredCertificates
+		// 6. kCFStreamSSLCertificates
+		// 7. kCFStreamSSLLevel
+		// 8. GCDAsyncSocketSSLCipherSuites
+		// 9. GCDAsyncSocketSSLDiffieHellmanParameters
+		
+		id value;
+		
+		// 1. kCFStreamSSLPeerName
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLPeerName];
+		if ([value isKindOfClass:[NSString class]])
+		{
+			NSString *peerName = (NSString *)value;
+			
+			const char *peer = [peerName UTF8String];
+			size_t peerLen = strlen(peer);
+			
+			status = SSLSetPeerDomainName(sslContext, peer, peerLen);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]];
+				return;
+			}
+		}
+		
+		// 2. kCFStreamSSLAllowsAnyRoot
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
+		if (value)
+		{
+			BOOL allowsAnyRoot = [value boolValue];
+			
+			status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]];
+				return;
+			}
+		}
+		
+		// 3. kCFStreamSSLAllowsExpiredRoots
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
+		if (value)
+		{
+			BOOL allowsExpiredRoots = [value boolValue];
+			
+			status = SSLSetAllowsExpiredRoots(sslContext, allowsExpiredRoots);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredRoots"]];
+				return;
+			}
+		}
+		
+		// 4. kCFStreamSSLValidatesCertificateChain
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+		if (value)
+		{
+			BOOL validatesCertChain = [value boolValue];
+			
+			status = SSLSetEnableCertVerify(sslContext, validatesCertChain);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
+				return;
+			}
+		}
+		
+		// 5. kCFStreamSSLAllowsExpiredCertificates
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
+		if (value)
+		{
+			BOOL allowsExpiredCerts = [value boolValue];
+			
+			status = SSLSetAllowsExpiredCerts(sslContext, allowsExpiredCerts);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredCerts"]];
+				return;
+			}
+		}
+		
+		// 6. kCFStreamSSLCertificates
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates];
+		if (value)
+		{
+			CFArrayRef certs = (CFArrayRef)value;
+			
+			status = SSLSetCertificate(sslContext, certs);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+				return;
+			}
+		}
+		
+		// 7. kCFStreamSSLLevel
+		
+		value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel];
+		if (value)
+		{
+			NSString *sslLevel = (NSString *)value;
+			
+			if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv2])
+			{
+				// kCFStreamSocketSecurityLevelSSLv2:
+				// 
+				// Specifies that SSL version 2 be set as the security protocol.
+				
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2,   YES);
+			}
+			else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3])
+			{
+				// kCFStreamSocketSecurityLevelSSLv3:
+				// 
+				// Specifies that SSL version 3 be set as the security protocol.
+				// If SSL version 3 is not available, specifies that SSL version 2 be set as the security protocol.
+				
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2,   YES);
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol3,   YES);
+			}
+			else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1])
+			{
+				// kCFStreamSocketSecurityLevelTLSv1:
+				// 
+				// Specifies that TLS version 1 be set as the security protocol.
+				
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+				SSLSetProtocolVersionEnabled(sslContext, kTLSProtocol1,   YES);
+			}
+			else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL])
+			{
+				// kCFStreamSocketSecurityLevelNegotiatedSSL:
+				// 
+				// Specifies that the highest level security protocol that can be negotiated be used.
+				
+				SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, YES);
+			}
+		}
+		
+		// 8. GCDAsyncSocketSSLCipherSuites
+		
+		value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
+		if (value)
+		{
+			NSArray *cipherSuites = (NSArray *)value;
+			NSUInteger numberCiphers = [cipherSuites count];
+			SSLCipherSuite ciphers[numberCiphers];
+			
+			NSUInteger cipherIndex;
+			for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
+			{
+				NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
+				ciphers[cipherIndex] = [cipherObject shortValue];
+			}
+			
+			status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+				return;
+			}
+		}
+		
+		// 9. GCDAsyncSocketSSLDiffieHellmanParameters
+		
+		value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
+		if (value)
+		{
+			NSData *diffieHellmanData = (NSData *)value;
+			
+			status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
+				return;
+			}
+		}
+		
+		// Setup the sslReadBuffer
+		// 
+		// If there is any data in the partialReadBuffer,
+		// this needs to be moved into the sslReadBuffer,
+		// as this data is now part of the secure read stream.
+		
+		sslReadBuffer = [[NSMutableData alloc] init];
+		
+		if ([partialReadBuffer length] > 0)
+		{
+			[sslReadBuffer appendData:partialReadBuffer];
+			[partialReadBuffer setLength:0];
+		}
+		
+		// Start the SSL Handshake process
+		
+		[self continueSSLHandshake];
+	}
+}
+
+- (void)continueSSLHandshake
+{
+	LogTrace();
+	
+	// If the return value is noErr, the session is ready for normal secure communication.
+	// If the return value is errSSLWouldBlock, the SSLHandshake function must be called again.
+	// Otherwise, the return value indicates an error code.
+	
+	OSStatus status = SSLHandshake(sslContext);
+	
+	if (status == noErr)
+	{
+		LogVerbose(@"SSLHandshake complete");
+		
+		flags &= ~kStartingReadTLS;
+		flags &= ~kStartingWriteTLS;
+		
+		flags |=  kSocketSecure;
+		
+		if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)])
+		{
+			id theDelegate = delegate;
+			
+			dispatch_async(delegateQueue, ^{
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				[theDelegate socketDidSecure:self];
+				
+				[pool drain];
+			});
+		}
+		
+		[self endCurrentRead];
+		[self endCurrentWrite];
+		
+		[self maybeDequeueRead];
+		[self maybeDequeueWrite];
+	}
+	else if (status == errSSLWouldBlock)
+	{
+		LogVerbose(@"SSLHandshake continues...");
+		
+		// Handshake continues...
+		// 
+		// This method will be called again from doReadData or doWriteData.
+	}
+	else
+	{
+		[self closeWithError:[self sslError:status]];
+	}
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security - iOS
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+- (void)finishSSLHandshake
+{
+	LogTrace();
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		flags &= ~kStartingReadTLS;
+		flags &= ~kStartingWriteTLS;
+		
+		flags |= kSocketSecure;
+		
+		if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)])
+		{
+			id theDelegate = delegate;
+		
+			dispatch_async(delegateQueue, ^{
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				[theDelegate socketDidSecure:self];
+				
+				[pool drain];
+			});
+		}
+		
+		[self endCurrentRead];
+		[self endCurrentWrite];
+		
+		[self maybeDequeueRead];
+		[self maybeDequeueWrite];
+	}
+}
+
+- (void)abortSSLHandshake:(NSError *)error
+{
+	LogTrace();
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		flags &= ~kStartingReadTLS;
+		flags &= ~kStartingWriteTLS;
+		
+		[self closeWithError:error];
+	}
+}
+
+- (void)maybeStartTLS
+{
+	LogTrace();
+	
+	// We can't start TLS until:
+	// - All queued reads prior to the user calling startTLS are complete
+	// - All queued writes prior to the user calling startTLS are complete
+	// 
+	// We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		LogVerbose(@"Starting TLS...");
+		
+		if ([partialReadBuffer length] > 0)
+		{
+			NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket.";
+			
+			[self closeWithError:[self otherError:msg]];
+			return;
+		}
+		
+		[self suspendReadSource];
+		[self suspendWriteSource];
+		
+		socketFDBytesAvailable = 0;
+		flags &= ~kSocketCanAcceptBytes;
+		flags &= ~kSecureSocketHasBytesAvailable;
+		
+		if (![self createReadAndWriteStream])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]];
+			return;
+		}
+		
+		if (![self registerForStreamCallbacksIncludingReadWrite:YES])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+			return;
+		}
+		
+		if (![self addStreamsToRunLoop])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+			return;
+		}
+		
+		NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS");
+		NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS");
+		
+		GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+		NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+		
+		// Getting an error concerning kCFStreamPropertySSLSettings ?
+		// You need to add the CFNetwork framework to your iOS application.
+		
+		BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFDictionaryRef)tlsSettings);
+		BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFDictionaryRef)tlsSettings);
+		
+		// For some reason, starting around the time of iOS 4.3,
+		// the first call to set the kCFStreamPropertySSLSettings will return true,
+		// but the second will return false.
+		// 
+		// Order doesn't seem to matter.
+		// So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order.
+		// Either way, the first call will return true, and the second returns false.
+		// 
+		// Interestingly, this doesn't seem to affect anything.
+		// Which is not altogether unusual, as the documentation seems to suggest that (for many settings)
+		// setting it on one side of the stream automatically sets it for the other side of the stream.
+		// 
+		// Although there isn't anything in the documentation to suggest that the second attempt would fail.
+		// 
+		// Furthermore, this only seems to affect streams that are negotiating a security upgrade.
+		// In other words, the socket gets connected, there is some back-and-forth communication over the unsecure
+		// connection, and then a startTLS is issued.
+		// So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS).
+		
+		if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug.
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]];
+			return;
+		}
+		
+		if (![self openStreams])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamOpen"]];
+			return;
+		}
+		
+		LogVerbose(@"Waiting for SSL Handshake to complete...");
+	}
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
++ (void)startListenerThreadIfNeeded
+{
+	static dispatch_once_t predicate;
+	dispatch_once(&predicate, ^{
+		
+		listenerThread = [[NSThread alloc] initWithTarget:self
+		                                         selector:@selector(listenerThread)
+		                                           object:nil];
+		[listenerThread start];
+	});
+}
+
++ (void)listenerThread
+{
+	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+	
+	LogInfo(@"ListenerThread: Started");
+	
+	// We can't run the run loop unless it has an associated input source or a timer.
+	// So we'll just create a timer that will never fire - unless the server runs for a decades.
+	[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+	                                 target:self
+	                               selector:@selector(ignore:)
+	                               userInfo:nil
+	                                repeats:YES];
+	
+	[[NSRunLoop currentRunLoop] run];
+	
+	LogInfo(@"ListenerThread: Stopped");
+	
+	[pool drain];
+}
+
++ (void)addStreamListener:(GCDAsyncSocket *)asyncSocket
+{
+	LogTrace();
+	NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+	
+	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+	
+	if (asyncSocket->readStream)
+		CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncSocket->writeStream)
+		CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
++ (void)removeStreamListener:(GCDAsyncSocket *)asyncSocket
+{
+	LogTrace();
+	NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+	
+	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+	
+	if (asyncSocket->readStream)
+		CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncSocket->writeStream)
+		CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
+static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+	GCDAsyncSocket *asyncSocket = [(GCDAsyncSocket *)pInfo retain];
+	
+	switch(type)
+	{
+		case kCFStreamEventHasBytesAvailable:
+		{
+			dispatch_async(asyncSocket->socketQueue, ^{
+				
+				LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
+				
+				if (asyncSocket->readStream != stream)
+					return_from_block;
+				
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+					// (A callback related to the tcp stream, but not to the SSL layer).
+					
+					if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
+					{
+						asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+						[asyncSocket finishSSLHandshake];
+					}
+				}
+				else
+				{
+					asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+					[asyncSocket doReadData];
+				}
+				
+				[pool drain];
+			});
+			
+			break;
+		}
+		default:
+		{
+			NSError *error = NSMakeCollectable(CFReadStreamCopyError(stream));
+			if (error == nil && type == kCFStreamEventEndEncountered)
+			{
+				error = [[asyncSocket connectionClosedError] retain];
+			}
+			
+			dispatch_async(asyncSocket->socketQueue, ^{
+				
+				LogCVerbose(@"CFReadStreamCallback - Other");
+				
+				if (asyncSocket->readStream != stream)
+					return_from_block;
+				
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					[asyncSocket abortSSLHandshake:error];
+				}
+				else
+				{
+					[asyncSocket closeWithError:error];
+				}
+				
+				[pool drain];
+			});
+			
+			[error release];
+			break;
+		}
+	}
+	
+	[asyncSocket release];
+}
+
+static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+	GCDAsyncSocket *asyncSocket = [(GCDAsyncSocket *)pInfo retain];
+	
+	switch(type)
+	{
+		case kCFStreamEventCanAcceptBytes:
+		{
+			dispatch_async(asyncSocket->socketQueue, ^{
+				
+				LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
+				
+				if (asyncSocket->writeStream != stream)
+					return_from_block;
+				
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+					// (A callback related to the tcp stream, but not to the SSL layer).
+					
+					if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream))
+					{
+						asyncSocket->flags |= kSocketCanAcceptBytes;
+						[asyncSocket finishSSLHandshake];
+					}
+				}
+				else
+				{
+					asyncSocket->flags |= kSocketCanAcceptBytes;
+					[asyncSocket doWriteData];
+				}
+				
+				[pool drain];
+			});
+			
+			break;
+		}
+		default:
+		{
+			NSError *error = NSMakeCollectable(CFWriteStreamCopyError(stream));
+			if (error == nil && type == kCFStreamEventEndEncountered)
+			{
+				error = [[asyncSocket connectionClosedError] retain];
+			}
+			
+			dispatch_async(asyncSocket->socketQueue, ^{
+				
+				LogCVerbose(@"CFWriteStreamCallback - Other");
+				
+				if (asyncSocket->writeStream != stream)
+					return_from_block;
+				
+				NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					[asyncSocket abortSSLHandshake:error];
+				}
+				else
+				{
+					[asyncSocket closeWithError:error];
+				}
+				
+				[pool drain];
+			});
+			
+			[error release];
+			break;
+		}
+	}
+	
+	[asyncSocket release];
+}
+
+- (BOOL)createReadAndWriteStream
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	
+	
+	if (readStream || writeStream)
+	{
+		// Streams already created
+		return YES;
+	}
+	
+	int socketFD = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD;
+	
+	if (socketFD == SOCKET_NULL)
+	{
+		// Cannot create streams without a file descriptor
+		return NO;
+	}
+	
+	if (![self isConnected])
+	{
+		// Cannot create streams until file descriptor is connected
+		return NO;
+	}
+	
+	LogVerbose(@"Creating read and write stream...");
+	
+	CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream);
+	
+	// The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case).
+	// But let's not take any chances.
+	
+	if (readStream)
+		CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	if (writeStream)
+		CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	
+	if ((readStream == NULL) || (writeStream == NULL))
+	{
+		LogWarn(@"Unable to create read and write stream...");
+		
+		if (readStream)
+		{
+			CFReadStreamClose(readStream);
+			CFRelease(readStream);
+			readStream = NULL;
+		}
+		if (writeStream)
+		{
+			CFWriteStreamClose(writeStream);
+			CFRelease(writeStream);
+			writeStream = NULL;
+		}
+		
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
+{
+	LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	streamContext.version = 0;
+	streamContext.info = self;
+	streamContext.retain = nil;
+	streamContext.release = nil;
+	streamContext.copyDescription = nil;
+	
+	CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+	if (includeReadWrite)
+		readStreamEvents |= kCFStreamEventHasBytesAvailable;
+	
+	if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
+	{
+		return NO;
+	}
+	
+	CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+	if (includeReadWrite)
+		writeStreamEvents |= kCFStreamEventCanAcceptBytes;
+	
+	if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
+	{
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)addStreamsToRunLoop
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	if (!(flags & kAddedStreamListener))
+	{
+		LogVerbose(@"Adding streams to runloop...");
+		
+		[[self class] startListenerThreadIfNeeded];
+		[[self class] performSelector:@selector(addStreamListener:)
+		                     onThread:listenerThread
+		                   withObject:self
+		                waitUntilDone:YES];
+		
+		flags |= kAddedStreamListener;
+	}
+	
+	return YES;
+}
+
+- (void)removeStreamsFromRunLoop
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	if (flags & kAddedStreamListener)
+	{
+		LogVerbose(@"Removing streams from runloop...");
+		
+		[[self class] performSelector:@selector(removeStreamListener:)
+		                     onThread:listenerThread
+		                   withObject:self
+		                waitUntilDone:YES];
+		
+		flags &= ~kAddedStreamListener;
+	}
+}
+
+- (BOOL)openStreams
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_current_queue() == socketQueue, @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	CFStreamStatus readStatus = CFReadStreamGetStatus(readStream);
+	CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream);
+	
+	if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen))
+	{
+		LogVerbose(@"Opening read and write stream...");
+		
+		BOOL r1 = CFReadStreamOpen(readStream);
+		BOOL r2 = CFWriteStreamOpen(writeStream);
+		
+		if (!r1 || !r2)
+		{
+			LogError(@"Error in CFStreamOpen");
+			return NO;
+		}
+	}
+	
+	return YES;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Advanced
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)performBlock:(dispatch_block_t)block
+{
+	dispatch_sync(socketQueue, block);
+}
+
+- (int)socketFD
+{
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	if (socket4FD != SOCKET_NULL)
+		return socket4FD;
+	else
+		return socket6FD;
+}
+
+- (int)socket4FD
+{
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	return socket4FD;
+}
+
+- (int)socket6FD
+{
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	return socket6FD;
+}
+
+#if TARGET_OS_IPHONE
+
+- (CFReadStreamRef)readStream
+{
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NULL;
+	}
+	
+	if (readStream == NULL)
+		[self createReadAndWriteStream];
+	
+	return readStream;
+}
+
+- (CFWriteStreamRef)writeStream
+{
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NULL;
+	}
+	
+	if (writeStream == NULL)
+		[self createReadAndWriteStream];
+	
+	return writeStream;
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
+{
+	if (![self createReadAndWriteStream])
+	{
+		// Error occured creating streams (perhaps socket isn't open)
+		return NO;
+	}
+	
+	BOOL r1, r2;
+	
+	LogVerbose(@"Enabling backgrouding on socket");
+	
+	r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+	r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+	
+	if (!r1 || !r2)
+	{
+		return NO;
+	}
+	
+	if (!caveat)
+	{
+		if (![self openStreams])
+		{
+			return NO;
+		}
+	}
+	
+	return YES;
+}
+
+- (BOOL)enableBackgroundingOnSocket
+{
+	LogTrace();
+	
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NO;
+	}
+	
+	return [self enableBackgroundingOnSocketWithCaveat:NO];
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.???
+{
+	// This method was created as a workaround for a bug in iOS.
+	// Apple has since fixed this bug.
+	// I'm not entirely sure which version of iOS they fixed it in...
+	
+	LogTrace();
+	
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NO;
+	}
+	
+	return [self enableBackgroundingOnSocketWithCaveat:YES];
+}
+
+#else
+
+- (SSLContextRef)sslContext
+{
+	if (dispatch_get_current_queue() != socketQueue)
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NULL;
+	}
+	
+	return sslContext;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Class Methods
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+	char addrBuf[INET_ADDRSTRLEN];
+	
+	if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+	{
+		addrBuf[0] = '\0';
+	}
+	
+	return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+	char addrBuf[INET6_ADDRSTRLEN];
+	
+	if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+	{
+		addrBuf[0] = '\0';
+	}
+	
+	return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+	return ntohs(pSockaddr4->sin_port);
+}
+
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+	return ntohs(pSockaddr6->sin6_port);
+}
+
++ (NSString *)hostFromAddress:(NSData *)address
+{
+	NSString *host;
+	
+	if ([self getHost:&host port:NULL fromAddress:address])
+		return host;
+	else
+		return nil;
+}
+
++ (uint16_t)portFromAddress:(NSData *)address
+{
+	uint16_t port;
+	
+	if ([self getHost:NULL port:&port fromAddress:address])
+		return port;
+	else
+		return 0;
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
+{
+	if ([address length] >= sizeof(struct sockaddr))
+	{
+		const struct sockaddr *sockaddrX = [address bytes];
+		
+		if (sockaddrX->sa_family == AF_INET)
+		{
+			if ([address length] >= sizeof(struct sockaddr_in))
+			{
+				struct sockaddr_in sockaddr4;
+				memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4));
+				
+				if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4];
+				if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4];
+				
+				return YES;
+			}
+		}
+		else if (sockaddrX->sa_family == AF_INET6)
+		{
+			if ([address length] >= sizeof(struct sockaddr_in6))
+			{
+				struct sockaddr_in6 sockaddr6;
+				memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6));
+				
+				if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6];
+				if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6];
+				
+				return YES;
+			}
+		}
+	}
+	
+	return NO;
+}
+
++ (NSData *)CRLFData
+{
+	return [NSData dataWithBytes:"\x0D\x0A" length:2];
+}
+
++ (NSData *)CRData
+{
+	return [NSData dataWithBytes:"\x0D" length:1];
+}
+
++ (NSData *)LFData
+{
+	return [NSData dataWithBytes:"\x0A" length:1];
+}
+
++ (NSData *)ZeroData
+{
+	return [NSData dataWithBytes:"" length:1];
+}
+
+@end	

+ 29 - 0
shell_integration/MacOSX/LiferayNativityFinder/IconCache.h

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+@interface IconCache : NSObject {
+	int _currentIconId;
+	NSMutableDictionary* _iconIdDictionary;
+    NSMutableDictionary* _iconPathDictionary;
+}
+
++ (IconCache*)sharedInstance;
+
+- (NSImage*)getIcon:(NSNumber*)iconId;
+- (NSNumber*)registerIcon:(NSString*)path;
+- (void)unregisterIcon:(NSNumber*)iconId;
+
+@end

+ 115 - 0
shell_integration/MacOSX/LiferayNativityFinder/IconCache.m

@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import "IconCache.h"
+
+@implementation IconCache
+
+static IconCache* sharedInstance = nil;
+
+- init
+{
+	self = [super init];
+
+	if (self)
+	{
+		_iconIdDictionary = [[NSMutableDictionary alloc] init];
+		_iconPathDictionary = [[NSMutableDictionary alloc] init];
+		_currentIconId = 0;
+	}
+
+	return self;
+}
+
+- (void)dealloc
+{
+	[_iconIdDictionary release];
+	[_iconPathDictionary release];
+	sharedInstance = nil;
+
+	[super dealloc];
+}
+
++ (IconCache*)sharedInstance
+{
+	@synchronized(self)
+	{
+		if (sharedInstance == nil)
+		{
+			sharedInstance = [[self alloc] init];
+		}
+	}
+	return sharedInstance;
+}
+
+- (NSImage*)getIcon:(NSNumber*)iconId
+{
+	NSImage* image = [_iconIdDictionary objectForKey:iconId];
+
+	return image;
+}
+
+- (NSNumber*)registerIcon:(NSString*)path
+{
+	if (path == nil)
+	{
+		return [NSNumber numberWithInt:-1];
+	}
+
+	NSImage* image = [[NSImage alloc]initWithContentsOfFile:path];
+
+	if (image == nil)
+	{
+		return [NSNumber numberWithInt:-1];
+	}
+
+	NSNumber* iconId = [_iconPathDictionary objectForKey:path];
+
+	if (iconId == nil)
+	{
+		_currentIconId++;
+
+		iconId = [NSNumber numberWithInt:_currentIconId];
+
+		[_iconPathDictionary setObject:iconId forKey:path];
+	}
+
+	[_iconIdDictionary setObject:image forKey:iconId];
+	[image release];
+
+	return iconId;
+}
+
+- (void)unregisterIcon:(NSNumber*)iconId
+{
+	NSString* path = @"";
+
+	for (NSString* key in _iconPathDictionary)
+	{
+		NSNumber* value = [_iconPathDictionary objectForKey:key];
+
+		if ([value isEqualToNumber:iconId])
+		{
+			path = key;
+
+			break;
+		}
+	}
+
+	[_iconPathDictionary removeObjectForKey:path];
+
+	[_iconIdDictionary removeObjectForKey:iconId];
+}
+
+@end

+ 23 - 0
shell_integration/MacOSX/LiferayNativityFinder/IconOverlayHandlers.h

@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Foundation/Foundation.h>
+
+@interface NSObject (IconOverlayHandlers)
+
+- (void)IconOverlayHandlers_drawIconWithFrame:(struct CGRect)arg1;
+- (void)IconOverlayHandlers_drawImage:(id)arg1;
+- (void)IconOverlayHandlers_drawRect:(struct CGRect)arg1;
+
+@end

+ 140 - 0
shell_integration/MacOSX/LiferayNativityFinder/IconOverlayHandlers.m

@@ -0,0 +1,140 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <objc/runtime.h>
+#import "ContentManager.h"
+#import "IconCache.h"
+#import "IconOverlayHandlers.h"
+#import "Finder/Finder.h"
+
+@implementation NSObject (IconOverlayHandlers)
+
+- (void)IconOverlayHandlers_drawIconWithFrame:(struct CGRect)arg1
+{
+	[self IconOverlayHandlers_drawIconWithFrame:arg1];
+
+	NSURL* url = [[NSClassFromString(@"FINode") nodeFromNodeRef:[(TIconAndTextCell*)self node]->fNodeRef] previewItemURL];
+
+	NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path]];
+
+	NSLog(@"The icon index is %d", [imageIndex intValue]);
+	if ([imageIndex intValue] > 0)
+	{
+		NSImage* image = [[IconCache sharedInstance] getIcon:imageIndex];
+
+		if (image != nil)
+		{
+			struct CGRect arg2 = [(TIconViewCell*)self imageRectForBounds:arg1];
+
+			[image drawInRect:NSMakeRect(arg2.origin.x, arg2.origin.y, arg2.size.width, arg2.size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:TRUE hints:nil];
+		}
+	}
+}
+
+- (void)IconOverlayHandlers_IKImageBrowserCell_drawImage:(id)arg1
+{
+	IKImageWrapper*imageWrapper = [self IconOverlayHandlers_imageWrapper:arg1];
+
+	[self IconOverlayHandlers_IKImageBrowserCell_drawImage:imageWrapper];
+}
+
+- (void)IconOverlayHandlers_IKFinderReflectiveIconCell_drawImage:(id)arg1
+{
+	IKImageWrapper*imageWrapper = [self IconOverlayHandlers_imageWrapper:arg1];
+
+	[self IconOverlayHandlers_IKFinderReflectiveIconCell_drawImage:imageWrapper];
+}
+
+- (IKImageWrapper*)IconOverlayHandlers_imageWrapper:(id)arg1
+{
+	TIconViewCell* realSelf = (TIconViewCell*)self;
+	FINode* node = (FINode*)[realSelf representedItem];
+
+	NSURL* url = [node previewItemURL];
+
+	NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path]];
+	NSLog(@"The icon index is %d", [imageIndex intValue]);
+
+	if ([imageIndex intValue] > 0)
+	{
+		NSImage* icon = [arg1 _nsImage];
+
+		[icon lockFocus];
+
+		CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
+
+		NSImage* iconimage = [[IconCache sharedInstance] getIcon:[NSNumber numberWithInt:[imageIndex intValue]]];
+
+		if (iconimage != nil)
+		{
+			CGImageSourceRef source;
+			NSData* data = [iconimage TIFFRepresentation];
+
+			source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
+			CGImageRef maskRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
+			CGContextDrawImage(myContext, CGRectMake(0, 0, [icon size].width, [icon size].height), maskRef);
+			CFRelease(source);
+			CFRelease(maskRef);
+		}
+
+		[icon unlockFocus];
+
+		return [[[IKImageWrapper alloc] initWithNSImage:icon] autorelease];
+	}
+	else
+	{
+		return arg1;
+	}
+}
+
+- (void)IconOverlayHandlers_drawRect:(struct CGRect)arg1
+{
+	[self IconOverlayHandlers_drawRect:arg1];
+
+	NSView* supersuperview = [[(NSView*)self superview] superview];
+
+	if ([supersuperview isKindOfClass:(id)objc_getClass("TListRowView")])
+	{
+		TListRowView *listRowView = (TListRowView*) supersuperview;
+		FINode *fiNode;
+
+		object_getInstanceVariable(listRowView, "_node", (void**)&fiNode);
+
+		NSURL *url;
+
+		if ([fiNode respondsToSelector:@selector(previewItemURL)])
+		{
+			url = [fiNode previewItemURL];
+		}
+		else {
+			return;
+		}
+
+		NSNumber* imageIndex = [[ContentManager sharedInstance] iconByPath:[url path]];
+		NSLog(@"The icon index is %d", [imageIndex intValue]);
+
+		if ([imageIndex intValue] > 0)
+		{
+			NSImage* image = [[IconCache sharedInstance] getIcon:imageIndex];
+
+			if (image != nil)
+			{
+				[image drawInRect:NSMakeRect(arg1.origin.x, arg1.origin.y, arg1.size.width, arg1.size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0 respectFlipped:TRUE hints:nil];
+			}
+		}
+
+	}
+}
+
+@end

+ 46 - 0
shell_integration/MacOSX/LiferayNativityFinder/Info.plist

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>NSPrincipalClass</key>
+	<string>FinderHook</string>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>com.liferay.nativity</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>BNDL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>NVTY</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>CFPlugInDynamicRegisterFunction</key>
+	<string></string>
+	<key>CFPlugInDynamicRegistration</key>
+	<string>NO</string>
+	<key>CFPlugInFactories</key>
+	<dict>
+		<key>00000000-0000-0000-0000-000000000000</key>
+		<string>MyFactoryFunction</string>
+	</dict>
+	<key>CFPlugInTypes</key>
+	<dict>
+		<key>00000000-0000-0000-0000-000000000000</key>
+		<array>
+			<string>00000000-0000-0000-0000-000000000000</string>
+		</array>
+	</dict>
+	<key>CFPlugInUnloadFunction</key>
+	<string></string>
+</dict>
+</plist>

+ 251 - 0
shell_integration/MacOSX/LiferayNativityFinder/JSONKit.h

@@ -0,0 +1,251 @@
+//
+//  JSONKit.h
+//  http://github.com/johnezang/JSONKit
+//  Dual licensed under either the terms of the BSD License, or alternatively
+//  under the terms of the Apache License, Version 2.0, as specified below.
+//
+
+/*
+ Copyright (c) 2011, John Engelhart
+ 
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 
+ * Neither the name of the Zang Industries nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ Copyright 2011 John Engelhart
+ 
+ Licensed 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.
+*/
+
+#include <stddef.h>
+#include <stdint.h>
+#include <limits.h>
+#include <TargetConditionals.h>
+#include <AvailabilityMacros.h>
+
+#ifdef    __OBJC__
+#import <Foundation/NSArray.h>
+#import <Foundation/NSData.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSError.h>
+#import <Foundation/NSObjCRuntime.h>
+#import <Foundation/NSString.h>
+#endif // __OBJC__
+ 
+#ifdef __cplusplus
+extern "C" {
+#endif
+  
+
+// For Mac OS X < 10.5.
+#ifndef   NSINTEGER_DEFINED
+#define   NSINTEGER_DEFINED
+#if       defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
+typedef long           NSInteger;
+typedef unsigned long  NSUInteger;
+#define NSIntegerMin   LONG_MIN
+#define NSIntegerMax   LONG_MAX
+#define NSUIntegerMax  ULONG_MAX
+#else  // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
+typedef int            NSInteger;
+typedef unsigned int   NSUInteger;
+#define NSIntegerMin   INT_MIN
+#define NSIntegerMax   INT_MAX
+#define NSUIntegerMax  UINT_MAX
+#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
+#endif // NSINTEGER_DEFINED
+
+
+#ifndef _JSONKIT_H_
+#define _JSONKIT_H_
+
+#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465)
+#define JK_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
+#else
+#define JK_DEPRECATED_ATTRIBUTE
+#endif
+  
+#define JSONKIT_VERSION_MAJOR 1
+#define JSONKIT_VERSION_MINOR 4
+
+typedef NSUInteger JKFlags;
+
+/*
+  JKParseOptionComments        : Allow C style // and /_* ... *_/ (without a _, obviously) comments in JSON.
+  JKParseOptionUnicodeNewlines : Allow Unicode recommended (?:\r\n|[\n\v\f\r\x85\p{Zl}\p{Zp}]) newlines.
+  JKParseOptionLooseUnicode    : Normally the decoder will stop with an error at any malformed Unicode.
+                                 This option allows JSON with malformed Unicode to be parsed without reporting an error.
+                                 Any malformed Unicode is replaced with \uFFFD, or "REPLACEMENT CHARACTER".
+ */
+
+enum {
+  JKParseOptionNone                     = 0,
+  JKParseOptionStrict                   = 0,
+  JKParseOptionComments                 = (1 << 0),
+  JKParseOptionUnicodeNewlines          = (1 << 1),
+  JKParseOptionLooseUnicode             = (1 << 2),
+  JKParseOptionPermitTextAfterValidJSON = (1 << 3),
+  JKParseOptionValidFlags               = (JKParseOptionComments | JKParseOptionUnicodeNewlines | JKParseOptionLooseUnicode | JKParseOptionPermitTextAfterValidJSON),
+};
+typedef JKFlags JKParseOptionFlags;
+
+enum {
+  JKSerializeOptionNone                 = 0,
+  JKSerializeOptionPretty               = (1 << 0),
+  JKSerializeOptionEscapeUnicode        = (1 << 1),
+  JKSerializeOptionEscapeForwardSlashes = (1 << 4),
+  JKSerializeOptionValidFlags           = (JKSerializeOptionPretty | JKSerializeOptionEscapeUnicode | JKSerializeOptionEscapeForwardSlashes),
+};
+typedef JKFlags JKSerializeOptionFlags;
+
+#ifdef    __OBJC__
+
+typedef struct JKParseState JKParseState; // Opaque internal, private type.
+
+// As a general rule of thumb, if you use a method that doesn't accept a JKParseOptionFlags argument, it defaults to JKParseOptionStrict
+
+@interface JSONDecoder : NSObject {
+  JKParseState *parseState;
+}
++ (id)decoder;
++ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
+- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
+- (void)clearCache;
+
+// The parse... methods were deprecated in v1.4 in favor of the v1.4 objectWith... methods.
+- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length                         JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4.  Use objectWithUTF8String:length:        instead.
+- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4.  Use objectWithUTF8String:length:error:  instead.
+// The NSData MUST be UTF8 encoded JSON.
+- (id)parseJSONData:(NSData *)jsonData                                                            JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4.  Use objectWithData:                     instead.
+- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error                                    JK_DEPRECATED_ATTRIBUTE; // Deprecated in JSONKit v1.4.  Use objectWithData:error:               instead.
+
+// Methods that return immutable collection objects.
+- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length;
+- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error;
+// The NSData MUST be UTF8 encoded JSON.
+- (id)objectWithData:(NSData *)jsonData;
+- (id)objectWithData:(NSData *)jsonData error:(NSError **)error;
+
+// Methods that return mutable collection objects.
+- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length;
+- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error;
+// The NSData MUST be UTF8 encoded JSON.
+- (id)mutableObjectWithData:(NSData *)jsonData;
+- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error;
+
+@end
+
+////////////
+#pragma mark Deserializing methods
+////////////
+
+@interface NSString (JSONKitDeserializing)
+- (id)objectFromJSONString;
+- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
+- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
+- (id)mutableObjectFromJSONString;
+- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
+- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
+@end
+
+@interface NSData (JSONKitDeserializing)
+// The NSData MUST be UTF8 encoded JSON.
+- (id)objectFromJSONData;
+- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
+- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
+- (id)mutableObjectFromJSONData;
+- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags;
+- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error;
+@end
+
+////////////
+#pragma mark Serializing methods
+////////////
+  
+@interface NSString (JSONKitSerializing)
+// Convenience methods for those that need to serialize the receiving NSString (i.e., instead of having to serialize a NSArray with a single NSString, you can "serialize to JSON" just the NSString).
+// Normally, a string that is serialized to JSON has quotation marks surrounding it, which you may or may not want when serializing a single string, and can be controlled with includeQuotes:
+// includeQuotes:YES `a "test"...` -> `"a \"test\"..."`
+// includeQuotes:NO  `a "test"...` -> `a \"test\"...`
+- (NSData *)JSONData;     // Invokes JSONDataWithOptions:JKSerializeOptionNone   includeQuotes:YES
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
+- (NSString *)JSONString; // Invokes JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error;
+@end
+
+@interface NSArray (JSONKitSerializing)
+- (NSData *)JSONData;
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
+- (NSString *)JSONString;
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
+@end
+
+@interface NSDictionary (JSONKitSerializing)
+- (NSData *)JSONData;
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
+- (NSString *)JSONString;
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error;
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
+@end
+
+#ifdef __BLOCKS__
+
+@interface NSArray (JSONKitSerializingBlockAdditions)
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
+@end
+
+@interface NSDictionary (JSONKitSerializingBlockAdditions)
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error;
+@end
+  
+#endif
+
+
+#endif // __OBJC__
+
+#endif // _JSONKIT_H_
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif

+ 3065 - 0
shell_integration/MacOSX/LiferayNativityFinder/JSONKit.m

@@ -0,0 +1,3065 @@
+//
+//  JSONKit.m
+//  http://github.com/johnezang/JSONKit
+//  Dual licensed under either the terms of the BSD License, or alternatively
+//  under the terms of the Apache License, Version 2.0, as specified below.
+//
+
+/*
+ Copyright (c) 2011, John Engelhart
+ 
+ All rights reserved.
+ 
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ 
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 
+ * Neither the name of the Zang Industries nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+ 
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ Copyright 2011 John Engelhart
+ 
+ Licensed 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.
+*/
+
+
+/*
+  Acknowledgments:
+
+  The bulk of the UTF8 / UTF32 conversion and verification comes
+  from ConvertUTF.[hc].  It has been modified from the original sources.
+
+  The original sources were obtained from http://www.unicode.org/.
+  However, the web site no longer seems to host the files.  Instead,
+  the Unicode FAQ http://www.unicode.org/faq//utf_bom.html#gen4
+  points to International Components for Unicode (ICU)
+  http://site.icu-project.org/ as an example of how to write a UTF
+  converter.
+
+  The decision to use the ConvertUTF.[ch] code was made to leverage
+  "proven" code.  Hopefully the local modifications are bug free.
+
+  The code in isValidCodePoint() is derived from the ICU code in
+  utf.h for the macros U_IS_UNICODE_NONCHAR and U_IS_UNICODE_CHAR.
+
+  From the original ConvertUTF.[ch]:
+
+ * Copyright 2001-2004 Unicode, Inc.
+ * 
+ * Disclaimer
+ * 
+ * This source code is provided as is by Unicode, Inc. No claims are
+ * made as to fitness for any particular purpose. No warranties of any
+ * kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been
+ * purchased on magnetic or optical media from Unicode, Inc., the
+ * sole remedy for any claim will be exchange of defective media
+ * within 90 days of receipt.
+ * 
+ * Limitations on Rights to Redistribute This Code
+ * 
+ * Unicode, Inc. hereby grants the right to freely use the information
+ * supplied in this file in the creation of products supporting the
+ * Unicode Standard, and to make copies of this file in any form
+ * for internal or external distribution as long as this notice
+ * remains attached.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/errno.h>
+#include <math.h>
+#include <limits.h>
+#include <objc/runtime.h>
+
+#import "JSONKit.h"
+
+//#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFString.h>
+#include <CoreFoundation/CFArray.h>
+#include <CoreFoundation/CFDictionary.h>
+#include <CoreFoundation/CFNumber.h>
+
+//#import <Foundation/Foundation.h>
+#import <Foundation/NSArray.h>
+#import <Foundation/NSAutoreleasePool.h>
+#import <Foundation/NSData.h>
+#import <Foundation/NSDictionary.h>
+#import <Foundation/NSException.h>
+#import <Foundation/NSNull.h>
+#import <Foundation/NSObjCRuntime.h>
+
+#ifndef __has_feature
+#define __has_feature(x) 0
+#endif
+
+#ifdef JK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS
+#warning As of JSONKit v1.4, JK_ENABLE_CF_TRANSFER_OWNERSHIP_CALLBACKS is no longer required.  It is no longer a valid option.
+#endif
+
+#ifdef __OBJC_GC__
+#error JSONKit does not support Objective-C Garbage Collection
+#endif
+
+#if __has_feature(objc_arc)
+#error JSONKit does not support Objective-C Automatic Reference Counting (ARC)
+#endif
+
+// The following checks are really nothing more than sanity checks.
+// JSONKit technically has a few problems from a "strictly C99 conforming" standpoint, though they are of the pedantic nitpicking variety.
+// In practice, though, for the compilers and architectures we can reasonably expect this code to be compiled for, these pedantic nitpicks aren't really a problem.
+// Since we're limited as to what we can do with pre-processor #if checks, these checks are not nearly as through as they should be.
+
+#if (UINT_MAX != 0xffffffffU) || (INT_MIN != (-0x7fffffff-1)) || (ULLONG_MAX != 0xffffffffffffffffULL) || (LLONG_MIN != (-0x7fffffffffffffffLL-1LL))
+#error JSONKit requires the C 'int' and 'long long' types to be 32 and 64 bits respectively.
+#endif
+
+#if !defined(__LP64__) && ((UINT_MAX != ULONG_MAX) || (INT_MAX != LONG_MAX) || (INT_MIN != LONG_MIN) || (WORD_BIT != LONG_BIT))
+#error JSONKit requires the C 'int' and 'long' types to be the same on 32-bit architectures.
+#endif
+
+// Cocoa / Foundation uses NS*Integer as the type for a lot of arguments.  We make sure that NS*Integer is something we are expecting and is reasonably compatible with size_t / ssize_t
+
+#if (NSUIntegerMax != ULONG_MAX) || (NSIntegerMax != LONG_MAX) || (NSIntegerMin != LONG_MIN)
+#error JSONKit requires NSInteger and NSUInteger to be the same size as the C 'long' type.
+#endif
+
+#if (NSUIntegerMax != SIZE_MAX) || (NSIntegerMax != SSIZE_MAX)
+#error JSONKit requires NSInteger and NSUInteger to be the same size as the C 'size_t' type.
+#endif
+
+
+// For DJB hash.
+#define JK_HASH_INIT           (1402737925UL)
+
+// Use __builtin_clz() instead of trailingBytesForUTF8[] table lookup.
+#define JK_FAST_TRAILING_BYTES
+
+// JK_CACHE_SLOTS must be a power of 2.  Default size is 1024 slots.
+#define JK_CACHE_SLOTS_BITS    (10)
+#define JK_CACHE_SLOTS         (1UL << JK_CACHE_SLOTS_BITS)
+// JK_CACHE_PROBES is the number of probe attempts.
+#define JK_CACHE_PROBES        (4UL)
+// JK_INIT_CACHE_AGE must be < (1 << AGE) - 1, where AGE is sizeof(typeof(AGE)) * 8.
+#define JK_INIT_CACHE_AGE      (0)
+
+// JK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes)
+#define JK_TOKENBUFFER_SIZE    (1024UL * 2UL)
+
+// JK_STACK_OBJS is the default number of spaces reserved on the stack for temporarily storing pointers to Obj-C objects before they can be transferred to a NSArray / NSDictionary.
+#define JK_STACK_OBJS          (1024UL * 1UL)
+
+#define JK_JSONBUFFER_SIZE     (1024UL * 4UL)
+#define JK_UTF8BUFFER_SIZE     (1024UL * 16UL)
+
+#define JK_ENCODE_CACHE_SLOTS  (1024UL)
+
+
+#if       defined (__GNUC__) && (__GNUC__ >= 4)
+#define JK_ATTRIBUTES(attr, ...)        __attribute__((attr, ##__VA_ARGS__))
+#define JK_EXPECTED(cond, expect)       __builtin_expect((long)(cond), (expect))
+#define JK_EXPECT_T(cond)               JK_EXPECTED(cond, 1U)
+#define JK_EXPECT_F(cond)               JK_EXPECTED(cond, 0U)
+#define JK_PREFETCH(ptr)                __builtin_prefetch(ptr)
+#else  // defined (__GNUC__) && (__GNUC__ >= 4) 
+#define JK_ATTRIBUTES(attr, ...)
+#define JK_EXPECTED(cond, expect)       (cond)
+#define JK_EXPECT_T(cond)               (cond)
+#define JK_EXPECT_F(cond)               (cond)
+#define JK_PREFETCH(ptr)
+#endif // defined (__GNUC__) && (__GNUC__ >= 4) 
+
+#define JK_STATIC_INLINE                         static __inline__ JK_ATTRIBUTES(always_inline)
+#define JK_ALIGNED(arg)                                            JK_ATTRIBUTES(aligned(arg))
+#define JK_UNUSED_ARG                                              JK_ATTRIBUTES(unused)
+#define JK_WARN_UNUSED                                             JK_ATTRIBUTES(warn_unused_result)
+#define JK_WARN_UNUSED_CONST                                       JK_ATTRIBUTES(warn_unused_result, const)
+#define JK_WARN_UNUSED_PURE                                        JK_ATTRIBUTES(warn_unused_result, pure)
+#define JK_WARN_UNUSED_SENTINEL                                    JK_ATTRIBUTES(warn_unused_result, sentinel)
+#define JK_NONNULL_ARGS(arg, ...)                                  JK_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__))
+#define JK_WARN_UNUSED_NONNULL_ARGS(arg, ...)                      JK_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__))
+#define JK_WARN_UNUSED_CONST_NONNULL_ARGS(arg, ...)                JK_ATTRIBUTES(warn_unused_result, const, nonnull(arg, ##__VA_ARGS__))
+#define JK_WARN_UNUSED_PURE_NONNULL_ARGS(arg, ...)                 JK_ATTRIBUTES(warn_unused_result, pure, nonnull(arg, ##__VA_ARGS__))
+
+#if       defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
+#define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as))
+#else  // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
+#define JK_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) JK_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__))
+#endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
+
+
+@class JKArray, JKDictionaryEnumerator, JKDictionary;
+
+enum {
+  JSONNumberStateStart                 = 0,
+  JSONNumberStateFinished              = 1,
+  JSONNumberStateError                 = 2,
+  JSONNumberStateWholeNumberStart      = 3,
+  JSONNumberStateWholeNumberMinus      = 4,
+  JSONNumberStateWholeNumberZero       = 5,
+  JSONNumberStateWholeNumber           = 6,
+  JSONNumberStatePeriod                = 7,
+  JSONNumberStateFractionalNumberStart = 8,
+  JSONNumberStateFractionalNumber      = 9,
+  JSONNumberStateExponentStart         = 10,
+  JSONNumberStateExponentPlusMinus     = 11,
+  JSONNumberStateExponent              = 12,
+};
+
+enum {
+  JSONStringStateStart                           = 0,
+  JSONStringStateParsing                         = 1,
+  JSONStringStateFinished                        = 2,
+  JSONStringStateError                           = 3,
+  JSONStringStateEscape                          = 4,
+  JSONStringStateEscapedUnicode1                 = 5,
+  JSONStringStateEscapedUnicode2                 = 6,
+  JSONStringStateEscapedUnicode3                 = 7,
+  JSONStringStateEscapedUnicode4                 = 8,
+  JSONStringStateEscapedUnicodeSurrogate1        = 9,
+  JSONStringStateEscapedUnicodeSurrogate2        = 10,
+  JSONStringStateEscapedUnicodeSurrogate3        = 11,
+  JSONStringStateEscapedUnicodeSurrogate4        = 12,
+  JSONStringStateEscapedNeedEscapeForSurrogate   = 13,
+  JSONStringStateEscapedNeedEscapedUForSurrogate = 14,
+};
+
+enum {
+  JKParseAcceptValue      = (1 << 0),
+  JKParseAcceptComma      = (1 << 1),
+  JKParseAcceptEnd        = (1 << 2),
+  JKParseAcceptValueOrEnd = (JKParseAcceptValue | JKParseAcceptEnd),
+  JKParseAcceptCommaOrEnd = (JKParseAcceptComma | JKParseAcceptEnd),
+};
+
+enum {
+  JKClassUnknown    = 0,
+  JKClassString     = 1,
+  JKClassNumber     = 2,
+  JKClassArray      = 3,
+  JKClassDictionary = 4,
+  JKClassNull       = 5,
+};
+
+enum {
+  JKManagedBufferOnStack        = 1,
+  JKManagedBufferOnHeap         = 2,
+  JKManagedBufferLocationMask   = (0x3),
+  JKManagedBufferLocationShift  = (0),
+  
+  JKManagedBufferMustFree       = (1 << 2),
+};
+typedef JKFlags JKManagedBufferFlags;
+
+enum {
+  JKObjectStackOnStack        = 1,
+  JKObjectStackOnHeap         = 2,
+  JKObjectStackLocationMask   = (0x3),
+  JKObjectStackLocationShift  = (0),
+  
+  JKObjectStackMustFree       = (1 << 2),
+};
+typedef JKFlags JKObjectStackFlags;
+
+enum {
+  JKTokenTypeInvalid     = 0,
+  JKTokenTypeNumber      = 1,
+  JKTokenTypeString      = 2,
+  JKTokenTypeObjectBegin = 3,
+  JKTokenTypeObjectEnd   = 4,
+  JKTokenTypeArrayBegin  = 5,
+  JKTokenTypeArrayEnd    = 6,
+  JKTokenTypeSeparator   = 7,
+  JKTokenTypeComma       = 8,
+  JKTokenTypeTrue        = 9,
+  JKTokenTypeFalse       = 10,
+  JKTokenTypeNull        = 11,
+  JKTokenTypeWhiteSpace  = 12,
+};
+typedef NSUInteger JKTokenType;
+
+// These are prime numbers to assist with hash slot probing.
+enum {
+  JKValueTypeNone             = 0,
+  JKValueTypeString           = 5,
+  JKValueTypeLongLong         = 7,
+  JKValueTypeUnsignedLongLong = 11,
+  JKValueTypeDouble           = 13,
+};
+typedef NSUInteger JKValueType;
+
+enum {
+  JKEncodeOptionAsData              = 1,
+  JKEncodeOptionAsString            = 2,
+  JKEncodeOptionAsTypeMask          = 0x7,
+  JKEncodeOptionCollectionObj       = (1 << 3),
+  JKEncodeOptionStringObj           = (1 << 4),
+  JKEncodeOptionStringObjTrimQuotes = (1 << 5),
+  
+};
+typedef NSUInteger JKEncodeOptionType;
+
+typedef NSUInteger JKHash;
+
+typedef struct JKTokenCacheItem  JKTokenCacheItem;
+typedef struct JKTokenCache      JKTokenCache;
+typedef struct JKTokenValue      JKTokenValue;
+typedef struct JKParseToken      JKParseToken;
+typedef struct JKPtrRange        JKPtrRange;
+typedef struct JKObjectStack     JKObjectStack;
+typedef struct JKBuffer          JKBuffer;
+typedef struct JKConstBuffer     JKConstBuffer;
+typedef struct JKConstPtrRange   JKConstPtrRange;
+typedef struct JKRange           JKRange;
+typedef struct JKManagedBuffer   JKManagedBuffer;
+typedef struct JKFastClassLookup JKFastClassLookup;
+typedef struct JKEncodeCache     JKEncodeCache;
+typedef struct JKEncodeState     JKEncodeState;
+typedef struct JKObjCImpCache    JKObjCImpCache;
+typedef struct JKHashTableEntry  JKHashTableEntry;
+
+typedef id (*NSNumberAllocImp)(id receiver, SEL selector);
+typedef id (*NSNumberInitWithUnsignedLongLongImp)(id receiver, SEL selector, unsigned long long value);
+typedef id (*JKClassFormatterIMP)(id receiver, SEL selector, id object);
+#ifdef __BLOCKS__
+typedef id (^JKClassFormatterBlock)(id formatObject);
+#endif
+
+
+struct JKPtrRange {
+  unsigned char *ptr;
+  size_t         length;
+};
+
+struct JKConstPtrRange {
+  const unsigned char *ptr;
+  size_t               length;
+};
+
+struct JKRange {
+  size_t location, length;
+};
+
+struct JKManagedBuffer {
+  JKPtrRange           bytes;
+  JKManagedBufferFlags flags;
+  size_t               roundSizeUpToMultipleOf;
+};
+
+struct JKObjectStack {
+  void               **objects, **keys;
+  CFHashCode          *cfHashes;
+  size_t               count, index, roundSizeUpToMultipleOf;
+  JKObjectStackFlags   flags;
+};
+
+struct JKBuffer {
+  JKPtrRange bytes;
+};
+
+struct JKConstBuffer {
+  JKConstPtrRange bytes;
+};
+
+struct JKTokenValue {
+  JKConstPtrRange   ptrRange;
+  JKValueType       type;
+  JKHash            hash;
+  union {
+    long long          longLongValue;
+    unsigned long long unsignedLongLongValue;
+    double             doubleValue;
+  } number;
+  JKTokenCacheItem *cacheItem;
+};
+
+struct JKParseToken {
+  JKConstPtrRange tokenPtrRange;
+  JKTokenType     type;
+  JKTokenValue    value;
+  JKManagedBuffer tokenBuffer;
+};
+
+struct JKTokenCacheItem {
+  void          *object;
+  JKHash         hash;
+  CFHashCode     cfHash;
+  size_t         size;
+  unsigned char *bytes;
+  JKValueType    type;
+};
+
+struct JKTokenCache {
+  JKTokenCacheItem *items;
+  size_t            count;
+  unsigned int      prng_lfsr;
+  unsigned char     age[JK_CACHE_SLOTS];
+};
+
+struct JKObjCImpCache {
+  Class                               NSNumberClass;
+  NSNumberAllocImp                    NSNumberAlloc;
+  NSNumberInitWithUnsignedLongLongImp NSNumberInitWithUnsignedLongLong;
+};
+
+struct JKParseState {
+  JKParseOptionFlags  parseOptionFlags;
+  JKConstBuffer       stringBuffer;
+  size_t              atIndex, lineNumber, lineStartIndex;
+  size_t              prev_atIndex, prev_lineNumber, prev_lineStartIndex;
+  JKParseToken        token;
+  JKObjectStack       objectStack;
+  JKTokenCache        cache;
+  JKObjCImpCache      objCImpCache;
+  NSError            *error;
+  int                 errorIsPrev;
+  BOOL                mutableCollections;
+};
+
+struct JKFastClassLookup {
+  void *stringClass;
+  void *numberClass;
+  void *arrayClass;
+  void *dictionaryClass;
+  void *nullClass;
+};
+
+struct JKEncodeCache {
+  id object;
+  size_t offset;
+  size_t length;
+};
+
+struct JKEncodeState {
+  JKManagedBuffer         utf8ConversionBuffer;
+  JKManagedBuffer         stringBuffer;
+  size_t                  atIndex;
+  JKFastClassLookup       fastClassLookup;
+  JKEncodeCache           cache[JK_ENCODE_CACHE_SLOTS];
+  JKSerializeOptionFlags  serializeOptionFlags;
+  JKEncodeOptionType      encodeOption;
+  size_t                  depth;
+  NSError                *error;
+  id                      classFormatterDelegate;
+  SEL                     classFormatterSelector;
+  JKClassFormatterIMP     classFormatterIMP;
+#ifdef __BLOCKS__
+  JKClassFormatterBlock   classFormatterBlock;
+#endif
+};
+
+// This is a JSONKit private class.
+@interface JKSerializer : NSObject {
+  JKEncodeState *encodeState;
+}
+
+#ifdef __BLOCKS__
+#define JKSERIALIZER_BLOCKS_PROTO id(^)(id object)
+#else
+#define JKSERIALIZER_BLOCKS_PROTO id
+#endif
+
++ (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
+- (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error;
+- (void)releaseState;
+
+@end
+
+struct JKHashTableEntry {
+  NSUInteger keyHash;
+  id key, object;
+};
+
+
+typedef uint32_t UTF32; /* at least 32 bits */
+typedef uint16_t UTF16; /* at least 16 bits */
+typedef uint8_t  UTF8;  /* typically 8 bits */
+
+typedef enum {
+  conversionOK,           /* conversion successful */
+  sourceExhausted,        /* partial character in source, but hit end */
+  targetExhausted,        /* insuff. room in target for conversion */
+  sourceIllegal           /* source sequence is illegal/malformed */
+} ConversionResult;
+
+#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
+#define UNI_MAX_BMP          (UTF32)0x0000FFFF
+#define UNI_MAX_UTF16        (UTF32)0x0010FFFF
+#define UNI_MAX_UTF32        (UTF32)0x7FFFFFFF
+#define UNI_MAX_LEGAL_UTF32  (UTF32)0x0010FFFF
+#define UNI_SUR_HIGH_START   (UTF32)0xD800
+#define UNI_SUR_HIGH_END     (UTF32)0xDBFF
+#define UNI_SUR_LOW_START    (UTF32)0xDC00
+#define UNI_SUR_LOW_END      (UTF32)0xDFFF
+
+
+#if !defined(JK_FAST_TRAILING_BYTES)
+static const char trailingBytesForUTF8[256] = {
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
+};
+#endif
+
+static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
+static const UTF8  firstByteMark[7]   = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
+
+#define JK_AT_STRING_PTR(x)  (&((x)->stringBuffer.bytes.ptr[(x)->atIndex]))
+#define JK_END_STRING_PTR(x) (&((x)->stringBuffer.bytes.ptr[(x)->stringBuffer.bytes.length]))
+
+
+static JKArray          *_JKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection);
+static void              _JKArrayInsertObjectAtIndex(JKArray *array, id newObject, NSUInteger objectIndex);
+static void              _JKArrayReplaceObjectAtIndexWithObject(JKArray *array, NSUInteger objectIndex, id newObject);
+static void              _JKArrayRemoveObjectAtIndex(JKArray *array, NSUInteger objectIndex);
+
+
+static NSUInteger        _JKDictionaryCapacityForCount(NSUInteger count);
+static JKDictionary     *_JKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection);
+static JKHashTableEntry *_JKDictionaryHashEntry(JKDictionary *dictionary);
+static NSUInteger        _JKDictionaryCapacity(JKDictionary *dictionary);
+static void              _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary);
+static void              _JKDictionaryRemoveObjectWithEntry(JKDictionary *dictionary, JKHashTableEntry *entry);
+static void              _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash, id key, id object);
+static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey);
+
+
+static void _JSONDecoderCleanup(JSONDecoder *decoder);
+
+static id _NSStringObjectFromJSONString(NSString *jsonString, JKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection);
+
+
+static void jk_managedBuffer_release(JKManagedBuffer *managedBuffer);
+static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length);
+static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize);
+static void jk_objectStack_release(JKObjectStack *objectStack);
+static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count);
+static int  jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount);
+
+static void   jk_error(JKParseState *parseState, NSString *format, ...);
+static int    jk_parse_string(JKParseState *parseState);
+static int    jk_parse_number(JKParseState *parseState);
+static size_t jk_parse_is_newline(JKParseState *parseState, const unsigned char *atCharacterPtr);
+JK_STATIC_INLINE int jk_parse_skip_newline(JKParseState *parseState);
+JK_STATIC_INLINE void jk_parse_skip_whitespace(JKParseState *parseState);
+static int    jk_parse_next_token(JKParseState *parseState);
+static void   jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String);
+static void  *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex);
+static void  *jk_parse_dictionary(JKParseState *parseState);
+static void  *jk_parse_array(JKParseState *parseState);
+static void  *jk_object_for_token(JKParseState *parseState);
+static void  *jk_cachedObjects(JKParseState *parseState);
+JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState);
+JK_STATIC_INLINE void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy);
+
+
+static void jk_encode_error(JKEncodeState *encodeState, NSString *format, ...);
+static int jk_encode_printf(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...);
+static int jk_encode_write(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format);
+static int jk_encode_writePrettyPrintWhiteSpace(JKEncodeState *encodeState);
+static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format);
+static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format);
+static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length);
+JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr);
+JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object);
+static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr);
+
+#define jk_encode_write1(es, dc, f)  (JK_EXPECT_F(_jk_encode_prettyPrint) ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f))
+
+
+JK_STATIC_INLINE size_t jk_min(size_t a, size_t b);
+JK_STATIC_INLINE size_t jk_max(size_t a, size_t b);
+JK_STATIC_INLINE JKHash jk_calculateHash(JKHash currentHash, unsigned char c);
+
+// JSONKit v1.4 used both a JKArray : NSArray and JKMutableArray : NSMutableArray, and the same for the dictionary collection type.
+// However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the 
+// mutable version, and keeps an ivar bit for whether or not that instance is mutable.  This means that the immutable versions of the collection
+// classes receive the mutating methods, but this is handled by having those methods throw an exception when the ivar bit is set to immutable.
+// We adopt the same strategy here.  It's both cleaner and gets rid of the method swizzling hackery used in JSONKit v1.4.
+
+
+// This is a workaround for issue #23 https://github.com/johnezang/JSONKit/pull/23
+// Basically, there seem to be a problem with using +load in static libraries on iOS.  However, __attribute__ ((constructor)) does work correctly.
+// Since we do not require anything "special" that +load provides, and we can accomplish the same thing using __attribute__ ((constructor)), the +load logic was moved here.
+
+static Class                               _JKArrayClass                           = NULL;
+static size_t                              _JKArrayInstanceSize                    = 0UL;
+static Class                               _JKDictionaryClass                      = NULL;
+static size_t                              _JKDictionaryInstanceSize               = 0UL;
+
+// For JSONDecoder...
+static Class                               _jk_NSNumberClass                       = NULL;
+static NSNumberAllocImp                    _jk_NSNumberAllocImp                    = NULL;
+static NSNumberInitWithUnsignedLongLongImp _jk_NSNumberInitWithUnsignedLongLongImp = NULL;
+
+extern void jk_collectionClassLoadTimeInitialization(void) __attribute__ ((constructor));
+
+void jk_collectionClassLoadTimeInitialization(void) {
+  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Though technically not required, the run time environment at load time initialization may be less than ideal.
+  
+  _JKArrayClass             = objc_getClass("JKArray");
+  _JKArrayInstanceSize      = jk_max(16UL, class_getInstanceSize(_JKArrayClass));
+  
+  _JKDictionaryClass        = objc_getClass("JKDictionary");
+  _JKDictionaryInstanceSize = jk_max(16UL, class_getInstanceSize(_JKDictionaryClass));
+  
+  // For JSONDecoder...
+  _jk_NSNumberClass = [NSNumber class];
+  _jk_NSNumberAllocImp = (NSNumberAllocImp)[NSNumber methodForSelector:@selector(alloc)];
+  
+  // Hacktacular.  Need to do it this way due to the nature of class clusters.
+  id temp_NSNumber = [NSNumber alloc];
+  _jk_NSNumberInitWithUnsignedLongLongImp = (NSNumberInitWithUnsignedLongLongImp)[temp_NSNumber methodForSelector:@selector(initWithUnsignedLongLong:)];
+  [[temp_NSNumber init] release];
+  temp_NSNumber = NULL;
+  
+  [pool release]; pool = NULL;
+}
+
+
+#pragma mark -
+@interface JKArray : NSMutableArray <NSCopying, NSMutableCopying, NSFastEnumeration> {
+  id         *objects;
+  NSUInteger  count, capacity, mutations;
+}
+@end
+
+@implementation JKArray
+
++ (id)allocWithZone:(NSZone *)zone
+{
+#pragma unused(zone)
+  [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to JSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])];
+  return(NULL);
+}
+
+static JKArray *_JKArrayCreate(id *objects, NSUInteger count, BOOL mutableCollection) {
+  NSCParameterAssert((objects != NULL) && (_JKArrayClass != NULL) && (_JKArrayInstanceSize > 0UL));
+  JKArray *array = NULL;
+  if(JK_EXPECT_T((array = (JKArray *)calloc(1UL, _JKArrayInstanceSize)) != NULL)) { // Directly allocate the JKArray instance via calloc.
+    array->isa      = _JKArrayClass;
+    if((array = [array init]) == NULL) { return(NULL); }
+    array->capacity = count;
+    array->count    = count;
+    if(JK_EXPECT_F((array->objects = (id *)malloc(sizeof(id) * array->capacity)) == NULL)) { [array autorelease]; return(NULL); }
+    memcpy(array->objects, objects, array->capacity * sizeof(id));
+    array->mutations = (mutableCollection == NO) ? 0UL : 1UL;
+  }
+  return(array);
+}
+
+// Note: The caller is responsible for -retaining the object that is to be added.
+static void _JKArrayInsertObjectAtIndex(JKArray *array, id newObject, NSUInteger objectIndex) {
+  NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex <= array->count) && (newObject != NULL));
+  if(!((array != NULL) && (array->objects != NULL) && (objectIndex <= array->count) && (newObject != NULL))) { [newObject autorelease]; return; }
+  if((array->count + 1UL) >= array->capacity) {
+    id *newObjects = NULL;
+    if((newObjects = (id *)realloc(array->objects, sizeof(id) * (array->capacity + 16UL))) == NULL) { [NSException raise:NSMallocException format:@"Unable to resize objects array."]; }
+    array->objects = newObjects;
+    array->capacity += 16UL;
+    memset(&array->objects[array->count], 0, sizeof(id) * (array->capacity - array->count));
+  }
+  array->count++;
+  if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex + 1UL], &array->objects[objectIndex], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[objectIndex] = NULL; }
+  array->objects[objectIndex] = newObject;
+}
+
+// Note: The caller is responsible for -retaining the object that is to be added.
+static void _JKArrayReplaceObjectAtIndexWithObject(JKArray *array, NSUInteger objectIndex, id newObject) {
+  NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL));
+  if(!((array != NULL) && (array->objects != NULL) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL) && (newObject != NULL))) { [newObject autorelease]; return; }
+  CFRelease(array->objects[objectIndex]);
+  array->objects[objectIndex] = NULL;
+  array->objects[objectIndex] = newObject;
+}
+
+static void _JKArrayRemoveObjectAtIndex(JKArray *array, NSUInteger objectIndex) {
+  NSCParameterAssert((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL));
+  if(!((array != NULL) && (array->objects != NULL) && (array->count > 0UL) && (array->count <= array->capacity) && (objectIndex < array->count) && (array->objects[objectIndex] != NULL))) { return; }
+  CFRelease(array->objects[objectIndex]);
+  array->objects[objectIndex] = NULL;
+  if((objectIndex + 1UL) < array->count) { memmove(&array->objects[objectIndex], &array->objects[objectIndex + 1UL], sizeof(id) * ((array->count - 1UL) - objectIndex)); array->objects[array->count - 1UL] = NULL; }
+  array->count--;
+}
+
+- (void)dealloc
+{
+  if(JK_EXPECT_T(objects != NULL)) {
+    NSUInteger atObject = 0UL;
+    for(atObject = 0UL; atObject < count; atObject++) { if(JK_EXPECT_T(objects[atObject] != NULL)) { CFRelease(objects[atObject]); objects[atObject] = NULL; } }
+    free(objects); objects = NULL;
+  }
+  
+  [super dealloc];
+}
+
+- (NSUInteger)count
+{
+  NSParameterAssert((objects != NULL) && (count <= capacity));
+  return(count);
+}
+
+- (void)getObjects:(id *)objectsPtr range:(NSRange)range
+{
+  NSParameterAssert((objects != NULL) && (count <= capacity));
+  if((objectsPtr     == NULL)  && (NSMaxRange(range) > 0UL))   { [NSException raise:NSRangeException format:@"*** -[%@ %@]: pointer to objects array is NULL but range length is %lu", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)NSMaxRange(range)];        }
+  if((range.location >  count) || (NSMaxRange(range) > count)) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)",                          NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)NSMaxRange(range), (unsigned long)count]; }
+#ifndef __clang_analyzer__
+  memcpy(objectsPtr, objects + range.location, range.length * sizeof(id));
+#endif
+}
+
+- (id)objectAtIndex:(NSUInteger)objectIndex
+{
+  if(objectIndex >= count) { [NSException raise:NSRangeException format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)count]; }
+  NSParameterAssert((objects != NULL) && (count <= capacity) && (objects[objectIndex] != NULL));
+  return(objects[objectIndex]);
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
+{
+  NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (objects != NULL) && (count <= capacity));
+  if(JK_EXPECT_F(state->state == 0UL))   { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; }
+  if(JK_EXPECT_F(state->state >= count)) { return(0UL); }
+  
+  NSUInteger enumeratedCount  = 0UL;
+  while(JK_EXPECT_T(enumeratedCount < len) && JK_EXPECT_T(state->state < count)) { NSParameterAssert(objects[state->state] != NULL); stackbuf[enumeratedCount++] = objects[state->state++]; }
+  
+  return(enumeratedCount);
+}
+
+- (void)insertObject:(id)anObject atIndex:(NSUInteger)objectIndex
+{
+  if(mutations   == 0UL)   { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  if(anObject    == NULL)  { [NSException raise:NSInvalidArgumentException       format:@"*** -[%@ %@]: attempt to insert nil",                    NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  if(objectIndex >  count) { [NSException raise:NSRangeException                 format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)",          NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)(count + 1UL)]; }
+#ifdef __clang_analyzer__
+  [anObject retain]; // Stupid clang analyzer...  Issue #19.
+#else
+  anObject = [anObject retain];
+#endif
+  _JKArrayInsertObjectAtIndex(self, anObject, objectIndex);
+  mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
+}
+
+- (void)removeObjectAtIndex:(NSUInteger)objectIndex
+{
+  if(mutations   == 0UL)   { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  if(objectIndex >= count) { [NSException raise:NSRangeException                 format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)",          NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)count]; }
+  _JKArrayRemoveObjectAtIndex(self, objectIndex);
+  mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
+}
+
+- (void)replaceObjectAtIndex:(NSUInteger)objectIndex withObject:(id)anObject
+{
+  if(mutations   == 0UL)   { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  if(anObject    == NULL)  { [NSException raise:NSInvalidArgumentException       format:@"*** -[%@ %@]: attempt to insert nil",                    NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  if(objectIndex >= count) { [NSException raise:NSRangeException                 format:@"*** -[%@ %@]: index (%lu) beyond bounds (%lu)",          NSStringFromClass([self class]), NSStringFromSelector(_cmd), (unsigned long)objectIndex, (unsigned long)count]; }
+#ifdef __clang_analyzer__
+  [anObject retain]; // Stupid clang analyzer...  Issue #19.
+#else
+  anObject = [anObject retain];
+#endif
+  _JKArrayReplaceObjectAtIndexWithObject(self, objectIndex, anObject);
+  mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+  NSParameterAssert((objects != NULL) && (count <= capacity));
+  return((mutations == 0UL) ? [self retain] : [(NSArray *)[NSArray allocWithZone:zone] initWithObjects:objects count:count]);
+}
+
+- (id)mutableCopyWithZone:(NSZone *)zone
+{
+  NSParameterAssert((objects != NULL) && (count <= capacity));
+  return([(NSMutableArray *)[NSMutableArray allocWithZone:zone] initWithObjects:objects count:count]);
+}
+
+@end
+
+
+#pragma mark -
+@interface JKDictionaryEnumerator : NSEnumerator {
+  id         collection;
+  NSUInteger nextObject;
+}
+
+- (id)initWithJKDictionary:(JKDictionary *)initDictionary;
+- (NSArray *)allObjects;
+- (id)nextObject;
+
+@end
+
+@implementation JKDictionaryEnumerator
+
+- (id)initWithJKDictionary:(JKDictionary *)initDictionary
+{
+  NSParameterAssert(initDictionary != NULL);
+  if((self = [super init]) == NULL) { return(NULL); }
+  if((collection = (id)CFRetain(initDictionary)) == NULL) { [self autorelease]; return(NULL); }
+  return(self);
+}
+
+- (void)dealloc
+{
+  if(collection != NULL) { CFRelease(collection); collection = NULL; }
+  [super dealloc];
+}
+
+- (NSArray *)allObjects
+{
+  NSParameterAssert(collection != NULL);
+  NSUInteger count = [(NSDictionary *)collection count], atObject = 0UL;
+  id         objects[count];
+
+  while((objects[atObject] = [self nextObject]) != NULL) { NSParameterAssert(atObject < count); atObject++; }
+
+  return([NSArray arrayWithObjects:objects count:atObject]);
+}
+
+- (id)nextObject
+{
+  NSParameterAssert((collection != NULL) && (_JKDictionaryHashEntry(collection) != NULL));
+  JKHashTableEntry *entry        = _JKDictionaryHashEntry(collection);
+  NSUInteger        capacity     = _JKDictionaryCapacity(collection);
+  id                returnObject = NULL;
+
+  if(entry != NULL) { while((nextObject < capacity) && ((returnObject = entry[nextObject++].key) == NULL)) { /* ... */ } }
+  
+  return(returnObject);
+}
+
+@end
+
+#pragma mark -
+@interface JKDictionary : NSMutableDictionary <NSCopying, NSMutableCopying, NSFastEnumeration> {
+  NSUInteger count, capacity, mutations;
+  JKHashTableEntry *entry;
+}
+@end
+
+@implementation JKDictionary
+
++ (id)allocWithZone:(NSZone *)zone
+{
+#pragma unused(zone)
+  [NSException raise:NSInvalidArgumentException format:@"*** - [%@ %@]: The %@ class is private to JSONKit and should not be used in this fashion.", NSStringFromClass([self class]), NSStringFromSelector(_cmd), NSStringFromClass([self class])];
+  return(NULL);
+}
+
+// These values are taken from Core Foundation CF-550 CFBasicHash.m.  As a bonus, they align very well with our JKHashTableEntry struct too.
+static const NSUInteger jk_dictionaryCapacities[] = {
+  0UL, 3UL, 7UL, 13UL, 23UL, 41UL, 71UL, 127UL, 191UL, 251UL, 383UL, 631UL, 1087UL, 1723UL,
+  2803UL, 4523UL, 7351UL, 11959UL, 19447UL, 31231UL, 50683UL, 81919UL, 132607UL,
+  214519UL, 346607UL, 561109UL, 907759UL, 1468927UL, 2376191UL, 3845119UL,
+  6221311UL, 10066421UL, 16287743UL, 26354171UL, 42641881UL, 68996069UL,
+  111638519UL, 180634607UL, 292272623UL, 472907251UL
+};
+
+static NSUInteger _JKDictionaryCapacityForCount(NSUInteger count) {
+  NSUInteger bottom = 0UL, top = sizeof(jk_dictionaryCapacities) / sizeof(NSUInteger), mid = 0UL, tableSize = (NSUInteger)lround(floor(((double)count) * 1.33));
+  while(top > bottom) { mid = (top + bottom) / 2UL; if(jk_dictionaryCapacities[mid] < tableSize) { bottom = mid + 1UL; } else { top = mid; } }
+  return(jk_dictionaryCapacities[bottom]);
+}
+
+static void _JKDictionaryResizeIfNeccessary(JKDictionary *dictionary) {
+  NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity));
+
+  NSUInteger capacityForCount = 0UL;
+  if(dictionary->capacity < (capacityForCount = _JKDictionaryCapacityForCount(dictionary->count + 1UL))) { // resize
+    NSUInteger        oldCapacity = dictionary->capacity;
+#ifndef NS_BLOCK_ASSERTIONS
+    NSUInteger oldCount = dictionary->count;
+#endif
+    JKHashTableEntry *oldEntry    = dictionary->entry;
+    if(JK_EXPECT_F((dictionary->entry = (JKHashTableEntry *)calloc(1UL, sizeof(JKHashTableEntry) * capacityForCount)) == NULL)) { [NSException raise:NSMallocException format:@"Unable to allocate memory for hash table."]; }
+    dictionary->capacity = capacityForCount;
+    dictionary->count    = 0UL;
+    
+    NSUInteger idx = 0UL;
+    for(idx = 0UL; idx < oldCapacity; idx++) { if(oldEntry[idx].key != NULL) { _JKDictionaryAddObject(dictionary, oldEntry[idx].keyHash, oldEntry[idx].key, oldEntry[idx].object); oldEntry[idx].keyHash = 0UL; oldEntry[idx].key = NULL; oldEntry[idx].object = NULL; } }
+    NSCParameterAssert((oldCount == dictionary->count));
+    free(oldEntry); oldEntry = NULL;
+  }
+}
+
+static JKDictionary *_JKDictionaryCreate(id *keys, NSUInteger *keyHashes, id *objects, NSUInteger count, BOOL mutableCollection) {
+  NSCParameterAssert((keys != NULL) && (keyHashes != NULL) && (objects != NULL) && (_JKDictionaryClass != NULL) && (_JKDictionaryInstanceSize > 0UL));
+  JKDictionary *dictionary = NULL;
+  if(JK_EXPECT_T((dictionary = (JKDictionary *)calloc(1UL, _JKDictionaryInstanceSize)) != NULL)) { // Directly allocate the JKDictionary instance via calloc.
+    dictionary->isa      = _JKDictionaryClass;
+    if((dictionary = [dictionary init]) == NULL) { return(NULL); }
+    dictionary->capacity = _JKDictionaryCapacityForCount(count);
+    dictionary->count    = 0UL;
+    
+    if(JK_EXPECT_F((dictionary->entry = (JKHashTableEntry *)calloc(1UL, sizeof(JKHashTableEntry) * dictionary->capacity)) == NULL)) { [dictionary autorelease]; return(NULL); }
+
+    NSUInteger idx = 0UL;
+    for(idx = 0UL; idx < count; idx++) { _JKDictionaryAddObject(dictionary, keyHashes[idx], keys[idx], objects[idx]); }
+
+    dictionary->mutations = (mutableCollection == NO) ? 0UL : 1UL;
+  }
+  return(dictionary);
+}
+
+- (void)dealloc
+{
+  if(JK_EXPECT_T(entry != NULL)) {
+    NSUInteger atEntry = 0UL;
+    for(atEntry = 0UL; atEntry < capacity; atEntry++) {
+      if(JK_EXPECT_T(entry[atEntry].key    != NULL)) { CFRelease(entry[atEntry].key);    entry[atEntry].key    = NULL; }
+      if(JK_EXPECT_T(entry[atEntry].object != NULL)) { CFRelease(entry[atEntry].object); entry[atEntry].object = NULL; }
+    }
+  
+    free(entry); entry = NULL;
+  }
+
+  [super dealloc];
+}
+
+static JKHashTableEntry *_JKDictionaryHashEntry(JKDictionary *dictionary) {
+  NSCParameterAssert(dictionary != NULL);
+  return(dictionary->entry);
+}
+
+static NSUInteger _JKDictionaryCapacity(JKDictionary *dictionary) {
+  NSCParameterAssert(dictionary != NULL);
+  return(dictionary->capacity);
+}
+
+static void _JKDictionaryRemoveObjectWithEntry(JKDictionary *dictionary, JKHashTableEntry *entry) {
+  NSCParameterAssert((dictionary != NULL) && (entry != NULL) && (entry->key != NULL) && (entry->object != NULL) && (dictionary->count > 0UL) && (dictionary->count <= dictionary->capacity));
+  CFRelease(entry->key);    entry->key    = NULL;
+  CFRelease(entry->object); entry->object = NULL;
+  entry->keyHash = 0UL;
+  dictionary->count--;
+  // In order for certain invariants that are used to speed up the search for a particular key, we need to "re-add" all the entries in the hash table following this entry until we hit a NULL entry.
+  NSUInteger removeIdx = entry - dictionary->entry, idx = 0UL;
+  NSCParameterAssert((removeIdx < dictionary->capacity));
+  for(idx = 0UL; idx < dictionary->capacity; idx++) {
+    NSUInteger entryIdx = (removeIdx + idx + 1UL) % dictionary->capacity;
+    JKHashTableEntry *atEntry = &dictionary->entry[entryIdx];
+    if(atEntry->key == NULL) { break; }
+    NSUInteger keyHash = atEntry->keyHash;
+    id key = atEntry->key, object = atEntry->object;
+    NSCParameterAssert(object != NULL);
+    atEntry->keyHash = 0UL;
+    atEntry->key     = NULL;
+    atEntry->object  = NULL;
+    NSUInteger addKeyEntry = keyHash % dictionary->capacity, addIdx = 0UL;
+    for(addIdx = 0UL; addIdx < dictionary->capacity; addIdx++) {
+      JKHashTableEntry *atAddEntry = &dictionary->entry[((addKeyEntry + addIdx) % dictionary->capacity)];
+      if(JK_EXPECT_T(atAddEntry->key == NULL)) { NSCParameterAssert((atAddEntry->keyHash == 0UL) && (atAddEntry->object == NULL)); atAddEntry->key = key; atAddEntry->object = object; atAddEntry->keyHash = keyHash; break; }
+    }
+  }
+}
+
+static void _JKDictionaryAddObject(JKDictionary *dictionary, NSUInteger keyHash, id key, id object) {
+  NSCParameterAssert((dictionary != NULL) && (key != NULL) && (object != NULL) && (dictionary->count < dictionary->capacity) && (dictionary->entry != NULL));
+  NSUInteger keyEntry = keyHash % dictionary->capacity, idx = 0UL;
+  for(idx = 0UL; idx < dictionary->capacity; idx++) {
+    NSUInteger entryIdx = (keyEntry + idx) % dictionary->capacity;
+    JKHashTableEntry *atEntry = &dictionary->entry[entryIdx];
+    if(JK_EXPECT_F(atEntry->keyHash == keyHash) && JK_EXPECT_T(atEntry->key != NULL) && (JK_EXPECT_F(key == atEntry->key) || JK_EXPECT_F(CFEqual(atEntry->key, key)))) { _JKDictionaryRemoveObjectWithEntry(dictionary, atEntry); }
+    if(JK_EXPECT_T(atEntry->key == NULL)) { NSCParameterAssert((atEntry->keyHash == 0UL) && (atEntry->object == NULL)); atEntry->key = key; atEntry->object = object; atEntry->keyHash = keyHash; dictionary->count++; return; }
+  }
+
+  // We should never get here.  If we do, we -release the key / object because it's our responsibility.
+  CFRelease(key);
+  CFRelease(object);
+}
+
+- (NSUInteger)count
+{
+  return(count);
+}
+
+static JKHashTableEntry *_JKDictionaryHashTableEntryForKey(JKDictionary *dictionary, id aKey) {
+  NSCParameterAssert((dictionary != NULL) && (dictionary->entry != NULL) && (dictionary->count <= dictionary->capacity));
+  if((aKey == NULL) || (dictionary->capacity == 0UL)) { return(NULL); }
+  NSUInteger        keyHash = CFHash(aKey), keyEntry = (keyHash % dictionary->capacity), idx = 0UL;
+  JKHashTableEntry *atEntry = NULL;
+  for(idx = 0UL; idx < dictionary->capacity; idx++) {
+    atEntry = &dictionary->entry[(keyEntry + idx) % dictionary->capacity];
+    if(JK_EXPECT_T(atEntry->keyHash == keyHash) && JK_EXPECT_T(atEntry->key != NULL) && ((atEntry->key == aKey) || CFEqual(atEntry->key, aKey))) { NSCParameterAssert(atEntry->object != NULL); return(atEntry); break; }
+    if(JK_EXPECT_F(atEntry->key == NULL)) { NSCParameterAssert(atEntry->object == NULL); return(NULL); break; } // If the key was in the table, we would have found it by now.
+  }
+  return(NULL);
+}
+
+- (id)objectForKey:(id)aKey
+{
+  NSParameterAssert((entry != NULL) && (count <= capacity));
+  JKHashTableEntry *entryForKey = _JKDictionaryHashTableEntryForKey(self, aKey);
+  return((entryForKey != NULL) ? entryForKey->object : NULL);
+}
+
+- (void)getObjects:(id *)objects andKeys:(id *)keys
+{
+  NSParameterAssert((entry != NULL) && (count <= capacity));
+  NSUInteger atEntry = 0UL; NSUInteger arrayIdx = 0UL;
+  for(atEntry = 0UL; atEntry < capacity; atEntry++) {
+    if(JK_EXPECT_T(entry[atEntry].key != NULL)) {
+      NSCParameterAssert((entry[atEntry].object != NULL) && (arrayIdx < count));
+      if(JK_EXPECT_T(keys    != NULL)) { keys[arrayIdx]    = entry[atEntry].key;    }
+      if(JK_EXPECT_T(objects != NULL)) { objects[arrayIdx] = entry[atEntry].object; }
+      arrayIdx++;
+    }
+  }
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len
+{
+  NSParameterAssert((state != NULL) && (stackbuf != NULL) && (len > 0UL) && (entry != NULL) && (count <= capacity));
+  if(JK_EXPECT_F(state->state == 0UL))      { state->mutationsPtr = (unsigned long *)&mutations; state->itemsPtr = stackbuf; }
+  if(JK_EXPECT_F(state->state >= capacity)) { return(0UL); }
+  
+  NSUInteger enumeratedCount  = 0UL;
+  while(JK_EXPECT_T(enumeratedCount < len) && JK_EXPECT_T(state->state < capacity)) { if(JK_EXPECT_T(entry[state->state].key != NULL)) { stackbuf[enumeratedCount++] = entry[state->state].key; } state->state++; }
+    
+  return(enumeratedCount);
+}
+
+- (NSEnumerator *)keyEnumerator
+{
+  return([[[JKDictionaryEnumerator alloc] initWithJKDictionary:self] autorelease]);
+}
+
+- (void)setObject:(id)anObject forKey:(id)aKey
+{
+  if(mutations == 0UL)  { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)];       }
+  if(aKey      == NULL) { [NSException raise:NSInvalidArgumentException       format:@"*** -[%@ %@]: attempt to insert nil key",                NSStringFromClass([self class]), NSStringFromSelector(_cmd)];       }
+  if(anObject  == NULL) { [NSException raise:NSInvalidArgumentException       format:@"*** -[%@ %@]: attempt to insert nil value (key: %@)",    NSStringFromClass([self class]), NSStringFromSelector(_cmd), aKey]; }
+  
+  _JKDictionaryResizeIfNeccessary(self);
+#ifndef __clang_analyzer__
+  aKey     = [aKey     copy];   // Why on earth would clang complain that this -copy "might leak", 
+  anObject = [anObject retain]; // but this -retain doesn't!?
+#endif // __clang_analyzer__
+  _JKDictionaryAddObject(self, CFHash(aKey), aKey, anObject);
+  mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
+}
+
+- (void)removeObjectForKey:(id)aKey
+{
+  if(mutations == 0UL)  { [NSException raise:NSInternalInconsistencyException format:@"*** -[%@ %@]: mutating method sent to immutable object", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  if(aKey      == NULL) { [NSException raise:NSInvalidArgumentException       format:@"*** -[%@ %@]: attempt to remove nil key",                NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; }
+  JKHashTableEntry *entryForKey = _JKDictionaryHashTableEntryForKey(self, aKey);
+  if(entryForKey != NULL) {
+    _JKDictionaryRemoveObjectWithEntry(self, entryForKey);
+    mutations = (mutations == NSUIntegerMax) ? 1UL : mutations + 1UL;
+  }
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+  NSParameterAssert((entry != NULL) && (count <= capacity));
+  return((mutations == 0UL) ? [self retain] : [[NSDictionary allocWithZone:zone] initWithDictionary:self]);
+}
+
+- (id)mutableCopyWithZone:(NSZone *)zone
+{
+  NSParameterAssert((entry != NULL) && (count <= capacity));
+  return([[NSMutableDictionary allocWithZone:zone] initWithDictionary:self]);
+}
+
+@end
+
+
+
+#pragma mark -
+
+JK_STATIC_INLINE size_t jk_min(size_t a, size_t b) { return((a < b) ? a : b); }
+JK_STATIC_INLINE size_t jk_max(size_t a, size_t b) { return((a > b) ? a : b); }
+
+JK_STATIC_INLINE JKHash jk_calculateHash(JKHash currentHash, unsigned char c) { return((((currentHash << 5) + currentHash) + (c - 29)) ^ (currentHash >> 19)); }
+
+
+static void jk_error(JKParseState *parseState, NSString *format, ...) {
+  NSCParameterAssert((parseState != NULL) && (format != NULL));
+
+  va_list varArgsList;
+  va_start(varArgsList, format);
+  NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease];
+  va_end(varArgsList);
+
+#if 0
+  const unsigned char *lineStart      = parseState->stringBuffer.bytes.ptr + parseState->lineStartIndex;
+  const unsigned char *lineEnd        = lineStart;
+  const unsigned char *atCharacterPtr = NULL;
+
+  for(atCharacterPtr = lineStart; atCharacterPtr < JK_END_STRING_PTR(parseState); atCharacterPtr++) { lineEnd = atCharacterPtr; if(jk_parse_is_newline(parseState, atCharacterPtr)) { break; } }
+
+  NSString *lineString = @"", *carretString = @"";
+  if(lineStart < JK_END_STRING_PTR(parseState)) {
+    lineString   = [[[NSString alloc] initWithBytes:lineStart length:(lineEnd - lineStart) encoding:NSUTF8StringEncoding] autorelease];
+    carretString = [NSString stringWithFormat:@"%*.*s^", (int)(parseState->atIndex - parseState->lineStartIndex), (int)(parseState->atIndex - parseState->lineStartIndex), " "];
+  }
+#endif
+
+  if(parseState->error == NULL) {
+    parseState->error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:
+                                   [NSDictionary dictionaryWithObjectsAndKeys:
+                                                                              formatString,                                             NSLocalizedDescriptionKey,
+                                                                              [NSNumber numberWithUnsignedLong:parseState->atIndex],    @"JKAtIndexKey",
+                                                                              [NSNumber numberWithUnsignedLong:parseState->lineNumber], @"JKLineNumberKey",
+                                                 //lineString,   @"JKErrorLine0Key",
+                                                 //carretString, @"JKErrorLine1Key",
+                                                                              NULL]];
+  }
+}
+
+#pragma mark -
+#pragma mark Buffer and Object Stack management functions
+
+static void jk_managedBuffer_release(JKManagedBuffer *managedBuffer) {
+  if((managedBuffer->flags & JKManagedBufferMustFree)) {
+    if(managedBuffer->bytes.ptr != NULL) { free(managedBuffer->bytes.ptr); managedBuffer->bytes.ptr = NULL; }
+    managedBuffer->flags &= ~JKManagedBufferMustFree;
+  }
+
+  managedBuffer->bytes.ptr     = NULL;
+  managedBuffer->bytes.length  = 0UL;
+  managedBuffer->flags        &= ~JKManagedBufferLocationMask;
+}
+
+static void jk_managedBuffer_setToStackBuffer(JKManagedBuffer *managedBuffer, unsigned char *ptr, size_t length) {
+  jk_managedBuffer_release(managedBuffer);
+  managedBuffer->bytes.ptr     = ptr;
+  managedBuffer->bytes.length  = length;
+  managedBuffer->flags         = (managedBuffer->flags & ~JKManagedBufferLocationMask) | JKManagedBufferOnStack;
+}
+
+static unsigned char *jk_managedBuffer_resize(JKManagedBuffer *managedBuffer, size_t newSize) {
+  size_t roundedUpNewSize = newSize;
+
+  if(managedBuffer->roundSizeUpToMultipleOf > 0UL) { roundedUpNewSize = newSize + ((managedBuffer->roundSizeUpToMultipleOf - (newSize % managedBuffer->roundSizeUpToMultipleOf)) % managedBuffer->roundSizeUpToMultipleOf); }
+
+  if((roundedUpNewSize != managedBuffer->bytes.length) && (roundedUpNewSize > managedBuffer->bytes.length)) {
+    if((managedBuffer->flags & JKManagedBufferLocationMask) == JKManagedBufferOnStack) {
+      NSCParameterAssert((managedBuffer->flags & JKManagedBufferMustFree) == 0);
+      unsigned char *newBuffer = NULL, *oldBuffer = managedBuffer->bytes.ptr;
+      
+      if((newBuffer = (unsigned char *)malloc(roundedUpNewSize)) == NULL) { return(NULL); }
+      memcpy(newBuffer, oldBuffer, jk_min(managedBuffer->bytes.length, roundedUpNewSize));
+      managedBuffer->flags        = (managedBuffer->flags & ~JKManagedBufferLocationMask) | (JKManagedBufferOnHeap | JKManagedBufferMustFree);
+      managedBuffer->bytes.ptr    = newBuffer;
+      managedBuffer->bytes.length = roundedUpNewSize;
+    } else {
+      NSCParameterAssert(((managedBuffer->flags & JKManagedBufferMustFree) != 0) && ((managedBuffer->flags & JKManagedBufferLocationMask) == JKManagedBufferOnHeap));
+      if((managedBuffer->bytes.ptr = (unsigned char *)reallocf(managedBuffer->bytes.ptr, roundedUpNewSize)) == NULL) { return(NULL); }
+      managedBuffer->bytes.length = roundedUpNewSize;
+    }
+  }
+
+  return(managedBuffer->bytes.ptr);
+}
+
+
+
+static void jk_objectStack_release(JKObjectStack *objectStack) {
+  NSCParameterAssert(objectStack != NULL);
+
+  NSCParameterAssert(objectStack->index <= objectStack->count);
+  size_t atIndex = 0UL;
+  for(atIndex = 0UL; atIndex < objectStack->index; atIndex++) {
+    if(objectStack->objects[atIndex] != NULL) { CFRelease(objectStack->objects[atIndex]); objectStack->objects[atIndex] = NULL; }
+    if(objectStack->keys[atIndex]    != NULL) { CFRelease(objectStack->keys[atIndex]);    objectStack->keys[atIndex]    = NULL; }
+  }
+  objectStack->index = 0UL;
+
+  if(objectStack->flags & JKObjectStackMustFree) {
+    NSCParameterAssert((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap);
+    if(objectStack->objects  != NULL) { free(objectStack->objects);  objectStack->objects  = NULL; }
+    if(objectStack->keys     != NULL) { free(objectStack->keys);     objectStack->keys     = NULL; }
+    if(objectStack->cfHashes != NULL) { free(objectStack->cfHashes); objectStack->cfHashes = NULL; }
+    objectStack->flags &= ~JKObjectStackMustFree;
+  }
+
+  objectStack->objects  = NULL;
+  objectStack->keys     = NULL;
+  objectStack->cfHashes = NULL;
+
+  objectStack->count    = 0UL;
+  objectStack->flags   &= ~JKObjectStackLocationMask;
+}
+
+static void jk_objectStack_setToStackBuffer(JKObjectStack *objectStack, void **objects, void **keys, CFHashCode *cfHashes, size_t count) {
+  NSCParameterAssert((objectStack != NULL) && (objects != NULL) && (keys != NULL) && (cfHashes != NULL) && (count > 0UL));
+  jk_objectStack_release(objectStack);
+  objectStack->objects  = objects;
+  objectStack->keys     = keys;
+  objectStack->cfHashes = cfHashes;
+  objectStack->count    = count;
+  objectStack->flags    = (objectStack->flags & ~JKObjectStackLocationMask) | JKObjectStackOnStack;
+#ifndef NS_BLOCK_ASSERTIONS
+  size_t idx;
+  for(idx = 0UL; idx < objectStack->count; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; }
+#endif
+}
+
+static int jk_objectStack_resize(JKObjectStack *objectStack, size_t newCount) {
+  size_t roundedUpNewCount = newCount;
+  int    returnCode = 0;
+
+  void       **newObjects  = NULL, **newKeys = NULL;
+  CFHashCode  *newCFHashes = NULL;
+
+  if(objectStack->roundSizeUpToMultipleOf > 0UL) { roundedUpNewCount = newCount + ((objectStack->roundSizeUpToMultipleOf - (newCount % objectStack->roundSizeUpToMultipleOf)) % objectStack->roundSizeUpToMultipleOf); }
+
+  if((roundedUpNewCount != objectStack->count) && (roundedUpNewCount > objectStack->count)) {
+    if((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnStack) {
+      NSCParameterAssert((objectStack->flags & JKObjectStackMustFree) == 0);
+
+      if((newObjects  = (void **     )calloc(1UL, roundedUpNewCount * sizeof(void *    ))) == NULL) { returnCode = 1; goto errorExit; }
+      memcpy(newObjects, objectStack->objects,   jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *));
+      if((newKeys     = (void **     )calloc(1UL, roundedUpNewCount * sizeof(void *    ))) == NULL) { returnCode = 1; goto errorExit; }
+      memcpy(newKeys,     objectStack->keys,     jk_min(objectStack->count, roundedUpNewCount) * sizeof(void *));
+
+      if((newCFHashes = (CFHashCode *)calloc(1UL, roundedUpNewCount * sizeof(CFHashCode))) == NULL) { returnCode = 1; goto errorExit; }
+      memcpy(newCFHashes, objectStack->cfHashes, jk_min(objectStack->count, roundedUpNewCount) * sizeof(CFHashCode));
+
+      objectStack->flags    = (objectStack->flags & ~JKObjectStackLocationMask) | (JKObjectStackOnHeap | JKObjectStackMustFree);
+      objectStack->objects  = newObjects;  newObjects  = NULL;
+      objectStack->keys     = newKeys;     newKeys     = NULL;
+      objectStack->cfHashes = newCFHashes; newCFHashes = NULL;
+      objectStack->count    = roundedUpNewCount;
+    } else {
+      NSCParameterAssert(((objectStack->flags & JKObjectStackMustFree) != 0) && ((objectStack->flags & JKObjectStackLocationMask) == JKObjectStackOnHeap));
+      if((newObjects  = (void  **    )realloc(objectStack->objects,  roundedUpNewCount * sizeof(void *    ))) != NULL) { objectStack->objects  = newObjects;  newObjects  = NULL; } else { returnCode = 1; goto errorExit; }
+      if((newKeys     = (void  **    )realloc(objectStack->keys,     roundedUpNewCount * sizeof(void *    ))) != NULL) { objectStack->keys     = newKeys;     newKeys     = NULL; } else { returnCode = 1; goto errorExit; }
+      if((newCFHashes = (CFHashCode *)realloc(objectStack->cfHashes, roundedUpNewCount * sizeof(CFHashCode))) != NULL) { objectStack->cfHashes = newCFHashes; newCFHashes = NULL; } else { returnCode = 1; goto errorExit; }
+
+#ifndef NS_BLOCK_ASSERTIONS
+      size_t idx;
+      for(idx = objectStack->count; idx < roundedUpNewCount; idx++) { objectStack->objects[idx] = NULL; objectStack->keys[idx] = NULL; objectStack->cfHashes[idx] = 0UL; }
+#endif
+      objectStack->count = roundedUpNewCount;
+    }
+  }
+
+ errorExit:
+  if(newObjects  != NULL) { free(newObjects);  newObjects  = NULL; }
+  if(newKeys     != NULL) { free(newKeys);     newKeys     = NULL; }
+  if(newCFHashes != NULL) { free(newCFHashes); newCFHashes = NULL; }
+
+  return(returnCode);
+}
+
+////////////
+#pragma mark -
+#pragma mark Unicode related functions
+
+JK_STATIC_INLINE ConversionResult isValidCodePoint(UTF32 *u32CodePoint) {
+  ConversionResult result = conversionOK;
+  UTF32            ch     = *u32CodePoint;
+
+  if(JK_EXPECT_F(ch >= UNI_SUR_HIGH_START) && (JK_EXPECT_T(ch <= UNI_SUR_LOW_END)))                                                        { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; }
+  if(JK_EXPECT_F(ch >= 0xFDD0U) && (JK_EXPECT_F(ch <= 0xFDEFU) || JK_EXPECT_F((ch & 0xFFFEU) == 0xFFFEU)) && JK_EXPECT_T(ch <= 0x10FFFFU)) { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; }
+  if(JK_EXPECT_F(ch == 0U))                                                                                                                { result = sourceIllegal; ch = UNI_REPLACEMENT_CHAR; goto finished; }
+
+ finished:
+  *u32CodePoint = ch;
+  return(result);
+}
+
+
+static int isLegalUTF8(const UTF8 *source, size_t length) {
+  const UTF8 *srcptr = source + length;
+  UTF8 a;
+
+  switch(length) {
+    default: return(0); // Everything else falls through when "true"...
+    case 4: if(JK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); }
+    case 3: if(JK_EXPECT_F(((a = (*--srcptr)) < 0x80) || (a > 0xBF))) { return(0); }
+    case 2: if(JK_EXPECT_F( (a = (*--srcptr)) > 0xBF               )) { return(0); }
+      
+      switch(*source) { // no fall-through in this inner switch
+        case 0xE0: if(JK_EXPECT_F(a < 0xA0)) { return(0); } break;
+        case 0xED: if(JK_EXPECT_F(a > 0x9F)) { return(0); } break;
+        case 0xF0: if(JK_EXPECT_F(a < 0x90)) { return(0); } break;
+        case 0xF4: if(JK_EXPECT_F(a > 0x8F)) { return(0); } break;
+        default:   if(JK_EXPECT_F(a < 0x80)) { return(0); }
+      }
+      
+    case 1: if(JK_EXPECT_F((JK_EXPECT_T(*source < 0xC2)) && JK_EXPECT_F(*source >= 0x80))) { return(0); }
+  }
+
+  if(JK_EXPECT_F(*source > 0xF4)) { return(0); }
+
+  return(1);
+}
+
+static ConversionResult ConvertSingleCodePointInUTF8(const UTF8 *sourceStart, const UTF8 *sourceEnd, UTF8 const **nextUTF8, UTF32 *convertedUTF32) {
+  ConversionResult result = conversionOK;
+  const UTF8 *source = sourceStart;
+  UTF32 ch = 0UL;
+
+#if !defined(JK_FAST_TRAILING_BYTES)
+  unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
+#else
+  unsigned short extraBytesToRead = __builtin_clz(((*source)^0xff) << 25);
+#endif
+
+  if(JK_EXPECT_F((source + extraBytesToRead + 1) > sourceEnd) || JK_EXPECT_F(!isLegalUTF8(source, extraBytesToRead + 1))) {
+    source++;
+    while((source < sourceEnd) && (((*source) & 0xc0) == 0x80) && ((source - sourceStart) < (extraBytesToRead + 1))) { source++; } 
+    NSCParameterAssert(source <= sourceEnd);
+    result = ((source < sourceEnd) && (((*source) & 0xc0) != 0x80)) ? sourceIllegal : ((sourceStart + extraBytesToRead + 1) > sourceEnd) ? sourceExhausted : sourceIllegal;
+    ch = UNI_REPLACEMENT_CHAR;
+    goto finished;
+  }
+
+  switch(extraBytesToRead) { // The cases all fall through.
+    case 5: ch += *source++; ch <<= 6;
+    case 4: ch += *source++; ch <<= 6;
+    case 3: ch += *source++; ch <<= 6;
+    case 2: ch += *source++; ch <<= 6;
+    case 1: ch += *source++; ch <<= 6;
+    case 0: ch += *source++;
+  }
+  ch -= offsetsFromUTF8[extraBytesToRead];
+
+  result = isValidCodePoint(&ch);
+  
+ finished:
+  *nextUTF8       = source;
+  *convertedUTF32 = ch;
+  
+  return(result);
+}
+
+
+static ConversionResult ConvertUTF32toUTF8 (UTF32 u32CodePoint, UTF8 **targetStart, UTF8 *targetEnd) {
+  const UTF32       byteMask     = 0xBF, byteMark = 0x80;
+  ConversionResult  result       = conversionOK;
+  UTF8             *target       = *targetStart;
+  UTF32             ch           = u32CodePoint;
+  unsigned short    bytesToWrite = 0;
+
+  result = isValidCodePoint(&ch);
+
+  // Figure out how many bytes the result will require. Turn any illegally large UTF32 things (> Plane 17) into replacement chars.
+       if(ch < (UTF32)0x80)          { bytesToWrite = 1; }
+  else if(ch < (UTF32)0x800)         { bytesToWrite = 2; }
+  else if(ch < (UTF32)0x10000)       { bytesToWrite = 3; }
+  else if(ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; }
+  else {                               bytesToWrite = 3; ch = UNI_REPLACEMENT_CHAR; result = sourceIllegal; }
+        
+  target += bytesToWrite;
+  if (target > targetEnd) { target -= bytesToWrite; result = targetExhausted; goto finished; }
+
+  switch (bytesToWrite) { // note: everything falls through.
+    case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
+    case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
+    case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
+    case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]);
+  }
+
+  target += bytesToWrite;
+
+ finished:
+  *targetStart = target;
+  return(result);
+}
+
+JK_STATIC_INLINE int jk_string_add_unicodeCodePoint(JKParseState *parseState, uint32_t unicodeCodePoint, size_t *tokenBufferIdx, JKHash *stringHash) {
+  UTF8             *u8s = &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx];
+  ConversionResult  result;
+
+  if((result = ConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } }
+  size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len;
+  
+  while(*tokenBufferIdx < nextIdx) { *stringHash = jk_calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); }
+
+  return(0);
+}
+
+////////////
+#pragma mark -
+#pragma mark Decoding / parsing / deserializing functions
+
+static int jk_parse_string(JKParseState *parseState) {
+  NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
+  const unsigned char *stringStart       = JK_AT_STRING_PTR(parseState) + 1;
+  const unsigned char *endOfBuffer       = JK_END_STRING_PTR(parseState);
+  const unsigned char *atStringCharacter = stringStart;
+  unsigned char       *tokenBuffer       = parseState->token.tokenBuffer.bytes.ptr;
+  size_t               tokenStartIndex   = parseState->atIndex;
+  size_t               tokenBufferIdx    = 0UL;
+
+  int      onlySimpleString        = 1,  stringState     = JSONStringStateStart;
+  uint16_t escapedUnicode1         = 0U, escapedUnicode2 = 0U;
+  uint32_t escapedUnicodeCodePoint = 0U;
+  JKHash   stringHash              = JK_HASH_INIT;
+    
+  while(1) {
+    unsigned long currentChar;
+
+    if(JK_EXPECT_F(atStringCharacter == endOfBuffer)) { /* XXX Add error message */ stringState = JSONStringStateError; goto finishedParsing; }
+    
+    if(JK_EXPECT_F((currentChar = *atStringCharacter++) >= 0x80UL)) {
+      const unsigned char *nextValidCharacter = NULL;
+      UTF32                u32ch              = 0U;
+      ConversionResult     result;
+
+      if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; }
+      stringHash = jk_calculateHash(stringHash, currentChar);
+      while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = jk_calculateHash(stringHash, *atStringCharacter++); }
+      continue;
+    } else {
+      if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; goto finishedParsing; }
+
+      if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) {
+      switchToSlowPath:
+        onlySimpleString = 0;
+        stringState      = JSONStringStateParsing;
+        tokenBufferIdx   = (atStringCharacter - stringStart) - 1L;
+        if(JK_EXPECT_F((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length)) { if((tokenBuffer = jk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { jk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
+        memcpy(tokenBuffer, stringStart, tokenBufferIdx);
+        goto slowMatch;
+      }
+
+      if(JK_EXPECT_F(currentChar < 0x20UL)) { jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; }
+
+      stringHash = jk_calculateHash(stringHash, currentChar);
+    }
+  }
+
+ slowMatch:
+
+  for(atStringCharacter = (stringStart + ((atStringCharacter - stringStart) - 1L)); (atStringCharacter < endOfBuffer) && (tokenBufferIdx < parseState->token.tokenBuffer.bytes.length); atStringCharacter++) {
+    if((tokenBufferIdx + 16UL) > parseState->token.tokenBuffer.bytes.length) { if((tokenBuffer = jk_managedBuffer_resize(&parseState->token.tokenBuffer, tokenBufferIdx + 1024UL)) == NULL) { jk_error(parseState, @"Internal error: Unable to resize temporary buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
+
+    NSCParameterAssert(tokenBufferIdx < parseState->token.tokenBuffer.bytes.length);
+
+    unsigned long currentChar = (*atStringCharacter), escapedChar;
+
+    if(JK_EXPECT_T(stringState == JSONStringStateParsing)) {
+      if(JK_EXPECT_T(currentChar >= 0x20UL)) {
+        if(JK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence
+          if(JK_EXPECT_F(currentChar == (unsigned long)'"'))  { stringState = JSONStringStateFinished; atStringCharacter++; goto finishedParsing; }
+          if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = JSONStringStateEscape; continue; }
+          stringHash = jk_calculateHash(stringHash, currentChar);
+          tokenBuffer[tokenBufferIdx++] = currentChar;
+          continue;
+        } else { // UTF8 sequence
+          const unsigned char *nextValidCharacter = NULL;
+          UTF32                u32ch              = 0U;
+          ConversionResult     result;
+          
+          if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) {
+            if((result == sourceIllegal) && ((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0)) { jk_error(parseState, @"Illegal UTF8 sequence found in \"\" string.");              stringState = JSONStringStateError; goto finishedParsing; }
+            if(result == sourceExhausted)                                                                      { jk_error(parseState, @"End of buffer reached while parsing UTF8 in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; }
+            if(jk_string_add_unicodeCodePoint(parseState, u32ch, &tokenBufferIdx, &stringHash))                { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; }
+            atStringCharacter = nextValidCharacter - 1;
+            continue;
+          } else {
+            while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = jk_calculateHash(stringHash, *atStringCharacter++); }
+            atStringCharacter--;
+            continue;
+          }
+        }
+      } else { // currentChar < 0x20
+        jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing;
+      }
+
+    } else { // stringState != JSONStringStateParsing
+      int isSurrogate = 1;
+
+      switch(stringState) {
+        case JSONStringStateEscape:
+          switch(currentChar) {
+            case 'u': escapedUnicode1 = 0U; escapedUnicode2 = 0U; escapedUnicodeCodePoint = 0U; stringState = JSONStringStateEscapedUnicode1; break;
+
+            case 'b':  escapedChar = '\b'; goto parsedEscapedChar;
+            case 'f':  escapedChar = '\f'; goto parsedEscapedChar;
+            case 'n':  escapedChar = '\n'; goto parsedEscapedChar;
+            case 'r':  escapedChar = '\r'; goto parsedEscapedChar;
+            case 't':  escapedChar = '\t'; goto parsedEscapedChar;
+            case '\\': escapedChar = '\\'; goto parsedEscapedChar;
+            case '/':  escapedChar = '/';  goto parsedEscapedChar;
+            case '"':  escapedChar = '"';  goto parsedEscapedChar;
+              
+            parsedEscapedChar:
+              stringState = JSONStringStateParsing;
+              stringHash  = jk_calculateHash(stringHash, escapedChar);
+              tokenBuffer[tokenBufferIdx++] = escapedChar;
+              break;
+              
+            default: jk_error(parseState, @"Invalid escape sequence found in \"\" string."); stringState = JSONStringStateError; goto finishedParsing; break;
+          }
+          break;
+
+        case JSONStringStateEscapedUnicode1:
+        case JSONStringStateEscapedUnicode2:
+        case JSONStringStateEscapedUnicode3:
+        case JSONStringStateEscapedUnicode4:           isSurrogate = 0;
+        case JSONStringStateEscapedUnicodeSurrogate1:
+        case JSONStringStateEscapedUnicodeSurrogate2:
+        case JSONStringStateEscapedUnicodeSurrogate3:
+        case JSONStringStateEscapedUnicodeSurrogate4:
+          {
+            uint16_t hexValue = 0U;
+
+            switch(currentChar) {
+              case '0' ... '9': hexValue =  currentChar - '0';        goto parsedHex;
+              case 'a' ... 'f': hexValue = (currentChar - 'a') + 10U; goto parsedHex;
+              case 'A' ... 'F': hexValue = (currentChar - 'A') + 10U; goto parsedHex;
+                
+              parsedHex:
+              if(!isSurrogate) { escapedUnicode1 = (escapedUnicode1 << 4) | hexValue; } else { escapedUnicode2 = (escapedUnicode2 << 4) | hexValue; }
+                
+              if(stringState == JSONStringStateEscapedUnicode4) {
+                if(((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xE000U))) {
+                  if((escapedUnicode1 >= 0xD800U) && (escapedUnicode1 < 0xDC00U)) { stringState = JSONStringStateEscapedNeedEscapeForSurrogate; }
+                  else if((escapedUnicode1 >= 0xDC00U) && (escapedUnicode1 < 0xE000U)) { 
+                    if((parseState->parseOptionFlags & JKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = UNI_REPLACEMENT_CHAR; }
+                    else { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
+                  }
+                }
+                else { escapedUnicodeCodePoint = escapedUnicode1; }
+              }
+
+              if(stringState == JSONStringStateEscapedUnicodeSurrogate4) {
+                if((escapedUnicode2 < 0xdc00) || (escapedUnicode2 > 0xdfff)) {
+                  if((parseState->parseOptionFlags & JKParseOptionLooseUnicode)) { escapedUnicodeCodePoint = UNI_REPLACEMENT_CHAR; }
+                  else { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
+                }
+                else { escapedUnicodeCodePoint = ((escapedUnicode1 - 0xd800) * 0x400) + (escapedUnicode2 - 0xdc00) + 0x10000; }
+              }
+                
+              if((stringState == JSONStringStateEscapedUnicode4) || (stringState == JSONStringStateEscapedUnicodeSurrogate4)) { 
+                if((isValidCodePoint(&escapedUnicodeCodePoint) == sourceIllegal) && ((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0)) { jk_error(parseState, @"Illegal \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
+                stringState = JSONStringStateParsing;
+                if(jk_string_add_unicodeCodePoint(parseState, escapedUnicodeCodePoint, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; }
+              }
+              else if((stringState >= JSONStringStateEscapedUnicode1) && (stringState <= JSONStringStateEscapedUnicodeSurrogate4)) { stringState++; }
+              break;
+
+              default: jk_error(parseState, @"Unexpected character found in \\u Unicode escape sequence.  Found '%c', expected [0-9a-fA-F].", currentChar); stringState = JSONStringStateError; goto finishedParsing; break;
+            }
+          }
+          break;
+
+        case JSONStringStateEscapedNeedEscapeForSurrogate:
+          if(currentChar == '\\') { stringState = JSONStringStateEscapedNeedEscapedUForSurrogate; }
+          else { 
+            if((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) { jk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
+            else { stringState = JSONStringStateParsing; atStringCharacter--;    if(jk_string_add_unicodeCodePoint(parseState, UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
+          }
+          break;
+
+        case JSONStringStateEscapedNeedEscapedUForSurrogate:
+          if(currentChar == 'u') { stringState = JSONStringStateEscapedUnicodeSurrogate1; }
+          else { 
+            if((parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) { jk_error(parseState, @"Required a second \\u Unicode escape sequence following a surrogate \\u Unicode escape sequence."); stringState = JSONStringStateError; goto finishedParsing; }
+            else { stringState = JSONStringStateParsing; atStringCharacter -= 2; if(jk_string_add_unicodeCodePoint(parseState, UNI_REPLACEMENT_CHAR, &tokenBufferIdx, &stringHash)) { jk_error(parseState, @"Internal error: Unable to add UTF8 sequence to internal string buffer. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; } }
+          }
+          break;
+
+        default: jk_error(parseState, @"Internal error: Unknown stringState. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); stringState = JSONStringStateError; goto finishedParsing; break;
+      }
+    }
+  }
+
+finishedParsing:
+
+  if(JK_EXPECT_T(stringState == JSONStringStateFinished)) {
+    NSCParameterAssert((parseState->stringBuffer.bytes.ptr + tokenStartIndex) < atStringCharacter);
+
+    parseState->token.tokenPtrRange.ptr    = parseState->stringBuffer.bytes.ptr + tokenStartIndex;
+    parseState->token.tokenPtrRange.length = (atStringCharacter - parseState->token.tokenPtrRange.ptr);
+
+    if(JK_EXPECT_T(onlySimpleString)) {
+      NSCParameterAssert(((parseState->token.tokenPtrRange.ptr + 1) < endOfBuffer) && (parseState->token.tokenPtrRange.length >= 2UL) && (((parseState->token.tokenPtrRange.ptr + 1) + (parseState->token.tokenPtrRange.length - 2)) < endOfBuffer));
+      parseState->token.value.ptrRange.ptr    = parseState->token.tokenPtrRange.ptr    + 1;
+      parseState->token.value.ptrRange.length = parseState->token.tokenPtrRange.length - 2UL;
+    } else {
+      parseState->token.value.ptrRange.ptr    = parseState->token.tokenBuffer.bytes.ptr;
+      parseState->token.value.ptrRange.length = tokenBufferIdx;
+    }
+    
+    parseState->token.value.hash = stringHash;
+    parseState->token.value.type = JKValueTypeString;
+    parseState->atIndex          = (atStringCharacter - parseState->stringBuffer.bytes.ptr);
+  }
+
+  if(JK_EXPECT_F(stringState != JSONStringStateFinished)) { jk_error(parseState, @"Invalid string."); }
+  return(JK_EXPECT_T(stringState == JSONStringStateFinished) ? 0 : 1);
+}
+
+static int jk_parse_number(JKParseState *parseState) {
+  NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
+  const unsigned char *numberStart       = JK_AT_STRING_PTR(parseState);
+  const unsigned char *endOfBuffer       = JK_END_STRING_PTR(parseState);
+  const unsigned char *atNumberCharacter = NULL;
+  int                  numberState       = JSONNumberStateWholeNumberStart, isFloatingPoint = 0, isNegative = 0, backup = 0;
+  size_t               startingIndex     = parseState->atIndex;
+  
+  for(atNumberCharacter = numberStart; (JK_EXPECT_T(atNumberCharacter < endOfBuffer)) && (JK_EXPECT_T(!(JK_EXPECT_F(numberState == JSONNumberStateFinished) || JK_EXPECT_F(numberState == JSONNumberStateError)))); atNumberCharacter++) {
+    unsigned long currentChar = (unsigned long)(*atNumberCharacter), lowerCaseCC = currentChar | 0x20UL;
+    
+    switch(numberState) {
+      case JSONNumberStateWholeNumberStart: if   (currentChar == '-')                                                                              { numberState = JSONNumberStateWholeNumberMinus;      isNegative      = 1; break; }
+      case JSONNumberStateWholeNumberMinus: if   (currentChar == '0')                                                                              { numberState = JSONNumberStateWholeNumberZero;                            break; }
+                                       else if(  (currentChar >= '1') && (currentChar <= '9'))                                                     { numberState = JSONNumberStateWholeNumber;                                break; }
+                                       else                                                     { /* XXX Add error message */                        numberState = JSONNumberStateError;                                      break; }
+      case JSONNumberStateExponentStart:    if(  (currentChar == '+') || (currentChar == '-'))                                                     { numberState = JSONNumberStateExponentPlusMinus;                          break; }
+      case JSONNumberStateFractionalNumberStart:
+      case JSONNumberStateExponentPlusMinus:if(!((currentChar >= '0') && (currentChar <= '9'))) { /* XXX Add error message */                        numberState = JSONNumberStateError;                                      break; }
+                                       else {                                              if(numberState == JSONNumberStateFractionalNumberStart) { numberState = JSONNumberStateFractionalNumber; }
+                                                                                           else                                                    { numberState = JSONNumberStateExponent;         }                         break; }
+      case JSONNumberStateWholeNumberZero:
+      case JSONNumberStateWholeNumber:      if   (currentChar == '.')                                                                              { numberState = JSONNumberStateFractionalNumberStart; isFloatingPoint = 1; break; }
+      case JSONNumberStateFractionalNumber: if   (lowerCaseCC == 'e')                                                                              { numberState = JSONNumberStateExponentStart;         isFloatingPoint = 1; break; }
+      case JSONNumberStateExponent:         if(!((currentChar >= '0') && (currentChar <= '9')) || (numberState == JSONNumberStateWholeNumberZero)) { numberState = JSONNumberStateFinished;              backup          = 1; break; }
+        break;
+      default:                                                                                    /* XXX Add error message */                        numberState = JSONNumberStateError;                                      break;
+    }
+  }
+  
+  parseState->token.tokenPtrRange.ptr    = parseState->stringBuffer.bytes.ptr + startingIndex;
+  parseState->token.tokenPtrRange.length = (atNumberCharacter - parseState->token.tokenPtrRange.ptr) - backup;
+  parseState->atIndex                    = (parseState->token.tokenPtrRange.ptr + parseState->token.tokenPtrRange.length) - parseState->stringBuffer.bytes.ptr;
+
+  if(JK_EXPECT_T(numberState == JSONNumberStateFinished)) {
+    unsigned char  numberTempBuf[parseState->token.tokenPtrRange.length + 4UL];
+    unsigned char *endOfNumber = NULL;
+
+    memcpy(numberTempBuf, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length);
+    numberTempBuf[parseState->token.tokenPtrRange.length] = 0;
+
+    errno = 0;
+    
+    // Treat "-0" as a floating point number, which is capable of representing negative zeros.
+    if(JK_EXPECT_F(parseState->token.tokenPtrRange.length == 2UL) && JK_EXPECT_F(numberTempBuf[1] == '0') && JK_EXPECT_F(isNegative)) { isFloatingPoint = 1; }
+
+    if(isFloatingPoint) {
+      parseState->token.value.number.doubleValue = strtod((const char *)numberTempBuf, (char **)&endOfNumber); // strtod is documented to return U+2261 (identical to) 0.0 on an underflow error (along with setting errno to ERANGE).
+      parseState->token.value.type               = JKValueTypeDouble;
+      parseState->token.value.ptrRange.ptr       = (const unsigned char *)&parseState->token.value.number.doubleValue;
+      parseState->token.value.ptrRange.length    = sizeof(double);
+      parseState->token.value.hash               = (JK_HASH_INIT + parseState->token.value.type);
+    } else {
+      if(isNegative) {
+        parseState->token.value.number.longLongValue = strtoll((const char *)numberTempBuf, (char **)&endOfNumber, 10);
+        parseState->token.value.type                 = JKValueTypeLongLong;
+        parseState->token.value.ptrRange.ptr         = (const unsigned char *)&parseState->token.value.number.longLongValue;
+        parseState->token.value.ptrRange.length      = sizeof(long long);
+        parseState->token.value.hash                 = (JK_HASH_INIT + parseState->token.value.type) + (JKHash)parseState->token.value.number.longLongValue;
+      } else {
+        parseState->token.value.number.unsignedLongLongValue = strtoull((const char *)numberTempBuf, (char **)&endOfNumber, 10);
+        parseState->token.value.type                         = JKValueTypeUnsignedLongLong;
+        parseState->token.value.ptrRange.ptr                 = (const unsigned char *)&parseState->token.value.number.unsignedLongLongValue;
+        parseState->token.value.ptrRange.length              = sizeof(unsigned long long);
+        parseState->token.value.hash                         = (JK_HASH_INIT + parseState->token.value.type) + (JKHash)parseState->token.value.number.unsignedLongLongValue;
+      }
+    }
+
+    if(JK_EXPECT_F(errno != 0)) {
+      numberState = JSONNumberStateError;
+      if(errno == ERANGE) {
+        switch(parseState->token.value.type) {
+          case JKValueTypeDouble:           jk_error(parseState, @"The value '%s' could not be represented as a 'double' due to %s.",           numberTempBuf, (parseState->token.value.number.doubleValue == 0.0) ? "underflow" : "overflow"); break; // see above for == 0.0.
+          case JKValueTypeLongLong:         jk_error(parseState, @"The value '%s' exceeded the minimum value that could be represented: %lld.", numberTempBuf, parseState->token.value.number.longLongValue);                                   break;
+          case JKValueTypeUnsignedLongLong: jk_error(parseState, @"The value '%s' exceeded the maximum value that could be represented: %llu.", numberTempBuf, parseState->token.value.number.unsignedLongLongValue);                           break;
+          default:                          jk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld",                     [NSString stringWithUTF8String:__FILE__], (long)__LINE__);                                      break;
+        }
+      }
+    }
+    if(JK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && JK_EXPECT_F(numberState != JSONNumberStateError)) { numberState = JSONNumberStateError; jk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); }
+
+    size_t hashIndex = 0UL;
+    for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = jk_calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); }
+  }
+
+  if(JK_EXPECT_F(numberState != JSONNumberStateFinished)) { jk_error(parseState, @"Invalid number."); }
+  return(JK_EXPECT_T((numberState == JSONNumberStateFinished)) ? 0 : 1);
+}
+
+JK_STATIC_INLINE void jk_set_parsed_token(JKParseState *parseState, const unsigned char *ptr, size_t length, JKTokenType type, size_t advanceBy) {
+  parseState->token.tokenPtrRange.ptr     = ptr;
+  parseState->token.tokenPtrRange.length  = length;
+  parseState->token.type                  = type;
+  parseState->atIndex                    += advanceBy;
+}
+
+static size_t jk_parse_is_newline(JKParseState *parseState, const unsigned char *atCharacterPtr) {
+  NSCParameterAssert((parseState != NULL) && (atCharacterPtr != NULL) && (atCharacterPtr >= parseState->stringBuffer.bytes.ptr) && (atCharacterPtr < JK_END_STRING_PTR(parseState)));
+  const unsigned char *endOfStringPtr = JK_END_STRING_PTR(parseState);
+
+  if(JK_EXPECT_F(atCharacterPtr >= endOfStringPtr)) { return(0UL); }
+
+  if(JK_EXPECT_F((*(atCharacterPtr + 0)) == '\n')) { return(1UL); }
+  if(JK_EXPECT_F((*(atCharacterPtr + 0)) == '\r')) { if((JK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr)) && ((*(atCharacterPtr + 1)) == '\n')) { return(2UL); } return(1UL); }
+  if(parseState->parseOptionFlags & JKParseOptionUnicodeNewlines) {
+    if((JK_EXPECT_F((*(atCharacterPtr + 0)) == 0xc2)) && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x85))) { return(2UL); }
+    if((JK_EXPECT_F((*(atCharacterPtr + 0)) == 0xe2)) && (((atCharacterPtr + 2) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == 0x80) && (((*(atCharacterPtr + 2)) == 0xa8) || ((*(atCharacterPtr + 2)) == 0xa9)))) { return(3UL); }
+  }
+
+  return(0UL);
+}
+
+JK_STATIC_INLINE int jk_parse_skip_newline(JKParseState *parseState) {
+  size_t newlineAdvanceAtIndex = 0UL;
+  if(JK_EXPECT_F((newlineAdvanceAtIndex = jk_parse_is_newline(parseState, JK_AT_STRING_PTR(parseState))) > 0UL)) { parseState->lineNumber++; parseState->atIndex += (newlineAdvanceAtIndex - 1UL); parseState->lineStartIndex = parseState->atIndex + 1UL; return(1); }
+  return(0);
+}
+
+JK_STATIC_INLINE void jk_parse_skip_whitespace(JKParseState *parseState) {
+#ifndef __clang_analyzer__
+  NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
+  const unsigned char *atCharacterPtr   = NULL;
+  const unsigned char *endOfStringPtr   = JK_END_STRING_PTR(parseState);
+
+  for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) {
+    if(((*(atCharacterPtr + 0)) == ' ') || ((*(atCharacterPtr + 0)) == '\t')) { continue; }
+    if(jk_parse_skip_newline(parseState)) { continue; }
+    if(parseState->parseOptionFlags & JKParseOptionComments) {
+      if((JK_EXPECT_F((*(atCharacterPtr + 0)) == '/')) && (JK_EXPECT_T((atCharacterPtr + 1) < endOfStringPtr))) {
+        if((*(atCharacterPtr + 1)) == '/') {
+          parseState->atIndex++;
+          for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) { if(jk_parse_skip_newline(parseState)) { break; } }
+          continue;
+        }
+        if((*(atCharacterPtr + 1)) == '*') {
+          parseState->atIndex++;
+          for(atCharacterPtr = JK_AT_STRING_PTR(parseState); (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr)); parseState->atIndex++) {
+            if(jk_parse_skip_newline(parseState)) { continue; }
+            if(((*(atCharacterPtr + 0)) == '*') && (((atCharacterPtr + 1) < endOfStringPtr) && ((*(atCharacterPtr + 1)) == '/'))) { parseState->atIndex++; break; }
+          }
+          continue;
+        }
+      }
+    }
+    break;
+  }
+#endif
+}
+
+static int jk_parse_next_token(JKParseState *parseState) {
+  NSCParameterAssert((parseState != NULL) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
+  const unsigned char *atCharacterPtr   = NULL;
+  const unsigned char *endOfStringPtr   = JK_END_STRING_PTR(parseState);
+  unsigned char        currentCharacter = 0U;
+  int                  stopParsing      = 0;
+
+  parseState->prev_atIndex        = parseState->atIndex;
+  parseState->prev_lineNumber     = parseState->lineNumber;
+  parseState->prev_lineStartIndex = parseState->lineStartIndex;
+
+  jk_parse_skip_whitespace(parseState);
+
+  if((JK_AT_STRING_PTR(parseState) == endOfStringPtr)) { stopParsing = 1; }
+
+  if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((atCharacterPtr = JK_AT_STRING_PTR(parseState)) < endOfStringPtr))) {
+    currentCharacter = *atCharacterPtr;
+
+         if(JK_EXPECT_T(currentCharacter == '"')) { if(JK_EXPECT_T((stopParsing = jk_parse_string(parseState)) == 0)) { jk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, JKTokenTypeString, 0UL); } }
+    else if(JK_EXPECT_T(currentCharacter == ':')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeSeparator,   1UL); }
+    else if(JK_EXPECT_T(currentCharacter == ',')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeComma,       1UL); }
+    else if((JK_EXPECT_T(currentCharacter >= '0') && JK_EXPECT_T(currentCharacter <= '9')) || JK_EXPECT_T(currentCharacter == '-')) { if(JK_EXPECT_T((stopParsing = jk_parse_number(parseState)) == 0)) { jk_set_parsed_token(parseState, parseState->token.tokenPtrRange.ptr, parseState->token.tokenPtrRange.length, JKTokenTypeNumber, 0UL); } }
+    else if(JK_EXPECT_T(currentCharacter == '{')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeObjectBegin, 1UL); }
+    else if(JK_EXPECT_T(currentCharacter == '}')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeObjectEnd,   1UL); }
+    else if(JK_EXPECT_T(currentCharacter == '[')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeArrayBegin,  1UL); }
+    else if(JK_EXPECT_T(currentCharacter == ']')) { jk_set_parsed_token(parseState, atCharacterPtr, 1UL, JKTokenTypeArrayEnd,    1UL); }
+    
+    else if(JK_EXPECT_T(currentCharacter == 't')) { if(!((JK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'r')) && (JK_EXPECT_T(atCharacterPtr[2] == 'u')) && (JK_EXPECT_T(atCharacterPtr[3] == 'e'))))                                            { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 4UL, JKTokenTypeTrue,  4UL); } }
+    else if(JK_EXPECT_T(currentCharacter == 'f')) { if(!((JK_EXPECT_T((atCharacterPtr + 5UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'a')) && (JK_EXPECT_T(atCharacterPtr[2] == 'l')) && (JK_EXPECT_T(atCharacterPtr[3] == 's')) && (JK_EXPECT_T(atCharacterPtr[4] == 'e')))) { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 5UL, JKTokenTypeFalse, 5UL); } }
+    else if(JK_EXPECT_T(currentCharacter == 'n')) { if(!((JK_EXPECT_T((atCharacterPtr + 4UL) < endOfStringPtr)) && (JK_EXPECT_T(atCharacterPtr[1] == 'u')) && (JK_EXPECT_T(atCharacterPtr[2] == 'l')) && (JK_EXPECT_T(atCharacterPtr[3] == 'l'))))                                            { stopParsing = 1; /* XXX Add error message */ } else { jk_set_parsed_token(parseState, atCharacterPtr, 4UL, JKTokenTypeNull,  4UL); } }
+    else { stopParsing = 1; /* XXX Add error message */ }    
+  }
+
+  if(JK_EXPECT_F(stopParsing)) { jk_error(parseState, @"Unexpected token, wanted '{', '}', '[', ']', ',', ':', 'true', 'false', 'null', '\"STRING\"', 'NUMBER'."); }
+  return(stopParsing);
+}
+
+static void jk_error_parse_accept_or3(JKParseState *parseState, int state, NSString *or1String, NSString *or2String, NSString *or3String) {
+  NSString *acceptStrings[16];
+  int acceptIdx = 0;
+  if(state & JKParseAcceptValue) { acceptStrings[acceptIdx++] = or1String; }
+  if(state & JKParseAcceptComma) { acceptStrings[acceptIdx++] = or2String; }
+  if(state & JKParseAcceptEnd)   { acceptStrings[acceptIdx++] = or3String; }
+       if(acceptIdx == 1) { jk_error(parseState, @"Expected %@, not '%*.*s'",           acceptStrings[0],                                     (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); }
+  else if(acceptIdx == 2) { jk_error(parseState, @"Expected %@ or %@, not '%*.*s'",     acceptStrings[0], acceptStrings[1],                   (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); }
+  else if(acceptIdx == 3) { jk_error(parseState, @"Expected %@, %@, or %@, not '%*.*s", acceptStrings[0], acceptStrings[1], acceptStrings[2], (int)parseState->token.tokenPtrRange.length, (int)parseState->token.tokenPtrRange.length, parseState->token.tokenPtrRange.ptr); }
+}
+
+static void *jk_parse_array(JKParseState *parseState) {
+  size_t  startingObjectIndex = parseState->objectStack.index;
+  int     arrayState          = JKParseAcceptValueOrEnd, stopParsing = 0;
+  void   *parsedArray         = NULL;
+
+  while(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) {
+    if(JK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(jk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { jk_error(parseState, @"Internal error: [array] objectsIndex > %zu, resize failed? %@ line %#ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } }
+
+    if(JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)) {
+      void *object = NULL;
+#ifndef NS_BLOCK_ASSERTIONS
+      parseState->objectStack.objects[parseState->objectStack.index] = NULL;
+      parseState->objectStack.keys   [parseState->objectStack.index] = NULL;
+#endif
+      switch(parseState->token.type) {
+        case JKTokenTypeNumber:
+        case JKTokenTypeString:
+        case JKTokenTypeTrue:
+        case JKTokenTypeFalse:
+        case JKTokenTypeNull:
+        case JKTokenTypeArrayBegin:
+        case JKTokenTypeObjectBegin:
+          if(JK_EXPECT_F((arrayState & JKParseAcceptValue)          == 0))    { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected value.");              stopParsing = 1; break; }
+          if(JK_EXPECT_F((object = jk_object_for_token(parseState)) == NULL)) {                              jk_error(parseState, @"Internal error: Object == NULL"); stopParsing = 1; break; } else { parseState->objectStack.objects[parseState->objectStack.index++] = object; arrayState = JKParseAcceptCommaOrEnd; }
+          break;
+        case JKTokenTypeArrayEnd: if(JK_EXPECT_T(arrayState & JKParseAcceptEnd)) { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedArray = (void *)_JKArrayCreate((id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ']'."); } stopParsing = 1; break;
+        case JKTokenTypeComma:    if(JK_EXPECT_T(arrayState & JKParseAcceptComma)) { arrayState = JKParseAcceptValue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break;
+        default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, arrayState, @"a value", @"a comma", @"a ']'"); stopParsing = 1; break;
+      }
+    }
+  }
+
+  if(JK_EXPECT_F(parsedArray == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } }
+#if !defined(NS_BLOCK_ASSERTIONS)
+  else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } }
+#endif
+  
+  parseState->objectStack.index = startingObjectIndex;
+  return(parsedArray);
+}
+
+static void *jk_create_dictionary(JKParseState *parseState, size_t startingObjectIndex) {
+  void *parsedDictionary = NULL;
+
+  parseState->objectStack.index--;
+
+  parsedDictionary = _JKDictionaryCreate((id *)&parseState->objectStack.keys[startingObjectIndex], (NSUInteger *)&parseState->objectStack.cfHashes[startingObjectIndex], (id *)&parseState->objectStack.objects[startingObjectIndex], (parseState->objectStack.index - startingObjectIndex), parseState->mutableCollections);
+
+  return(parsedDictionary);
+}
+
+static void *jk_parse_dictionary(JKParseState *parseState) {
+  size_t  startingObjectIndex = parseState->objectStack.index;
+  int     dictState           = JKParseAcceptValueOrEnd, stopParsing = 0;
+  void   *parsedDictionary    = NULL;
+
+  while(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length)))) {
+    if(JK_EXPECT_F(parseState->objectStack.index > (parseState->objectStack.count - 4UL))) { if(jk_objectStack_resize(&parseState->objectStack, parseState->objectStack.count + 128UL)) { jk_error(parseState, @"Internal error: [dictionary] objectsIndex > %zu, resize failed? %@ line #%ld", (parseState->objectStack.count - 4UL), [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break; } }
+
+    size_t objectStackIndex = parseState->objectStack.index++;
+    parseState->objectStack.keys[objectStackIndex]    = NULL;
+    parseState->objectStack.objects[objectStackIndex] = NULL;
+    void *key = NULL, *object = NULL;
+
+    if(JK_EXPECT_T((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)))) {
+      switch(parseState->token.type) {
+        case JKTokenTypeString:
+          if(JK_EXPECT_F((dictState & JKParseAcceptValue)        == 0))    { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected string.");           stopParsing = 1; break; }
+          if(JK_EXPECT_F((key = jk_object_for_token(parseState)) == NULL)) {                              jk_error(parseState, @"Internal error: Key == NULL."); stopParsing = 1; break; }
+          else {
+            parseState->objectStack.keys[objectStackIndex] = key;
+            if(JK_EXPECT_T(parseState->token.value.cacheItem != NULL)) { if(JK_EXPECT_F(parseState->token.value.cacheItem->cfHash == 0UL)) { parseState->token.value.cacheItem->cfHash = CFHash(key); } parseState->objectStack.cfHashes[objectStackIndex] = parseState->token.value.cacheItem->cfHash; }
+            else { parseState->objectStack.cfHashes[objectStackIndex] = CFHash(key); }
+          }
+          break;
+
+        case JKTokenTypeObjectEnd: if((JK_EXPECT_T(dictState & JKParseAcceptEnd)))   { NSCParameterAssert(parseState->objectStack.index >= startingObjectIndex); parsedDictionary = jk_create_dictionary(parseState, startingObjectIndex); } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected '}'."); } stopParsing = 1; break;
+        case JKTokenTypeComma:     if((JK_EXPECT_T(dictState & JKParseAcceptComma))) { dictState = JKParseAcceptValue; parseState->objectStack.index--; continue; } else { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected ','."); stopParsing = 1; } break;
+
+        default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a \"STRING\"", @"a comma", @"a '}'"); stopParsing = 1; break;
+      }
+    }
+
+    if(JK_EXPECT_T(stopParsing == 0)) {
+      if(JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0)) { if(JK_EXPECT_F(parseState->token.type != JKTokenTypeSeparator)) { parseState->errorIsPrev = 1; jk_error(parseState, @"Expected ':'."); stopParsing = 1; } }
+    }
+
+    if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0))) {
+      switch(parseState->token.type) {
+        case JKTokenTypeNumber:
+        case JKTokenTypeString:
+        case JKTokenTypeTrue:
+        case JKTokenTypeFalse:
+        case JKTokenTypeNull:
+        case JKTokenTypeArrayBegin:
+        case JKTokenTypeObjectBegin:
+          if(JK_EXPECT_F((dictState & JKParseAcceptValue)           == 0))    { parseState->errorIsPrev = 1; jk_error(parseState, @"Unexpected value.");               stopParsing = 1; break; }
+          if(JK_EXPECT_F((object = jk_object_for_token(parseState)) == NULL)) {                              jk_error(parseState, @"Internal error: Object == NULL."); stopParsing = 1; break; } else { parseState->objectStack.objects[objectStackIndex] = object; dictState = JKParseAcceptCommaOrEnd; }
+          break;
+        default: parseState->errorIsPrev = 1; jk_error_parse_accept_or3(parseState, dictState, @"a value", @"a comma", @"a '}'"); stopParsing = 1; break;
+      }
+    }
+  }
+
+  if(JK_EXPECT_F(parsedDictionary == NULL)) { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { if(parseState->objectStack.keys[idx] != NULL) { CFRelease(parseState->objectStack.keys[idx]); parseState->objectStack.keys[idx] = NULL; } if(parseState->objectStack.objects[idx] != NULL) { CFRelease(parseState->objectStack.objects[idx]); parseState->objectStack.objects[idx] = NULL; } } }
+#if !defined(NS_BLOCK_ASSERTIONS)
+  else { size_t idx = 0UL; for(idx = startingObjectIndex; idx < parseState->objectStack.index; idx++) { parseState->objectStack.objects[idx] = NULL; parseState->objectStack.keys[idx] = NULL; } }
+#endif
+
+  parseState->objectStack.index = startingObjectIndex;
+  return(parsedDictionary);
+}
+
+static id json_parse_it(JKParseState *parseState) {
+  id  parsedObject = NULL;
+  int stopParsing  = 0;
+
+  while((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T(parseState->atIndex < parseState->stringBuffer.bytes.length))) {
+    if((JK_EXPECT_T(stopParsing == 0)) && (JK_EXPECT_T((stopParsing = jk_parse_next_token(parseState)) == 0))) {
+      switch(parseState->token.type) {
+        case JKTokenTypeArrayBegin:
+        case JKTokenTypeObjectBegin: parsedObject = [(id)jk_object_for_token(parseState) autorelease]; stopParsing = 1; break;
+        default:                     jk_error(parseState, @"Expected either '[' or '{'.");             stopParsing = 1; break;
+      }
+    }
+  }
+
+  NSCParameterAssert((parseState->objectStack.index == 0) && (JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)));
+
+  if((parsedObject == NULL) && (JK_AT_STRING_PTR(parseState) == JK_END_STRING_PTR(parseState))) { jk_error(parseState, @"Reached the end of the buffer."); }
+  if(parsedObject == NULL) { jk_error(parseState, @"Unable to parse JSON."); }
+
+  if((parsedObject != NULL) && (JK_AT_STRING_PTR(parseState) < JK_END_STRING_PTR(parseState))) {
+    jk_parse_skip_whitespace(parseState);
+    if((parsedObject != NULL) && ((parseState->parseOptionFlags & JKParseOptionPermitTextAfterValidJSON) == 0) && (JK_AT_STRING_PTR(parseState) < JK_END_STRING_PTR(parseState))) {
+      jk_error(parseState, @"A valid JSON object was parsed but there were additional non-white-space characters remaining.");
+      parsedObject = NULL;
+    }
+  }
+
+  return(parsedObject);
+}
+
+////////////
+#pragma mark -
+#pragma mark Object cache
+
+// This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1.
+// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. The LFSR is initalized to 1 in -initWithParseOptions:
+JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) {
+  NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U));
+  parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U);
+  parseState->cache.age[parseState->cache.prng_lfsr & (parseState->cache.count - 1UL)] >>= 1;
+}
+
+// The object cache is nothing more than a hash table with open addressing collision resolution that is bounded by JK_CACHE_PROBES attempts.
+//
+// The hash table is a linear C array of JKTokenCacheItem.  The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket].
+//
+// Items in the cache have an age associated with them.  An items age is incremented using saturating unsigned arithmetic and decremeted using unsigned right shifts.
+// Thus, an items age is managed using an AIMD policy- additive increase, multiplicative decrease.  All age calculations and manipulations are branchless.
+// The primitive C type MUST be unsigned.  It is currently a "char", which allows (at a minimum and in practice) 8 bits.
+//
+// A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0.
+//
+// When an item is found in the cache, it's age is incremented.
+// If a useable bucket hasn't been found, the current item (bucket) is aged along with two random items.
+//
+// If a value is not found in the cache, and no useable bucket has been found, that value is not added to the cache.
+
+static void *jk_cachedObjects(JKParseState *parseState) {
+  unsigned long  bucket     = parseState->token.value.hash & (parseState->cache.count - 1UL), setBucket = 0UL, useableBucket = 0UL, x = 0UL;
+  void          *parsedAtom = NULL;
+    
+  if(JK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && JK_EXPECT_T(parseState->token.value.type == JKValueTypeString)) { return(@""); }
+
+  for(x = 0UL; x < JK_CACHE_PROBES; x++) {
+    if(JK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; }
+    
+    if((JK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (JK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (JK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (JK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (JK_EXPECT_T(memcmp(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) {
+      parseState->cache.age[bucket]     = (((uint32_t)parseState->cache.age[bucket]) + 1U) - (((((uint32_t)parseState->cache.age[bucket]) + 1U) >> 31) ^ 1U);
+      parseState->token.value.cacheItem = &parseState->cache.items[bucket];
+      NSCParameterAssert(parseState->cache.items[bucket].object != NULL);
+      return((void *)CFRetain(parseState->cache.items[bucket].object));
+    } else {
+      if(JK_EXPECT_F(setBucket == 0UL) && JK_EXPECT_F(parseState->cache.age[bucket] == 0U)) { setBucket = 1UL; useableBucket = bucket; }
+      if(JK_EXPECT_F(setBucket == 0UL))                                                     { parseState->cache.age[bucket] >>= 1; jk_cache_age(parseState); jk_cache_age(parseState); }
+      // This is the open addressing function.  The values length and type are used as a form of "double hashing" to distribute values with the same effective value hash across different object cache buckets.
+      // The values type is a prime number that is relatively coprime to the other primes in the set of value types and the number of hash table buckets.
+      bucket = (parseState->token.value.hash + (parseState->token.value.ptrRange.length * (x + 1UL)) + (parseState->token.value.type * (x + 1UL)) + (3UL * (x + 1UL))) & (parseState->cache.count - 1UL);
+    }
+  }
+  
+  switch(parseState->token.value.type) {
+    case JKValueTypeString:           parsedAtom = (void *)CFStringCreateWithBytes(NULL, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length, kCFStringEncodingUTF8, 0); break;
+    case JKValueTypeLongLong:         parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.longLongValue);                                             break;
+    case JKValueTypeUnsignedLongLong:
+      if(parseState->token.value.number.unsignedLongLongValue <= LLONG_MAX) { parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberLongLongType, &parseState->token.value.number.unsignedLongLongValue); }
+      else { parsedAtom = (void *)parseState->objCImpCache.NSNumberInitWithUnsignedLongLong(parseState->objCImpCache.NSNumberAlloc(parseState->objCImpCache.NSNumberClass, @selector(alloc)), @selector(initWithUnsignedLongLong:), parseState->token.value.number.unsignedLongLongValue); }
+      break;
+    case JKValueTypeDouble:           parsedAtom = (void *)CFNumberCreate(NULL, kCFNumberDoubleType,   &parseState->token.value.number.doubleValue);                                               break;
+    default: jk_error(parseState, @"Internal error: Unknown token value type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break;
+  }
+  
+  if(JK_EXPECT_T(setBucket) && (JK_EXPECT_T(parsedAtom != NULL))) {
+    bucket = useableBucket;
+    if(JK_EXPECT_T((parseState->cache.items[bucket].object != NULL))) { CFRelease(parseState->cache.items[bucket].object); parseState->cache.items[bucket].object = NULL; }
+    
+    if(JK_EXPECT_T((parseState->cache.items[bucket].bytes = (unsigned char *)reallocf(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.length)) != NULL)) {
+      memcpy(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length);
+      parseState->cache.items[bucket].object = (void *)CFRetain(parsedAtom);
+      parseState->cache.items[bucket].hash   = parseState->token.value.hash;
+      parseState->cache.items[bucket].cfHash = 0UL;
+      parseState->cache.items[bucket].size   = parseState->token.value.ptrRange.length;
+      parseState->cache.items[bucket].type   = parseState->token.value.type;
+      parseState->token.value.cacheItem      = &parseState->cache.items[bucket];
+      parseState->cache.age[bucket]          = JK_INIT_CACHE_AGE;
+    } else { // The realloc failed, so clear the appropriate fields.
+      parseState->cache.items[bucket].hash   = 0UL;
+      parseState->cache.items[bucket].cfHash = 0UL;
+      parseState->cache.items[bucket].size   = 0UL;
+      parseState->cache.items[bucket].type   = 0UL;
+    }
+  }
+  
+  return(parsedAtom);
+}
+
+
+static void *jk_object_for_token(JKParseState *parseState) {
+  void *parsedAtom = NULL;
+  
+  parseState->token.value.cacheItem = NULL;
+  switch(parseState->token.type) {
+    case JKTokenTypeString:      parsedAtom = jk_cachedObjects(parseState);    break;
+    case JKTokenTypeNumber:      parsedAtom = jk_cachedObjects(parseState);    break;
+    case JKTokenTypeObjectBegin: parsedAtom = jk_parse_dictionary(parseState); break;
+    case JKTokenTypeArrayBegin:  parsedAtom = jk_parse_array(parseState);      break;
+    case JKTokenTypeTrue:        parsedAtom = (void *)kCFBooleanTrue;          break;
+    case JKTokenTypeFalse:       parsedAtom = (void *)kCFBooleanFalse;         break;
+    case JKTokenTypeNull:        parsedAtom = (void *)kCFNull;                 break;
+    default: jk_error(parseState, @"Internal error: Unknown token type. %@ line #%ld", [NSString stringWithUTF8String:__FILE__], (long)__LINE__); break;
+  }
+  
+  return(parsedAtom);
+}
+
+#pragma mark -
+@implementation JSONDecoder
+
++ (id)decoder
+{
+  return([self decoderWithParseOptions:JKParseOptionStrict]);
+}
+
++ (id)decoderWithParseOptions:(JKParseOptionFlags)parseOptionFlags
+{
+  return([[[self alloc] initWithParseOptions:parseOptionFlags] autorelease]);
+}
+
+- (id)init
+{
+  return([self initWithParseOptions:JKParseOptionStrict]);
+}
+
+- (id)initWithParseOptions:(JKParseOptionFlags)parseOptionFlags
+{
+  if((self = [super init]) == NULL) { return(NULL); }
+
+  if(parseOptionFlags & ~JKParseOptionValidFlags) { [self autorelease]; [NSException raise:NSInvalidArgumentException format:@"Invalid parse options."]; }
+
+  if((parseState = (JKParseState *)calloc(1UL, sizeof(JKParseState))) == NULL) { goto errorExit; }
+
+  parseState->parseOptionFlags = parseOptionFlags;
+  
+  parseState->token.tokenBuffer.roundSizeUpToMultipleOf = 4096UL;
+  parseState->objectStack.roundSizeUpToMultipleOf       = 2048UL;
+
+  parseState->objCImpCache.NSNumberClass                    = _jk_NSNumberClass;
+  parseState->objCImpCache.NSNumberAlloc                    = _jk_NSNumberAllocImp;
+  parseState->objCImpCache.NSNumberInitWithUnsignedLongLong = _jk_NSNumberInitWithUnsignedLongLongImp;
+  
+  parseState->cache.prng_lfsr = 1U;
+  parseState->cache.count     = JK_CACHE_SLOTS;
+  if((parseState->cache.items = (JKTokenCacheItem *)calloc(1UL, sizeof(JKTokenCacheItem) * parseState->cache.count)) == NULL) { goto errorExit; }
+
+  return(self);
+
+ errorExit:
+  if(self) { [self autorelease]; self = NULL; }
+  return(NULL);
+}
+
+// This is here primarily to support the NSString and NSData convenience functions so the autoreleased JSONDecoder can release most of its resources before the pool pops.
+static void _JSONDecoderCleanup(JSONDecoder *decoder) {
+  if((decoder != NULL) && (decoder->parseState != NULL)) {
+    jk_managedBuffer_release(&decoder->parseState->token.tokenBuffer);
+    jk_objectStack_release(&decoder->parseState->objectStack);
+    
+    [decoder clearCache];
+    if(decoder->parseState->cache.items != NULL) { free(decoder->parseState->cache.items); decoder->parseState->cache.items = NULL; }
+    
+    free(decoder->parseState); decoder->parseState = NULL;
+  }
+}
+
+- (void)dealloc
+{
+  _JSONDecoderCleanup(self);
+  [super dealloc];
+}
+
+- (void)clearCache
+{
+  if(JK_EXPECT_T(parseState != NULL)) {
+    if(JK_EXPECT_T(parseState->cache.items != NULL)) {
+      size_t idx = 0UL;
+      for(idx = 0UL; idx < parseState->cache.count; idx++) {
+        if(JK_EXPECT_T(parseState->cache.items[idx].object != NULL)) { CFRelease(parseState->cache.items[idx].object); parseState->cache.items[idx].object = NULL; }
+        if(JK_EXPECT_T(parseState->cache.items[idx].bytes  != NULL)) { free(parseState->cache.items[idx].bytes);       parseState->cache.items[idx].bytes  = NULL; }
+        memset(&parseState->cache.items[idx], 0, sizeof(JKTokenCacheItem));
+        parseState->cache.age[idx] = 0U;
+      }
+    }
+  }
+}
+
+// This needs to be completely rewritten.
+static id _JKParseUTF8String(JKParseState *parseState, BOOL mutableCollections, const unsigned char *string, size_t length, NSError **error) {
+  NSCParameterAssert((parseState != NULL) && (string != NULL) && (parseState->cache.prng_lfsr != 0U));
+  parseState->stringBuffer.bytes.ptr    = string;
+  parseState->stringBuffer.bytes.length = length;
+  parseState->atIndex                   = 0UL;
+  parseState->lineNumber                = 1UL;
+  parseState->lineStartIndex            = 0UL;
+  parseState->prev_atIndex              = 0UL;
+  parseState->prev_lineNumber           = 1UL;
+  parseState->prev_lineStartIndex       = 0UL;
+  parseState->error                     = NULL;
+  parseState->errorIsPrev               = 0;
+  parseState->mutableCollections        = (mutableCollections == NO) ? NO : YES;
+  
+  unsigned char stackTokenBuffer[JK_TOKENBUFFER_SIZE] JK_ALIGNED(64);
+  jk_managedBuffer_setToStackBuffer(&parseState->token.tokenBuffer, stackTokenBuffer, sizeof(stackTokenBuffer));
+  
+  void       *stackObjects [JK_STACK_OBJS] JK_ALIGNED(64);
+  void       *stackKeys    [JK_STACK_OBJS] JK_ALIGNED(64);
+  CFHashCode  stackCFHashes[JK_STACK_OBJS] JK_ALIGNED(64);
+  jk_objectStack_setToStackBuffer(&parseState->objectStack, stackObjects, stackKeys, stackCFHashes, JK_STACK_OBJS);
+  
+  id parsedJSON = json_parse_it(parseState);
+  
+  if((error != NULL) && (parseState->error != NULL)) { *error = parseState->error; }
+  
+  jk_managedBuffer_release(&parseState->token.tokenBuffer);
+  jk_objectStack_release(&parseState->objectStack);
+  
+  parseState->stringBuffer.bytes.ptr    = NULL;
+  parseState->stringBuffer.bytes.length = 0UL;
+  parseState->atIndex                   = 0UL;
+  parseState->lineNumber                = 1UL;
+  parseState->lineStartIndex            = 0UL;
+  parseState->prev_atIndex              = 0UL;
+  parseState->prev_lineNumber           = 1UL;
+  parseState->prev_lineStartIndex       = 0UL;
+  parseState->error                     = NULL;
+  parseState->errorIsPrev               = 0;
+  parseState->mutableCollections        = NO;
+  
+  return(parsedJSON);
+}
+
+////////////
+#pragma mark Deprecated as of v1.4
+////////////
+
+// Deprecated in JSONKit v1.4.  Use objectWithUTF8String:length: instead.
+- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length
+{
+  return([self objectWithUTF8String:string length:length error:NULL]);
+}
+
+// Deprecated in JSONKit v1.4.  Use objectWithUTF8String:length:error: instead.
+- (id)parseUTF8String:(const unsigned char *)string length:(size_t)length error:(NSError **)error
+{
+  return([self objectWithUTF8String:string length:length error:error]);
+}
+
+// Deprecated in JSONKit v1.4.  Use objectWithData: instead.
+- (id)parseJSONData:(NSData *)jsonData
+{
+  return([self objectWithData:jsonData error:NULL]);
+}
+
+// Deprecated in JSONKit v1.4.  Use objectWithData:error: instead.
+- (id)parseJSONData:(NSData *)jsonData error:(NSError **)error
+{
+  return([self objectWithData:jsonData error:error]);
+}
+
+////////////
+#pragma mark Methods that return immutable collection objects
+////////////
+
+- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length
+{
+  return([self objectWithUTF8String:string length:length error:NULL]);
+}
+
+- (id)objectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error
+{
+  if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."];          }
+  if(string     == NULL) { [NSException raise:NSInvalidArgumentException       format:@"The string argument is NULL."]; }
+  
+  return(_JKParseUTF8String(parseState, NO, string, (size_t)length, error));
+}
+
+- (id)objectWithData:(NSData *)jsonData
+{
+  return([self objectWithData:jsonData error:NULL]);
+}
+
+- (id)objectWithData:(NSData *)jsonData error:(NSError **)error
+{
+  if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; }
+  return([self objectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]);
+}
+
+////////////
+#pragma mark Methods that return mutable collection objects
+////////////
+
+- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length
+{
+  return([self mutableObjectWithUTF8String:string length:length error:NULL]);
+}
+
+- (id)mutableObjectWithUTF8String:(const unsigned char *)string length:(NSUInteger)length error:(NSError **)error
+{
+  if(parseState == NULL) { [NSException raise:NSInternalInconsistencyException format:@"parseState is NULL."];          }
+  if(string     == NULL) { [NSException raise:NSInvalidArgumentException       format:@"The string argument is NULL."]; }
+  
+  return(_JKParseUTF8String(parseState, YES, string, (size_t)length, error));
+}
+
+- (id)mutableObjectWithData:(NSData *)jsonData
+{
+  return([self mutableObjectWithData:jsonData error:NULL]);
+}
+
+- (id)mutableObjectWithData:(NSData *)jsonData error:(NSError **)error
+{
+  if(jsonData == NULL) { [NSException raise:NSInvalidArgumentException format:@"The jsonData argument is NULL."]; }
+  return([self mutableObjectWithUTF8String:(const unsigned char *)[jsonData bytes] length:[jsonData length] error:error]);
+}
+
+@end
+
+/*
+ The NSString and NSData convenience methods need a little bit of explanation.
+ 
+ Prior to JSONKit v1.4, the NSString -objectFromJSONStringWithParseOptions:error: method looked like
+ 
+ const unsigned char *utf8String = (const unsigned char *)[self UTF8String];
+ if(utf8String == NULL) { return(NULL); }
+ size_t               utf8Length = strlen((const char *)utf8String); 
+ return([[JSONDecoder decoderWithParseOptions:parseOptionFlags] parseUTF8String:utf8String length:utf8Length error:error]);
+ 
+ This changed with v1.4 to a more complicated method.  The reason for this is to keep the amount of memory that is
+ allocated, but not yet freed because it is dependent on the autorelease pool to pop before it can be reclaimed.
+ 
+ In the simpler v1.3 code, this included all the bytes used to store the -UTF8String along with the JSONDecoder and all its overhead.
+ 
+ Now we use an autoreleased CFMutableData that is sized to the UTF8 length of the NSString in question and is used to hold the UTF8
+ conversion of said string.
+ 
+ Once parsed, the CFMutableData has its length set to 0.  This should, hopefully, allow the CFMutableData to realloc and/or free
+ the buffer.
+ 
+ Another change made was a slight modification to JSONDecoder so that most of the cleanup work that was done in -dealloc was moved
+ to a private, internal function.  These convenience routines keep the pointer to the autoreleased JSONDecoder and calls
+ _JSONDecoderCleanup() to early release the decoders resources since we already know that particular decoder is not going to be used
+ again.  
+ 
+ If everything goes smoothly, this will most likely result in perhaps a few hundred bytes that are allocated but waiting for the
+ autorelease pool to pop.  This is compared to the thousands and easily hundreds of thousands of bytes that would have been in
+ autorelease limbo.  It's more complicated for us, but a win for the user.
+ 
+ Autorelease objects are used in case things don't go smoothly.  By having them autoreleased, we effectively guarantee that our
+ requirement to -release the object is always met, not matter what goes wrong.  The downside is having a an object or two in
+ autorelease limbo, but we've done our best to minimize that impact, so it all balances out.
+ */
+
+@implementation NSString (JSONKitDeserializing)
+
+static id _NSStringObjectFromJSONString(NSString *jsonString, JKParseOptionFlags parseOptionFlags, NSError **error, BOOL mutableCollection) {
+  id                returnObject = NULL;
+  CFMutableDataRef  mutableData  = NULL;
+  JSONDecoder      *decoder      = NULL;
+  
+  CFIndex    stringLength     = CFStringGetLength((CFStringRef)jsonString);
+  NSUInteger stringUTF8Length = [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+  
+  if((mutableData = (CFMutableDataRef)[(id)CFDataCreateMutable(NULL, (NSUInteger)stringUTF8Length) autorelease]) != NULL) {
+    UInt8   *utf8String = CFDataGetMutableBytePtr(mutableData);
+    CFIndex  usedBytes  = 0L, convertedCount = 0L;
+    
+    convertedCount = CFStringGetBytes((CFStringRef)jsonString, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, utf8String, (NSUInteger)stringUTF8Length, &usedBytes);
+    if(JK_EXPECT_F(convertedCount != stringLength) || JK_EXPECT_F(usedBytes < 0L)) { if(error != NULL) { *error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:[NSDictionary dictionaryWithObject:@"An error occurred converting the contents of a NSString to UTF8." forKey:NSLocalizedDescriptionKey]]; } goto exitNow; }
+    
+    if(mutableCollection == NO) { returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags])        objectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; }
+    else                        { returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithUTF8String:(const unsigned char *)utf8String length:(size_t)usedBytes error:error]; }
+  }
+  
+exitNow:
+  if(mutableData != NULL) { CFDataSetLength(mutableData, 0L); }
+  if(decoder     != NULL) { _JSONDecoderCleanup(decoder);     }
+  return(returnObject);
+}
+
+- (id)objectFromJSONString
+{
+  return([self objectFromJSONStringWithParseOptions:JKParseOptionStrict error:NULL]);
+}
+
+- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags
+{
+  return([self objectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]);
+}
+
+- (id)objectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
+{
+  return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, NO));
+}
+
+
+- (id)mutableObjectFromJSONString
+{
+  return([self mutableObjectFromJSONStringWithParseOptions:JKParseOptionStrict error:NULL]);
+}
+
+- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags
+{
+  return([self mutableObjectFromJSONStringWithParseOptions:parseOptionFlags error:NULL]);
+}
+
+- (id)mutableObjectFromJSONStringWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
+{
+  return(_NSStringObjectFromJSONString(self, parseOptionFlags, error, YES));
+}
+
+@end
+
+@implementation NSData (JSONKitDeserializing)
+
+- (id)objectFromJSONData
+{
+  return([self objectFromJSONDataWithParseOptions:JKParseOptionStrict error:NULL]);
+}
+
+- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags
+{
+  return([self objectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]);
+}
+
+- (id)objectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
+{
+  JSONDecoder *decoder = NULL;
+  id returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) objectWithData:self error:error];
+  if(decoder != NULL) { _JSONDecoderCleanup(decoder); }
+  return(returnObject);
+}
+
+- (id)mutableObjectFromJSONData
+{
+  return([self mutableObjectFromJSONDataWithParseOptions:JKParseOptionStrict error:NULL]);
+}
+
+- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags
+{
+  return([self mutableObjectFromJSONDataWithParseOptions:parseOptionFlags error:NULL]);
+}
+
+- (id)mutableObjectFromJSONDataWithParseOptions:(JKParseOptionFlags)parseOptionFlags error:(NSError **)error
+{
+  JSONDecoder *decoder = NULL;
+  id returnObject = [(decoder = [JSONDecoder decoderWithParseOptions:parseOptionFlags]) mutableObjectWithData:self error:error];
+  if(decoder != NULL) { _JSONDecoderCleanup(decoder); }
+  return(returnObject);
+}
+
+
+@end
+
+////////////
+#pragma mark -
+#pragma mark Encoding / deserializing functions
+
+static void jk_encode_error(JKEncodeState *encodeState, NSString *format, ...) {
+  NSCParameterAssert((encodeState != NULL) && (format != NULL));
+
+  va_list varArgsList;
+  va_start(varArgsList, format);
+  NSString *formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease];
+  va_end(varArgsList);
+
+  if(encodeState->error == NULL) {
+    encodeState->error = [NSError errorWithDomain:@"JKErrorDomain" code:-1L userInfo:
+                                   [NSDictionary dictionaryWithObjectsAndKeys:
+                                                                              formatString, NSLocalizedDescriptionKey,
+                                                                              NULL]];
+  }
+}
+
+JK_STATIC_INLINE void jk_encode_updateCache(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object) {
+  NSCParameterAssert(encodeState != NULL);
+  if(JK_EXPECT_T(cacheSlot != NULL)) {
+    NSCParameterAssert((object != NULL) && (startingAtIndex <= encodeState->atIndex));
+    cacheSlot->object = object;
+    cacheSlot->offset = startingAtIndex;
+    cacheSlot->length = (size_t)(encodeState->atIndex - startingAtIndex);  
+  }
+}
+
+static int jk_encode_printf(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, ...) {
+  va_list varArgsList, varArgsListCopy;
+  va_start(varArgsList, format);
+  va_copy(varArgsListCopy, varArgsList);
+
+  NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL));
+
+  ssize_t  formattedStringLength = 0L;
+  int      returnValue           = 0;
+
+  if(JK_EXPECT_T((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsList)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) {
+    NSCParameterAssert(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length));
+    if(JK_EXPECT_F(((encodeState->atIndex + (formattedStringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (formattedStringLength * 2UL)+ 4096UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); returnValue = 1; goto exitNow; }
+    if(JK_EXPECT_F((formattedStringLength = vsnprintf((char *)&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex], (encodeState->stringBuffer.bytes.length - encodeState->atIndex), format, varArgsListCopy)) >= (ssize_t)(encodeState->stringBuffer.bytes.length - encodeState->atIndex))) { jk_encode_error(encodeState, @"vsnprintf failed unexpectedly."); returnValue = 1; goto exitNow; }
+  }
+  
+exitNow:
+  va_end(varArgsList);
+  va_end(varArgsListCopy);
+  if(JK_EXPECT_T(returnValue == 0)) { encodeState->atIndex += formattedStringLength; jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object); }
+  return(returnValue);
+}
+
+static int jk_encode_write(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format) {
+  NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex) && (format != NULL));
+  if(JK_EXPECT_F(((encodeState->atIndex + strlen(format) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + strlen(format) + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+
+  size_t formatIdx = 0UL;
+  for(formatIdx = 0UL; format[formatIdx] != 0; formatIdx++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[formatIdx]; }
+  jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object);
+  return(0);
+}
+
+static int jk_encode_writePrettyPrintWhiteSpace(JKEncodeState *encodeState) {
+  NSCParameterAssert((encodeState != NULL) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) != 0UL));
+  if(JK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_T(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+  encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\n';
+  size_t depthWhiteSpace = 0UL;
+  for(depthWhiteSpace = 0UL; depthWhiteSpace < (encodeState->depth * 2UL); depthWhiteSpace++) { NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length); encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; }
+  return(0);
+}  
+
+static int jk_encode_write1slow(JKEncodeState *encodeState, ssize_t depthChange, const char *format) {
+  NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (format != NULL) && ((depthChange >= -1L) && (depthChange <= 1L)) && ((encodeState->depth == 0UL) ? (depthChange >= 0L) : 1) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) != 0UL));
+  if(JK_EXPECT_F((encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 16UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + ((encodeState->depth + 1UL) * 2UL) + 4096UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+  encodeState->depth += depthChange;
+  if(JK_EXPECT_T(format[0] == ':')) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = ' '; }
+  else {
+    if(JK_EXPECT_F(depthChange == -1L)) { if(JK_EXPECT_F(jk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } }
+    encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0];
+    if(JK_EXPECT_T(depthChange != -1L)) { if(JK_EXPECT_F(jk_encode_writePrettyPrintWhiteSpace(encodeState))) { return(1); } }
+  }
+  NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
+  return(0);
+}
+
+static int jk_encode_write1fast(JKEncodeState *encodeState, ssize_t depthChange JK_UNUSED_ARG, const char *format) {
+  NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && ((encodeState->serializeOptionFlags & JKSerializeOptionPretty) == 0UL));
+  if(JK_EXPECT_T((encodeState->atIndex + 4UL) < encodeState->stringBuffer.bytes.length)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = format[0]; }
+  else { return(jk_encode_write(encodeState, NULL, 0UL, NULL, format)); }
+  return(0);
+}
+
+static int jk_encode_writen(JKEncodeState *encodeState, JKEncodeCache *cacheSlot, size_t startingAtIndex, id object, const char *format, size_t length) {
+  NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (startingAtIndex <= encodeState->atIndex));
+  if(JK_EXPECT_F((encodeState->stringBuffer.bytes.length - encodeState->atIndex) < (length + 4UL))) { if(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + 4096UL + length) == NULL) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); } }
+  memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, format, length);
+  encodeState->atIndex += length;
+  jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, object);
+  return(0);
+}
+
+JK_STATIC_INLINE JKHash jk_encode_object_hash(void *objectPtr) {
+  return( ( (((JKHash)objectPtr) >> 21) ^ (((JKHash)objectPtr) >> 9)   ) + (((JKHash)objectPtr) >> 4) );
+}
+
+static int jk_encode_add_atom_to_buffer(JKEncodeState *encodeState, void *objectPtr) {
+  NSCParameterAssert((encodeState != NULL) && (encodeState->atIndex < encodeState->stringBuffer.bytes.length) && (objectPtr != NULL));
+
+  id     object          = (id)objectPtr, encodeCacheObject = object;
+  int    isClass         = JKClassUnknown;
+  size_t startingAtIndex = encodeState->atIndex;
+
+  JKHash         objectHash = jk_encode_object_hash(objectPtr);
+  JKEncodeCache *cacheSlot  = &encodeState->cache[objectHash % JK_ENCODE_CACHE_SLOTS];
+
+  if(JK_EXPECT_T(cacheSlot->object == object)) {
+    NSCParameterAssert((cacheSlot->object != NULL) &&
+                       (cacheSlot->offset < encodeState->atIndex)                   && ((cacheSlot->offset + cacheSlot->length) < encodeState->atIndex)                                    &&
+                       (cacheSlot->offset < encodeState->stringBuffer.bytes.length) && ((cacheSlot->offset + cacheSlot->length) < encodeState->stringBuffer.bytes.length)                  &&
+                       ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex)                     < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
+                       ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset)                        < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
+                       ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length)    < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)));
+    if(JK_EXPECT_F(((encodeState->atIndex + cacheSlot->length + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + cacheSlot->length + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+    NSCParameterAssert(((encodeState->atIndex + cacheSlot->length) < encodeState->stringBuffer.bytes.length) &&
+                       ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex)                     < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
+                       ((encodeState->stringBuffer.bytes.ptr + encodeState->atIndex + cacheSlot->length) < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
+                       ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset)                        < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
+                       ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length)    < (encodeState->stringBuffer.bytes.ptr + encodeState->stringBuffer.bytes.length)) &&
+                       ((encodeState->stringBuffer.bytes.ptr + cacheSlot->offset + cacheSlot->length)    < (encodeState->stringBuffer.bytes.ptr + encodeState->atIndex)));
+    memcpy(encodeState->stringBuffer.bytes.ptr + encodeState->atIndex, encodeState->stringBuffer.bytes.ptr + cacheSlot->offset, cacheSlot->length);
+    encodeState->atIndex += cacheSlot->length;
+    return(0);
+  }
+
+  // When we encounter a class that we do not handle, and we have either a delegate or block that the user supplied to format unsupported classes,
+  // we "re-run" the object check.  However, we re-run the object check exactly ONCE.  If the user supplies an object that isn't one of the
+  // supported classes, we fail the second time (i.e., double fault error).
+  BOOL rerunningAfterClassFormatter = NO;
+ rerunAfterClassFormatter:;
+
+  // XXX XXX XXX XXX
+  //     
+  //     We need to work around a bug in 10.7, which breaks ABI compatibility with Objective-C going back not just to 10.0, but OpenStep and even NextStep.
+  //     
+  //     It has long been documented that "the very first thing that a pointer to an Objective-C object "points to" is a pointer to that objects class".
+  //     
+  //     This is euphemistically called "tagged pointers".  There are a number of highly technical problems with this, most involving long passages from
+  //     the C standard(s).  In short, one can make a strong case, couched from the perspective of the C standard(s), that that 10.7 "tagged pointers" are
+  //     fundamentally Wrong and Broken, and should have never been implemented.  Assuming those points are glossed over, because the change is very clearly
+  //     breaking ABI compatibility, this should have resulted in a minimum of a "minimum version required" bump in various shared libraries to prevent
+  //     causes code that used to work just fine to suddenly break without warning.
+  //
+  //     In fact, the C standard says that the hack below is "undefined behavior"- there is no requirement that the 10.7 tagged pointer hack of setting the
+  //     "lower, unused bits" must be preserved when casting the result to an integer type, but this "works" because for most architectures
+  //     `sizeof(long) == sizeof(void *)` and the compiler uses the same representation for both.  (note: this is informal, not meant to be
+  //     normative or pedantically correct).
+  //     
+  //     In other words, while this "works" for now, technically the compiler is not obligated to do "what we want", and a later version of the compiler
+  //     is not required in any way to produce the same results or behavior that earlier versions of the compiler did for the statement below.
+  //
+  //     Fan-fucking-tastic.
+  //     
+  //     Why not just use `object_getClass()`?  Because `object->isa` reduces to (typically) a *single* instruction.  Calling `object_getClass()` requires
+  //     that the compiler potentially spill registers, establish a function call frame / environment, and finally execute a "jump subroutine" instruction.
+  //     Then, the called subroutine must spend half a dozen instructions in its prolog, however many instructions doing whatever it does, then half a dozen
+  //     instructions in its prolog.  One instruction compared to dozens, maybe a hundred instructions.
+  //     
+  //     Yes, that's one to two orders of magnitude difference.  Which is compelling in its own right.  When going for performance, you're often happy with
+  //     gains in the two to three percent range.
+  //     
+  // XXX XXX XXX XXX
+
+
+  BOOL   workAroundMacOSXABIBreakingBug = (JK_EXPECT_F(((NSUInteger)object) & 0x1))     ? YES  : NO;
+  void  *objectISA                      = (JK_EXPECT_F(workAroundMacOSXABIBreakingBug)) ? NULL : *((void **)objectPtr);
+  if(JK_EXPECT_F(workAroundMacOSXABIBreakingBug)) { goto slowClassLookup; }
+
+       if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.stringClass))     { isClass = JKClassString;     }
+  else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.numberClass))     { isClass = JKClassNumber;     }
+  else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.dictionaryClass)) { isClass = JKClassDictionary; }
+  else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.arrayClass))      { isClass = JKClassArray;      }
+  else if(JK_EXPECT_T(objectISA == encodeState->fastClassLookup.nullClass))       { isClass = JKClassNull;       }
+  else {
+  slowClassLookup:
+         if(JK_EXPECT_T([object isKindOfClass:[NSString     class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.stringClass     = objectISA; } isClass = JKClassString;     }
+    else if(JK_EXPECT_T([object isKindOfClass:[NSNumber     class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.numberClass     = objectISA; } isClass = JKClassNumber;     }
+    else if(JK_EXPECT_T([object isKindOfClass:[NSDictionary class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.dictionaryClass = objectISA; } isClass = JKClassDictionary; }
+    else if(JK_EXPECT_T([object isKindOfClass:[NSArray      class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.arrayClass      = objectISA; } isClass = JKClassArray;      }
+    else if(JK_EXPECT_T([object isKindOfClass:[NSNull       class]])) { if(workAroundMacOSXABIBreakingBug == NO) { encodeState->fastClassLookup.nullClass       = objectISA; } isClass = JKClassNull;       }
+    else {
+      if((rerunningAfterClassFormatter == NO) && (
+#ifdef __BLOCKS__
+           ((encodeState->classFormatterBlock) && ((object = encodeState->classFormatterBlock(object))                                                                         != NULL)) ||
+#endif
+           ((encodeState->classFormatterIMP)   && ((object = encodeState->classFormatterIMP(encodeState->classFormatterDelegate, encodeState->classFormatterSelector, object)) != NULL))    )) { rerunningAfterClassFormatter = YES; goto rerunAfterClassFormatter; }
+      
+      if(rerunningAfterClassFormatter == NO) { jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([encodeCacheObject class])); return(1); }
+      else { jk_encode_error(encodeState, @"Unable to serialize object class %@ that was returned by the unsupported class formatter.  Original object class was %@.", (object == NULL) ? @"NULL" : NSStringFromClass([object class]), NSStringFromClass([encodeCacheObject class])); return(1); }
+    }
+  }
+
+  // This is here for the benefit of the optimizer.  It allows the optimizer to do loop invariant code motion for the JKClassArray
+  // and JKClassDictionary cases when printing simple, single characters via jk_encode_write(), which is actually a macro:
+  // #define jk_encode_write1(es, dc, f) (_jk_encode_prettyPrint ? jk_encode_write1slow(es, dc, f) : jk_encode_write1fast(es, dc, f))
+  int _jk_encode_prettyPrint = JK_EXPECT_T((encodeState->serializeOptionFlags & JKSerializeOptionPretty) == 0) ? 0 : 1;
+  
+  switch(isClass) {
+    case JKClassString:
+      {
+        {
+          const unsigned char *cStringPtr = (const unsigned char *)CFStringGetCStringPtr((CFStringRef)object, kCFStringEncodingMacRoman);
+          if(cStringPtr != NULL) {
+            const unsigned char *utf8String = cStringPtr;
+            size_t               utf8Idx    = 0UL;
+
+            CFIndex stringLength = CFStringGetLength((CFStringRef)object);
+            if(JK_EXPECT_F(((encodeState->atIndex + (stringLength * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length)) && JK_EXPECT_F((jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (stringLength * 2UL) + 1024UL) == NULL))) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+
+            if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
+            for(utf8Idx = 0UL; utf8String[utf8Idx] != 0U; utf8Idx++) {
+              NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length);
+              NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
+              if(JK_EXPECT_F(utf8String[utf8Idx] >= 0x80U)) { encodeState->atIndex = startingAtIndex; goto slowUTF8Path; }
+              if(JK_EXPECT_F(utf8String[utf8Idx] <  0x20U)) {
+                switch(utf8String[utf8Idx]) {
+                  case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break;
+                  case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break;
+                  case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break;
+                  case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break;
+                  case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break;
+                  default: if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break;
+                }
+              } else {
+                if(JK_EXPECT_F(utf8String[utf8Idx] == '\"') || JK_EXPECT_F(utf8String[utf8Idx] == '\\') || (JK_EXPECT_F(encodeState->serializeOptionFlags & JKSerializeOptionEscapeForwardSlashes) && JK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; }
+                encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx];
+              }
+            }
+            NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length);
+            if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
+            jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject);
+            return(0);
+          }
+        }
+
+      slowUTF8Path:
+        {
+          CFIndex stringLength        = CFStringGetLength((CFStringRef)object);
+          CFIndex maxStringUTF8Length = CFStringGetMaximumSizeForEncoding(stringLength, kCFStringEncodingUTF8) + 32L;
+        
+          if(JK_EXPECT_F((size_t)maxStringUTF8Length > encodeState->utf8ConversionBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->utf8ConversionBuffer, maxStringUTF8Length + 1024UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+        
+          CFIndex usedBytes = 0L, convertedCount = 0L;
+          convertedCount = CFStringGetBytes((CFStringRef)object, CFRangeMake(0L, stringLength), kCFStringEncodingUTF8, '?', NO, encodeState->utf8ConversionBuffer.bytes.ptr, encodeState->utf8ConversionBuffer.bytes.length - 16L, &usedBytes);
+          if(JK_EXPECT_F(convertedCount != stringLength) || JK_EXPECT_F(usedBytes < 0L)) { jk_encode_error(encodeState, @"An error occurred converting the contents of a NSString to UTF8."); return(1); }
+        
+          if(JK_EXPECT_F((encodeState->atIndex + (maxStringUTF8Length * 2UL) + 256UL) > encodeState->stringBuffer.bytes.length) && JK_EXPECT_F(jk_managedBuffer_resize(&encodeState->stringBuffer, encodeState->atIndex + (maxStringUTF8Length * 2UL) + 1024UL) == NULL)) { jk_encode_error(encodeState, @"Unable to resize temporary buffer."); return(1); }
+        
+          const unsigned char *utf8String = encodeState->utf8ConversionBuffer.bytes.ptr;
+        
+          size_t utf8Idx = 0UL;
+          if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
+          for(utf8Idx = 0UL; utf8Idx < (size_t)usedBytes; utf8Idx++) {
+            NSCParameterAssert(((&encodeState->stringBuffer.bytes.ptr[encodeState->atIndex]) - encodeState->stringBuffer.bytes.ptr) < (ssize_t)encodeState->stringBuffer.bytes.length);
+            NSCParameterAssert(encodeState->atIndex < encodeState->stringBuffer.bytes.length);
+            NSCParameterAssert((CFIndex)utf8Idx < usedBytes);
+            if(JK_EXPECT_F(utf8String[utf8Idx] < 0x20U)) {
+              switch(utf8String[utf8Idx]) {
+                case '\b': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'b'; break;
+                case '\f': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'f'; break;
+                case '\n': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'n'; break;
+                case '\r': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 'r'; break;
+                case '\t': encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = 't'; break;
+                default: if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", utf8String[utf8Idx]))) { return(1); } break;
+              }
+            } else {
+              if(JK_EXPECT_F(utf8String[utf8Idx] >= 0x80U) && (encodeState->serializeOptionFlags & JKSerializeOptionEscapeUnicode)) {
+                const unsigned char *nextValidCharacter = NULL;
+                UTF32                u32ch              = 0U;
+                ConversionResult     result;
+
+                if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(&utf8String[utf8Idx], &utf8String[usedBytes], (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { jk_encode_error(encodeState, @"Error converting UTF8."); return(1); }
+                else {
+                  utf8Idx = (nextValidCharacter - utf8String) - 1UL;
+                  if(JK_EXPECT_T(u32ch <= 0xffffU)) { if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x", u32ch)))                                                           { return(1); } }
+                  else                              { if(JK_EXPECT_F(jk_encode_printf(encodeState, NULL, 0UL, NULL, "\\u%4.4x\\u%4.4x", (0xd7c0U + (u32ch >> 10)), (0xdc00U + (u32ch & 0x3ffU))))) { return(1); } }
+                }
+              } else {
+                if(JK_EXPECT_F(utf8String[utf8Idx] == '\"') || JK_EXPECT_F(utf8String[utf8Idx] == '\\') || (JK_EXPECT_F(encodeState->serializeOptionFlags & JKSerializeOptionEscapeForwardSlashes) && JK_EXPECT_F(utf8String[utf8Idx] == '/'))) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\\'; }
+                encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = utf8String[utf8Idx];
+              }
+            }
+          }
+          NSCParameterAssert((encodeState->atIndex + 1UL) < encodeState->stringBuffer.bytes.length);
+          if(JK_EXPECT_T((encodeState->encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) { encodeState->stringBuffer.bytes.ptr[encodeState->atIndex++] = '\"'; }
+          jk_encode_updateCache(encodeState, cacheSlot, startingAtIndex, encodeCacheObject);
+          return(0);
+        }
+      }
+      break;
+
+    case JKClassNumber:
+      {
+             if(object == (id)kCFBooleanTrue)  { return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "true",  4UL)); }
+        else if(object == (id)kCFBooleanFalse) { return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "false", 5UL)); }
+        
+        const char         *objCType = [object objCType];
+        char                anum[256], *aptr = &anum[255];
+        int                 isNegative = 0;
+        unsigned long long  ullv;
+        long long           llv;
+        
+        if(JK_EXPECT_F(objCType == NULL) || JK_EXPECT_F(objCType[0] == 0) || JK_EXPECT_F(objCType[1] != 0)) { jk_encode_error(encodeState, @"NSNumber conversion error, unknown type.  Type: '%s'", (objCType == NULL) ? "<NULL>" : objCType); return(1); }
+        
+        switch(objCType[0]) {
+          case 'c': case 'i': case 's': case 'l': case 'q':
+            if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &llv)))  {
+              if(llv < 0LL)  { ullv = -llv; isNegative = 1; } else { ullv = llv; isNegative = 0; }
+              goto convertNumber;
+            } else { jk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); }
+            break;
+          case 'C': case 'I': case 'S': case 'L': case 'Q': case 'B':
+            if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberLongLongType, &ullv))) {
+            convertNumber:
+              if(JK_EXPECT_F(ullv < 10ULL)) { *--aptr = ullv + '0'; } else { while(JK_EXPECT_T(ullv > 0ULL)) { *--aptr = (ullv % 10ULL) + '0'; ullv /= 10ULL; NSCParameterAssert(aptr > anum); } }
+              if(isNegative) { *--aptr = '-'; }
+              NSCParameterAssert(aptr > anum);
+              return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, aptr, &anum[255] - aptr));
+            } else { jk_encode_error(encodeState, @"Unable to get scalar value from number object."); return(1); }
+            break;
+          case 'f': case 'd':
+            {
+              double dv;
+              if(JK_EXPECT_T(CFNumberGetValue((CFNumberRef)object, kCFNumberDoubleType, &dv))) {
+                if(JK_EXPECT_F(!isfinite(dv))) { jk_encode_error(encodeState, @"Floating point values must be finite.  JSON does not support NaN or Infinity."); return(1); }
+                return(jk_encode_printf(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "%.17g", dv));
+              } else { jk_encode_error(encodeState, @"Unable to get floating point value from number object."); return(1); }
+            }
+            break;
+          default: jk_encode_error(encodeState, @"NSNumber conversion error, unknown type.  Type: '%c' / 0x%2.2x", objCType[0], objCType[0]); return(1); break;
+        }
+      }
+      break;
+    
+    case JKClassArray:
+      {
+        int     printComma = 0;
+        CFIndex arrayCount = CFArrayGetCount((CFArrayRef)object), idx = 0L;
+        if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "["))) { return(1); }
+        if(JK_EXPECT_F(arrayCount > 1020L)) {
+          for(id arrayObject in object)          { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, arrayObject)))  { return(1); } }
+        } else {
+          void *objects[1024];
+          CFArrayGetValues((CFArrayRef)object, CFRangeMake(0L, arrayCount), (const void **)objects);
+          for(idx = 0L; idx < arrayCount; idx++) { if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } } printComma = 1; if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); } }
+        }
+        return(jk_encode_write1(encodeState, -1L, "]"));
+      }
+      break;
+
+    case JKClassDictionary:
+      {
+        int     printComma      = 0;
+        CFIndex dictionaryCount = CFDictionaryGetCount((CFDictionaryRef)object), idx = 0L;
+        id      enumerateObject = JK_EXPECT_F(_jk_encode_prettyPrint) ? [[object allKeys] sortedArrayUsingSelector:@selector(compare:)] : object;
+
+        if(JK_EXPECT_F(jk_encode_write1(encodeState, 1L, "{"))) { return(1); }
+        if(JK_EXPECT_F(_jk_encode_prettyPrint) || JK_EXPECT_F(dictionaryCount > 1020L)) {
+          for(id keyObject in enumerateObject) {
+            if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } }
+            printComma = 1;
+            void *keyObjectISA = *((void **)keyObject);
+            if(JK_EXPECT_F((keyObjectISA != encodeState->fastClassLookup.stringClass)) && JK_EXPECT_F(([keyObject isKindOfClass:[NSString class]] == NO))) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); }
+            if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keyObject)))                                                        { return(1); }
+            if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":")))                                                                      { return(1); }
+            if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, (void *)CFDictionaryGetValue((CFDictionaryRef)object, keyObject)))) { return(1); }
+          }
+        } else {
+          void *keys[1024], *objects[1024];
+          CFDictionaryGetKeysAndValues((CFDictionaryRef)object, (const void **)keys, (const void **)objects);
+          for(idx = 0L; idx < dictionaryCount; idx++) {
+            if(JK_EXPECT_T(printComma)) { if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ","))) { return(1); } }
+            printComma = 1;
+            void *keyObjectISA = *((void **)keys[idx]);
+            if(JK_EXPECT_F(keyObjectISA != encodeState->fastClassLookup.stringClass) && JK_EXPECT_F([(id)keys[idx] isKindOfClass:[NSString class]] == NO)) { jk_encode_error(encodeState, @"Key must be a string object."); return(1); }
+            if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, keys[idx])))    { return(1); }
+            if(JK_EXPECT_F(jk_encode_write1(encodeState, 0L, ":")))                  { return(1); }
+            if(JK_EXPECT_F(jk_encode_add_atom_to_buffer(encodeState, objects[idx]))) { return(1); }
+          }
+        }
+        return(jk_encode_write1(encodeState, -1L, "}"));
+      }
+      break;
+
+    case JKClassNull: return(jk_encode_writen(encodeState, cacheSlot, startingAtIndex, encodeCacheObject, "null", 4UL)); break;
+
+    default: jk_encode_error(encodeState, @"Unable to serialize object class %@.", NSStringFromClass([object class])); return(1); break;
+  }
+
+  return(0);
+}
+
+
+@implementation JKSerializer
+
++ (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error
+{
+  return([[[[self alloc] init] autorelease] serializeObject:object options:optionFlags encodeOption:encodeOption block:block delegate:delegate selector:selector error:error]);
+}
+
+- (id)serializeObject:(id)object options:(JKSerializeOptionFlags)optionFlags encodeOption:(JKEncodeOptionType)encodeOption block:(JKSERIALIZER_BLOCKS_PROTO)block delegate:(id)delegate selector:(SEL)selector error:(NSError **)error
+{
+#ifndef __BLOCKS__
+#pragma unused(block)
+#endif
+  NSParameterAssert((object != NULL) && (encodeState == NULL) && ((delegate != NULL) ? (block == NULL) : 1) && ((block != NULL) ? (delegate == NULL) : 1) &&
+                    (((encodeOption & JKEncodeOptionCollectionObj) != 0UL) ? (((encodeOption & JKEncodeOptionStringObj)     == 0UL) && ((encodeOption & JKEncodeOptionStringObjTrimQuotes) == 0UL)) : 1) &&
+                    (((encodeOption & JKEncodeOptionStringObj)     != 0UL) ?  ((encodeOption & JKEncodeOptionCollectionObj) == 0UL)                                                                 : 1));
+
+  id returnObject = NULL;
+
+  if(encodeState != NULL) { [self releaseState]; }
+  if((encodeState = (struct JKEncodeState *)calloc(1UL, sizeof(JKEncodeState))) == NULL) { [NSException raise:NSMallocException format:@"Unable to allocate state structure."]; return(NULL); }
+
+  if((error != NULL) && (*error != NULL)) { *error = NULL; }
+
+  if(delegate != NULL) {
+    if(selector                               == NULL) { [NSException raise:NSInvalidArgumentException format:@"The delegate argument is not NULL, but the selector argument is NULL."]; }
+    if([delegate respondsToSelector:selector] == NO)   { [NSException raise:NSInvalidArgumentException format:@"The serializeUnsupportedClassesUsingDelegate: delegate does not respond to the selector argument."]; }
+    encodeState->classFormatterDelegate = delegate;
+    encodeState->classFormatterSelector = selector;
+    encodeState->classFormatterIMP      = (JKClassFormatterIMP)[delegate methodForSelector:selector];
+    NSCParameterAssert(encodeState->classFormatterIMP != NULL);
+  }
+
+#ifdef __BLOCKS__
+  encodeState->classFormatterBlock                          = block;
+#endif
+  encodeState->serializeOptionFlags                         = optionFlags;
+  encodeState->encodeOption                                 = encodeOption;
+  encodeState->stringBuffer.roundSizeUpToMultipleOf         = (1024UL * 32UL);
+  encodeState->utf8ConversionBuffer.roundSizeUpToMultipleOf = 4096UL;
+    
+  unsigned char stackJSONBuffer[JK_JSONBUFFER_SIZE] JK_ALIGNED(64);
+  jk_managedBuffer_setToStackBuffer(&encodeState->stringBuffer,         stackJSONBuffer, sizeof(stackJSONBuffer));
+
+  unsigned char stackUTF8Buffer[JK_UTF8BUFFER_SIZE] JK_ALIGNED(64);
+  jk_managedBuffer_setToStackBuffer(&encodeState->utf8ConversionBuffer, stackUTF8Buffer, sizeof(stackUTF8Buffer));
+
+  if(((encodeOption & JKEncodeOptionCollectionObj) != 0UL) && (([object isKindOfClass:[NSArray  class]] == NO) && ([object isKindOfClass:[NSDictionary class]] == NO))) { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSArray or NSDictionary.", NSStringFromClass([object class])); goto errorExit; }
+  if(((encodeOption & JKEncodeOptionStringObj)     != 0UL) &&  ([object isKindOfClass:[NSString class]] == NO))                                                         { jk_encode_error(encodeState, @"Unable to serialize object class %@, expected a NSString.", NSStringFromClass([object class])); goto errorExit; }
+
+  if(jk_encode_add_atom_to_buffer(encodeState, object) == 0) {
+    BOOL stackBuffer = ((encodeState->stringBuffer.flags & JKManagedBufferMustFree) == 0UL) ? YES : NO;
+    
+    if((encodeState->atIndex < 2UL))
+    if((stackBuffer == NO) && ((encodeState->stringBuffer.bytes.ptr = (unsigned char *)reallocf(encodeState->stringBuffer.bytes.ptr, encodeState->atIndex + 16UL)) == NULL)) { jk_encode_error(encodeState, @"Unable to realloc buffer"); goto errorExit; }
+
+    switch((encodeOption & JKEncodeOptionAsTypeMask)) {
+      case JKEncodeOptionAsData:
+        if(stackBuffer == YES) { if((returnObject = [(id)CFDataCreate(                 NULL,                encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex)                                  autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSData object"); } }
+        else                   { if((returnObject = [(id)CFDataCreateWithBytesNoCopy(  NULL,                encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, NULL)                            autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSData object"); } }
+        break;
+
+      case JKEncodeOptionAsString:
+        if(stackBuffer == YES) { if((returnObject = [(id)CFStringCreateWithBytes(      NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO)       autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSString object"); } }
+        else                   { if((returnObject = [(id)CFStringCreateWithBytesNoCopy(NULL, (const UInt8 *)encodeState->stringBuffer.bytes.ptr, (CFIndex)encodeState->atIndex, kCFStringEncodingUTF8, NO, NULL) autorelease]) == NULL) { jk_encode_error(encodeState, @"Unable to create NSString object"); } }
+        break;
+
+      default: jk_encode_error(encodeState, @"Unknown encode as type."); break;
+    }
+
+    if((returnObject != NULL) && (stackBuffer == NO)) { encodeState->stringBuffer.flags &= ~JKManagedBufferMustFree; encodeState->stringBuffer.bytes.ptr = NULL; encodeState->stringBuffer.bytes.length = 0UL; }
+  }
+
+errorExit:
+  if((encodeState != NULL) && (error != NULL) && (encodeState->error != NULL)) { *error = encodeState->error; encodeState->error = NULL; }
+  [self releaseState];
+
+  return(returnObject);
+}
+
+- (void)releaseState
+{
+  if(encodeState != NULL) {
+    jk_managedBuffer_release(&encodeState->stringBuffer);
+    jk_managedBuffer_release(&encodeState->utf8ConversionBuffer);
+    free(encodeState); encodeState = NULL;
+  }  
+}
+
+- (void)dealloc
+{
+  [self releaseState];
+  [super dealloc];
+}
+
+@end
+
+@implementation NSString (JSONKitSerializing)
+
+////////////
+#pragma mark Methods for serializing a single NSString.
+////////////
+
+// Useful for those who need to serialize just a NSString.  Otherwise you would have to do something like [NSArray arrayWithObject:stringToBeJSONSerialized], serializing the array, and then chopping of the extra ^\[.*\]$ square brackets.
+
+// NSData returning methods...
+
+- (NSData *)JSONData
+{
+  return([self JSONDataWithOptions:JKSerializeOptionNone includeQuotes:YES error:NULL]);
+}
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | ((includeQuotes == NO) ? JKEncodeOptionStringObjTrimQuotes : 0UL) | JKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]);
+}
+
+// NSString returning methods...
+
+- (NSString *)JSONString
+{
+  return([self JSONStringWithOptions:JKSerializeOptionNone includeQuotes:YES error:NULL]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions includeQuotes:(BOOL)includeQuotes error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | ((includeQuotes == NO) ? JKEncodeOptionStringObjTrimQuotes : 0UL) | JKEncodeOptionStringObj) block:NULL delegate:NULL selector:NULL error:error]);
+}
+
+@end
+
+@implementation NSArray (JSONKitSerializing)
+
+// NSData returning methods...
+
+- (NSData *)JSONData
+{
+  return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
+}
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
+}
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
+}
+
+// NSString returning methods...
+
+- (NSString *)JSONString
+{
+  return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
+}
+
+@end
+
+@implementation NSDictionary (JSONKitSerializing)
+
+// NSData returning methods...
+
+- (NSData *)JSONData
+{
+  return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
+}
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
+}
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
+}
+
+// NSString returning methods...
+
+- (NSString *)JSONString
+{
+  return([JKSerializer serializeObject:self options:JKSerializeOptionNone encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:NULL]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:NULL selector:NULL error:error]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingDelegate:(id)delegate selector:(SEL)selector error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:NULL delegate:delegate selector:selector error:error]);
+}
+
+@end
+
+
+#ifdef __BLOCKS__
+
+@implementation NSArray (JSONKitSerializingBlockAdditions)
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
+}
+
+@end
+
+@implementation NSDictionary (JSONKitSerializingBlockAdditions)
+
+- (NSData *)JSONDataWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsData | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
+}
+
+- (NSString *)JSONStringWithOptions:(JKSerializeOptionFlags)serializeOptions serializeUnsupportedClassesUsingBlock:(id(^)(id object))block error:(NSError **)error
+{
+  return([JKSerializer serializeObject:self options:serializeOptions encodeOption:(JKEncodeOptionAsString | JKEncodeOptionCollectionObj) block:block delegate:NULL selector:NULL error:error]);
+}
+
+@end
+
+#endif // __BLOCKS__
+

+ 339 - 0
shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/project.pbxproj

@@ -0,0 +1,339 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */; };
+		0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BFC9ACB173C57E400CDD329 /* Security.framework */; };
+		0BFAF21C16F8E6C10017EA7E /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFAF21B16F8E6C10017EA7E /* JSONKit.m */; };
+		692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */; };
+		692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */; };
+		692C18AC1666392700BF6A53 /* MenuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 692C18AB1666392700BF6A53 /* MenuManager.m */; };
+		6993878616494C000044E4DF /* RequestManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6993878516494C000044E4DF /* RequestManager.m */; };
+		69948B361636D50E0093B6CE /* ContentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 69948B351636D50E0093B6CE /* ContentManager.m */; };
+		8C37DD9F161593BD00016A95 /* FinderHook.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */; };
+		8C37DDB2161593FF00016A95 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C37DDB1161593FF00016A95 /* Cocoa.framework */; };
+		8C37DDBA161594B400016A95 /* Quartz.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C37DDB9161594B400016A95 /* Quartz.framework */; };
+		8C99F6941622D145002D2135 /* IconCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C99F6931622D145002D2135 /* IconCache.m */; };
+		8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; };
+		8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
+		0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
+		0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
+		0B2BF60B176A43DB001246CD /* Finder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Finder.h; sourceTree = "<group>"; };
+		0BFAF21A16F8E6C10017EA7E /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = "<group>"; };
+		0BFAF21B16F8E6C10017EA7E /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = "<group>"; };
+		0BFC9ACB173C57E400CDD329 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+		692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContextMenuHandlers.h; sourceTree = "<group>"; };
+		692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContextMenuHandlers.m; sourceTree = "<group>"; };
+		692C18A7166617F500BF6A53 /* IconOverlayHandlers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconOverlayHandlers.h; sourceTree = "<group>"; };
+		692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconOverlayHandlers.m; sourceTree = "<group>"; };
+		692C18AA1666392700BF6A53 /* MenuManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuManager.h; sourceTree = "<group>"; };
+		692C18AB1666392700BF6A53 /* MenuManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuManager.m; sourceTree = "<group>"; };
+		6993878416494C000044E4DF /* RequestManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RequestManager.h; sourceTree = "<group>"; };
+		6993878516494C000044E4DF /* RequestManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RequestManager.m; sourceTree = "<group>"; usesTabs = 1; };
+		69948B341636D50E0093B6CE /* ContentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentManager.h; sourceTree = "<group>"; };
+		69948B351636D50E0093B6CE /* ContentManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContentManager.m; sourceTree = "<group>"; };
+		8C37DD99161593BD00016A95 /* FinderHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FinderHook.h; sourceTree = "<group>"; };
+		8C37DD9A161593BD00016A95 /* FinderHook.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FinderHook.m; sourceTree = "<group>"; };
+		8C37DDB1161593FF00016A95 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+		8C37DDB9161594B400016A95 /* Quartz.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quartz.framework; path = System/Library/Frameworks/Quartz.framework; sourceTree = SDKROOT; };
+		8C99F6921622D145002D2135 /* IconCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IconCache.h; sourceTree = "<group>"; };
+		8C99F6931622D145002D2135 /* IconCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IconCache.m; sourceTree = "<group>"; };
+		8D576316048677EA00EA77CD /* LiferayNativityFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LiferayNativityFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
+		8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		8D576313048677EA00EA77CD /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				0B13ECAF173C687900548DA1 /* Security.framework in Frameworks */,
+				8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */,
+				8C37DDB2161593FF00016A95 /* Cocoa.framework in Frameworks */,
+				8C37DDBA161594B400016A95 /* Quartz.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		089C166AFE841209C02AAC07 /* LiferayNativityFinder */ = {
+			isa = PBXGroup;
+			children = (
+				08FB77AFFE84173DC02AAC07 /* Source */,
+				089C167CFE841241C02AAC07 /* Resources */,
+				089C1671FE841209C02AAC07 /* External Frameworks and Libraries */,
+				19C28FB6FE9D52B211CA2CBB /* Products */,
+			);
+			name = LiferayNativityFinder;
+			sourceTree = "<group>";
+			usesTabs = 1;
+		};
+		089C1671FE841209C02AAC07 /* External Frameworks and Libraries */ = {
+			isa = PBXGroup;
+			children = (
+				0BFC9ACB173C57E400CDD329 /* Security.framework */,
+				8C37DDB9161594B400016A95 /* Quartz.framework */,
+				0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */,
+				8C37DDB1161593FF00016A95 /* Cocoa.framework */,
+			);
+			name = "External Frameworks and Libraries";
+			sourceTree = "<group>";
+		};
+		089C167CFE841241C02AAC07 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				8D576317048677EA00EA77CD /* Info.plist */,
+				8D5B49A704867FD3000E48DA /* InfoPlist.strings */,
+			);
+			name = Resources;
+			sourceTree = "<group>";
+		};
+		08FB77AFFE84173DC02AAC07 /* Source */ = {
+			isa = PBXGroup;
+			children = (
+				0B2BF60A176A43DB001246CD /* Finder */,
+				0B08BAC21759627700C8351E /* GCDAsyncSocket */,
+				0BFAF21916F8E6910017EA7E /* JSONKit */,
+				8C37DD99161593BD00016A95 /* FinderHook.h */,
+				8C37DD9A161593BD00016A95 /* FinderHook.m */,
+				692C18A316660C4600BF6A53 /* ContextMenuHandlers.h */,
+				692C18A416660C4600BF6A53 /* ContextMenuHandlers.m */,
+				692C18A7166617F500BF6A53 /* IconOverlayHandlers.h */,
+				692C18A8166617F500BF6A53 /* IconOverlayHandlers.m */,
+				6993878416494C000044E4DF /* RequestManager.h */,
+				6993878516494C000044E4DF /* RequestManager.m */,
+				69948B341636D50E0093B6CE /* ContentManager.h */,
+				69948B351636D50E0093B6CE /* ContentManager.m */,
+				8C99F6921622D145002D2135 /* IconCache.h */,
+				8C99F6931622D145002D2135 /* IconCache.m */,
+				692C18AA1666392700BF6A53 /* MenuManager.h */,
+				692C18AB1666392700BF6A53 /* MenuManager.m */,
+			);
+			name = Source;
+			sourceTree = "<group>";
+		};
+		0B08BAC21759627700C8351E /* GCDAsyncSocket */ = {
+			isa = PBXGroup;
+			children = (
+				0B13ECAC173C686900548DA1 /* GCDAsyncSocket.h */,
+				0B13ECAD173C686A00548DA1 /* GCDAsyncSocket.m */,
+			);
+			name = GCDAsyncSocket;
+			sourceTree = "<group>";
+		};
+		0B2BF60A176A43DB001246CD /* Finder */ = {
+			isa = PBXGroup;
+			children = (
+				0B2BF60B176A43DB001246CD /* Finder.h */,
+			);
+			path = Finder;
+			sourceTree = "<group>";
+		};
+		0BFAF21916F8E6910017EA7E /* JSONKit */ = {
+			isa = PBXGroup;
+			children = (
+				0BFAF21A16F8E6C10017EA7E /* JSONKit.h */,
+				0BFAF21B16F8E6C10017EA7E /* JSONKit.m */,
+			);
+			name = JSONKit;
+			sourceTree = "<group>";
+		};
+		19C28FB6FE9D52B211CA2CBB /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				8D576316048677EA00EA77CD /* LiferayNativityFinder.bundle */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		8D57630D048677EA00EA77CD /* LiferayNativityFinder */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "LiferayNativityFinder" */;
+			buildPhases = (
+				8D57630F048677EA00EA77CD /* Resources */,
+				8D576311048677EA00EA77CD /* Sources */,
+				8D576313048677EA00EA77CD /* Frameworks */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = LiferayNativityFinder;
+			productInstallPath = "$(HOME)/Library/Bundles";
+			productName = LiferayNativityFinder;
+			productReference = 8D576316048677EA00EA77CD /* LiferayNativityFinder.bundle */;
+			productType = "com.apple.product-type.bundle";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		089C1669FE841209C02AAC07 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 0460;
+			};
+			buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "LiferayNativityFinder" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 1;
+			knownRegions = (
+				English,
+				Japanese,
+				French,
+				German,
+			);
+			mainGroup = 089C166AFE841209C02AAC07 /* LiferayNativityFinder */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				8D57630D048677EA00EA77CD /* LiferayNativityFinder */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		8D57630F048677EA00EA77CD /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		8D576311048677EA00EA77CD /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				0B13ECAE173C687400548DA1 /* GCDAsyncSocket.m in Sources */,
+				8C37DD9F161593BD00016A95 /* FinderHook.m in Sources */,
+				8C99F6941622D145002D2135 /* IconCache.m in Sources */,
+				69948B361636D50E0093B6CE /* ContentManager.m in Sources */,
+				6993878616494C000044E4DF /* RequestManager.m in Sources */,
+				692C18A516660C4700BF6A53 /* ContextMenuHandlers.m in Sources */,
+				692C18A9166617F500BF6A53 /* IconOverlayHandlers.m in Sources */,
+				692C18AC1666392700BF6A53 /* MenuManager.m in Sources */,
+				0BFAF21C16F8E6C10017EA7E /* JSONKit.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		8D5B49A704867FD3000E48DA /* InfoPlist.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				089C167EFE841241C02AAC07 /* English */,
+			);
+			name = InfoPlist.strings;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		1DEB911B08733D790010E9CD /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_LINK_OBJC_RUNTIME = NO;
+				COMBINE_HIDPI_IMAGES = YES;
+				CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
+				COPY_PHASE_STRIP = NO;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_MODEL_TUNING = G5;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				INFOPLIST_FILE = Info.plist;
+				INSTALL_PATH = "$(HOME)/Library/Bundles";
+				PRODUCT_NAME = LiferayNativityFinder;
+				WRAPPER_EXTENSION = bundle;
+			};
+			name = Debug;
+		};
+		1DEB911C08733D790010E9CD /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_LINK_OBJC_RUNTIME = NO;
+				COMBINE_HIDPI_IMAGES = YES;
+				CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				GCC_MODEL_TUNING = G5;
+				INFOPLIST_FILE = Info.plist;
+				INSTALL_PATH = "$(HOME)/Library/Bundles";
+				PRODUCT_NAME = LiferayNativityFinder;
+				WRAPPER_EXTENSION = bundle;
+			};
+			name = Release;
+		};
+		1DEB911F08733D790010E9CD /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+				COPY_PHASE_STRIP = NO;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = _DEBUG_LOG;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.7;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		1DEB912008733D790010E9CD /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+				COPY_PHASE_STRIP = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.7;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "LiferayNativityFinder" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DEB911B08733D790010E9CD /* Debug */,
+				1DEB911C08733D790010E9CD /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "LiferayNativityFinder" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DEB911F08733D790010E9CD /* Debug */,
+				1DEB912008733D790010E9CD /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 089C1669FE841209C02AAC07 /* Project object */;
+}

+ 7 - 0
shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:LiferayNativityFinder.xcodeproj">
+   </FileRef>
+</Workspace>

+ 1542 - 0
shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/svp.mode1v3

@@ -0,0 +1,1542 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ActivePerspectiveName</key>
+	<string>Project</string>
+	<key>AllowedModules</key>
+	<array>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXSmartGroupTreeModule</string>
+			<key>Name</key>
+			<string>Groups and Files Outline View</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXNavigatorGroup</string>
+			<key>Name</key>
+			<string>Editor</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCTaskListModule</string>
+			<key>Name</key>
+			<string>Task List</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCDetailModule</string>
+			<key>Name</key>
+			<string>File and Smart Group Detail Viewer</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXBuildResultsModule</string>
+			<key>Name</key>
+			<string>Detailed Build Results Viewer</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXProjectFindModule</string>
+			<key>Name</key>
+			<string>Project Batch Find Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCProjectFormatConflictsModule</string>
+			<key>Name</key>
+			<string>Project Format Conflicts List</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXBookmarksModule</string>
+			<key>Name</key>
+			<string>Bookmarks Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXClassBrowserModule</string>
+			<key>Name</key>
+			<string>Class Browser</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXCVSModule</string>
+			<key>Name</key>
+			<string>Source Code Control Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXDebugBreakpointsModule</string>
+			<key>Name</key>
+			<string>Debug Breakpoints Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCDockableInspector</string>
+			<key>Name</key>
+			<string>Inspector</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>PBXOpenQuicklyModule</string>
+			<key>Name</key>
+			<string>Open Quickly Tool</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXDebugSessionModule</string>
+			<key>Name</key>
+			<string>Debugger</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>1</string>
+			<key>Module</key>
+			<string>PBXDebugCLIModule</string>
+			<key>Name</key>
+			<string>Debug Console</string>
+		</dict>
+		<dict>
+			<key>BundleLoadPath</key>
+			<string></string>
+			<key>MaxInstances</key>
+			<string>n</string>
+			<key>Module</key>
+			<string>XCSnapshotModule</string>
+			<key>Name</key>
+			<string>Snapshots Tool</string>
+		</dict>
+	</array>
+	<key>BundlePath</key>
+	<string>/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources</string>
+	<key>Description</key>
+	<string>DefaultDescriptionKey</string>
+	<key>DockingSystemVisible</key>
+	<false/>
+	<key>Extension</key>
+	<string>mode1v3</string>
+	<key>FavBarConfig</key>
+	<dict>
+		<key>PBXProjectModuleGUID</key>
+		<string>8C37DD941615938A00016A95</string>
+		<key>XCBarModuleItemNames</key>
+		<dict/>
+		<key>XCBarModuleItems</key>
+		<array/>
+	</dict>
+	<key>FirstTimeWindowDisplayed</key>
+	<false/>
+	<key>Identifier</key>
+	<string>com.apple.perspectives.project.mode1v3</string>
+	<key>MajorVersion</key>
+	<integer>33</integer>
+	<key>MinorVersion</key>
+	<integer>0</integer>
+	<key>Name</key>
+	<string>Default</string>
+	<key>Notifications</key>
+	<array/>
+	<key>OpenEditors</key>
+	<array>
+		<dict>
+			<key>Content</key>
+			<dict>
+				<key>PBXProjectModuleGUID</key>
+				<string>8C99F89B16284444002D2135</string>
+				<key>PBXProjectModuleLabel</key>
+				<string>FinderHook.m</string>
+				<key>PBXSplitModuleInNavigatorKey</key>
+				<dict>
+					<key>Split0</key>
+					<dict>
+						<key>PBXProjectModuleGUID</key>
+						<string>8C99F89C16284444002D2135</string>
+						<key>PBXProjectModuleLabel</key>
+						<string>FinderHook.m</string>
+						<key>_historyCapacity</key>
+						<integer>0</integer>
+						<key>bookmark</key>
+						<string>8C95FFD716301448003BB463</string>
+						<key>history</key>
+						<array>
+							<string>8C95FFB2162D771B003BB463</string>
+						</array>
+					</dict>
+					<key>SplitCount</key>
+					<string>1</string>
+				</dict>
+				<key>StatusBarVisibility</key>
+				<true/>
+			</dict>
+			<key>Geometry</key>
+			<dict>
+				<key>Frame</key>
+				<string>{{0, 20}, {1882, 573}}</string>
+				<key>PBXModuleWindowStatusBarHidden2</key>
+				<false/>
+				<key>RubberWindowFrame</key>
+				<string>38 444 1882 614 0 0 1920 1058 </string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Content</key>
+			<dict>
+				<key>PBXProjectModuleGUID</key>
+				<string>8C95FF84162D59A0003BB463</string>
+				<key>PBXProjectModuleLabel</key>
+				<string>FinderHook.m</string>
+				<key>PBXSplitModuleInNavigatorKey</key>
+				<dict>
+					<key>Split0</key>
+					<dict>
+						<key>PBXProjectModuleGUID</key>
+						<string>8C95FF85162D59A0003BB463</string>
+						<key>PBXProjectModuleLabel</key>
+						<string>FinderHook.m</string>
+						<key>_historyCapacity</key>
+						<integer>0</integer>
+						<key>bookmark</key>
+						<string>8C95FFD816301448003BB463</string>
+						<key>history</key>
+						<array>
+							<string>8C95FFC1162D7A3D003BB463</string>
+							<string>8C95FFC2162D7A3D003BB463</string>
+						</array>
+					</dict>
+					<key>SplitCount</key>
+					<string>1</string>
+				</dict>
+				<key>StatusBarVisibility</key>
+				<true/>
+			</dict>
+			<key>Geometry</key>
+			<dict>
+				<key>Frame</key>
+				<string>{{0, 20}, {1882, 629}}</string>
+				<key>PBXModuleWindowStatusBarHidden2</key>
+				<false/>
+				<key>RubberWindowFrame</key>
+				<string>916 388 1882 670 0 0 1920 1058 </string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Content</key>
+			<dict>
+				<key>PBXProjectModuleGUID</key>
+				<string>8C99F7B216283292002D2135</string>
+				<key>PBXProjectModuleLabel</key>
+				<string>IconCache.m</string>
+				<key>PBXSplitModuleInNavigatorKey</key>
+				<dict>
+					<key>Split0</key>
+					<dict>
+						<key>PBXProjectModuleGUID</key>
+						<string>8C99F7B316283292002D2135</string>
+						<key>PBXProjectModuleLabel</key>
+						<string>IconCache.m</string>
+						<key>_historyCapacity</key>
+						<integer>0</integer>
+						<key>bookmark</key>
+						<string>8C95FFD916301448003BB463</string>
+						<key>history</key>
+						<array>
+							<string>8C99F90216284ECF002D2135</string>
+						</array>
+					</dict>
+					<key>SplitCount</key>
+					<string>1</string>
+				</dict>
+				<key>StatusBarVisibility</key>
+				<true/>
+			</dict>
+			<key>Geometry</key>
+			<dict>
+				<key>Frame</key>
+				<string>{{0, 20}, {1882, 573}}</string>
+				<key>PBXModuleWindowStatusBarHidden2</key>
+				<false/>
+				<key>RubberWindowFrame</key>
+				<string>38 444 1882 614 0 0 1920 1058 </string>
+			</dict>
+		</dict>
+		<dict>
+			<key>Content</key>
+			<dict>
+				<key>PBXProjectModuleGUID</key>
+				<string>8C99F7B616283292002D2135</string>
+				<key>PBXProjectModuleLabel</key>
+				<string>IconCache.h</string>
+				<key>PBXSplitModuleInNavigatorKey</key>
+				<dict>
+					<key>Split0</key>
+					<dict>
+						<key>PBXProjectModuleGUID</key>
+						<string>8C99F7B716283292002D2135</string>
+						<key>PBXProjectModuleLabel</key>
+						<string>IconCache.h</string>
+						<key>_historyCapacity</key>
+						<integer>0</integer>
+						<key>bookmark</key>
+						<string>8C95FFDA16301448003BB463</string>
+						<key>history</key>
+						<array>
+							<string>8C99F90316284ECF002D2135</string>
+						</array>
+					</dict>
+					<key>SplitCount</key>
+					<string>1</string>
+				</dict>
+				<key>StatusBarVisibility</key>
+				<true/>
+			</dict>
+			<key>Geometry</key>
+			<dict>
+				<key>Frame</key>
+				<string>{{0, 20}, {1882, 573}}</string>
+				<key>PBXModuleWindowStatusBarHidden2</key>
+				<false/>
+				<key>RubberWindowFrame</key>
+				<string>352 444 1882 614 0 0 1920 1058 </string>
+			</dict>
+		</dict>
+	</array>
+	<key>PerspectiveWidths</key>
+	<array>
+		<integer>-1</integer>
+		<integer>-1</integer>
+	</array>
+	<key>Perspectives</key>
+	<array>
+		<dict>
+			<key>ChosenToolbarItems</key>
+			<array>
+				<string>active-combo-popup</string>
+				<string>action</string>
+				<string>NSToolbarFlexibleSpaceItem</string>
+				<string>debugger-enable-breakpoints</string>
+				<string>build-and-go</string>
+				<string>com.apple.ide.PBXToolbarStopButton</string>
+				<string>get-info</string>
+				<string>NSToolbarFlexibleSpaceItem</string>
+				<string>com.apple.pbx.toolbar.searchfield</string>
+			</array>
+			<key>ControllerClassBaseName</key>
+			<string></string>
+			<key>IconName</key>
+			<string>WindowOfProjectWithEditor</string>
+			<key>Identifier</key>
+			<string>perspective.project</string>
+			<key>IsVertical</key>
+			<false/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>ContentConfiguration</key>
+					<dict>
+						<key>PBXBottomSmartGroupGIDs</key>
+						<array>
+							<string>1C37FBAC04509CD000000102</string>
+							<string>1C37FAAC04509CD000000102</string>
+							<string>1C37FABC05509CD000000102</string>
+							<string>1C37FABC05539CD112110102</string>
+							<string>E2644B35053B69B200211256</string>
+							<string>1C37FABC04509CD000100104</string>
+							<string>1CC0EA4004350EF90044410B</string>
+							<string>1CC0EA4004350EF90041110B</string>
+						</array>
+						<key>PBXProjectModuleGUID</key>
+						<string>1CE0B1FE06471DED0097A5F4</string>
+						<key>PBXProjectModuleLabel</key>
+						<string>Files</string>
+						<key>PBXProjectStructureProvided</key>
+						<string>yes</string>
+						<key>PBXSmartGroupTreeModuleColumnData</key>
+						<dict>
+							<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+							<array>
+								<real>186</real>
+							</array>
+							<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+							<array>
+								<string>MainColumn</string>
+							</array>
+						</dict>
+						<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+						<dict>
+							<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+							<array>
+								<string>089C166AFE841209C02AAC07</string>
+								<string>08FB77AFFE84173DC02AAC07</string>
+								<string>089C1671FE841209C02AAC07</string>
+								<string>1C37FABC05509CD000000102</string>
+							</array>
+							<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+							<array>
+								<array>
+									<integer>3</integer>
+									<integer>1</integer>
+									<integer>0</integer>
+								</array>
+							</array>
+							<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+							<string>{{0, 0}, {186, 556}}</string>
+						</dict>
+						<key>PBXTopSmartGroupGIDs</key>
+						<array/>
+						<key>XCIncludePerspectivesSwitch</key>
+						<true/>
+						<key>XCSharingToken</key>
+						<string>com.apple.Xcode.GFSharingToken</string>
+					</dict>
+					<key>GeometryConfiguration</key>
+					<dict>
+						<key>Frame</key>
+						<string>{{0, 0}, {203, 574}}</string>
+						<key>GroupTreeTableConfiguration</key>
+						<array>
+							<string>MainColumn</string>
+							<real>186</real>
+						</array>
+						<key>RubberWindowFrame</key>
+						<string>632 443 1235 615 0 0 1920 1058 </string>
+					</dict>
+					<key>Module</key>
+					<string>PBXSmartGroupTreeModule</string>
+					<key>Proportion</key>
+					<string>203pt</string>
+				</dict>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CE0B20306471E060097A5F4</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>FinderHook.m</string>
+								<key>PBXSplitModuleInNavigatorKey</key>
+								<dict>
+									<key>Split0</key>
+									<dict>
+										<key>PBXProjectModuleGUID</key>
+										<string>1CE0B20406471E060097A5F4</string>
+										<key>PBXProjectModuleLabel</key>
+										<string>FinderHook.m</string>
+										<key>_historyCapacity</key>
+										<integer>0</integer>
+										<key>bookmark</key>
+										<string>8C95FFD616301448003BB463</string>
+										<key>history</key>
+										<array>
+											<string>8C99F6D716241A83002D2135</string>
+											<string>8C99F7E91628351A002D2135</string>
+											<string>8C99F89616284444002D2135</string>
+											<string>8C99F8FC16284ECF002D2135</string>
+											<string>8C95FF21162C3A8B003BB463</string>
+											<string>8C95FFD516301448003BB463</string>
+										</array>
+									</dict>
+									<key>SplitCount</key>
+									<string>1</string>
+								</dict>
+								<key>StatusBarVisibility</key>
+								<true/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {1027, 357}}</string>
+								<key>RubberWindowFrame</key>
+								<string>632 443 1235 615 0 0 1920 1058 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>357pt</string>
+						</dict>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CE0B20506471E060097A5F4</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Detail</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 362}, {1027, 212}}</string>
+								<key>RubberWindowFrame</key>
+								<string>632 443 1235 615 0 0 1920 1058 </string>
+							</dict>
+							<key>Module</key>
+							<string>XCDetailModule</string>
+							<key>Proportion</key>
+							<string>212pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>1027pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Project</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCModuleDock</string>
+				<string>PBXSmartGroupTreeModule</string>
+				<string>XCModuleDock</string>
+				<string>PBXNavigatorGroup</string>
+				<string>XCDetailModule</string>
+			</array>
+			<key>TableOfContents</key>
+			<array>
+				<string>8C95FEE8162C08FC003BB463</string>
+				<string>1CE0B1FE06471DED0097A5F4</string>
+				<string>8C95FEE9162C08FC003BB463</string>
+				<string>1CE0B20306471E060097A5F4</string>
+				<string>1CE0B20506471E060097A5F4</string>
+			</array>
+			<key>ToolbarConfigUserDefaultsMinorVersion</key>
+			<string>2</string>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.defaultV3</string>
+		</dict>
+		<dict>
+			<key>ControllerClassBaseName</key>
+			<string></string>
+			<key>IconName</key>
+			<string>WindowOfProject</string>
+			<key>Identifier</key>
+			<string>perspective.morph</string>
+			<key>IsVertical</key>
+			<integer>0</integer>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>BecomeActive</key>
+					<integer>1</integer>
+					<key>ContentConfiguration</key>
+					<dict>
+						<key>PBXBottomSmartGroupGIDs</key>
+						<array>
+							<string>1C37FBAC04509CD000000102</string>
+							<string>1C37FAAC04509CD000000102</string>
+							<string>1C08E77C0454961000C914BD</string>
+							<string>1C37FABC05509CD000000102</string>
+							<string>1C37FABC05539CD112110102</string>
+							<string>E2644B35053B69B200211256</string>
+							<string>1C37FABC04509CD000100104</string>
+							<string>1CC0EA4004350EF90044410B</string>
+							<string>1CC0EA4004350EF90041110B</string>
+						</array>
+						<key>PBXProjectModuleGUID</key>
+						<string>11E0B1FE06471DED0097A5F4</string>
+						<key>PBXProjectModuleLabel</key>
+						<string>Files</string>
+						<key>PBXProjectStructureProvided</key>
+						<string>yes</string>
+						<key>PBXSmartGroupTreeModuleColumnData</key>
+						<dict>
+							<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+							<array>
+								<real>186</real>
+							</array>
+							<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+							<array>
+								<string>MainColumn</string>
+							</array>
+						</dict>
+						<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+						<dict>
+							<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+							<array>
+								<string>29B97314FDCFA39411CA2CEA</string>
+								<string>1C37FABC05509CD000000102</string>
+							</array>
+							<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+							<array>
+								<array>
+									<integer>0</integer>
+								</array>
+							</array>
+							<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+							<string>{{0, 0}, {186, 337}}</string>
+						</dict>
+						<key>PBXTopSmartGroupGIDs</key>
+						<array/>
+						<key>XCIncludePerspectivesSwitch</key>
+						<integer>1</integer>
+						<key>XCSharingToken</key>
+						<string>com.apple.Xcode.GFSharingToken</string>
+					</dict>
+					<key>GeometryConfiguration</key>
+					<dict>
+						<key>Frame</key>
+						<string>{{0, 0}, {203, 355}}</string>
+						<key>GroupTreeTableConfiguration</key>
+						<array>
+							<string>MainColumn</string>
+							<real>186</real>
+						</array>
+						<key>RubberWindowFrame</key>
+						<string>373 269 690 397 0 0 1440 878 </string>
+					</dict>
+					<key>Module</key>
+					<string>PBXSmartGroupTreeModule</string>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Morph</string>
+			<key>PreferredWidth</key>
+			<integer>300</integer>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCModuleDock</string>
+				<string>PBXSmartGroupTreeModule</string>
+			</array>
+			<key>TableOfContents</key>
+			<array>
+				<string>11E0B1FE06471DED0097A5F4</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.default.shortV3</string>
+		</dict>
+	</array>
+	<key>PerspectivesBarVisible</key>
+	<false/>
+	<key>ShelfIsVisible</key>
+	<false/>
+	<key>SourceDescription</key>
+	<string>file at '/Developer/Library/PrivateFrameworks/DevToolsInterface.framework/Resources/XCPerspectivesSpecificationMode1.xcperspec'</string>
+	<key>StatusbarIsVisible</key>
+	<true/>
+	<key>TimeStamp</key>
+	<real>0.0</real>
+	<key>ToolbarConfigUserDefaultsMinorVersion</key>
+	<string>2</string>
+	<key>ToolbarDisplayMode</key>
+	<integer>1</integer>
+	<key>ToolbarIsVisible</key>
+	<true/>
+	<key>ToolbarSizeMode</key>
+	<integer>1</integer>
+	<key>Type</key>
+	<string>Perspectives</string>
+	<key>UpdateMessage</key>
+	<string>The Default Workspace in this version of Xcode now includes support to hide and show the detail view (what has been referred to as the "Metro-Morph" feature).  You must discard your current Default Workspace settings and update to the latest Default Workspace in order to gain this feature.  Do you wish to update to the latest Workspace defaults for project '%@'?</string>
+	<key>WindowJustification</key>
+	<integer>5</integer>
+	<key>WindowOrderList</key>
+	<array>
+		<string>8C37DD951615938A00016A95</string>
+		<string>8C99F7B616283292002D2135</string>
+		<string>8C99F7B216283292002D2135</string>
+		<string>8C95FF84162D59A0003BB463</string>
+		<string>8C99F89B16284444002D2135</string>
+		<string>/Users/svp/Liferay/trunk/LiferayFinderCore/LiferayFinderCore.xcodeproj</string>
+	</array>
+	<key>WindowString</key>
+	<string>632 443 1235 615 0 0 1920 1058 </string>
+	<key>WindowToolsV3</key>
+	<array>
+		<dict>
+			<key>FirstTimeWindowDisplayed</key>
+			<false/>
+			<key>Identifier</key>
+			<string>windowTool.build</string>
+			<key>IsVertical</key>
+			<true/>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CD0528F0623707200166675</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>FinderHook.m</string>
+								<key>StatusBarVisibility</key>
+								<true/>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {698, 229}}</string>
+								<key>RubberWindowFrame</key>
+								<string>1121 132 698 522 0 0 1920 1058 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>229pt</string>
+						</dict>
+						<dict>
+							<key>BecomeActive</key>
+							<true/>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>XCMainBuildResultsModuleGUID</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Build Results</string>
+								<key>XCBuildResultsTrigger_Collapse</key>
+								<integer>1021</integer>
+								<key>XCBuildResultsTrigger_Open</key>
+								<integer>1011</integer>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 234}, {698, 247}}</string>
+								<key>RubberWindowFrame</key>
+								<string>1121 132 698 522 0 0 1920 1058 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXBuildResultsModule</string>
+							<key>Proportion</key>
+							<string>247pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>481pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Build Results</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXBuildResultsModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<true/>
+			<key>TableOfContents</key>
+			<array>
+				<string>8C37DD951615938A00016A95</string>
+				<string>8C95FEED162C08FC003BB463</string>
+				<string>1CD0528F0623707200166675</string>
+				<string>XCMainBuildResultsModuleGUID</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.buildV3</string>
+			<key>WindowContentMinSize</key>
+			<string>486 300</string>
+			<key>WindowString</key>
+			<string>1121 132 698 522 0 0 1920 1058 </string>
+			<key>WindowToolGUID</key>
+			<string>8C37DD951615938A00016A95</string>
+			<key>WindowToolIsVisible</key>
+			<true/>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.debugger</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>Debugger</key>
+								<dict>
+									<key>HorizontalSplitView</key>
+									<dict>
+										<key>_collapsingFrameDimension</key>
+										<real>0.0</real>
+										<key>_indexOfCollapsedView</key>
+										<integer>0</integer>
+										<key>_percentageOfCollapsedView</key>
+										<real>0.0</real>
+										<key>isCollapsed</key>
+										<string>yes</string>
+										<key>sizes</key>
+										<array>
+											<string>{{0, 0}, {317, 164}}</string>
+											<string>{{317, 0}, {377, 164}}</string>
+										</array>
+									</dict>
+									<key>VerticalSplitView</key>
+									<dict>
+										<key>_collapsingFrameDimension</key>
+										<real>0.0</real>
+										<key>_indexOfCollapsedView</key>
+										<integer>0</integer>
+										<key>_percentageOfCollapsedView</key>
+										<real>0.0</real>
+										<key>isCollapsed</key>
+										<string>yes</string>
+										<key>sizes</key>
+										<array>
+											<string>{{0, 0}, {694, 164}}</string>
+											<string>{{0, 164}, {694, 216}}</string>
+										</array>
+									</dict>
+								</dict>
+								<key>LauncherConfigVersion</key>
+								<string>8</string>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C162984064C10D400B95A72</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Debug - GLUTExamples (Underwater)</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>DebugConsoleDrawerSize</key>
+								<string>{100, 120}</string>
+								<key>DebugConsoleVisible</key>
+								<string>None</string>
+								<key>DebugConsoleWindowFrame</key>
+								<string>{{200, 200}, {500, 300}}</string>
+								<key>DebugSTDIOWindowFrame</key>
+								<string>{{200, 200}, {500, 300}}</string>
+								<key>Frame</key>
+								<string>{{0, 0}, {694, 380}}</string>
+								<key>RubberWindowFrame</key>
+								<string>321 238 694 422 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXDebugSessionModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Debugger</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXDebugSessionModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1CD10A99069EF8BA00B06720</string>
+				<string>1C0AD2AB069F1E9B00FABCE6</string>
+				<string>1C162984064C10D400B95A72</string>
+				<string>1C0AD2AC069F1E9B00FABCE6</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.debugV3</string>
+			<key>WindowString</key>
+			<string>321 238 694 422 0 0 1440 878 </string>
+			<key>WindowToolGUID</key>
+			<string>1CD10A99069EF8BA00B06720</string>
+			<key>WindowToolIsVisible</key>
+			<integer>0</integer>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.find</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Dock</key>
+							<array>
+								<dict>
+									<key>ContentConfiguration</key>
+									<dict>
+										<key>PBXProjectModuleGUID</key>
+										<string>1CDD528C0622207200134675</string>
+										<key>PBXProjectModuleLabel</key>
+										<string>&lt;No Editor&gt;</string>
+										<key>PBXSplitModuleInNavigatorKey</key>
+										<dict>
+											<key>Split0</key>
+											<dict>
+												<key>PBXProjectModuleGUID</key>
+												<string>1CD0528D0623707200166675</string>
+											</dict>
+											<key>SplitCount</key>
+											<string>1</string>
+										</dict>
+										<key>StatusBarVisibility</key>
+										<integer>1</integer>
+									</dict>
+									<key>GeometryConfiguration</key>
+									<dict>
+										<key>Frame</key>
+										<string>{{0, 0}, {781, 167}}</string>
+										<key>RubberWindowFrame</key>
+										<string>62 385 781 470 0 0 1440 878 </string>
+									</dict>
+									<key>Module</key>
+									<string>PBXNavigatorGroup</string>
+									<key>Proportion</key>
+									<string>781pt</string>
+								</dict>
+							</array>
+							<key>Proportion</key>
+							<string>50%</string>
+						</dict>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CD0528E0623707200166675</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Project Find</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{8, 0}, {773, 254}}</string>
+								<key>RubberWindowFrame</key>
+								<string>62 385 781 470 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXProjectFindModule</string>
+							<key>Proportion</key>
+							<string>50%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>428pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Project Find</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXProjectFindModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C530D57069F1CE1000CFCEE</string>
+				<string>1C530D58069F1CE1000CFCEE</string>
+				<string>1C530D59069F1CE1000CFCEE</string>
+				<string>1CDD528C0622207200134675</string>
+				<string>1C530D5A069F1CE1000CFCEE</string>
+				<string>1CE0B1FE06471DED0097A5F4</string>
+				<string>1CD0528E0623707200166675</string>
+			</array>
+			<key>WindowString</key>
+			<string>62 385 781 470 0 0 1440 878 </string>
+			<key>WindowToolGUID</key>
+			<string>1C530D57069F1CE1000CFCEE</string>
+			<key>WindowToolIsVisible</key>
+			<integer>0</integer>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>MENUSEPARATOR</string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.debuggerConsole</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C78EAAC065D492600B07095</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Debugger Console</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {650, 250}}</string>
+								<key>RubberWindowFrame</key>
+								<string>516 632 650 250 0 0 1680 1027 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXDebugCLIModule</string>
+							<key>Proportion</key>
+							<string>209pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>209pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Debugger Console</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXDebugCLIModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C78EAAD065D492600B07095</string>
+				<string>1C78EAAE065D492600B07095</string>
+				<string>1C78EAAC065D492600B07095</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.consoleV3</string>
+			<key>WindowString</key>
+			<string>650 41 650 250 0 0 1280 1002 </string>
+			<key>WindowToolGUID</key>
+			<string>1C78EAAD065D492600B07095</string>
+			<key>WindowToolIsVisible</key>
+			<integer>0</integer>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.snapshots</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>XCSnapshotModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Snapshots</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCSnapshotModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<string>Yes</string>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.snapshots</string>
+			<key>WindowString</key>
+			<string>315 824 300 550 0 0 1440 878 </string>
+			<key>WindowToolIsVisible</key>
+			<string>Yes</string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.scm</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1C78EAB2065D492600B07095</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>&lt;No Editor&gt;</string>
+								<key>PBXSplitModuleInNavigatorKey</key>
+								<dict>
+									<key>Split0</key>
+									<dict>
+										<key>PBXProjectModuleGUID</key>
+										<string>1C78EAB3065D492600B07095</string>
+									</dict>
+									<key>SplitCount</key>
+									<string>1</string>
+								</dict>
+								<key>StatusBarVisibility</key>
+								<integer>1</integer>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {452, 0}}</string>
+								<key>RubberWindowFrame</key>
+								<string>743 379 452 308 0 0 1280 1002 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>0pt</string>
+						</dict>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CD052920623707200166675</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>SCM</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>ConsoleFrame</key>
+								<string>{{0, 259}, {452, 0}}</string>
+								<key>Frame</key>
+								<string>{{0, 7}, {452, 259}}</string>
+								<key>RubberWindowFrame</key>
+								<string>743 379 452 308 0 0 1280 1002 </string>
+								<key>TableConfiguration</key>
+								<array>
+									<string>Status</string>
+									<real>30</real>
+									<string>FileName</string>
+									<real>199</real>
+									<string>Path</string>
+									<real>197.0950012207031</real>
+								</array>
+								<key>TableFrame</key>
+								<string>{{0, 0}, {452, 250}}</string>
+							</dict>
+							<key>Module</key>
+							<string>PBXCVSModule</string>
+							<key>Proportion</key>
+							<string>262pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>266pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>SCM</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXCVSModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C78EAB4065D492600B07095</string>
+				<string>1C78EAB5065D492600B07095</string>
+				<string>1C78EAB2065D492600B07095</string>
+				<string>1CD052920623707200166675</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.scm</string>
+			<key>WindowString</key>
+			<string>743 379 452 308 0 0 1280 1002 </string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.breakpoints</string>
+			<key>IsVertical</key>
+			<integer>0</integer>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXBottomSmartGroupGIDs</key>
+								<array>
+									<string>1C77FABC04509CD000000102</string>
+								</array>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CE0B1FE06471DED0097A5F4</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Files</string>
+								<key>PBXProjectStructureProvided</key>
+								<string>no</string>
+								<key>PBXSmartGroupTreeModuleColumnData</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleColumnWidthsKey</key>
+									<array>
+										<real>168</real>
+									</array>
+									<key>PBXSmartGroupTreeModuleColumnsKey_v4</key>
+									<array>
+										<string>MainColumn</string>
+									</array>
+								</dict>
+								<key>PBXSmartGroupTreeModuleOutlineStateKey_v7</key>
+								<dict>
+									<key>PBXSmartGroupTreeModuleOutlineStateExpansionKey</key>
+									<array>
+										<string>1C77FABC04509CD000000102</string>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateSelectionKey</key>
+									<array>
+										<array>
+											<integer>0</integer>
+										</array>
+									</array>
+									<key>PBXSmartGroupTreeModuleOutlineStateVisibleRectKey</key>
+									<string>{{0, 0}, {168, 350}}</string>
+								</dict>
+								<key>PBXTopSmartGroupGIDs</key>
+								<array/>
+								<key>XCIncludePerspectivesSwitch</key>
+								<integer>0</integer>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{0, 0}, {185, 368}}</string>
+								<key>GroupTreeTableConfiguration</key>
+								<array>
+									<string>MainColumn</string>
+									<real>168</real>
+								</array>
+								<key>RubberWindowFrame</key>
+								<string>315 424 744 409 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXSmartGroupTreeModule</string>
+							<key>Proportion</key>
+							<string>185pt</string>
+						</dict>
+						<dict>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CA1AED706398EBD00589147</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Detail</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{{190, 0}, {554, 368}}</string>
+								<key>RubberWindowFrame</key>
+								<string>315 424 744 409 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>XCDetailModule</string>
+							<key>Proportion</key>
+							<string>554pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>368pt</string>
+				</dict>
+			</array>
+			<key>MajorVersion</key>
+			<integer>3</integer>
+			<key>MinorVersion</key>
+			<integer>0</integer>
+			<key>Name</key>
+			<string>Breakpoints</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXSmartGroupTreeModule</string>
+				<string>XCDetailModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1CDDB66807F98D9800BB5817</string>
+				<string>1CDDB66907F98D9800BB5817</string>
+				<string>1CE0B1FE06471DED0097A5F4</string>
+				<string>1CA1AED706398EBD00589147</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.breakpointsV3</string>
+			<key>WindowString</key>
+			<string>315 424 744 409 0 0 1440 878 </string>
+			<key>WindowToolGUID</key>
+			<string>1CDDB66807F98D9800BB5817</string>
+			<key>WindowToolIsVisible</key>
+			<integer>1</integer>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.debugAnimator</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>PBXNavigatorGroup</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Debug Visualizer</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXNavigatorGroup</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>1</integer>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.debugAnimatorV3</string>
+			<key>WindowString</key>
+			<string>100 100 700 500 0 0 1280 1002 </string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.bookmarks</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>PBXBookmarksModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Bookmarks</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXBookmarksModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>0</integer>
+			<key>WindowString</key>
+			<string>538 42 401 187 0 0 1280 1002 </string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.projectFormatConflicts</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>Module</key>
+							<string>XCProjectFormatConflictsModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Project Format Conflicts</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCProjectFormatConflictsModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>0</integer>
+			<key>WindowContentMinSize</key>
+			<string>450 300</string>
+			<key>WindowString</key>
+			<string>50 850 472 307 0 0 1440 877</string>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.classBrowser</string>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>ContentConfiguration</key>
+							<dict>
+								<key>OptionsSetName</key>
+								<string>Hierarchy, all classes</string>
+								<key>PBXProjectModuleGUID</key>
+								<string>1CA6456E063B45B4001379D8</string>
+								<key>PBXProjectModuleLabel</key>
+								<string>Class Browser - NSObject</string>
+							</dict>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>ClassesFrame</key>
+								<string>{{0, 0}, {374, 96}}</string>
+								<key>ClassesTreeTableConfiguration</key>
+								<array>
+									<string>PBXClassNameColumnIdentifier</string>
+									<real>208</real>
+									<string>PBXClassBookColumnIdentifier</string>
+									<real>22</real>
+								</array>
+								<key>Frame</key>
+								<string>{{0, 0}, {630, 331}}</string>
+								<key>MembersFrame</key>
+								<string>{{0, 105}, {374, 395}}</string>
+								<key>MembersTreeTableConfiguration</key>
+								<array>
+									<string>PBXMemberTypeIconColumnIdentifier</string>
+									<real>22</real>
+									<string>PBXMemberNameColumnIdentifier</string>
+									<real>216</real>
+									<string>PBXMemberTypeColumnIdentifier</string>
+									<real>97</real>
+									<string>PBXMemberBookColumnIdentifier</string>
+									<real>22</real>
+								</array>
+								<key>PBXModuleWindowStatusBarHidden2</key>
+								<integer>1</integer>
+								<key>RubberWindowFrame</key>
+								<string>385 179 630 352 0 0 1440 878 </string>
+							</dict>
+							<key>Module</key>
+							<string>PBXClassBrowserModule</string>
+							<key>Proportion</key>
+							<string>332pt</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>332pt</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Class Browser</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>PBXClassBrowserModule</string>
+			</array>
+			<key>StatusbarIsVisible</key>
+			<integer>0</integer>
+			<key>TableOfContents</key>
+			<array>
+				<string>1C0AD2AF069F1E9B00FABCE6</string>
+				<string>1C0AD2B0069F1E9B00FABCE6</string>
+				<string>1CA6456E063B45B4001379D8</string>
+			</array>
+			<key>ToolbarConfiguration</key>
+			<string>xcode.toolbar.config.classbrowser</string>
+			<key>WindowString</key>
+			<string>385 179 630 352 0 0 1440 878 </string>
+			<key>WindowToolGUID</key>
+			<string>1C0AD2AF069F1E9B00FABCE6</string>
+			<key>WindowToolIsVisible</key>
+			<integer>0</integer>
+		</dict>
+		<dict>
+			<key>Identifier</key>
+			<string>windowTool.refactoring</string>
+			<key>IncludeInToolsMenu</key>
+			<integer>0</integer>
+			<key>Layout</key>
+			<array>
+				<dict>
+					<key>Dock</key>
+					<array>
+						<dict>
+							<key>BecomeActive</key>
+							<integer>1</integer>
+							<key>GeometryConfiguration</key>
+							<dict>
+								<key>Frame</key>
+								<string>{0, 0}, {500, 335}</string>
+								<key>RubberWindowFrame</key>
+								<string>{0, 0}, {500, 335}</string>
+							</dict>
+							<key>Module</key>
+							<string>XCRefactoringModule</string>
+							<key>Proportion</key>
+							<string>100%</string>
+						</dict>
+					</array>
+					<key>Proportion</key>
+					<string>100%</string>
+				</dict>
+			</array>
+			<key>Name</key>
+			<string>Refactoring</string>
+			<key>ServiceClasses</key>
+			<array>
+				<string>XCRefactoringModule</string>
+			</array>
+			<key>WindowString</key>
+			<string>200 200 500 356 0 0 1920 1200 </string>
+		</dict>
+	</array>
+</dict>
+</plist>

+ 1899 - 0
shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/svp.pbxuser

@@ -0,0 +1,1899 @@
+// !$*UTF8*$!
+{
+	089C1669FE841209C02AAC07 /* Project object */ = {
+		activeBuildConfigurationName = Debug;
+		activeTarget = 8D57630D048677EA00EA77CD /* LiferayFinderCore */;
+		addToTargets = (
+			8D57630D048677EA00EA77CD /* LiferayFinderCore */,
+		);
+		breakpoints = (
+			8C99F8BD162849F3002D2135 /* FinderHook.m:112 */,
+		);
+		codeSenseManager = 8C37DD981615938A00016A95 /* Code sense */;
+		perUserDictionary = {
+			PBXConfiguration.PBXFileTableDataSource3.PBXFileTableDataSource = {
+				PBXFileTableDataSourceColumnSortingDirectionKey = 1;
+				PBXFileTableDataSourceColumnSortingKey = PBXFileDataSource_Filename_ColumnID;
+				PBXFileTableDataSourceColumnWidthsKey = (
+					20,
+					788,
+					20,
+					48,
+					43,
+					43,
+					20,
+				);
+				PBXFileTableDataSourceColumnsKey = (
+					PBXFileDataSource_FiletypeID,
+					PBXFileDataSource_Filename_ColumnID,
+					PBXFileDataSource_Built_ColumnID,
+					PBXFileDataSource_ObjectSize_ColumnID,
+					PBXFileDataSource_Errors_ColumnID,
+					PBXFileDataSource_Warnings_ColumnID,
+					PBXFileDataSource_Target_ColumnID,
+				);
+			};
+			PBXPerProjectTemplateStateSaveDate = 371984582;
+			PBXWorkspaceStateSaveDate = 371984582;
+		};
+		perUserProjectItems = {
+			8C95FEE7162C08FC003BB463 /* PBXTextBookmark */ = 8C95FEE7162C08FC003BB463 /* PBXTextBookmark */;
+			8C95FEEA162C08FC003BB463 /* PBXTextBookmark */ = 8C95FEEA162C08FC003BB463 /* PBXTextBookmark */;
+			8C95FEEB162C08FC003BB463 /* PBXTextBookmark */ = 8C95FEEB162C08FC003BB463 /* PBXTextBookmark */;
+			8C95FEEC162C08FC003BB463 /* PBXTextBookmark */ = 8C95FEEC162C08FC003BB463 /* PBXTextBookmark */;
+			8C95FF03162C3429003BB463 /* PBXTextBookmark */ = 8C95FF03162C3429003BB463 /* PBXTextBookmark */;
+			8C95FF04162C3429003BB463 /* PBXTextBookmark */ = 8C95FF04162C3429003BB463 /* PBXTextBookmark */;
+			8C95FF05162C3429003BB463 /* PBXTextBookmark */ = 8C95FF05162C3429003BB463 /* PBXTextBookmark */;
+			8C95FF06162C3429003BB463 /* PBXTextBookmark */ = 8C95FF06162C3429003BB463 /* PBXTextBookmark */;
+			8C95FF21162C3A8B003BB463 /* PBXTextBookmark */ = 8C95FF21162C3A8B003BB463 /* PBXTextBookmark */;
+			8C95FF22162C3A8B003BB463 /* XCBuildMessageTextBookmark */ = 8C95FF22162C3A8B003BB463 /* XCBuildMessageTextBookmark */;
+			8C95FF23162C3A8B003BB463 /* PBXTextBookmark */ = 8C95FF23162C3A8B003BB463 /* PBXTextBookmark */;
+			8C95FF24162C3A8B003BB463 /* PBXTextBookmark */ = 8C95FF24162C3A8B003BB463 /* PBXTextBookmark */;
+			8C95FF25162C3A8B003BB463 /* PBXTextBookmark */ = 8C95FF25162C3A8B003BB463 /* PBXTextBookmark */;
+			8C95FF26162C3A8B003BB463 /* PBXTextBookmark */ = 8C95FF26162C3A8B003BB463 /* PBXTextBookmark */;
+			8C95FF4B162C3D26003BB463 /* PBXTextBookmark */ = 8C95FF4B162C3D26003BB463 /* PBXTextBookmark */;
+			8C95FF4C162C3D26003BB463 /* PBXTextBookmark */ = 8C95FF4C162C3D26003BB463 /* PBXTextBookmark */;
+			8C95FF4D162C3D26003BB463 /* PBXTextBookmark */ = 8C95FF4D162C3D26003BB463 /* PBXTextBookmark */;
+			8C95FF4E162C3D26003BB463 /* PBXTextBookmark */ = 8C95FF4E162C3D26003BB463 /* PBXTextBookmark */;
+			8C95FF5A162C3ED2003BB463 /* XCBuildMessageTextBookmark */ = 8C95FF5A162C3ED2003BB463 /* XCBuildMessageTextBookmark */;
+			8C95FF5B162C3ED2003BB463 /* PBXTextBookmark */ = 8C95FF5B162C3ED2003BB463 /* PBXTextBookmark */;
+			8C95FF6A162D5608003BB463 /* PBXTextBookmark */ = 8C95FF6A162D5608003BB463 /* PBXTextBookmark */;
+			8C95FF6B162D5608003BB463 /* PBXTextBookmark */ = 8C95FF6B162D5608003BB463 /* PBXTextBookmark */;
+			8C95FF6C162D5608003BB463 /* PBXTextBookmark */ = 8C95FF6C162D5608003BB463 /* PBXTextBookmark */;
+			8C95FF6D162D5608003BB463 /* PBXTextBookmark */ = 8C95FF6D162D5608003BB463 /* PBXTextBookmark */;
+			8C95FF80162D59A0003BB463 /* XCBuildMessageTextBookmark */ = 8C95FF80162D59A0003BB463 /* XCBuildMessageTextBookmark */;
+			8C95FF81162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF81162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FF82162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF82162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FF83162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF83162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FF86162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF86162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FF87162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF87162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FF88162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF88162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FF89162D59A0003BB463 /* PBXTextBookmark */ = 8C95FF89162D59A0003BB463 /* PBXTextBookmark */;
+			8C95FFAF162D771B003BB463 /* PBXTextBookmark */ = 8C95FFAF162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFB0162D771B003BB463 /* PBXTextBookmark */ = 8C95FFB0162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFB1162D771B003BB463 /* PBXTextBookmark */ = 8C95FFB1162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFB2162D771B003BB463 /* PBXTextBookmark */ = 8C95FFB2162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFB3162D771B003BB463 /* PBXTextBookmark */ = 8C95FFB3162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFB4162D771B003BB463 /* PBXTextBookmark */ = 8C95FFB4162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFB5162D771B003BB463 /* PBXTextBookmark */ = 8C95FFB5162D771B003BB463 /* PBXTextBookmark */;
+			8C95FFC0162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC0162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFC1162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC1162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFC2162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC2162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFC3162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC3162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFC4162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC4162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFC5162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC5162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFC6162D7A3D003BB463 /* PBXTextBookmark */ = 8C95FFC6162D7A3D003BB463 /* PBXTextBookmark */;
+			8C95FFD516301448003BB463 /* PBXTextBookmark */ = 8C95FFD516301448003BB463 /* PBXTextBookmark */;
+			8C95FFD616301448003BB463 /* PBXTextBookmark */ = 8C95FFD616301448003BB463 /* PBXTextBookmark */;
+			8C95FFD716301448003BB463 /* PBXTextBookmark */ = 8C95FFD716301448003BB463 /* PBXTextBookmark */;
+			8C95FFD816301448003BB463 /* PBXTextBookmark */ = 8C95FFD816301448003BB463 /* PBXTextBookmark */;
+			8C95FFD916301448003BB463 /* PBXTextBookmark */ = 8C95FFD916301448003BB463 /* PBXTextBookmark */;
+			8C95FFDA16301448003BB463 /* PBXTextBookmark */ = 8C95FFDA16301448003BB463 /* PBXTextBookmark */;
+			8C99F6D716241A83002D2135 = 8C99F6D716241A83002D2135 /* PBXTextBookmark */;
+			8C99F6D816241A83002D2135 = 8C99F6D816241A83002D2135 /* PBXTextBookmark */;
+			8C99F6D916241A83002D2135 = 8C99F6D916241A83002D2135 /* PBXTextBookmark */;
+			8C99F6DA16241A83002D2135 = 8C99F6DA16241A83002D2135 /* PBXTextBookmark */;
+			8C99F6DB16241A83002D2135 = 8C99F6DB16241A83002D2135 /* PBXTextBookmark */;
+			8C99F6E11626C51C002D2135 = 8C99F6E11626C51C002D2135 /* PBXTextBookmark */;
+			8C99F70916280074002D2135 = 8C99F70916280074002D2135 /* PBXTextBookmark */;
+			8C99F70A16280074002D2135 = 8C99F70A16280074002D2135 /* PBXTextBookmark */;
+			8C99F73216280B63002D2135 = 8C99F73216280B63002D2135 /* PBXTextBookmark */;
+			8C99F73316280B63002D2135 = 8C99F73316280B63002D2135 /* PBXTextBookmark */;
+			8C99F73416280B63002D2135 = 8C99F73416280B63002D2135 /* PBXTextBookmark */;
+			8C99F75E16280FF3002D2135 = 8C99F75E16280FF3002D2135 /* PBXTextBookmark */;
+			8C99F75F16280FF3002D2135 = 8C99F75F16280FF3002D2135 /* PBXTextBookmark */;
+			8C99F76016280FF3002D2135 = 8C99F76016280FF3002D2135 /* PBXTextBookmark */;
+			8C99F79516282FF8002D2135 = 8C99F79516282FF8002D2135 /* PBXTextBookmark */;
+			8C99F7AA16283292002D2135 = 8C99F7AA16283292002D2135 /* PBXTextBookmark */;
+			8C99F7AB16283292002D2135 = 8C99F7AB16283292002D2135 /* PBXTextBookmark */;
+			8C99F7AC16283292002D2135 = 8C99F7AC16283292002D2135 /* PBXTextBookmark */;
+			8C99F7B116283292002D2135 = 8C99F7B116283292002D2135 /* PBXTextBookmark */;
+			8C99F7B416283292002D2135 = 8C99F7B416283292002D2135 /* PBXTextBookmark */;
+			8C99F7B516283292002D2135 = 8C99F7B516283292002D2135 /* PBXTextBookmark */;
+			8C99F7B816283292002D2135 = 8C99F7B816283292002D2135 /* PBXTextBookmark */;
+			8C99F7B916283292002D2135 = 8C99F7B916283292002D2135 /* PBXTextBookmark */;
+			8C99F7BC16283292002D2135 = 8C99F7BC16283292002D2135 /* PBXTextBookmark */;
+			8C99F7BE16283292002D2135 = 8C99F7BE16283292002D2135 /* PBXTextBookmark */;
+			8C99F7C4162832E3002D2135 = 8C99F7C4162832E3002D2135 /* PBXTextBookmark */;
+			8C99F7C5162832E3002D2135 = 8C99F7C5162832E3002D2135 /* PBXTextBookmark */;
+			8C99F7C6162832E3002D2135 = 8C99F7C6162832E3002D2135 /* PBXTextBookmark */;
+			8C99F7C7162832E3002D2135 = 8C99F7C7162832E3002D2135 /* PBXTextBookmark */;
+			8C99F7C8162832E3002D2135 = 8C99F7C8162832E3002D2135 /* PBXTextBookmark */;
+			8C99F7C916283348002D2135 = 8C99F7C916283348002D2135 /* PBXTextBookmark */;
+			8C99F7CA16283348002D2135 = 8C99F7CA16283348002D2135 /* PBXTextBookmark */;
+			8C99F7CB16283348002D2135 = 8C99F7CB16283348002D2135 /* PBXTextBookmark */;
+			8C99F7CC16283348002D2135 = 8C99F7CC16283348002D2135 /* PBXTextBookmark */;
+			8C99F7CD16283348002D2135 = 8C99F7CD16283348002D2135 /* PBXTextBookmark */;
+			8C99F7D316283395002D2135 = 8C99F7D316283395002D2135 /* PBXTextBookmark */;
+			8C99F7D416283395002D2135 = 8C99F7D416283395002D2135 /* PBXTextBookmark */;
+			8C99F7D516283395002D2135 = 8C99F7D516283395002D2135 /* PBXTextBookmark */;
+			8C99F7D616283395002D2135 = 8C99F7D616283395002D2135 /* PBXTextBookmark */;
+			8C99F7D716283395002D2135 = 8C99F7D716283395002D2135 /* PBXTextBookmark */;
+			8C99F7D816283395002D2135 = 8C99F7D816283395002D2135 /* PBXTextBookmark */;
+			8C99F7D9162833DB002D2135 = 8C99F7D9162833DB002D2135 /* PBXTextBookmark */;
+			8C99F7DA162833DB002D2135 = 8C99F7DA162833DB002D2135 /* PBXTextBookmark */;
+			8C99F7DB162833DB002D2135 = 8C99F7DB162833DB002D2135 /* PBXTextBookmark */;
+			8C99F7DC162833DB002D2135 = 8C99F7DC162833DB002D2135 /* PBXTextBookmark */;
+			8C99F7DD162833DB002D2135 = 8C99F7DD162833DB002D2135 /* PBXTextBookmark */;
+			8C99F7E91628351A002D2135 = 8C99F7E91628351A002D2135 /* PBXTextBookmark */;
+			8C99F7EA1628351A002D2135 = 8C99F7EA1628351A002D2135 /* PBXTextBookmark */;
+			8C99F7EB1628351A002D2135 = 8C99F7EB1628351A002D2135 /* PBXTextBookmark */;
+			8C99F7EC1628351A002D2135 = 8C99F7EC1628351A002D2135 /* PBXTextBookmark */;
+			8C99F7ED1628351A002D2135 = 8C99F7ED1628351A002D2135 /* PBXTextBookmark */;
+			8C99F7EE1628351A002D2135 = 8C99F7EE1628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F11628351A002D2135 = 8C99F7F11628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F21628351A002D2135 = 8C99F7F21628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F31628351A002D2135 = 8C99F7F31628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F41628351A002D2135 = 8C99F7F41628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F51628351A002D2135 = 8C99F7F51628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F61628351A002D2135 = 8C99F7F61628351A002D2135 /* PBXTextBookmark */;
+			8C99F7F71628351A002D2135 = 8C99F7F71628351A002D2135 /* PBXTextBookmark */;
+			8C99F80716283601002D2135 = 8C99F80716283601002D2135 /* PBXTextBookmark */;
+			8C99F80816283601002D2135 = 8C99F80816283601002D2135 /* PBXTextBookmark */;
+			8C99F80916283601002D2135 = 8C99F80916283601002D2135 /* PBXTextBookmark */;
+			8C99F80A16283601002D2135 = 8C99F80A16283601002D2135 /* PBXTextBookmark */;
+			8C99F80B16283601002D2135 = 8C99F80B16283601002D2135 /* PBXTextBookmark */;
+			8C99F80C16283601002D2135 = 8C99F80C16283601002D2135 /* PBXTextBookmark */;
+			8C99F80D16283601002D2135 = 8C99F80D16283601002D2135 /* PBXTextBookmark */;
+			8C99F84F16283CE1002D2135 = 8C99F84F16283CE1002D2135 /* PBXTextBookmark */;
+			8C99F85016283CE1002D2135 = 8C99F85016283CE1002D2135 /* PBXTextBookmark */;
+			8C99F85116283CE1002D2135 = 8C99F85116283CE1002D2135 /* PBXTextBookmark */;
+			8C99F85216283CE1002D2135 = 8C99F85216283CE1002D2135 /* PBXTextBookmark */;
+			8C99F85316283CE1002D2135 = 8C99F85316283CE1002D2135 /* PBXTextBookmark */;
+			8C99F85416283CE1002D2135 = 8C99F85416283CE1002D2135 /* PBXTextBookmark */;
+			8C99F85516283CE1002D2135 = 8C99F85516283CE1002D2135 /* PBXTextBookmark */;
+			8C99F87C1628406A002D2135 = 8C99F87C1628406A002D2135 /* PBXBookmark */;
+			8C99F89616284444002D2135 = 8C99F89616284444002D2135 /* PBXTextBookmark */;
+			8C99F89716284444002D2135 = 8C99F89716284444002D2135 /* PBXTextBookmark */;
+			8C99F89816284444002D2135 = 8C99F89816284444002D2135 /* PBXTextBookmark */;
+			8C99F89916284444002D2135 = 8C99F89916284444002D2135 /* PBXTextBookmark */;
+			8C99F89A16284444002D2135 = 8C99F89A16284444002D2135 /* PBXTextBookmark */;
+			8C99F89D16284444002D2135 = 8C99F89D16284444002D2135 /* PBXTextBookmark */;
+			8C99F89E16284444002D2135 = 8C99F89E16284444002D2135 /* PBXTextBookmark */;
+			8C99F8AB16284854002D2135 = 8C99F8AB16284854002D2135 /* PBXTextBookmark */;
+			8C99F8AC16284854002D2135 = 8C99F8AC16284854002D2135 /* PBXTextBookmark */;
+			8C99F8AD16284854002D2135 = 8C99F8AD16284854002D2135 /* PBXTextBookmark */;
+			8C99F8AE16284854002D2135 = 8C99F8AE16284854002D2135 /* PBXTextBookmark */;
+			8C99F8C716284A2D002D2135 = 8C99F8C716284A2D002D2135 /* PBXTextBookmark */;
+			8C99F8C816284A2D002D2135 = 8C99F8C816284A2D002D2135 /* PBXTextBookmark */;
+			8C99F8C916284A2D002D2135 = 8C99F8C916284A2D002D2135 /* PBXTextBookmark */;
+			8C99F8CA16284A2D002D2135 = 8C99F8CA16284A2D002D2135 /* PBXTextBookmark */;
+			8C99F8CB16284A2D002D2135 = 8C99F8CB16284A2D002D2135 /* PBXTextBookmark */;
+			8C99F8D816284A7B002D2135 = 8C99F8D816284A7B002D2135 /* PBXTextBookmark */;
+			8C99F8D916284A7B002D2135 = 8C99F8D916284A7B002D2135 /* PBXTextBookmark */;
+			8C99F8DA16284A7B002D2135 = 8C99F8DA16284A7B002D2135 /* PBXTextBookmark */;
+			8C99F8DB16284A7B002D2135 = 8C99F8DB16284A7B002D2135 /* PBXTextBookmark */;
+			8C99F8EA16284AB5002D2135 = 8C99F8EA16284AB5002D2135 /* PBXTextBookmark */;
+			8C99F8EB16284AB5002D2135 = 8C99F8EB16284AB5002D2135 /* PBXTextBookmark */;
+			8C99F8EC16284AB5002D2135 = 8C99F8EC16284AB5002D2135 /* PBXTextBookmark */;
+			8C99F8ED16284AB5002D2135 = 8C99F8ED16284AB5002D2135 /* PBXTextBookmark */;
+			8C99F8FC16284ECF002D2135 = 8C99F8FC16284ECF002D2135 /* PBXTextBookmark */;
+			8C99F8FD16284ECF002D2135 = 8C99F8FD16284ECF002D2135 /* PBXTextBookmark */;
+			8C99F8FE16284ECF002D2135 = 8C99F8FE16284ECF002D2135 /* PBXTextBookmark */;
+			8C99F8FF16284ECF002D2135 = 8C99F8FF16284ECF002D2135 /* PBXTextBookmark */;
+			8C99F90016284ECF002D2135 = 8C99F90016284ECF002D2135 /* PBXTextBookmark */;
+			8C99F90116284ECF002D2135 = 8C99F90116284ECF002D2135 /* PBXTextBookmark */;
+			8C99F90216284ECF002D2135 = 8C99F90216284ECF002D2135 /* PBXTextBookmark */;
+			8C99F90316284ECF002D2135 = 8C99F90316284ECF002D2135 /* PBXTextBookmark */;
+			8C99F90D16284F81002D2135 = 8C99F90D16284F81002D2135 /* PBXTextBookmark */;
+			8C99F90E16284F81002D2135 = 8C99F90E16284F81002D2135 /* PBXTextBookmark */;
+			8C99F91016284F81002D2135 = 8C99F91016284F81002D2135 /* PBXTextBookmark */;
+			8C99F91116284F81002D2135 = 8C99F91116284F81002D2135 /* PBXTextBookmark */;
+		};
+		sourceControlManager = 8C37DD971615938A00016A95 /* Source Control */;
+		userBuildSettings = {
+		};
+	};
+	8C37DD971615938A00016A95 /* Source Control */ = {
+		isa = PBXSourceControlManager;
+		fallbackIsa = XCSourceControlManager;
+		isSCMEnabled = 0;
+		scmConfiguration = {
+			repositoryNamesForRoots = {
+				"" = "";
+			};
+		};
+	};
+	8C37DD981615938A00016A95 /* Code sense */ = {
+		isa = PBXCodeSenseManager;
+		indexTemplatePath = "";
+	};
+	8C37DD99161593BD00016A95 /* FinderHook.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {1823, 854}}";
+			sepNavSelRange = "{212, 0}";
+			sepNavVisRange = "{0, 212}";
+		};
+	};
+	8C37DD9A161593BD00016A95 /* FinderHook.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {1823, 2275}}";
+			sepNavSelRange = "{2654, 15}";
+			sepNavVisRange = "{2334, 1837}";
+			sepNavWindowFrame = "{{38, 91}, {1882, 941}}";
+		};
+	};
+	8C37DD9B161593BD00016A95 /* logger.c */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {966, 455}}";
+			sepNavSelRange = "{0, 0}";
+			sepNavVisRange = "{0, 468}";
+			sepNavWindowFrame = "{{15, 112}, {1882, 941}}";
+		};
+	};
+	8C37DD9C161593BD00016A95 /* logger.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {966, 404}}";
+			sepNavSelRange = "{178, 0}";
+			sepNavVisRange = "{0, 178}";
+		};
+	};
+	8C95FEE7162C08FC003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FEEA162C08FC003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 101";
+		rLen = 0;
+		rLoc = 3293;
+		rType = 0;
+		vrLen = 2201;
+		vrLoc = 2277;
+	};
+	8C95FEEB162C08FC003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FEEC162C08FC003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF03162C3429003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF04162C3429003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 161";
+		rLen = 0;
+		rLoc = 5458;
+		rType = 0;
+		vrLen = 2235;
+		vrLoc = 3315;
+	};
+	8C95FF05162C3429003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FF06162C3429003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF21162C3A8B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF22162C3A8B003BB463 /* XCBuildMessageTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Incompatible type for argument 1 of 'drawInRect:fromRect:operation:fraction:'";
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		fallbackIsa = XCBuildMessageTextBookmark;
+		rLen = 1;
+		rLoc = 163;
+		rType = 1;
+	};
+	8C95FF23162C3A8B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 162";
+		rLen = 0;
+		rLoc = 5438;
+		rType = 0;
+		vrLen = 918;
+		vrLoc = 4615;
+	};
+	8C95FF24162C3A8B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 162";
+		rLen = 0;
+		rLoc = 5539;
+		rType = 0;
+		vrLen = 2216;
+		vrLoc = 3255;
+	};
+	8C95FF25162C3A8B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FF26162C3A8B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF4B162C3D26003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 163";
+		rLen = 0;
+		rLoc = 5438;
+		rType = 0;
+		vrLen = 1100;
+		vrLoc = 4615;
+	};
+	8C95FF4C162C3D26003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 162";
+		rLen = 0;
+		rLoc = 5347;
+		rType = 0;
+		vrLen = 2389;
+		vrLoc = 3255;
+	};
+	8C95FF4D162C3D26003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FF4E162C3D26003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF5A162C3ED2003BB463 /* XCBuildMessageTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Invalid initializer";
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		fallbackIsa = XCBuildMessageTextBookmark;
+		rLen = 1;
+		rLoc = 137;
+		rType = 1;
+	};
+	8C95FF5B162C3ED2003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 130";
+		rLen = 0;
+		rLoc = 4289;
+		rType = 0;
+		vrLen = 472;
+		vrLoc = 4042;
+	};
+	8C95FF6A162D5608003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 163";
+		rLen = 0;
+		rLoc = 5438;
+		rType = 0;
+		vrLen = 1202;
+		vrLoc = 4119;
+	};
+	8C95FF6B162D5608003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 130";
+		rLen = 0;
+		rLoc = 4297;
+		rType = 0;
+		vrLen = 2221;
+		vrLoc = 3493;
+	};
+	8C95FF6C162D5608003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FF6D162D5608003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF80162D59A0003BB463 /* XCBuildMessageTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "'FinderHook' may not respond to '-previewItemTitle'";
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		fallbackIsa = XCBuildMessageTextBookmark;
+		rLen = 1;
+		rLoc = 132;
+		rType = 1;
+	};
+	8C95FF81162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 129";
+		rLen = 0;
+		rLoc = 4242;
+		rType = 0;
+		vrLen = 1283;
+		vrLoc = 3691;
+	};
+	8C95FF82162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 47";
+		rLen = 703;
+		rLoc = 1521;
+		rType = 0;
+		vrLen = 1950;
+		vrLoc = 1305;
+	};
+	8C95FF83162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 163";
+		rLen = 0;
+		rLoc = 5539;
+		rType = 0;
+		vrLen = 2373;
+		vrLoc = 3279;
+	};
+	8C95FF86162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD99161593BD00016A95 /* FinderHook.h */;
+		rLen = 0;
+		rLoc = 9223372036854775807;
+		rType = 0;
+	};
+	8C95FF87162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD99161593BD00016A95 /* FinderHook.h */;
+		name = "FinderHook.h: 12";
+		rLen = 0;
+		rLoc = 188;
+		rType = 0;
+		vrLen = 212;
+		vrLoc = 0;
+	};
+	8C95FF88162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FF89162D59A0003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FFAF162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 129";
+		rLen = 0;
+		rLoc = 4242;
+		rType = 0;
+		vrLen = 1283;
+		vrLoc = 3691;
+	};
+	8C95FFB0162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 134";
+		rLen = 0;
+		rLoc = 4428;
+		rType = 0;
+		vrLen = 991;
+		vrLoc = 3489;
+	};
+	8C95FFB1162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD99161593BD00016A95 /* FinderHook.h */;
+		name = "FinderHook.h: 17";
+		rLen = 0;
+		rLoc = 212;
+		rType = 0;
+		vrLen = 212;
+		vrLoc = 0;
+	};
+	8C95FFB2162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 163";
+		rLen = 0;
+		rLoc = 5539;
+		rType = 0;
+		vrLen = 2373;
+		vrLoc = 3279;
+	};
+	8C95FFB3162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 150";
+		rLen = 0;
+		rLoc = 5140;
+		rType = 0;
+		vrLen = 2275;
+		vrLoc = 3275;
+	};
+	8C95FFB4162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FFB5162D771B003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FFC0162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 138";
+		rLen = 0;
+		rLoc = 4428;
+		rType = 0;
+		vrLen = 1182;
+		vrLoc = 2480;
+	};
+	8C95FFC1162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD99161593BD00016A95 /* FinderHook.h */;
+		name = "FinderHook.h: 17";
+		rLen = 0;
+		rLoc = 212;
+		rType = 0;
+		vrLen = 212;
+		vrLoc = 0;
+	};
+	8C95FFC2162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		rLen = 0;
+		rLoc = 9223372036854775930;
+		rType = 0;
+	};
+	8C95FFC3162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 88";
+		rLen = 0;
+		rLoc = 2528;
+		rType = 0;
+		vrLen = 2433;
+		vrLoc = 2334;
+	};
+	8C95FFC4162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 154";
+		rLen = 0;
+		rLoc = 5140;
+		rType = 0;
+		vrLen = 2522;
+		vrLoc = 2482;
+	};
+	8C95FFC5162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C95FFC6162D7A3D003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C95FFD516301448003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 138";
+		rLen = 0;
+		rLoc = 4428;
+		rType = 0;
+		vrLen = 1128;
+		vrLoc = 2480;
+	};
+	8C95FFD616301448003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 89";
+		rLen = 0;
+		rLoc = 2534;
+		rType = 0;
+		vrLen = 1132;
+		vrLoc = 2480;
+	};
+	8C95FFD716301448003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 154";
+		rLen = 0;
+		rLoc = 5140;
+		rType = 0;
+		vrLen = 1725;
+		vrLoc = 2482;
+	};
+	8C95FFD816301448003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 91";
+		rLen = 15;
+		rLoc = 2654;
+		rType = 0;
+		vrLen = 1837;
+		vrLoc = 2334;
+	};
+	8C95FFD916301448003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 763;
+		vrLoc = 209;
+	};
+	8C95FFDA16301448003BB463 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 16";
+		rLen = 0;
+		rLoc = 257;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F6921622D145002D2135 /* IconCache.h */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {1823, 542}}";
+			sepNavSelRange = "{257, 0}";
+			sepNavVisRange = "{0, 359}";
+			sepNavWindowFrame = "{{15, 112}, {1882, 941}}";
+		};
+	};
+	8C99F6931622D145002D2135 /* IconCache.m */ = {
+		uiCtxt = {
+			sepNavIntBoundsRect = "{{0, 0}, {1823, 1040}}";
+			sepNavSelRange = "{1312, 0}";
+			sepNavVisRange = "{209, 763}";
+			sepNavWindowFrame = "{{38, 91}, {1882, 941}}";
+		};
+	};
+	8C99F6D716241A83002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9C161593BD00016A95 /* logger.h */;
+		name = "logger.h: 10";
+		rLen = 0;
+		rLoc = 178;
+		rType = 0;
+		vrLen = 178;
+		vrLoc = 0;
+	};
+	8C99F6D816241A83002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD99161593BD00016A95 /* FinderHook.h */;
+		name = "FinderHook.h: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 212;
+		vrLoc = 0;
+	};
+	8C99F6D916241A83002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 990;
+		vrLoc = 3;
+	};
+	8C99F6DA16241A83002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 12";
+		rLen = 0;
+		rLoc = 255;
+		rType = 0;
+		vrLen = 207;
+		vrLoc = 0;
+	};
+	8C99F6DB16241A83002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Expected ')' before '{' token";
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		rLen = 1;
+		rLoc = 21;
+		rType = 1;
+	};
+	8C99F6E11626C51C002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 18";
+		rLen = 0;
+		rLoc = 312;
+		rType = 0;
+		vrLen = 565;
+		vrLoc = 0;
+	};
+	8C99F70916280074002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Instance variable 'dictionary_' accessed in class method";
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		rLen = 1;
+		rLoc = 44;
+		rType = 1;
+	};
+	8C99F70A16280074002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 30";
+		rLen = 0;
+		rLoc = 697;
+		rType = 0;
+		vrLen = 298;
+		vrLoc = 293;
+	};
+	8C99F73216280B63002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 30";
+		rLen = 0;
+		rLoc = 651;
+		rType = 0;
+		vrLen = 256;
+		vrLoc = 184;
+	};
+	8C99F73316280B63002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Expected specifier-qualifier-list before 'property'";
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		rLen = 1;
+		rLoc = 13;
+		rType = 1;
+	};
+	8C99F73416280B63002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 286;
+		vrLoc = 0;
+	};
+	8C99F75E16280FF3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 17";
+		rLen = 0;
+		rLoc = 290;
+		rType = 0;
+		vrLen = 251;
+		vrLoc = 76;
+	};
+	8C99F75F16280FF3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Passing argument 1 of 'removeObjectForKey:' makes pointer from integer without a cast";
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		rLen = 1;
+		rLoc = 44;
+		rType = 1;
+	};
+	8C99F76016280FF3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 33";
+		rLen = 0;
+		rLoc = 697;
+		rType = 0;
+		vrLen = 263;
+		vrLoc = 344;
+	};
+	8C99F79516282FF8002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "Stray '\\342' in program";
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		rLen = 0;
+		rLoc = 111;
+		rType = 1;
+	};
+	8C99F7AA16283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 41";
+		rLen = 0;
+		rLoc = 1155;
+		rType = 0;
+		vrLen = 589;
+		vrLoc = 209;
+	};
+	8C99F7AB16283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7AC16283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7B116283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 27";
+		rLen = 0;
+		rLoc = 785;
+		rType = 0;
+		vrLen = 1795;
+		vrLoc = 644;
+	};
+	8C99F7B416283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 34";
+		rLen = 0;
+		rLoc = 785;
+		rType = 0;
+		vrLen = 891;
+		vrLoc = 0;
+	};
+	8C99F7B516283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1122;
+		vrLoc = 0;
+	};
+	8C99F7B816283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 17";
+		rLen = 0;
+		rLoc = 290;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7B916283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7BC16283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BD16283292002D2135 /* NSValue.h */;
+		rLen = 1;
+		rLoc = 35;
+		rType = 1;
+	};
+	8C99F7BD16283292002D2135 /* NSValue.h */ = {
+		isa = PBXFileReference;
+		lastKnownFileType = sourcecode.c.h;
+		name = NSValue.h;
+		path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSValue.h;
+		sourceTree = "<absolute>";
+	};
+	8C99F7BE16283292002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F7BF16283292002D2135 /* NSValue.h */ = {
+		isa = PBXFileReference;
+		lastKnownFileType = sourcecode.c.h;
+		name = NSValue.h;
+		path = /Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSValue.h;
+		sourceTree = "<absolute>";
+	};
+	8C99F7C4162832E3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7C5162832E3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 27";
+		rLen = 0;
+		rLoc = 785;
+		rType = 0;
+		vrLen = 1795;
+		vrLoc = 644;
+	};
+	8C99F7C6162832E3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1122;
+		vrLoc = 0;
+	};
+	8C99F7C7162832E3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7C8162832E3002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BD16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F7C916283348002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7CA16283348002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 55";
+		rLen = 0;
+		rLoc = 1724;
+		rType = 0;
+		vrLen = 2171;
+		vrLoc = 1717;
+	};
+	8C99F7CB16283348002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1122;
+		vrLoc = 0;
+	};
+	8C99F7CC16283348002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7CD16283348002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F7D316283395002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7D416283395002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1122;
+		vrLoc = 0;
+	};
+	8C99F7D516283395002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 52";
+		rLen = 0;
+		rLoc = 816;
+		rType = 0;
+		vrLen = 1083;
+		vrLoc = 76;
+	};
+	8C99F7D616283395002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 34";
+		rLen = 0;
+		rLoc = 1014;
+		rType = 0;
+		vrLen = 1923;
+		vrLoc = 410;
+	};
+	8C99F7D716283395002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7D816283395002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F7D9162833DB002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7DA162833DB002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 789;
+		rType = 0;
+		vrLen = 1083;
+		vrLoc = 76;
+	};
+	8C99F7DB162833DB002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 34";
+		rLen = 0;
+		rLoc = 1014;
+		rType = 0;
+		vrLen = 1923;
+		vrLoc = 410;
+	};
+	8C99F7DC162833DB002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7DD162833DB002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F7E91628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD99161593BD00016A95 /* FinderHook.h */;
+		name = "FinderHook.h: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 212;
+		vrLoc = 0;
+	};
+	8C99F7EA1628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 53";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 602;
+		vrLoc = 552;
+	};
+	8C99F7EB1628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7EC1628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 27";
+		rLen = 12;
+		rLoc = 749;
+		rType = 0;
+		vrLen = 971;
+		vrLoc = 44;
+	};
+	8C99F7ED1628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 468;
+		vrLoc = 0;
+	};
+	8C99F7EE1628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 468;
+		vrLoc = 0;
+	};
+	8C99F7F11628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 32";
+		rLen = 0;
+		rLoc = 503;
+		rType = 0;
+		vrLen = 528;
+		vrLoc = 0;
+	};
+	8C99F7F21628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 32";
+		rLen = 0;
+		rLoc = 503;
+		rType = 0;
+		vrLen = 528;
+		vrLoc = 0;
+	};
+	8C99F7F31628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 34";
+		rLen = 0;
+		rLoc = 1014;
+		rType = 0;
+		vrLen = 1923;
+		vrLoc = 410;
+	};
+	8C99F7F41628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 116";
+		rLen = 0;
+		rLoc = 4210;
+		rType = 0;
+		vrLen = 2170;
+		vrLoc = 2179;
+	};
+	8C99F7F51628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1083;
+		vrLoc = 76;
+	};
+	8C99F7F61628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F7F71628351A002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F80716283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 468;
+		vrLoc = 0;
+	};
+	8C99F80816283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 116";
+		rLen = 0;
+		rLoc = 4210;
+		rType = 0;
+		vrLen = 2170;
+		vrLoc = 2179;
+	};
+	8C99F80916283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 106";
+		rLen = 0;
+		rLoc = 3894;
+		rType = 0;
+		vrLen = 2099;
+		vrLoc = 0;
+	};
+	8C99F80A16283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 36";
+		rLen = 0;
+		rLoc = 528;
+		rType = 0;
+		vrLen = 528;
+		vrLoc = 0;
+	};
+	8C99F80B16283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1083;
+		vrLoc = 76;
+	};
+	8C99F80C16283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F80D16283601002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F84F16283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 472;
+		vrLoc = 0;
+	};
+	8C99F85016283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 106";
+		rLen = 0;
+		rLoc = 3894;
+		rType = 0;
+		vrLen = 2099;
+		vrLoc = 0;
+	};
+	8C99F85116283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 106";
+		rLen = 0;
+		rLoc = 3894;
+		rType = 0;
+		vrLen = 2517;
+		vrLoc = 2432;
+	};
+	8C99F85216283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 36";
+		rLen = 0;
+		rLoc = 528;
+		rType = 0;
+		vrLen = 534;
+		vrLoc = 0;
+	};
+	8C99F85316283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F7BF16283292002D2135 /* NSValue.h */;
+		name = "NSValue.h: 20";
+		rLen = 0;
+		rLoc = 463;
+		rType = 0;
+		vrLen = 1777;
+		vrLoc = 566;
+	};
+	8C99F85416283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 38";
+		rLen = 0;
+		rLoc = 615;
+		rType = 0;
+		vrLen = 1083;
+		vrLoc = 76;
+	};
+	8C99F85516283CE1002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F87C1628406A002D2135 /* PBXBookmark */ = {
+		isa = PBXBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+	};
+	8C99F89616284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9B161593BD00016A95 /* logger.c */;
+		name = "logger.c: 1";
+		rLen = 0;
+		rLoc = 0;
+		rType = 0;
+		vrLen = 468;
+		vrLoc = 0;
+	};
+	8C99F89716284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 53";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 638;
+		vrLoc = 516;
+	};
+	8C99F89816284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 685;
+		vrLoc = 516;
+	};
+	8C99F89916284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 28";
+		rLen = 0;
+		rLoc = 452;
+		rType = 0;
+		vrLen = 1083;
+		vrLoc = 76;
+	};
+	8C99F89A16284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 61";
+		rLen = 0;
+		rLoc = 1056;
+		rType = 0;
+		vrLen = 1209;
+		vrLoc = 44;
+	};
+	8C99F89D16284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 24";
+		rLen = 0;
+		rLoc = 672;
+		rType = 0;
+		vrLen = 2099;
+		vrLoc = 0;
+	};
+	8C99F89E16284444002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8AB16284854002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 685;
+		vrLoc = 516;
+	};
+	8C99F8AC16284854002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 99";
+		rLen = 0;
+		rLoc = 3293;
+		rType = 0;
+		vrLen = 2299;
+		vrLoc = 1954;
+	};
+	8C99F8AD16284854002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 61";
+		rLen = 0;
+		rLoc = 1056;
+		rType = 0;
+		vrLen = 1209;
+		vrLoc = 44;
+	};
+	8C99F8AE16284854002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8BD162849F3002D2135 /* FinderHook.m:112 */ = {
+		isa = PBXFileBreakpoint;
+		actions = (
+		);
+		breakpointStyle = 0;
+		continueAfterActions = 0;
+		countType = 0;
+		delayBeforeContinue = 0;
+		fileReference = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		functionName = "-HOOK_drawIconWithFrame:";
+		hitCount = 0;
+		ignoreCount = 0;
+		lineNumber = 112;
+		location = LiferayFinderCore;
+		modificationTime = 371740467.865518;
+		originalNumberOfMultipleMatches = 1;
+		state = 0;
+	};
+	8C99F8C716284A2D002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 685;
+		vrLoc = 516;
+	};
+	8C99F8C816284A2D002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 101";
+		rLen = 0;
+		rLoc = 3293;
+		rType = 0;
+		vrLen = 2592;
+		vrLoc = 2478;
+	};
+	8C99F8C916284A2D002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 107";
+		rLen = 0;
+		rLoc = 3293;
+		rType = 0;
+		vrLen = 2484;
+		vrLoc = 2595;
+	};
+	8C99F8CA16284A2D002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 61";
+		rLen = 0;
+		rLoc = 1056;
+		rType = 0;
+		vrLen = 1209;
+		vrLoc = 44;
+	};
+	8C99F8CB16284A2D002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8D816284A7B002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 685;
+		vrLoc = 516;
+	};
+	8C99F8D916284A7B002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 109";
+		rLen = 0;
+		rLoc = 3615;
+		rType = 0;
+		vrLen = 2394;
+		vrLoc = 2595;
+	};
+	8C99F8DA16284A7B002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 61";
+		rLen = 0;
+		rLoc = 1056;
+		rType = 0;
+		vrLen = 1209;
+		vrLoc = 44;
+	};
+	8C99F8DB16284A7B002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8EA16284AB5002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 685;
+		vrLoc = 516;
+	};
+	8C99F8EB16284AB5002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 106";
+		rLen = 0;
+		rLoc = 3293;
+		rType = 0;
+		vrLen = 2394;
+		vrLoc = 2595;
+	};
+	8C99F8EC16284AB5002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 61";
+		rLen = 0;
+		rLoc = 1056;
+		rType = 0;
+		vrLen = 1209;
+		vrLoc = 44;
+	};
+	8C99F8ED16284AB5002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8FC16284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 51";
+		rLen = 0;
+		rLoc = 818;
+		rType = 0;
+		vrLen = 685;
+		vrLoc = 516;
+	};
+	8C99F8FD16284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8FE16284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 13";
+		rLen = 0;
+		rLoc = 199;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F8FF16284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 106";
+		rLen = 0;
+		rLoc = 3293;
+		rType = 0;
+		vrLen = 1840;
+		vrLoc = 1390;
+	};
+	8C99F90016284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 109";
+		rLen = 0;
+		rLoc = 3615;
+		rType = 0;
+		vrLen = 2148;
+		vrLoc = 2303;
+	};
+	8C99F90116284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 61";
+		rLen = 0;
+		rLoc = 1056;
+		rType = 0;
+		vrLen = 1209;
+		vrLoc = 44;
+	};
+	8C99F90216284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 71";
+		rLen = 0;
+		rLoc = 1312;
+		rType = 0;
+		vrLen = 1180;
+		vrLoc = 209;
+	};
+	8C99F90316284ECF002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6921622D145002D2135 /* IconCache.h */;
+		name = "IconCache.h: 18";
+		rLen = 0;
+		rLoc = 289;
+		rType = 0;
+		vrLen = 359;
+		vrLoc = 0;
+	};
+	8C99F90D16284F81002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F6931622D145002D2135 /* IconCache.m */;
+		name = "IconCache.m: 55";
+		rLen = 0;
+		rLoc = 1155;
+		rType = 0;
+		vrLen = 235;
+		vrLoc = 251;
+	};
+	8C99F90E16284F81002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C99F90F16284F81002D2135 /* Finder.h */;
+		name = "Finder.h: 796";
+		rLen = 0;
+		rLoc = 25014;
+		rType = 0;
+		vrLen = 426;
+		vrLoc = 24765;
+	};
+	8C99F90F16284F81002D2135 /* Finder.h */ = {
+		isa = PBXFileReference;
+		lastKnownFileType = sourcecode.c.h;
+		name = Finder.h;
+		path = /Users/svp/Liferay/trunk/LiferayFinderCore/Finder/Finder.h;
+		sourceTree = "<absolute>";
+	};
+	8C99F91016284F81002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		comments = "'IconCache' may not respond to '-registerIcon:'";
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		rLen = 1;
+		rLoc = 26;
+		rType = 1;
+	};
+	8C99F91116284F81002D2135 /* PBXTextBookmark */ = {
+		isa = PBXTextBookmark;
+		fRef = 8C37DD9A161593BD00016A95 /* FinderHook.m */;
+		name = "FinderHook.m: 27";
+		rLen = 0;
+		rLoc = 720;
+		rType = 0;
+		vrLen = 398;
+		vrLoc = 525;
+	};
+	8D57630D048677EA00EA77CD /* LiferayFinderCore */ = {
+		activeExec = 0;
+	};
+}

+ 14 - 0
shell_integration/MacOSX/LiferayNativityFinder/LiferayNativityFinder.xcodeproj/xcuserdata/mackie.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>8D57630D048677EA00EA77CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 29 - 0
shell_integration/MacOSX/LiferayNativityFinder/MenuManager.h

@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Foundation/Foundation.h>
+
+@class TContextMenu;
+struct TFENodeVector;
+
+@interface MenuManager : NSObject
+
+@property (nonatomic, strong) NSMutableArray* menuItems;
+
++ (MenuManager*)sharedInstance;
+
+- (void)addItemsToMenu:(TContextMenu*)menu forFiles:(NSArray*)files;
+- (NSArray*)pathsForNodes:(const struct TFENodeVector*)nodes;
+
+@end

+ 184 - 0
shell_integration/MacOSX/LiferayNativityFinder/MenuManager.m

@@ -0,0 +1,184 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import "MenuManager.h"
+#import "Finder/Finder.h"
+#import "RequestManager.h"
+
+@implementation MenuManager
+
+static MenuManager* sharedInstance = nil;
+
++ (MenuManager*)sharedInstance
+{
+	@synchronized(self)
+	{
+		if (sharedInstance == nil)
+		{
+			sharedInstance = [[self alloc] init];
+		}
+	}
+	return sharedInstance;
+}
+
+- init
+{
+	return [super init];
+}
+
+- (void)addChildrenSubMenuItems:(NSMenuItem*)parentMenuItem withChildren:(NSArray*)menuItemsDictionaries forFiles:(NSArray*)files
+{
+	NSMenu* menu = [[NSMenu alloc] init];
+
+	for (int i = 0; i < [menuItemsDictionaries count]; ++i)
+	{
+		NSDictionary* menuItemDictionary = [menuItemsDictionaries objectAtIndex:i];
+
+		NSString* submenuTitle = [menuItemDictionary objectForKey:@"title"];
+		BOOL enabled = [[menuItemDictionary objectForKey:@"enabled"] boolValue];
+		NSString* uuid = [menuItemDictionary objectForKey:@"uuid"];
+		NSArray* childrenSubMenuItems = (NSArray*)[menuItemDictionary objectForKey:@"contextMenuItems"];
+
+		if ([submenuTitle isEqualToString:@"_SEPARATOR_"])
+		{
+			[menu addItem:[NSMenuItem separatorItem]];
+		}
+		else if (childrenSubMenuItems != nil && [childrenSubMenuItems count] != 0)
+		{
+			NSMenuItem* submenuItem = [menu addItemWithTitle:submenuTitle action:nil keyEquivalent:@""];
+
+			[self addChildrenSubMenuItems:submenuItem withChildren:childrenSubMenuItems forFiles:files];
+		}
+		else
+		{
+			[self createActionMenuItemIn:menu withTitle:submenuTitle withIndex:i enabled:enabled withUuid:uuid forFiles:files];
+		}
+	}
+
+	[parentMenuItem setSubmenu:menu];
+
+	[menu release];
+}
+
+- (void)addItemsToMenu:(TContextMenu*)menu forFiles:(NSArray*)files
+{
+	NSArray* menuItemsArray = [[RequestManager sharedInstance] menuItemsForFiles:files];
+
+	if (menuItemsArray == nil)
+	{
+		return;
+	}
+
+	if ([menuItemsArray count] == 0)
+	{
+		return;
+	}
+
+	NSInteger menuIndex = 4;
+
+	BOOL hasSeparatorBefore = [[menu itemAtIndex:menuIndex - 1] isSeparatorItem];
+
+	if (!hasSeparatorBefore)
+	{
+		[menu insertItem:[NSMenuItem separatorItem] atIndex:menuIndex];
+	}
+
+	for (int i = 0; i < [menuItemsArray count]; ++i)
+	{
+		NSDictionary* menuItemDictionary = [menuItemsArray objectAtIndex:i];
+
+		NSString* mainMenuTitle = [menuItemDictionary objectForKey:@"title"];
+
+		if ([mainMenuTitle isEqualToString:@""])
+		{
+			continue;
+		}
+
+		menuIndex++;
+
+		BOOL enabled = [[menuItemDictionary objectForKey:@"enabled"] boolValue];
+		NSString* uuid = [menuItemDictionary objectForKey:@"uuid"];
+		NSArray* childrenSubMenuItems = (NSArray*)[menuItemDictionary objectForKey:@"contextMenuItems"];
+
+		if (childrenSubMenuItems != nil && [childrenSubMenuItems count] != 0)
+		{
+			NSMenuItem* mainMenuItem = [menu insertItemWithTitle:mainMenuTitle action:nil keyEquivalent:@"" atIndex:menuIndex];
+
+			[self addChildrenSubMenuItems:mainMenuItem withChildren:childrenSubMenuItems forFiles:files];
+		}
+		else
+		{
+			[self createActionMenuItemIn:menu withTitle:mainMenuTitle withIndex:menuIndex enabled:enabled withUuid:uuid forFiles:files];
+		}
+	}
+
+	BOOL hasSeparatorAfter = [[menu itemAtIndex:menuIndex + 1] isSeparatorItem];
+
+	if (!hasSeparatorAfter)
+	{
+		[menu insertItem:[NSMenuItem separatorItem] atIndex:menuIndex + 1];
+	}
+}
+
+- (void)createActionMenuItemIn:(NSMenu*)menu withTitle:(NSString*)title withIndex:(NSInteger*)index enabled:(BOOL)enabled withUuid:(NSString*)uuid forFiles:(NSArray*)files
+{
+	NSMenuItem* mainMenuItem = [menu insertItemWithTitle:title action:@selector(menuItemClicked:) keyEquivalent:@"" atIndex:index];
+
+	if (enabled)
+	{
+		[mainMenuItem setTarget:self];
+	}
+
+	NSDictionary* menuActionDictionary = [[NSMutableDictionary alloc] init];
+	[menuActionDictionary setValue:uuid forKey:@"uuid"];
+	NSMutableArray* filesArray = [files copy];
+	[menuActionDictionary setValue:filesArray forKey:@"files"];
+
+	[mainMenuItem setRepresentedObject:menuActionDictionary];
+
+	[filesArray release];
+	[menuActionDictionary release];
+}
+
+- (void)menuItemClicked:(id)param
+{
+	[[RequestManager sharedInstance] menuItemClicked:[param representedObject]];
+}
+
+- (NSArray*)pathsForNodes:(const struct TFENodeVector*)nodes
+{
+	struct TFENode* start = nodes->_M_impl._M_start;
+	struct TFENode* end = nodes->_M_impl._M_finish;
+
+	int count = end - start;
+
+	NSMutableArray* selectedItems = [[NSMutableArray alloc] initWithCapacity:count];
+	struct TFENode* current;
+
+	for (current = start; current < end; ++current)
+	{
+		FINode* node = (FINode*)[NSClassFromString(@"FINode") nodeFromNodeRef:current->fNodeRef];
+
+		NSString* path = [[node previewItemURL] path];
+
+		if (path)
+		{
+			[selectedItems addObject:path];
+		}
+	}
+
+	return [selectedItems autorelease];
+}
+
+@end

+ 48 - 0
shell_integration/MacOSX/LiferayNativityFinder/RequestManager.h

@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import <Foundation/Foundation.h>
+#import "GCDAsyncSocket.h"
+#import "RequestManager.h"
+
+@interface RequestManager : NSObject
+{
+	dispatch_queue_t _listenQueue;
+	dispatch_queue_t _callbackQueue;
+	
+	GCDAsyncSocket* _socket;
+
+	NSMutableArray* _connectedListenSockets;
+	NSMutableArray* _connectedCallbackSockets;
+	NSMutableDictionary* _callbackMsgs;
+
+	NSMutableArray* _requestQueue;
+	
+	NSString* _filterFolder;
+
+	BOOL _isRunning;
+	BOOL _isConnected;
+}
+
+@property (nonatomic, retain) NSString* filterFolder;
+
++ (RequestManager*)sharedInstance;
+
+- (void)askOnSocket:(NSString*)path;
+- (void)askForIcon:(NSString*)path;
+- (void)menuItemClicked:(NSDictionary*)actionDictionary;
+- (NSArray*)menuItemsForFiles:(NSArray*)files;
+- (void)start;
+
+@end

+ 170 - 0
shell_integration/MacOSX/LiferayNativityFinder/RequestManager.m

@@ -0,0 +1,170 @@
+/**
+ * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ */
+
+#import "ContentManager.h"
+#import "IconCache.h"
+#import "JSONKit.h"
+#import "RequestManager.h"
+
+#define READ_TAG 2422
+
+static RequestManager* sharedInstance = nil;
+
+@implementation RequestManager
+
+- (id)init
+{
+	if ((self = [super init]))
+	{
+		_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
+		
+		_filterFolder = nil;
+
+		_isRunning = NO;
+		_isConnected = NO;
+
+		[self start];
+	}
+
+	return self;
+}
+
+- (void)dealloc
+{
+	[_socket setDelegate:nil delegateQueue:NULL];
+	[_socket disconnect];
+	[_socket release];
+
+	// dispatch_release(_listenQueue);
+
+	// [_callbackMsgs release];
+
+	// [_filterFolder release];
+
+	sharedInstance = nil;
+
+	[super dealloc];
+}
+
++ (RequestManager*)sharedInstance
+{
+	@synchronized(self)
+	{
+		if (sharedInstance == nil)
+		{
+			sharedInstance = [[self alloc] init];
+		}
+	}
+
+	return sharedInstance;
+}
+
+
+- (void)askOnSocket:(NSString*)path
+{
+	NSLog(@"XX on Socket for %@", path);
+	NSData* data = [[NSString stringWithFormat:@"RETRIEVE_FILE_STATUS:%@\n", path] dataUsingEncoding:NSUTF8StringEncoding];
+	[_socket writeData:data withTimeout:5 tag:4711];
+	
+	NSData* stop = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
+	[_socket readDataToData:stop withTimeout:-1 tag:READ_TAG];
+}
+
+
+- (void)askForIcon:(NSString*)path
+{
+	if( _isConnected ) {
+		[self askOnSocket:path];
+	} else {
+		[_requestQueue addObject:path];
+	}
+}
+
+
+- (void)socket:(GCDAsyncSocket*)socket didConnectToHost:(NSString*)host port:(UInt16)port
+{
+	// [socket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:0];
+	
+	NSLog( @"Connected to host successfully!");
+	_isConnected = YES;
+	
+	if( [_requestQueue count] > 0 ) {
+		NSLog( @"We have to empty the queue");
+		for( NSString *path in _requestQueue ) {
+			[self askOnSocket:path];
+		}
+	}
+}
+
+
+- (void)socket:(GCDAsyncSocket*)socket didReadData:(NSData*)data withTag:(long)tag
+{
+	
+	if( tag == READ_TAG) {
+	NSString* a1 = [NSString stringWithUTF8String:[data bytes]];
+	
+	NSString *answer = [a1 stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
+    NSLog(@"READ from socket (%ld): AA%@OO", tag, answer);
+	
+	if( answer != nil ) {
+		NSArray *chunks = [answer componentsSeparatedByString: @":"];
+		// FIXME: Check if chunks[0] equals "STATUS"
+		if( [chunks count] > 0 && [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) {
+			ContentManager *contentman = [ContentManager sharedInstance];
+			[contentman setResultForPath:[chunks objectAtIndex:2] result:[chunks objectAtIndex:1]];
+		}
+	}
+	}
+}
+
+- (NSTimeInterval)socket:(GCDAsyncSocket*)socket shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length
+{
+	// Called if a read operation has reached its timeout without completing.
+	return 0.0;
+}
+
+- (void)socketDidDisconnect:(GCDAsyncSocket*)socket withError:(NSError*)err
+{
+	if ([_connectedListenSockets containsObject:socket])
+	{
+		[_connectedListenSockets removeObject:socket];
+
+		[[ContentManager sharedInstance] enableFileIcons:false];
+	}
+
+	if ([_connectedCallbackSockets containsObject:socket])
+	{
+		[_connectedCallbackSockets removeObject:socket];
+	}
+}
+
+
+- (void)start
+{
+	if (!_isRunning)
+	{
+		NSLog(@"Connect Socket!");
+		NSError *err = nil;
+		if (![_socket connectToHost:@"localhost" onPort:33001 error:&err]) // Asynchronous!
+		{
+			// If there was an error, it's likely something like "already connected" or "no delegate set"
+			NSLog(@"I goofed: %@", err);
+		}
+		NSLog(@"Socket Connected!");
+		
+		 _isRunning = YES;
+	}
+}
+
+@end

+ 2 - 0
shell_integration/MacOSX/LiferayNativityInjector/English.lproj/InfoPlist.strings

@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+

+ 59 - 0
shell_integration/MacOSX/LiferayNativityInjector/Info.plist

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIdentifier</key>
+	<string>com.liferay.nativity</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>LiferayNativityInjector</string>
+	<key>CFBundlePackageType</key>
+	<string>osax</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0.2</string>
+	<key>CFBundleSignature</key>
+	<string>NVTY</string>
+	<key>CFBundleVersion</key>
+	<string>1.0.2</string>
+	<key>OSAScriptingDefinition</key>
+	<string>LiferayNativityInjector.sdef</string>
+	<key>OSAXHandlers</key>
+	<dict>
+		<key>Events</key>
+		<dict>
+			<key>NVTYload</key>
+			<dict>
+				<key>Handler</key>
+				<string>HandleLoadEvent</string>
+				<key>ThreadSafe</key>
+				<false/>
+				<key>Context</key>
+				<string>Process</string>
+			</dict>
+			<key>NVTYunld</key>
+			<dict>
+				<key>Handler</key>
+				<string>HandleUnloadEvent</string>
+				<key>ThreadSafe</key>
+				<false/>
+				<key>Context</key>
+				<string>Process</string>
+			</dict>
+			<key>NVTYlded</key>
+			<dict>
+				<key>Handler</key>
+				<string>HandleLoadedEvent</string>
+				<key>ThreadSafe</key>
+				<false/>
+				<key>Context</key>
+				<string>Process</string>
+			</dict>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 35 - 0
shell_integration/MacOSX/LiferayNativityInjector/LNStandardVersionComparator.h

@@ -0,0 +1,35 @@
+//
+// LNStandardVersionComparator.h
+// Sparkle
+//
+// Created by Andy Matuschak on 12/21/07.
+// Copyright 2007 Andy Matuschak. All rights reserved.
+//
+
+#ifndef LNSTANDARDVERSIONCOMPARATOR_H
+#define LNSTANDARDVERSIONCOMPARATOR_H
+
+#import "LNVersionComparisonProtocol.h"
+
+/*!
+    @class
+    @abstract    Sparkle's default version comparator.
+        @discussion  This comparator is adapted from MacPAD, by Kevin Ballard. It's "dumb" in that it does essentially string comparison, in components split by character type.
+ */
+@interface LNStandardVersionComparator : NSObject<LNVersionComparison> { }
+
+/*!
+    @method
+    @abstract   Returns a singleton instance of the comparator.
+ */
++(LNStandardVersionComparator*)defaultComparator;
+
+/*!
+        @method
+        @abstract	Compares version strings through textual analysis.
+        @discussion	See the implementation for more details.
+ */
+-(NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB;
+@end
+
+#endif

+ 158 - 0
shell_integration/MacOSX/LiferayNativityInjector/LNStandardVersionComparator.m

@@ -0,0 +1,158 @@
+//
+// LNStandardVersionComparator.m
+// Sparkle
+//
+// Created by Andy Matuschak on 12/21/07.
+// Copyright 2007 Andy Matuschak. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "LNStandardVersionComparator.h"
+
+@implementation LNStandardVersionComparator
+
++(LNStandardVersionComparator*) defaultComparator {
+  static LNStandardVersionComparator* defaultComparator = nil;
+
+  if (defaultComparator == nil) defaultComparator = [[LNStandardVersionComparator alloc] init];
+  return defaultComparator;
+}
+
+typedef enum {
+  kNumberType,
+  kStringType,
+  kPeriodType
+} SUCharacterType;
+
+-(SUCharacterType) typeOfCharacter:(NSString*)character {
+  if ([character isEqualToString:@"."]) {
+    return kPeriodType;
+  } else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[character characterAtIndex:0]]) {
+    return kNumberType;
+  } else {
+    return kStringType;
+  }
+}
+
+-(NSArray*) splitVersionString:(NSString*)version {
+  NSString* character;
+  NSMutableString* s;
+  NSUInteger i, n;
+  SUCharacterType oldType, newType;
+  NSMutableArray* parts = [NSMutableArray array];
+
+  if ([version length] == 0) {
+    // Nothing to do here
+    return parts;
+  }
+  s = [[[version substringToIndex:1] mutableCopy] autorelease];
+  oldType = [self typeOfCharacter:s];
+  n = [version length] - 1;
+  for (i = 1; i <= n; ++i) {
+    character = [version substringWithRange:NSMakeRange(i, 1)];
+    newType = [self typeOfCharacter:character];
+    if ((oldType != newType) || (oldType == kPeriodType)) {
+      // We've reached a new segment
+      NSString* aPart = [[[NSString alloc] initWithString:s] autorelease];
+      [parts addObject:aPart];
+      [s setString:character];
+    } else {
+      // Add character to string and continue
+      [s appendString:character];
+    }
+    oldType = newType;
+  }
+
+  // Add the last part onto the array
+  [parts addObject:[NSString stringWithString:s]];
+  return parts;
+}
+
+-(NSComparisonResult) compareVersion:(NSString*)versionA toVersion:(NSString*)versionB;
+{
+  NSArray* partsA = [self splitVersionString:versionA];
+  NSArray* partsB = [self splitVersionString:versionB];
+
+  NSString* partA, * partB;
+  NSUInteger i, n;
+  int intA, intB;
+  SUCharacterType typeA, typeB;
+
+  n = MIN([partsA count], [partsB count]);
+  for (i = 0; i < n; ++i) {
+    partA = [partsA objectAtIndex:i];
+    partB = [partsB objectAtIndex:i];
+
+    typeA = [self typeOfCharacter:partA];
+    typeB = [self typeOfCharacter:partB];
+
+    // Compare types
+    if (typeA == typeB) {
+      // Same type; we can compare
+      if (typeA == kNumberType) {
+        intA = [partA intValue];
+        intB = [partB intValue];
+        if (intA > intB) {
+          return NSOrderedDescending;
+        } else if (intA < intB) {
+          return NSOrderedAscending;
+        }
+      } else if (typeA == kStringType) {
+        NSComparisonResult result = [partA compare:partB];
+        if (result != NSOrderedSame) {
+          return result;
+        }
+      }
+    } else {
+      // Not the same type? Now we have to do some validity checking
+      if ((typeA != kStringType) && (typeB == kStringType)) {
+        // typeA wins
+        return NSOrderedDescending;
+      } else if ((typeA == kStringType) && (typeB != kStringType)) {
+        // typeB wins
+        return NSOrderedAscending;
+      } else {
+        // One is a number and the other is a period. The period is invalid
+        if (typeA == kNumberType) {
+          return NSOrderedDescending;
+        } else {
+          return NSOrderedAscending;
+        }
+      }
+    }
+  }
+  // The versions are equal up to the point where they both still have parts
+  // Lets check to see if one is larger than the other
+  if ([partsA count] != [partsB count]) {
+    // Yep. Lets get the next part of the larger
+    // n holds the index of the part we want.
+    NSString* missingPart;
+    SUCharacterType missingType;
+    NSComparisonResult shorterResult, largerResult;
+
+    if ([partsA count] > [partsB count]) {
+      missingPart = [partsA objectAtIndex:n];
+      shorterResult = NSOrderedAscending;
+      largerResult = NSOrderedDescending;
+    } else {
+      missingPart = [partsB objectAtIndex:n];
+      shorterResult = NSOrderedDescending;
+      largerResult = NSOrderedAscending;
+    }
+
+    missingType = [self typeOfCharacter:missingPart];
+    // Check the type
+    if (missingType == kStringType) {
+      // It's a string. Shorter version wins
+      return shorterResult;
+    } else {
+      // It's a number/period. Larger version wins
+      return largerResult;
+    }
+  }
+
+  // The 2 strings are identical
+  return NSOrderedSame;
+}
+
+@end

+ 27 - 0
shell_integration/MacOSX/LiferayNativityInjector/LNVersionComparisonProtocol.h

@@ -0,0 +1,27 @@
+//
+// LNVersionComparisonProtocol.h
+// Sparkle
+//
+// Created by Andy Matuschak on 12/21/07.
+// Copyright 2007 Andy Matuschak. All rights reserved.
+//
+
+#ifndef LNVERSIONCOMPARISONPROTOCOL_H
+#define LNVERSIONCOMPARISONPROTOCOL_H
+
+/*!
+    @protocol
+    @abstract    Implement this protocol to provide version comparison facilities for Sparkle.
+ */
+@protocol LNVersionComparison
+
+/*!
+    @method
+    @abstract   An abstract method to compare two version strings.
+    @discussion Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a, and NSOrderedSame if they are equivalent.
+ */
+-(NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB;
+
+@end
+
+#endif

+ 270 - 0
shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.m

@@ -0,0 +1,270 @@
+#import <Cocoa/Cocoa.h>
+
+#import "LNStandardVersionComparator.h"
+
+#define EXPORT __attribute__((visibility("default")))
+
+#define WAIT_FOR_APPLE_EVENT_TO_ENTER_HANDLER_IN_SECONDS 1.0
+#define FINDER_MIN_TESTED_VERSION @"10.7"
+#define FINDER_MAX_TESTED_VERSION @"10.8.5"
+#define LIFERAYNATIVITY_INJECTED_NOTIFICATION @"LiferayNativityInjectedNotification"
+
+EXPORT OSErr HandleLoadEvent(const AppleEvent* ev, AppleEvent* reply, long refcon);
+
+static NSString* globalLock = @"I'm the global lock to prevent concruent handler executions";
+
+// SIMBL-compatible interface
+@interface LiferayNativityShell : NSObject { }
+-(void) install;
+-(void) uninstall;
+@end
+
+// just a dummy class for locating our bundle
+@interface LiferayNativityInjector : NSObject { }
+@end
+
+@implementation LiferayNativityInjector { }
+@end
+
+static bool liferayNativityLoaded = false;
+static NSString* liferayNativityBundleName = @"LiferayNativityFinder";
+
+typedef struct {
+  NSString* location;
+} configuration;
+
+static OSErr AEPutParamString(AppleEvent* event, AEKeyword keyword, NSString* string) {
+  UInt8* textBuf;
+  CFIndex length, maxBytes, actualBytes;
+
+  length = CFStringGetLength((CFStringRef)string);
+  maxBytes = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8);
+  textBuf = malloc(maxBytes);
+  if (textBuf) {
+    CFStringGetBytes((CFStringRef)string, CFRangeMake(0, length), kCFStringEncodingUTF8, 0, true, (UInt8*)textBuf, maxBytes, &actualBytes);
+    OSErr err = AEPutParamPtr(event, keyword, typeUTF8Text, textBuf, actualBytes);
+    free(textBuf);
+    return err;
+  } else {
+    return memFullErr;
+  }
+}
+
+static void reportError(AppleEvent* reply, NSString* msg) {
+  NSLog(@"LiferayNativityInjector: %@", msg);
+  AEPutParamString(reply, keyErrorString, msg);
+}
+
+typedef enum {
+  InvalidBundleType,
+  LiferayNativityBundleType,
+} LNBundleType;
+
+static OSErr loadBundle(LNBundleType type, AppleEvent* reply, long refcon) {
+  bool isLoaded = false;
+  NSString* bundleName = nil;
+  NSString* targetAppName = nil;
+  NSString* versionCheckKey = nil;
+  NSString* maxVersion = nil;
+  NSString* minVersion = nil;
+
+  switch (type) {
+    case LiferayNativityBundleType:
+      isLoaded = liferayNativityLoaded;
+      bundleName = liferayNativityBundleName;
+      targetAppName = @"Finder";
+      versionCheckKey = @"LiferayNativityFinderVersionCheck";
+      maxVersion = FINDER_MAX_TESTED_VERSION;
+      minVersion = FINDER_MIN_TESTED_VERSION;
+      break;
+    default:
+      NSLog(@"Failed to load bundle for type %d", type);
+      return 8;
+
+      break;
+  }
+
+  if (isLoaded) {
+    NSLog(@"LiferayNativityInjector: %@ already loaded.", bundleName);
+    return noErr;
+  }
+
+  @try {
+    NSBundle* mainBundle = [NSBundle mainBundle];
+    if (!mainBundle) {
+      reportError(reply, [NSString stringWithFormat:@"Unable to locate main %@ bundle!", targetAppName]);
+      return 4;
+    }
+
+    NSString* mainVersion = [mainBundle objectForInfoDictionaryKey:@"CFBundleVersion"];
+    if (!mainVersion || ![mainVersion isKindOfClass:[NSString class]]) {
+      reportError(reply, [NSString stringWithFormat:@"Unable to determine %@ version!", targetAppName]);
+      return 5;
+    }
+
+    // future compatibility check
+    if (type == LiferayNativityBundleType) {
+      // in Dock we cannot use NSAlert and similar UI stuff - this would hang the Dock process and cause 100% CPU load
+      NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+      if ([defaults boolForKey:versionCheckKey]) {
+        LNStandardVersionComparator* comparator = [LNStandardVersionComparator defaultComparator];
+        if (([comparator compareVersion:mainVersion toVersion:maxVersion] == NSOrderedDescending) ||
+            ([comparator compareVersion:mainVersion toVersion:minVersion] == NSOrderedAscending)) {
+          NSAlert* alert = [NSAlert new];
+          [alert setMessageText:[NSString stringWithFormat:@"You have %@ version %@", targetAppName, mainVersion]];
+          [alert setInformativeText:[NSString stringWithFormat:@"But %@ was properly tested only with %@ versions in range %@ - %@\n\nYou have probably updated your system and %@ version got bumped by Apple developers.\n\nYou may expect a new LiferayNativity release soon.", bundleName, targetAppName, targetAppName, minVersion, maxVersion]];
+          [alert setShowsSuppressionButton:YES];
+          [alert addButtonWithTitle:@"Launch LiferayNativity anyway"];
+          [alert addButtonWithTitle:@"Cancel"];
+          NSInteger res = [alert runModal];
+          if ([[alert suppressionButton] state] == NSOnState) {
+            [defaults setBool:NO forKey:versionCheckKey];
+          }
+          if (res != NSAlertFirstButtonReturn) {
+            // cancel
+            return noErr;
+          }
+        }
+      }
+    }
+
+    NSBundle* liferayNativityInjectorBundle = [NSBundle bundleForClass:[LiferayNativityInjector class]];
+    NSString* liferayNativityLocation = [liferayNativityInjectorBundle pathForResource:bundleName ofType:@"bundle"];
+    NSBundle* pluginBundle = [NSBundle bundleWithPath:liferayNativityLocation];
+    if (!pluginBundle) {
+      reportError(reply, [NSString stringWithFormat:@"Unable to create bundle from path: %@ [%@]", liferayNativityLocation, liferayNativityInjectorBundle]);
+      return 2;
+    }
+
+    NSError* error;
+    if (![pluginBundle loadAndReturnError:&error]) {
+      reportError(reply, [NSString stringWithFormat:@"Unable to load bundle from path: %@ error: %@", liferayNativityLocation, [error localizedDescription]]);
+      return 6;
+    }
+
+    Class principalClass = [pluginBundle principalClass];
+    if (!principalClass) {
+      reportError(reply, [NSString stringWithFormat:@"Unable to retrieve principalClass for bundle: %@", pluginBundle]);
+      return 3;
+    }
+    id principalClassObject = NSClassFromString(NSStringFromClass(principalClass));
+    if ([principalClassObject respondsToSelector:@selector(install)]) {
+      NSLog(@"LiferayNativityInjector: Installing %@ ...", bundleName);
+      [principalClassObject install];
+    }
+
+    return noErr;
+  } @catch (NSException* exception) {
+    reportError(reply, [NSString stringWithFormat:@"Failed to load %@ with exception: %@", bundleName, exception]);
+  }
+
+  return 1;
+}
+
+static LNBundleType mainBundleType(AppleEvent* reply) {
+  @try {
+    NSBundle* mainBundle = [NSBundle mainBundle];
+    if (!mainBundle) {
+      reportError(reply, [NSString stringWithFormat:@"Unable to locate main bundle!"]);
+      return InvalidBundleType;
+    }
+
+    if ([[mainBundle bundleIdentifier] isEqualToString:@"com.apple.finder"]) {
+      return LiferayNativityBundleType;
+    }
+  } @catch (NSException* exception) {
+    reportError(reply, [NSString stringWithFormat:@"Failed to load main bundle with exception: %@", exception]);
+  }
+
+  return InvalidBundleType;
+}
+
+EXPORT OSErr HandleLoadEvent(const AppleEvent* ev, AppleEvent* reply, long refcon) {
+  @synchronized(globalLock) {
+    @autoreleasepool {
+      NSBundle* injectorBundle = [NSBundle bundleForClass:[LiferayNativityInjector class]];
+      NSString* injectorVersion = [injectorBundle objectForInfoDictionaryKey:@"CFBundleVersion"];
+
+      if (!injectorVersion || ![injectorVersion isKindOfClass:[NSString class]]) {
+        reportError(reply, [NSString stringWithFormat:@"Unable to determine LiferayNativityInjector version!"]);
+        return 7;
+      }
+
+      @try {
+        OSErr err = loadBundle(mainBundleType(reply), reply, refcon);
+
+        if (err != noErr)
+        {
+          return err;
+        }
+
+        pid_t pid = [[NSProcessInfo processInfo] processIdentifier];
+
+        [[NSDistributedNotificationCenter defaultCenter]postNotificationName:LIFERAYNATIVITY_INJECTED_NOTIFICATION object:[[NSBundle mainBundle]bundleIdentifier] userInfo:@{@"pid": @(pid)}];
+
+        liferayNativityLoaded = true;
+
+        return noErr;
+      } @catch (NSException* exception) {
+        reportError(reply, [NSString stringWithFormat:@"Failed to load LiferayNativity with exception: %@", exception]);
+      }
+
+      return 1;
+    }
+  }
+}
+
+EXPORT OSErr HandleLoadedEvent(const AppleEvent* ev, AppleEvent* reply, long refcon) {
+  @synchronized(globalLock) {
+    @autoreleasepool {
+      LNBundleType type = mainBundleType(reply);
+      if ((type == LiferayNativityBundleType) && liferayNativityLoaded) {
+        return noErr;
+      }
+      reportError(reply, @"LiferayNativity not loaded");
+      return 1;
+    }
+  }
+}
+
+EXPORT OSErr HandleUnloadEvent(const AppleEvent* ev, AppleEvent* reply, long refcon) {
+  @synchronized(globalLock) {
+    @autoreleasepool {
+      @try {
+        if (!liferayNativityLoaded) {
+          NSLog(@"LiferayNativityInjector: not loaded.");
+          return noErr;
+        }
+
+        NSString* bundleName = liferayNativityBundleName;
+
+        NSBundle* liferayNativityInjectorBundle = [NSBundle bundleForClass:[LiferayNativityInjector class]];
+        NSString* liferayNativityLocation = [liferayNativityInjectorBundle pathForResource:bundleName ofType:@"bundle"];
+        NSBundle* pluginBundle = [NSBundle bundleWithPath:liferayNativityLocation];
+        if (!pluginBundle) {
+          reportError(reply, [NSString stringWithFormat:@"Unable to create bundle from path: %@ [%@]", liferayNativityLocation, liferayNativityInjectorBundle]);
+          return 2;
+        }
+
+        Class principalClass = [pluginBundle principalClass];
+        if (!principalClass) {
+          reportError(reply, [NSString stringWithFormat:@"Unable to retrieve principalClass for bundle: %@", pluginBundle]);
+          return 3;
+        }
+        id principalClassObject = NSClassFromString(NSStringFromClass(principalClass));
+        if ([principalClassObject respondsToSelector:@selector(uninstall)]) {
+          NSLog(@"LiferayNativityInjector: Uninstalling %@ ...", bundleName);
+          [principalClassObject uninstall];
+        }
+
+        liferayNativityLoaded = false;
+
+        return noErr;
+      } @catch (NSException* exception) {
+        reportError(reply, [NSString stringWithFormat:@"Failed to unload LiferayNativity with exception: %@", exception]);
+      }
+
+      return 1;
+    }
+  }
+}

+ 9 - 0
shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.sdef

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
+<dictionary title="LiferayNativityInjector Terminology">
+    <suite name="LiferayNativityInjector Suite" code="NVTY" description="LiferayNativity Injector commands">
+        <command name="install LiferayNativity" code="NVTYload" description="Loads LiferayNativity into the Finder process"/>
+        <command name="uninstall LiferayNativity" code="NVTYunld" description="Unloads LiferayNativity from the Finder process"/>
+        <command name="check LiferayNativity" code="NVTYlded" description="Checks if LiferayNativity is loaded in Finder process"/>
+    </suite>
+</dictionary>

+ 269 - 0
shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/project.pbxproj

@@ -0,0 +1,269 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		0B36CB92182461A10039B237 /* LiferayNativityFinder.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0B36CB91182461A10039B237 /* LiferayNativityFinder.bundle */; };
+		0BD9C38E1778EF450094CF5D /* license.txt in Resources */ = {isa = PBXBuildFile; fileRef = 0BD9C38D1778EF450094CF5D /* license.txt */; };
+		8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */; };
+		8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8D5B49A704867FD3000E48DA /* InfoPlist.strings */; };
+		D6ACBEA2117B7D5600F6691C /* LiferayNativityInjector.m in Sources */ = {isa = PBXBuildFile; fileRef = D6ACBE9E117B7D5600F6691C /* LiferayNativityInjector.m */; };
+		D6ACBEA3117B7D5600F6691C /* LNStandardVersionComparator.m in Sources */ = {isa = PBXBuildFile; fileRef = D6ACBEA0117B7D5600F6691C /* LNStandardVersionComparator.m */; };
+		D6ACBEA5117B7D6100F6691C /* LiferayNativityInjector.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D6ACBEA4117B7D6100F6691C /* LiferayNativityInjector.sdef */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+		089C167EFE841241C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
+		0B36CB91182461A10039B237 /* LiferayNativityFinder.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = LiferayNativityFinder.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
+		0BD9C38D1778EF450094CF5D /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = license.txt; sourceTree = "<group>"; };
+		8D576317048677EA00EA77CD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		D60A992314CE37030061AD6D /* LiferayNativity.osax */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LiferayNativity.osax; sourceTree = BUILT_PRODUCTS_DIR; };
+		D6ACBE9E117B7D5600F6691C /* LiferayNativityInjector.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LiferayNativityInjector.m; sourceTree = "<group>"; };
+		D6ACBE9F117B7D5600F6691C /* LNVersionComparisonProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNVersionComparisonProtocol.h; sourceTree = "<group>"; };
+		D6ACBEA0117B7D5600F6691C /* LNStandardVersionComparator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LNStandardVersionComparator.m; sourceTree = "<group>"; };
+		D6ACBEA1117B7D5600F6691C /* LNStandardVersionComparator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LNStandardVersionComparator.h; sourceTree = "<group>"; };
+		D6ACBEA4117B7D6100F6691C /* LiferayNativityInjector.sdef */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = LiferayNativityInjector.sdef; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		8D576313048677EA00EA77CD /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				8D576314048677EA00EA77CD /* CoreFoundation.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		089C166AFE841209C02AAC07 /* TotalFinder-osax */ = {
+			isa = PBXGroup;
+			children = (
+				08FB77AFFE84173DC02AAC07 /* Source */,
+				089C167CFE841241C02AAC07 /* Resources */,
+				089C1671FE841209C02AAC07 /* Dependencies */,
+				D60A992414CE37030061AD6D /* Products */,
+			);
+			indentWidth = 2;
+			name = "TotalFinder-osax";
+			sourceTree = "<group>";
+			tabWidth = 2;
+			usesTabs = 0;
+		};
+		089C1671FE841209C02AAC07 /* Dependencies */ = {
+			isa = PBXGroup;
+			children = (
+				0AA1909FFE8422F4C02AAC07 /* CoreFoundation.framework */,
+			);
+			name = Dependencies;
+			sourceTree = "<group>";
+		};
+		089C167CFE841241C02AAC07 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				0B36CB91182461A10039B237 /* LiferayNativityFinder.bundle */,
+				D6ACBEA4117B7D6100F6691C /* LiferayNativityInjector.sdef */,
+				8D576317048677EA00EA77CD /* Info.plist */,
+				8D5B49A704867FD3000E48DA /* InfoPlist.strings */,
+				0BD9C38D1778EF450094CF5D /* license.txt */,
+			);
+			name = Resources;
+			sourceTree = "<group>";
+		};
+		08FB77AFFE84173DC02AAC07 /* Source */ = {
+			isa = PBXGroup;
+			children = (
+				D6ACBE9E117B7D5600F6691C /* LiferayNativityInjector.m */,
+				D6ACBE9F117B7D5600F6691C /* LNVersionComparisonProtocol.h */,
+				D6ACBEA0117B7D5600F6691C /* LNStandardVersionComparator.m */,
+				D6ACBEA1117B7D5600F6691C /* LNStandardVersionComparator.h */,
+			);
+			name = Source;
+			sourceTree = "<group>";
+		};
+		D60A992414CE37030061AD6D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				D60A992314CE37030061AD6D /* LiferayNativity.osax */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		8D57630D048677EA00EA77CD /* LiferayNativity.osax */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "LiferayNativity.osax" */;
+			buildPhases = (
+				8D57630F048677EA00EA77CD /* Resources */,
+				8D576311048677EA00EA77CD /* Sources */,
+				8D576313048677EA00EA77CD /* Frameworks */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = LiferayNativity.osax;
+			productInstallPath = "$(HOME)/Library/Bundles";
+			productName = "TotalFinder-osax";
+			productReference = D60A992314CE37030061AD6D /* LiferayNativity.osax */;
+			productType = "com.apple.product-type.bundle";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		089C1669FE841209C02AAC07 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				BuildIndependentTargetsInParallel = YES;
+				LastUpgradeCheck = 0460;
+				ORGANIZATIONNAME = BinaryAge;
+			};
+			buildConfigurationList = 1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "LiferayNativityInjector" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 1;
+			knownRegions = (
+				en,
+				English,
+			);
+			mainGroup = 089C166AFE841209C02AAC07 /* TotalFinder-osax */;
+			productRefGroup = D60A992414CE37030061AD6D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				8D57630D048677EA00EA77CD /* LiferayNativity.osax */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		8D57630F048677EA00EA77CD /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				0B36CB92182461A10039B237 /* LiferayNativityFinder.bundle in Resources */,
+				8D5B49A804867FD3000E48DA /* InfoPlist.strings in Resources */,
+				D6ACBEA5117B7D6100F6691C /* LiferayNativityInjector.sdef in Resources */,
+				0BD9C38E1778EF450094CF5D /* license.txt in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		8D576311048677EA00EA77CD /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				D6ACBEA2117B7D5600F6691C /* LiferayNativityInjector.m in Sources */,
+				D6ACBEA3117B7D5600F6691C /* LNStandardVersionComparator.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		8D5B49A704867FD3000E48DA /* InfoPlist.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				089C167EFE841241C02AAC07 /* English */,
+			);
+			name = InfoPlist.strings;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		1DEB911B08733D790010E9CD /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+				COMBINE_HIDPI_IMAGES = YES;
+				GCC_DYNAMIC_NO_PIC = NO;
+				INFOPLIST_FILE = Info.plist;
+				OTHER_LDFLAGS = (
+					"-framework",
+					Foundation,
+					"-framework",
+					AppKit,
+				);
+				PRODUCT_NAME = LiferayNativity;
+				SKIP_INSTALL = YES;
+				WRAPPER_EXTENSION = osax;
+			};
+			name = Debug;
+		};
+		1DEB911C08733D790010E9CD /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
+				COMBINE_HIDPI_IMAGES = YES;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				INFOPLIST_FILE = Info.plist;
+				OTHER_LDFLAGS = (
+					"-framework",
+					Foundation,
+					"-framework",
+					AppKit,
+				);
+				PRODUCT_NAME = LiferayNativity;
+				WRAPPER_EXTENSION = osax;
+			};
+			name = Release;
+		};
+		1DEB911F08733D790010E9CD /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CONFIGURATION_TEMP_DIR = "$(PROJECT_TEMP_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)";
+				COPY_PHASE_STRIP = NO;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.7;
+				SDKROOT = macosx;
+			};
+			name = Debug;
+		};
+		1DEB912008733D790010E9CD /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.7;
+				SDKROOT = macosx;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		1DEB911A08733D790010E9CD /* Build configuration list for PBXNativeTarget "LiferayNativity.osax" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DEB911B08733D790010E9CD /* Debug */,
+				1DEB911C08733D790010E9CD /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		1DEB911E08733D790010E9CD /* Build configuration list for PBXProject "LiferayNativityInjector" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				1DEB911F08733D790010E9CD /* Debug */,
+				1DEB912008733D790010E9CD /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 089C1669FE841209C02AAC07 /* Project object */;
+}

+ 7 - 0
shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:LiferayNativityInjector.xcodeproj">
+   </FileRef>
+</Workspace>

+ 73 - 0
shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/xcshareddata/xcschemes/LiferayNativity.osax.xcscheme

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "0500"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "NO"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "8D57630D048677EA00EA77CD"
+               BuildableName = "LiferayNativityFinder.bundle"
+               BlueprintName = "LiferayNativityFinder"
+               ReferencedContainer = "container:../LiferayNativityFinder/LiferayNativityFinder.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "8D57630D048677EA00EA77CD"
+               BuildableName = "LiferayNativity.osax"
+               BlueprintName = "LiferayNativity.osax"
+               ReferencedContainer = "container:LiferayNativityInjector.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      buildConfiguration = "Debug">
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      buildConfiguration = "Debug"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      allowLocationSimulation = "YES">
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </LaunchAction>
+   <ProfileAction
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      buildConfiguration = "Release"
+      debugDocumentVersioning = "YES">
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 14 - 0
shell_integration/MacOSX/LiferayNativityInjector/LiferayNativityInjector.xcodeproj/xcuserdata/mackie.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>SuppressBuildableAutocreation</key>
+	<dict>
+		<key>8D57630D048677EA00EA77CD</key>
+		<dict>
+			<key>primary</key>
+			<true/>
+		</dict>
+	</dict>
+</dict>
+</plist>

+ 25 - 0
shell_integration/MacOSX/LiferayNativityInjector/license.txt

@@ -0,0 +1,25 @@
+Copyright (c) 2010-2013, BinaryAge Limited
+Contributors: https://github.com/binaryage/totalfinder-osax/contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Antonin Hildebrand nor the
+      names of other contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY BINARYAGE LIMITED ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Antonin Hildebrand BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 9 - 0
shell_integration/MacOSX/check.scpt

@@ -0,0 +1,9 @@
+tell application "Finder"
+    try
+        «event NVTYlded»
+        set the result to 0
+    on error msg number code
+        set the result to code
+    end try
+end tell
+

+ 11 - 0
shell_integration/MacOSX/deploy.sh

@@ -0,0 +1,11 @@
+#!/bin/sh
+# osascript $HOME/owncloud.com/mirall/shell_integration/MacOSX/unload.scpt
+
+sudo rm -rf /Library/ScriptingAdditions/LiferayNativity.osax
+sudo cp -r $HOME/Library/Developer/Xcode/DerivedData/LiferayNativity-flulyntyemvwtcgiriaddqfewwvc/Build/Products/Debug/LiferayNativity.osax /Library/ScriptingAdditions/
+
+sudo killall Finder
+sleep 1
+osascript $HOME/owncloud.com/mirall/shell_integration/MacOSX/load.scpt
+osascript $HOME/owncloud.com/mirall/shell_integration/MacOSX/check.scpt
+

+ 6 - 0
shell_integration/MacOSX/load.scpt

@@ -0,0 +1,6 @@
+tell application "Finder"
+    try
+        «event NVTYload»
+    end try
+end tell
+

+ 8 - 0
shell_integration/MacOSX/loadPlugin.sh

@@ -0,0 +1,8 @@
+#!/bin/sh
+
+osascript -e 'tell application "Finder" \
+                try \
+                   «event NVTYload» \
+                end try \
+              end tell'
+

+ 6 - 0
shell_integration/MacOSX/unload.scpt

@@ -0,0 +1,6 @@
+tell application "Finder"
+    try
+        «event NVTYunld»
+    end try
+end tell
+

BIN=BIN
shell_integration/icons/.DS_Store


BIN=BIN
shell_integration/icons/1024x1024/Error_1024.png


BIN=BIN
shell_integration/icons/1024x1024/Error_Shared_1024.png


BIN=BIN
shell_integration/icons/1024x1024/OK_1024.png


BIN=BIN
shell_integration/icons/1024x1024/OK_Shared_1024.png


BIN=BIN
shell_integration/icons/1024x1024/Sync_1024.png


BIN=BIN
shell_integration/icons/1024x1024/Sync_Shared_1024.png


BIN=BIN
shell_integration/icons/1024x1024/Warning_1024.png


BIN=BIN
shell_integration/icons/1024x1024/Warning_Shared_1024.png


BIN=BIN
shell_integration/icons/128x128/.DS_Store


BIN=BIN
shell_integration/icons/128x128/Error_128.png


BIN=BIN
shell_integration/icons/128x128/Error_Shared_128.png


BIN=BIN
shell_integration/icons/128x128/OK_128.png


BIN=BIN
shell_integration/icons/128x128/OK_Shared_128.png


BIN=BIN
shell_integration/icons/128x128/Sync_128.png


BIN=BIN
shell_integration/icons/128x128/Sync_Shared_128.png


BIN=BIN
shell_integration/icons/128x128/Warning_128.png


BIN=BIN
shell_integration/icons/128x128/Warning_Shared_128.png


BIN=BIN
shell_integration/icons/16x16/Error_16.png


BIN=BIN
shell_integration/icons/16x16/Error_Shared_16.png


BIN=BIN
shell_integration/icons/16x16/OK_16.png


BIN=BIN
shell_integration/icons/16x16/OK_Shared_16.png


BIN=BIN
shell_integration/icons/16x16/Sync_16.png


BIN=BIN
shell_integration/icons/16x16/Sync_Shared_16.png


BIN=BIN
shell_integration/icons/16x16/Warning_16.png


BIN=BIN
shell_integration/icons/16x16/Warning_Shared_16.png


BIN=BIN
shell_integration/icons/256x256/Error_256.png


BIN=BIN
shell_integration/icons/256x256/Error_Shared_256.png


BIN=BIN
shell_integration/icons/256x256/OK_256.png


BIN=BIN
shell_integration/icons/256x256/OK_Shared_256.png


BIN=BIN
shell_integration/icons/256x256/Sync_256.png


BIN=BIN
shell_integration/icons/256x256/Sync_Shared_256.png


BIN=BIN
shell_integration/icons/256x256/Warning_256.png


BIN=BIN
shell_integration/icons/256x256/Warning_Shared_256.png


BIN=BIN
shell_integration/icons/32x32/.DS_Store


BIN=BIN
shell_integration/icons/32x32/Error_32.png


BIN=BIN
shell_integration/icons/32x32/Error_Shared_32.png


BIN=BIN
shell_integration/icons/32x32/OK_32.png


BIN=BIN
shell_integration/icons/32x32/OK_Shared_32.png


BIN=BIN
shell_integration/icons/32x32/Sync_32.png


BIN=BIN
shell_integration/icons/32x32/Sync_Shared_32.png


BIN=BIN
shell_integration/icons/32x32/Warning_32.png


BIN=BIN
shell_integration/icons/32x32/Warning_Shared_32.png


BIN=BIN
shell_integration/icons/48x48/Error_48.png


BIN=BIN
shell_integration/icons/48x48/Error_Shared_48.png


BIN=BIN
shell_integration/icons/48x48/OK_48.png


BIN=BIN
shell_integration/icons/48x48/OK_Shared_48.png


BIN=BIN
shell_integration/icons/48x48/Sync_48.png


BIN=BIN
shell_integration/icons/48x48/Sync_Shared_48.png


BIN=BIN
shell_integration/icons/48x48/Warning_48.png


BIN=BIN
shell_integration/icons/48x48/Warning_Shared_48.png


BIN=BIN
shell_integration/icons/512x512/.DS_Store


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio