2
0
MasterPassword/External/google-plus-ios-sdk/OpenSource/GTL/GTLRuntimeCommon.m
Maarten Billemont 5e7b6ed60e Sharing on Facebook, Twitter and Google+
[FIXED]     Font of navbar.
[FIXED]     A few compile fixes.
[IMPROVED]  Made properties nonatomic.
[ADDED]     Support for facebook, twitter and google+ sharing.
2012-08-25 12:55:07 +02:00

1136 lines
41 KiB
Objective-C

/* Copyright (c) 2011 Google Inc.
*
* 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.
*/
//
// GTLRuntimeCommon.m
//
#include <objc/runtime.h>
#import "GTLRuntimeCommon.h"
#import "GTLDateTime.h"
#import "GTLObject.h"
#import "GTLUtilities.h"
static NSString *const kReturnClassKey = @"returnClass";
static NSString *const kContainedClassKey = @"containedClass";
static NSString *const kJSONKey = @"jsonKey";
// Note: NSObject's class is used as a marker for the expected/default class
// when Discovery says it can be any type of object.
@implementation GTLRuntimeCommon
// Helper to generically convert JSON to an api object type.
+ (id)objectFromJSON:(id)json
defaultClass:(Class)defaultClass
surrogates:(NSDictionary *)surrogates
isCacheable:(BOOL*)isCacheable {
id result = nil;
BOOL canBeCached = YES;
// TODO(TVL): use defaultClass to validate things like expectedClass is
// done in jsonFromAPIObject:expectedClass:isCacheable:?
if ([json isKindOfClass:[NSDictionary class]]) {
// If no default, or the default was any object, then default to base
// object here (and hope there is a kind to get the right thing).
if ((defaultClass == Nil) || [defaultClass isEqual:[NSObject class]]) {
defaultClass = [GTLObject class];
}
result = [GTLObject objectForJSON:json
defaultClass:defaultClass
surrogates:surrogates
batchClassMap:nil];
} else if ([json isKindOfClass:[NSArray class]]) {
NSArray *jsonArray = json;
// make an object for each JSON dictionary in the array
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[jsonArray count]];
for (id jsonItem in jsonArray) {
id item = [self objectFromJSON:jsonItem
defaultClass:defaultClass
surrogates:surrogates
isCacheable:NULL];
[resultArray addObject:item];
}
result = resultArray;
} else if ([json isKindOfClass:[NSString class]]) {
// DateTimes live in JSON as strings, so convert
if ([defaultClass isEqual:[GTLDateTime class]]) {
result = [GTLDateTime dateTimeWithRFC3339String:json];
} else {
result = json;
canBeCached = NO;
}
} else if ([json isKindOfClass:[NSNumber class]] ||
[json isKindOfClass:[NSNull class]]) {
result = json;
canBeCached = NO;
} else {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: unsupported class '%s' in objectFromJSON",
class_getName([json class]));
}
if (isCacheable) {
*isCacheable = canBeCached;
}
return result;
}
// Helper to generically convert an api object type to JSON.
// |expectedClass| is the type that was expected for |obj|.
+ (id)jsonFromAPIObject:(id)obj
expectedClass:(Class)expectedClass
isCacheable:(BOOL*)isCacheable {
id result = nil;
BOOL canBeCached = YES;
BOOL checkExpected = (expectedClass != Nil);
if ([obj isKindOfClass:[NSString class]]) {
result = [[obj copy] autorelease];
canBeCached = NO;
} else if ([obj isKindOfClass:[NSNumber class]] ||
[obj isKindOfClass:[NSNull class]]) {
result = obj;
canBeCached = NO;
} else if ([obj isKindOfClass:[GTLObject class]]) {
result = [obj JSON];
} else if ([obj isKindOfClass:[NSArray class]]) {
checkExpected = NO;
NSArray *array = obj;
// get the JSON for each thing in the array
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[array count]];
for (id item in array) {
id itemJSON = [self jsonFromAPIObject:item
expectedClass:expectedClass
isCacheable:NULL];
[resultArray addObject:itemJSON];
}
result = resultArray;
} else if ([obj isKindOfClass:[GTLDateTime class]]) {
// DateTimes live in JSON as strings, so convert.
GTLDateTime *dateTime = obj;
result = [dateTime stringValue];
} else {
checkExpected = NO;
GTL_DEBUG_LOG(@"GTLRuntimeCommon: unsupported class '%s' in jsonFromAPIObject",
class_getName([obj class]));
}
if (checkExpected) {
// If the default was any object, then clear it to skip validation checks.
if ([expectedClass isEqual:[NSObject class]] ||
[obj isKindOfClass:[NSNull class]]) {
expectedClass = nil;
}
if (expectedClass && ![obj isKindOfClass:expectedClass]) {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: jsonFromAPIObject expected class '%s' instead got '%s'",
class_getName(expectedClass), class_getName([obj class]));
}
}
if (isCacheable) {
*isCacheable = canBeCached;
}
return result;
}
#pragma mark JSON/Object Utilities
static NSMutableDictionary *gDispatchCache = nil;
static CFStringRef SelectorKeyCopyDescriptionCallBack(const void *key) {
// Make a CFString from the key
NSString *name = NSStringFromSelector((SEL) key);
CFStringRef str = CFStringCreateCopy(kCFAllocatorDefault, (CFStringRef) name);
return str;
}
// Save the dispatch details for the specified class and selector
+ (void)setStoredDispatchForClass:(Class<GTLRuntimeCommon>)dispatchClass
selector:(SEL)sel
returnClass:(Class)returnClass
containedClass:(Class)containedClass
jsonKey:(NSString *)jsonKey {
// cache structure:
// class ->
// selector ->
// returnClass
// containedClass
// jsonKey
@synchronized([GTLRuntimeCommon class]) {
if (gDispatchCache == nil) {
gDispatchCache = [GTLUtilities newStaticDictionary];
}
CFMutableDictionaryRef classDict =
(CFMutableDictionaryRef) [gDispatchCache objectForKey:dispatchClass];
if (classDict == nil) {
// We create a CFDictionary since the keys are raw selectors rather than
// NSStrings
const CFDictionaryKeyCallBacks keyCallBacks = {
.version = 0,
.retain = NULL,
.release = NULL,
.copyDescription = SelectorKeyCopyDescriptionCallBack,
.equal = NULL, // defaults to pointer comparison
.hash = NULL // defaults to the pointer value
};
const CFIndex capacity = 0; // no limit
classDict = CFDictionaryCreateMutable(kCFAllocatorDefault, capacity,
&keyCallBacks,
&kCFTypeDictionaryValueCallBacks);
[gDispatchCache setObject:(id)classDict
forKey:(id<NSCopying>)dispatchClass];
CFRelease(classDict);
}
NSDictionary *selDict = (NSDictionary *)CFDictionaryGetValue(classDict, sel);
if (selDict == nil) {
selDict = [NSDictionary dictionaryWithObjectsAndKeys:
jsonKey, kJSONKey,
returnClass, kReturnClassKey, // can be nil (primitive types)
containedClass, kContainedClassKey, // may be nil
nil];
CFDictionarySetValue(classDict, sel, selDict);
} else {
// we already have a dictionary for this selector on this class, which is
// surprising
GTL_DEBUG_LOG(@"Storing duplicate dispatch for %@ selector %@",
dispatchClass, NSStringFromSelector(sel));
}
}
}
+ (BOOL)getStoredDispatchForClass:(Class<GTLRuntimeCommon>)dispatchClass
selector:(SEL)sel
returnClass:(Class *)outReturnClass
containedClass:(Class *)outContainedClass
jsonKey:(NSString **)outJsonKey {
@synchronized([GTLRuntimeCommon class]) {
// walk from this class up the hierarchy to the ancestor class
Class<GTLRuntimeCommon> topClass = class_getSuperclass([dispatchClass ancestorClass]);
for (Class currClass = dispatchClass;
currClass != topClass;
currClass = class_getSuperclass(currClass)) {
CFMutableDictionaryRef classDict =
(CFMutableDictionaryRef) [gDispatchCache objectForKey:currClass];
if (classDict) {
NSMutableDictionary *selDict =
(NSMutableDictionary *) CFDictionaryGetValue(classDict, sel);
if (selDict) {
if (outReturnClass) {
*outReturnClass = [selDict objectForKey:kReturnClassKey];
}
if (outContainedClass) {
*outContainedClass = [selDict objectForKey:kContainedClassKey];
}
if (outJsonKey) {
*outJsonKey = [selDict objectForKey:kJSONKey];
}
return YES;
}
}
}
}
GTL_DEBUG_LOG(@"Failed to find stored dispatch info for %@ %s",
dispatchClass, sel_getName(sel));
return NO;
}
#pragma mark IMPs - getters and setters for specific object types
#if !__LP64__
// NSInteger on 32bit
static NSInteger DynamicInteger32Getter(id self, SEL sel) {
// get an NSInteger (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
NSInteger result = [num integerValue];
return result;
}
return 0;
}
static void DynamicInteger32Setter(id self, SEL sel, NSInteger val) {
// save an NSInteger (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithInteger:val];
[self setJSONValue:num forKey:jsonKey];
}
}
// NSUInteger on 32bit
static NSUInteger DynamicUInteger32Getter(id self, SEL sel) {
// get an NSUInteger (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
NSUInteger result = [num unsignedIntegerValue];
return result;
}
return 0;
}
static void DynamicUInteger32Setter(id self, SEL sel, NSUInteger val) {
// save an NSUInteger (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithUnsignedInteger:val];
[self setJSONValue:num forKey:jsonKey];
}
}
#endif // !__LP64__
// NSInteger on 64bit, long long on 32bit and 64bit
static long long DynamicLongLongGetter(id self, SEL sel) {
// get a long long (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
long long result = [num longLongValue];
return result;
}
return 0;
}
static void DynamicLongLongSetter(id self, SEL sel, long long val) {
// save a long long (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithLongLong:val];
[self setJSONValue:num forKey:jsonKey];
}
}
// NSUInteger on 64bit, unsiged long long on 32bit and 64bit
static unsigned long long DynamicULongLongGetter(id self, SEL sel) {
// get an unsigned long long (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
unsigned long long result = [num unsignedLongLongValue];
return result;
}
return 0;
}
static void DynamicULongLongSetter(id self, SEL sel, unsigned long long val) {
// save an unsigned long long (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithUnsignedLongLong:val];
[self setJSONValue:num forKey:jsonKey];
}
}
// float
static float DynamicFloatGetter(id self, SEL sel) {
// get a float (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
float result = [num floatValue];
return result;
}
return 0.0f;
}
static void DynamicFloatSetter(id self, SEL sel, float val) {
// save a float (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithFloat:val];
[self setJSONValue:num forKey:jsonKey];
}
}
// double
static double DynamicDoubleGetter(id self, SEL sel) {
// get a double (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
double result = [num doubleValue];
return result;
}
return 0.0;
}
static void DynamicDoubleSetter(id self, SEL sel, double val) {
// save a double (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithDouble:val];
[self setJSONValue:num forKey:jsonKey];
}
}
// BOOL
static BOOL DynamicBooleanGetter(id self, SEL sel) {
// get a BOOL (NSNumber) from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
BOOL flag = [num boolValue];
return flag;
}
return NO;
}
static void DynamicBooleanSetter(id self, SEL sel, BOOL val) {
// save a BOOL (NSNumber) into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [NSNumber numberWithBool:val];
[self setJSONValue:num forKey:jsonKey];
}
}
// NSString
static NSString *DynamicStringGetter(id<GTLRuntimeCommon> self, SEL sel) {
// get an NSString from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSString *str = [self JSONValueForKey:jsonKey];
return str;
}
return nil;
}
static void DynamicStringSetter(id<GTLRuntimeCommon> self, SEL sel,
NSString *str) {
// save an NSString into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSString *copiedStr = [str copy];
[self setJSONValue:copiedStr forKey:jsonKey];
[copiedStr release];
}
}
// GTLDateTime
static GTLDateTime *DynamicDateTimeGetter(id<GTLRuntimeCommon> self, SEL sel) {
// get a GTLDateTime from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
// Return the cached object before creating on demand.
GTLDateTime *cachedDateTime = [self cacheChildForKey:jsonKey];
if (cachedDateTime != nil) {
return cachedDateTime;
}
NSString *str = [self JSONValueForKey:jsonKey];
id cacheValue, resultValue;
if (![str isKindOfClass:[NSNull class]]) {
GTLDateTime *dateTime = [GTLDateTime dateTimeWithRFC3339String:str];
cacheValue = dateTime;
resultValue = dateTime;
} else {
cacheValue = nil;
resultValue = [NSNull null];
}
[self setCacheChild:cacheValue forKey:jsonKey];
return resultValue;
}
return nil;
}
static void DynamicDateTimeSetter(id<GTLRuntimeCommon> self, SEL sel,
GTLDateTime *dateTime) {
// save an GTLDateTime into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
id cacheValue, jsonValue;
if (![dateTime isKindOfClass:[NSNull class]]) {
jsonValue = [dateTime stringValue];
cacheValue = dateTime;
} else {
jsonValue = [NSNull null];
cacheValue = nil;
}
[self setJSONValue:jsonValue forKey:jsonKey];
[self setCacheChild:cacheValue forKey:jsonKey];
}
}
// NSNumber
static NSNumber *DynamicNumberGetter(id<GTLRuntimeCommon> self, SEL sel) {
// get an NSNumber from the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
NSNumber *num = [self JSONValueForKey:jsonKey];
num = GTL_EnsureNSNumber(num);
return num;
}
return nil;
}
static void DynamicNumberSetter(id<GTLRuntimeCommon> self, SEL sel,
NSNumber *num) {
// save an NSNumber into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
[self setJSONValue:num forKey:jsonKey];
}
}
// GTLObject
static GTLObject *DynamicObjectGetter(id<GTLRuntimeCommon> self, SEL sel) {
// get a GTLObject from the JSON dictionary
NSString *jsonKey = nil;
Class returnClass = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:&returnClass
containedClass:NULL
jsonKey:&jsonKey]) {
// Return the cached object before creating on demand.
GTLObject *cachedObj = [self cacheChildForKey:jsonKey];
if (cachedObj != nil) {
return cachedObj;
}
NSMutableDictionary *dict = [self JSONValueForKey:jsonKey];
if ([dict isKindOfClass:[NSMutableDictionary class]]) {
// get the class of the object being returned, and instantiate it
if (returnClass == Nil) {
returnClass = [GTLObject class];
}
NSDictionary *surrogates = self.surrogates;
GTLObject *obj = [GTLObject objectForJSON:dict
defaultClass:returnClass
surrogates:surrogates
batchClassMap:nil];
[self setCacheChild:obj forKey:jsonKey];
return obj;
} else if ([dict isKindOfClass:[NSNull class]]) {
[self setCacheChild:nil forKey:jsonKey];
return (id) [NSNull null];
} else if (dict != nil) {
// unexpected; probably got a string -- let the caller figure it out
GTL_DEBUG_LOG(@"GTLObject: unexpected JSON: %@.%@ should be a dictionary, actually is a %@:\n%@",
NSStringFromClass(selfClass), NSStringFromSelector(sel),
NSStringFromClass([dict class]), dict);
return (GTLObject *)dict;
}
}
return nil;
}
static void DynamicObjectSetter(id<GTLRuntimeCommon> self, SEL sel,
GTLObject *obj) {
// save a GTLObject into the JSON dictionary
NSString *jsonKey = nil;
Class<GTLRuntimeCommon> selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
id cacheValue, jsonValue;
if (![obj isKindOfClass:[NSNull class]]) {
NSMutableDictionary *dict = [obj JSON];
if (dict == nil && obj != nil) {
// adding an empty object; it should have a JSON dictionary so it can
// hold future assignments
obj.JSON = [NSMutableDictionary dictionary];
jsonValue = obj.JSON;
} else {
jsonValue = dict;
}
cacheValue = obj;
} else {
jsonValue = [NSNull null];
cacheValue = nil;
}
[self setJSONValue:jsonValue forKey:jsonKey];
[self setCacheChild:cacheValue forKey:jsonKey];
}
}
// get an NSArray of GTLObjects, NSStrings, or NSNumbers from the
// JSON dictionary for this object
static NSMutableArray *DynamicArrayGetter(id<GTLRuntimeCommon> self, SEL sel) {
NSString *jsonKey = nil;
Class containedClass = nil;
Class selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:&containedClass
jsonKey:&jsonKey]) {
// Return the cached array before creating on demand.
NSMutableArray *cachedArray = [self cacheChildForKey:jsonKey];
if (cachedArray != nil) {
return cachedArray;
}
NSMutableArray *result = nil;
NSArray *array = [self JSONValueForKey:jsonKey];
if (array != nil) {
if ([array isKindOfClass:[NSArray class]]) {
NSDictionary *surrogates = self.surrogates;
result = [GTLRuntimeCommon objectFromJSON:array
defaultClass:containedClass
surrogates:surrogates
isCacheable:NULL];
} else {
#if DEBUG
if (![array isKindOfClass:[NSNull class]]) {
GTL_DEBUG_LOG(@"GTLObject: unexpected JSON: %@.%@ should be an array, actually is a %@:\n%@",
NSStringFromClass(selfClass), NSStringFromSelector(sel),
NSStringFromClass([array class]), array);
}
#endif
result = (NSMutableArray *)array;
}
}
[self setCacheChild:result forKey:jsonKey];
return result;
}
return nil;
}
static void DynamicArraySetter(id<GTLRuntimeCommon> self, SEL sel,
NSMutableArray *array) {
// save an array of GTLObjects objects into the JSON dictionary
NSString *jsonKey = nil;
Class selfClass = [self class];
Class containedClass = nil;
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:&containedClass
jsonKey:&jsonKey]) {
id json = [GTLRuntimeCommon jsonFromAPIObject:array
expectedClass:containedClass
isCacheable:NULL];
[self setJSONValue:json forKey:jsonKey];
[self setCacheChild:array forKey:jsonKey];
}
}
// type 'id'
static id DynamicNSObjectGetter(id<GTLRuntimeCommon> self, SEL sel) {
NSString *jsonKey = nil;
Class returnClass = nil;
Class selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:&returnClass
containedClass:NULL
jsonKey:&jsonKey]) {
// Return the cached object before creating on demand.
id cachedObj = [self cacheChildForKey:jsonKey];
if (cachedObj != nil) {
return cachedObj;
}
id jsonObj = [self JSONValueForKey:jsonKey];
if (jsonObj != nil) {
BOOL shouldCache = NO;
NSDictionary *surrogates = self.surrogates;
id result = [GTLRuntimeCommon objectFromJSON:jsonObj
defaultClass:nil
surrogates:surrogates
isCacheable:&shouldCache];
[self setCacheChild:(shouldCache ? result : nil)
forKey:jsonKey];
return result;
}
}
return nil;
}
static void DynamicNSObjectSetter(id<GTLRuntimeCommon> self, SEL sel, id obj) {
NSString *jsonKey = nil;
Class selfClass = [self class];
if ([GTLRuntimeCommon getStoredDispatchForClass:selfClass
selector:sel
returnClass:NULL
containedClass:NULL
jsonKey:&jsonKey]) {
BOOL shouldCache = NO;
id json = [GTLRuntimeCommon jsonFromAPIObject:obj
expectedClass:Nil
isCacheable:&shouldCache];
[self setJSONValue:json forKey:jsonKey];
[self setCacheChild:(shouldCache ? obj : nil)
forKey:jsonKey];
}
}
#pragma mark Runtime lookup support
static objc_property_t PropertyForSel(Class<GTLRuntimeCommon> startClass,
SEL sel, BOOL isSetter,
Class<GTLRuntimeCommon> *outFoundClass) {
const char *baseName = sel_getName(sel);
size_t baseNameLen = strlen(baseName);
if (isSetter) {
baseName += 3; // skip "set"
baseNameLen -= 4; // subtract "set" and the final colon
}
// walk from this class up the hierarchy to the ancestor class
Class<GTLRuntimeCommon> topClass = class_getSuperclass([startClass ancestorClass]);
for (Class currClass = startClass;
currClass != topClass;
currClass = class_getSuperclass(currClass)) {
// step through this class's properties
objc_property_t foundProp = NULL;
objc_property_t *properties = class_copyPropertyList(currClass, NULL);
if (properties) {
for (objc_property_t *prop = properties; *prop != NULL; ++prop) {
const char *propName = property_getName(*prop);
size_t propNameLen = strlen(propName);
// search for an exact-name match (a getter), but case-insensitive on the
// first character (in case baseName comes from a setter)
if (baseNameLen == propNameLen
&& strncasecmp(baseName, propName, 1) == 0
&& (baseNameLen <= 1
|| strncmp(baseName + 1, propName + 1, baseNameLen - 1) == 0)) {
// return the actual property name
foundProp = *prop;
// if requested, return the class containing the property
if (outFoundClass) *outFoundClass = currClass;
break;
}
}
free(properties);
}
if (foundProp) return foundProp;
}
// not found; this occasionally happens when the system looks for a method
// like "getFoo" or "descriptionWithLocale:indent:"
return NULL;
}
typedef struct {
const char *attributePrefix;
const char *setterEncoding;
IMP setterFunction;
const char *getterEncoding;
IMP getterFunction;
// These are the "fixed" return classes, but some properties will require
// looking up the return class instead (because it is a subclass of
// GTLObject).
const char *returnClassName;
Class returnClass;
BOOL extractReturnClass;
} GTLDynamicImpInfo;
static const GTLDynamicImpInfo *DynamicImpInfoForProperty(objc_property_t prop,
Class *outReturnClass) {
if (outReturnClass) *outReturnClass = nil;
// dynamic method resolution:
// http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html
//
// property runtimes:
// http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
// Get and parse the property attributes, which look something like
// T@"NSString",&,D,P
// Ti,D -- NSInteger on 32bit
// Tq,D -- NSInteger on 64bit, long long on 32bit & 64bit
// Tc,D -- BOOL comes as char
// T@"NSString",D
// T@"GTLLink",D
// T@"NSArray",D
static GTLDynamicImpInfo kImplInfo[] = {
#if !__LP64__
{ // NSInteger on 32bit
"Ti",
"v@:i", (IMP)DynamicInteger32Setter,
"i@:", (IMP)DynamicInteger32Getter,
nil, nil,
NO
},
{ // NSUInteger on 32bit
"TI",
"v@:I", (IMP)DynamicUInteger32Setter,
"I@:", (IMP)DynamicUInteger32Getter,
nil, nil,
NO
},
#endif
{ // NSInteger on 64bit, long long on 32bit and 64bit.
"Tq",
"v@:q", (IMP)DynamicLongLongSetter,
"q@:", (IMP)DynamicLongLongGetter,
nil, nil,
NO
},
{ // NSUInteger on 64bit, long long on 32bit and 64bit.
"TQ",
"v@:Q", (IMP)DynamicULongLongSetter,
"Q@:", (IMP)DynamicULongLongGetter,
nil, nil,
NO
},
{ // float
"Tf",
"v@:f", (IMP)DynamicFloatSetter,
"f@:", (IMP)DynamicFloatGetter,
nil, nil,
NO
},
{ // double
"Td",
"v@:d", (IMP)DynamicDoubleSetter,
"d@:", (IMP)DynamicDoubleGetter,
nil, nil,
NO
},
{ // BOOL
"Tc",
"v@:c", (IMP)DynamicBooleanSetter,
"c@:", (IMP)DynamicBooleanGetter,
nil, nil,
NO
},
{ // NSString
"T@\"NSString\"",
"v@:@", (IMP)DynamicStringSetter,
"@@:", (IMP)DynamicStringGetter,
"NSString", nil,
NO
},
{ // NSNumber
"T@\"NSNumber\"",
"v@:@", (IMP)DynamicNumberSetter,
"@@:", (IMP)DynamicNumberGetter,
"NSNumber", nil,
NO
},
{ // GTLDateTime
#if !defined(GTL_TARGET_NAMESPACE)
"T@\"GTLDateTime\"",
"v@:@", (IMP)DynamicDateTimeSetter,
"@@:", (IMP)DynamicDateTimeGetter,
"GTLDateTime", nil,
NO
#else
"T@\"" GTL_TARGET_NAMESPACE_STRING "_" "GTLDateTime\"",
"v@:@", (IMP)DynamicDateTimeSetter,
"@@:", (IMP)DynamicDateTimeGetter,
GTL_TARGET_NAMESPACE_STRING "_" "GTLDateTime", nil,
NO
#endif
},
{ // NSArray with type
"T@\"NSArray\"",
"v@:@", (IMP)DynamicArraySetter,
"@@:", (IMP)DynamicArrayGetter,
"NSArray", nil,
NO
},
{ // id (any of the objects above)
"T@,",
"v@:@", (IMP)DynamicNSObjectSetter,
"@@:", (IMP)DynamicNSObjectGetter,
"NSObject", nil,
NO
},
{ // GTLObject - Last, cause it's a special case and prefix is general
"T@\"",
"v@:@", (IMP)DynamicObjectSetter,
"@@:", (IMP)DynamicObjectGetter,
nil, nil,
YES
},
};
static BOOL hasLookedUpClasses = NO;
if (!hasLookedUpClasses) {
// Unfortunately, you can't put [NSString class] into the static structure,
// so this lookup has to be done at runtime.
hasLookedUpClasses = YES;
for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) {
if (kImplInfo[idx].returnClassName) {
kImplInfo[idx].returnClass = objc_getClass(kImplInfo[idx].returnClassName);
NSCAssert1(kImplInfo[idx].returnClass != nil,
@"GTLRuntimeCommon: class lookup failed: %s", kImplInfo[idx].returnClassName);
}
}
}
const char *attr = property_getAttributes(prop);
const char *dynamicMarker = strstr(attr, ",D");
if (!dynamicMarker ||
(dynamicMarker[2] != 0 && dynamicMarker[2] != ',' )) {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: property %s isn't dynamic, attributes %s",
property_getName(prop), attr ? attr : "(nil)");
return NULL;
}
const GTLDynamicImpInfo *result = NULL;
// Cycle over the list
for (uint32_t idx = 0; idx < sizeof(kImplInfo)/sizeof(kImplInfo[0]); ++idx) {
const char *attributePrefix = kImplInfo[idx].attributePrefix;
if (strncmp(attr, attributePrefix, strlen(attributePrefix)) == 0) {
result = &kImplInfo[idx];
if (outReturnClass) *outReturnClass = result->returnClass;
break;
}
}
if (result == NULL) {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: unexpected attributes %s for property %s",
attr ? attr : "(nil)", property_getName(prop));
return NULL;
}
if (result->extractReturnClass && outReturnClass) {
// add a null at the next quotation mark
char *attrCopy = strdup(attr);
char *classNameStart = attrCopy + 3;
char *classNameEnd = strstr(classNameStart, "\"");
if (classNameEnd) {
*classNameEnd = '\0';
// Lookup the return class
*outReturnClass = objc_getClass(classNameStart);
if (*outReturnClass == nil) {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: did not find class with name \"%s\" "
"for property \"%s\" with attributes \"%s\"",
classNameStart, property_getName(prop), attr);
}
} else {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: Failed to find end of class name for "
"property \"%s\" with attributes \"%s\"",
property_getName(prop), attr);
}
free(attrCopy);
}
return result;
}
#pragma mark Runtime - wiring point
+ (BOOL)resolveInstanceMethod:(SEL)sel onClass:(Class<GTLRuntimeCommon>)onClass {
// dynamic method resolution:
// http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtDynamicResolution.html
//
// property runtimes:
// http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
const char *selName = sel_getName(sel);
size_t selNameLen = strlen(selName);
char lastChar = selName[selNameLen - 1];
BOOL isSetter = (lastChar == ':');
// look for a declared property matching this selector name exactly
Class<GTLRuntimeCommon> foundClass = nil;
objc_property_t prop = PropertyForSel(onClass, sel, isSetter, &foundClass);
if (prop != NULL && foundClass != nil) {
Class returnClass = nil;
const GTLDynamicImpInfo *implInfo = DynamicImpInfoForProperty(prop,
&returnClass);
if (implInfo == NULL) {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: unexpected return type class %s for "
"property \"%s\" of class \"%s\"",
returnClass ? class_getName(returnClass) : "<nil>",
property_getName(prop),
class_getName(onClass));
}
if (implInfo != NULL) {
IMP imp = ( isSetter ? implInfo->setterFunction : implInfo->getterFunction );
const char *encoding = ( isSetter ? implInfo->setterEncoding : implInfo->getterEncoding );
class_addMethod(foundClass, sel, imp, encoding);
const char *propName = property_getName(prop);
NSString *propStr = [NSString stringWithUTF8String:propName];
// replace the property name with the proper JSON key if it's
// special-cased with a map in the found class; otherwise, the property
// name is the JSON key
NSDictionary *keyMap =
[[foundClass ancestorClass] propertyToJSONKeyMapForClass:foundClass];
NSString *jsonKey = [keyMap objectForKey:propStr];
if (jsonKey == nil) {
jsonKey = propStr;
}
Class containedClass = nil;
// For arrays we need to look up what the contained class is.
if (imp == (IMP)DynamicArraySetter || imp == (IMP)DynamicArrayGetter) {
NSDictionary *classMap =
[[foundClass ancestorClass] arrayPropertyToClassMapForClass:foundClass];
containedClass = [classMap objectForKey:jsonKey];
if (containedClass == Nil) {
GTL_DEBUG_LOG(@"GTLRuntimeCommon: expected array item class for "
"property \"%s\" of class \"%s\"",
property_getName(prop), class_getName(foundClass));
}
}
// save the dispatch info to the cache
[GTLRuntimeCommon setStoredDispatchForClass:foundClass
selector:sel
returnClass:returnClass
containedClass:containedClass
jsonKey:jsonKey];
return YES;
}
}
return NO;
}
@end