2012-08-25 10:38:29 +00:00
|
|
|
/* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
//
|
|
|
|
// GTLObject.m
|
|
|
|
//
|
|
|
|
|
|
|
|
#define GTLOBJECT_DEFINE_GLOBALS 1
|
|
|
|
|
|
|
|
#include <objc/runtime.h>
|
|
|
|
|
|
|
|
#import "GTLObject.h"
|
|
|
|
#import "GTLRuntimeCommon.h"
|
|
|
|
#import "GTLJSONParser.h"
|
|
|
|
|
|
|
|
static NSString *const kUserDataPropertyKey = @"_userData";
|
|
|
|
|
|
|
|
@interface GTLObject () <GTLRuntimeCommon>
|
|
|
|
+ (NSMutableArray *)allDeclaredProperties;
|
|
|
|
+ (NSArray *)allKnownKeys;
|
|
|
|
|
|
|
|
+ (NSArray *)fieldsElementsForJSON:(NSDictionary *)targetJSON;
|
|
|
|
+ (NSString *)fieldsDescriptionForJSON:(NSDictionary *)targetJSON;
|
|
|
|
|
|
|
|
+ (NSMutableDictionary *)patchDictionaryForJSON:(NSDictionary *)newJSON
|
|
|
|
fromOriginalJSON:(NSDictionary *)originalJSON;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GTLObject
|
|
|
|
|
|
|
|
@synthesize JSON = json_,
|
|
|
|
surrogates = surrogates_,
|
|
|
|
userProperties = userProperties_;
|
|
|
|
|
|
|
|
+ (id)object {
|
|
|
|
return [[[self alloc] init] autorelease];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (id)objectWithJSON:(NSMutableDictionary *)dict {
|
|
|
|
GTLObject *obj = [self object];
|
|
|
|
obj.JSON = dict;
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSDictionary *)propertyToJSONKeyMap {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSDictionary *)arrayPropertyToClassMap {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (Class)classForAdditionalProperties {
|
|
|
|
return Nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isEqual:(GTLObject *)other {
|
|
|
|
if (self == other) return YES;
|
|
|
|
if (other == nil) return NO;
|
|
|
|
|
|
|
|
// The objects should be the same class, or one should be a subclass of the
|
|
|
|
// other's class
|
|
|
|
if (![other isKindOfClass:[self class]]
|
|
|
|
&& ![self isKindOfClass:[other class]]) return NO;
|
|
|
|
|
|
|
|
// What we're not comparing here:
|
|
|
|
// properties
|
|
|
|
return GTL_AreEqualOrBothNil(json_, [other JSON]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// By definition, for two objects to potentially be considered equal,
|
|
|
|
// they must have the same hash value. The hash is mostly ignored,
|
|
|
|
// but removeObjectsInArray: in Leopard does seem to check the hash,
|
|
|
|
// and NSObject's default hash method just returns the instance pointer.
|
|
|
|
// We'll define hash here for all of our GTLObjects.
|
|
|
|
- (NSUInteger)hash {
|
|
|
|
return (NSUInteger) (void *) [GTLObject class];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
|
|
GTLObject* newObject = [[[self class] allocWithZone:zone] init];
|
|
|
|
CFPropertyListRef ref = CFPropertyListCreateDeepCopy(kCFAllocatorDefault,
|
|
|
|
json_, kCFPropertyListMutableContainers);
|
|
|
|
GTL_DEBUG_ASSERT(ref != NULL, @"GTLObject: copy failed (probably a non-plist type in the JSON)");
|
|
|
|
newObject.JSON = [NSMakeCollectable(ref) autorelease];
|
|
|
|
newObject.surrogates = self.surrogates;
|
|
|
|
|
|
|
|
// What we're not copying:
|
|
|
|
// userProperties
|
|
|
|
return newObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)descriptionWithLocale:(id)locale {
|
|
|
|
return [self description];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)dealloc {
|
|
|
|
[json_ release];
|
|
|
|
[surrogates_ release];
|
|
|
|
[childCache_ release];
|
|
|
|
[userProperties_ release];
|
|
|
|
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark JSON values
|
|
|
|
|
|
|
|
- (void)setJSONValue:(id)obj forKey:(NSString *)key {
|
|
|
|
NSMutableDictionary *dict = self.JSON;
|
|
|
|
if (dict == nil && obj != nil) {
|
|
|
|
dict = [NSMutableDictionary dictionaryWithCapacity:1];
|
|
|
|
self.JSON = dict;
|
|
|
|
}
|
|
|
|
[dict setValue:obj forKey:key];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)JSONValueForKey:(NSString *)key {
|
|
|
|
id obj = [self.JSON objectForKey:key];
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)JSONString {
|
|
|
|
NSError *error = nil;
|
|
|
|
NSString *str = [GTLJSONParser stringWithObject:[self JSON]
|
|
|
|
humanReadable:YES
|
|
|
|
error:&error];
|
|
|
|
if (error) {
|
|
|
|
return [error description];
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *)additionalJSONKeys {
|
|
|
|
NSArray *knownKeys = [[self class] allKnownKeys];
|
|
|
|
NSMutableArray *result = [NSMutableArray arrayWithArray:[json_ allKeys]];
|
|
|
|
[result removeObjectsInArray:knownKeys];
|
|
|
|
// Return nil instead of an empty array.
|
|
|
|
if ([result count] == 0) {
|
|
|
|
result = nil;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Partial - Fields
|
|
|
|
|
|
|
|
- (NSString *)fieldsDescription {
|
|
|
|
NSString *str = [GTLObject fieldsDescriptionForJSON:self.JSON];
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSString *)fieldsDescriptionForJSON:(NSDictionary *)targetJSON {
|
|
|
|
// Internal routine: recursively generate a string field description
|
|
|
|
// by joining elements
|
|
|
|
NSArray *array = [self fieldsElementsForJSON:targetJSON];
|
|
|
|
NSString *str = [array componentsJoinedByString:@","];
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)fieldsElementsForJSON:(NSDictionary *)targetJSON {
|
|
|
|
// Internal routine: recursively generate an array of field description
|
|
|
|
// element strings
|
|
|
|
NSMutableArray *resultFields = [NSMutableArray array];
|
|
|
|
|
|
|
|
// Sorting the dictionary keys gives us deterministic results when iterating
|
|
|
|
NSArray *sortedKeys = [[targetJSON allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
|
|
|
|
for (NSString *key in sortedKeys) {
|
|
|
|
// We'll build a comma-separated list of fields
|
|
|
|
id value = [targetJSON objectForKey:key];
|
|
|
|
if ([value isKindOfClass:[NSString class]]
|
|
|
|
|| [value isKindOfClass:[NSNumber class]]) {
|
|
|
|
// Basic type (string, number), so the key is what we want
|
|
|
|
[resultFields addObject:key];
|
|
|
|
} else if ([value isKindOfClass:[NSDictionary class]]) {
|
|
|
|
// Object (dictionary): "parent/child1,parent/child2,parent/child3"
|
|
|
|
NSArray *subElements = [self fieldsElementsForJSON:value];
|
|
|
|
for (NSString *subElem in subElements) {
|
|
|
|
NSString *prepended = [NSString stringWithFormat:@"%@/%@",
|
|
|
|
key, subElem];
|
|
|
|
[resultFields addObject:prepended];
|
|
|
|
}
|
|
|
|
} else if ([value isKindOfClass:[NSArray class]]) {
|
|
|
|
// Array; we'll generate from the first array entry:
|
|
|
|
// "parent(child1,child2,child3)"
|
|
|
|
//
|
|
|
|
// Open question: should this instead create the union of elements for
|
|
|
|
// all items in the array, rather than just get fields from the first
|
|
|
|
// array object?
|
2013-04-27 21:14:05 +00:00
|
|
|
if ([(NSArray *)value count] > 0) {
|
2012-08-25 10:38:29 +00:00
|
|
|
id firstObj = [value objectAtIndex:0];
|
|
|
|
if ([firstObj isKindOfClass:[NSDictionary class]]) {
|
|
|
|
// An array of objects
|
|
|
|
NSString *contentsStr = [self fieldsDescriptionForJSON:firstObj];
|
|
|
|
NSString *encapsulated = [NSString stringWithFormat:@"%@(%@)",
|
|
|
|
key, contentsStr];
|
|
|
|
[resultFields addObject:encapsulated];
|
|
|
|
} else {
|
|
|
|
// An array of some basic type, or of arrays
|
|
|
|
[resultFields addObject:key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
GTL_ASSERT(0, @"GTLObject unknown field element for %@ (%@)",
|
|
|
|
key, NSStringFromClass([value class]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resultFields;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Partial - Patch
|
|
|
|
|
|
|
|
- (id)patchObjectFromOriginal:(GTLObject *)original {
|
|
|
|
id resultObj;
|
|
|
|
NSMutableDictionary *resultJSON = [GTLObject patchDictionaryForJSON:self.JSON
|
|
|
|
fromOriginalJSON:original.JSON];
|
|
|
|
if ([resultJSON count] > 0) {
|
|
|
|
resultObj = [[self class] objectWithJSON:resultJSON];
|
|
|
|
} else {
|
|
|
|
// Client apps should not attempt to patch with an object containing
|
|
|
|
// empty JSON
|
|
|
|
resultObj = nil;
|
|
|
|
}
|
|
|
|
return resultObj;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSMutableDictionary *)patchDictionaryForJSON:(NSDictionary *)newJSON
|
|
|
|
fromOriginalJSON:(NSDictionary *)originalJSON {
|
|
|
|
// Internal recursive routine to create an object suitable for
|
|
|
|
// our patch semantics
|
|
|
|
NSMutableDictionary *resultJSON = [NSMutableDictionary dictionary];
|
|
|
|
|
|
|
|
// Iterate through keys present in the old object
|
|
|
|
NSArray *originalKeys = [originalJSON allKeys];
|
|
|
|
for (NSString *key in originalKeys) {
|
|
|
|
id originalValue = [originalJSON objectForKey:key];
|
|
|
|
id newValue = [newJSON valueForKey:key];
|
|
|
|
if (newValue == nil) {
|
|
|
|
// There is no new value for this key, so set the value to NSNull
|
|
|
|
[resultJSON setValue:[NSNull null] forKey:key];
|
|
|
|
} else if (!GTL_AreEqualOrBothNil(originalValue, newValue)) {
|
|
|
|
// The values for this key differ
|
|
|
|
if ([originalValue isKindOfClass:[NSDictionary class]]
|
|
|
|
&& [newValue isKindOfClass:[NSDictionary class]]) {
|
|
|
|
// Both are objects; recurse
|
|
|
|
NSMutableDictionary *subDict = [self patchDictionaryForJSON:newValue
|
|
|
|
fromOriginalJSON:originalValue];
|
|
|
|
[resultJSON setValue:subDict forKey:key];
|
|
|
|
} else {
|
|
|
|
// They are non-object values; the new replaces the old. Per the
|
|
|
|
// documentation for patch, this replaces entire arrays.
|
|
|
|
[resultJSON setValue:newValue forKey:key];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The values are the same; omit this key-value pair
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate through keys present only in the new object, and add them to the
|
|
|
|
// result
|
|
|
|
NSMutableArray *newKeys = [NSMutableArray arrayWithArray:[newJSON allKeys]];
|
|
|
|
[newKeys removeObjectsInArray:originalKeys];
|
|
|
|
|
|
|
|
for (NSString *key in newKeys) {
|
|
|
|
id value = [newJSON objectForKey:key];
|
|
|
|
[resultJSON setValue:value forKey:key];
|
|
|
|
}
|
|
|
|
return resultJSON;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (id)nullValue {
|
|
|
|
return [NSNull null];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Additional Properties
|
|
|
|
|
|
|
|
- (id)additionalPropertyForName:(NSString *)name {
|
|
|
|
// Return the cached object, if any, before creating one.
|
|
|
|
id result = [self cacheChildForKey:name];
|
|
|
|
if (result != nil) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Class defaultClass = [[self class] classForAdditionalProperties];
|
|
|
|
id jsonObj = [self JSONValueForKey:name];
|
|
|
|
BOOL shouldCache = NO;
|
|
|
|
if (jsonObj != nil) {
|
|
|
|
NSDictionary *surrogates = self.surrogates;
|
|
|
|
result = [GTLRuntimeCommon objectFromJSON:jsonObj
|
|
|
|
defaultClass:defaultClass
|
|
|
|
surrogates:surrogates
|
|
|
|
isCacheable:&shouldCache];
|
|
|
|
}
|
|
|
|
|
|
|
|
[self setCacheChild:(shouldCache ? result : nil)
|
|
|
|
forKey:name];
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setAdditionalProperty:(id)obj forName:(NSString *)name {
|
|
|
|
BOOL shouldCache = NO;
|
|
|
|
Class defaultClass = [[self class] classForAdditionalProperties];
|
|
|
|
id json = [GTLRuntimeCommon jsonFromAPIObject:obj
|
|
|
|
expectedClass:defaultClass
|
|
|
|
isCacheable:&shouldCache];
|
|
|
|
[self setJSONValue:json forKey:name];
|
|
|
|
[self setCacheChild:(shouldCache ? obj : nil)
|
|
|
|
forKey:name];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDictionary *)additionalProperties {
|
|
|
|
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
|
|
|
|
|
|
NSArray *propertyNames = [self additionalJSONKeys];
|
|
|
|
for (NSString *name in propertyNames) {
|
|
|
|
id obj = [self additionalPropertyForName:name];
|
|
|
|
[result setObject:obj forKey:name];
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Child Cache methods
|
|
|
|
|
|
|
|
// There is no property for childCache_ as there shouldn't be KVC/KVO
|
|
|
|
// support for it, it's an implementation detail.
|
|
|
|
|
|
|
|
- (void)setCacheChild:(id)obj forKey:(NSString *)key {
|
|
|
|
if (childCache_ == nil && obj != nil) {
|
|
|
|
childCache_ = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
|
|
|
|
obj, key, nil];
|
|
|
|
} else {
|
|
|
|
[childCache_ setValue:obj forKey:key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)cacheChildForKey:(NSString *)key {
|
|
|
|
id obj = [childCache_ objectForKey:key];
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark userData and user properties
|
|
|
|
|
|
|
|
- (void)setUserData:(id)userData {
|
|
|
|
[self setProperty:userData forKey:kUserDataPropertyKey];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)userData {
|
|
|
|
// be sure the returned pointer has the life of the autorelease pool,
|
|
|
|
// in case self is released immediately
|
|
|
|
return [[[self propertyForKey:kUserDataPropertyKey] retain] autorelease];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setProperty:(id)obj forKey:(NSString *)key {
|
|
|
|
if (obj == nil) {
|
|
|
|
// user passed in nil, so delete the property
|
|
|
|
[userProperties_ removeObjectForKey:key];
|
|
|
|
} else {
|
|
|
|
// be sure the property dictionary exists
|
|
|
|
if (userProperties_ == nil) {
|
|
|
|
self.userProperties = [NSMutableDictionary dictionary];
|
|
|
|
}
|
|
|
|
[userProperties_ setObject:obj forKey:key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)propertyForKey:(NSString *)key {
|
|
|
|
id obj = [userProperties_ objectForKey:key];
|
|
|
|
|
|
|
|
// be sure the returned pointer has the life of the autorelease pool,
|
|
|
|
// in case self is released immediately
|
|
|
|
return [[obj retain] autorelease];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Support methods
|
|
|
|
|
|
|
|
+ (NSMutableArray *)allDeclaredProperties {
|
|
|
|
NSMutableArray *array = [NSMutableArray array];
|
|
|
|
|
|
|
|
// walk from this class up the hierarchy to GTLObject
|
|
|
|
Class topClass = class_getSuperclass([GTLObject class]);
|
|
|
|
for (Class currClass = self;
|
|
|
|
currClass != topClass;
|
|
|
|
currClass = class_getSuperclass(currClass)) {
|
|
|
|
// step through this class's properties, and add the property names to the
|
|
|
|
// array
|
|
|
|
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);
|
|
|
|
// We only want dynamic properties; their attributes contain ",D".
|
|
|
|
const char *attr = property_getAttributes(*prop);
|
|
|
|
const char *dynamicMarker = strstr(attr, ",D");
|
|
|
|
if (dynamicMarker &&
|
|
|
|
(dynamicMarker[2] == 0 || dynamicMarker[2] == ',' )) {
|
|
|
|
[array addObject:[NSString stringWithUTF8String:propName]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(properties);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return array;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSArray *)allKnownKeys {
|
|
|
|
NSArray *allProps = [self allDeclaredProperties];
|
|
|
|
NSMutableArray *knownKeys = [NSMutableArray arrayWithArray:allProps];
|
|
|
|
|
|
|
|
NSDictionary *propMap = [GTLObject propertyToJSONKeyMapForClass:[self class]];
|
|
|
|
|
|
|
|
NSUInteger idx = 0;
|
|
|
|
for (NSString *propName in allProps) {
|
|
|
|
NSString *jsonKey = [propMap objectForKey:propName];
|
|
|
|
if (jsonKey) {
|
|
|
|
[knownKeys replaceObjectAtIndex:idx
|
|
|
|
withObject:jsonKey];
|
|
|
|
}
|
|
|
|
++idx;
|
|
|
|
}
|
|
|
|
return knownKeys;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)description {
|
|
|
|
// find the list of declared and otherwise known JSON keys for this class
|
|
|
|
NSArray *knownKeys = [[self class] allKnownKeys];
|
|
|
|
|
|
|
|
NSMutableString *descStr = [NSMutableString string];
|
|
|
|
|
|
|
|
NSString *spacer = @"";
|
|
|
|
for (NSString *key in json_) {
|
|
|
|
NSString *value = nil;
|
|
|
|
// show question mark for JSON keys not supported by a declared property:
|
|
|
|
// foo?:"Hi mom."
|
|
|
|
NSString *qmark = [knownKeys containsObject:key] ? @"" : @"?";
|
|
|
|
|
|
|
|
// determine property value to dislay
|
|
|
|
id rawValue = [json_ valueForKey:key];
|
|
|
|
if ([rawValue isKindOfClass:[NSDictionary class]]) {
|
|
|
|
// for dictionaries, show the list of keys:
|
|
|
|
// {key1,key2,key3}
|
|
|
|
NSString *subkeyList = [[rawValue allKeys] componentsJoinedByString:@","];
|
|
|
|
value = [NSString stringWithFormat:@"{%@}", subkeyList];
|
|
|
|
} else if ([rawValue isKindOfClass:[NSArray class]]) {
|
|
|
|
// for arrays, show the number of items in the array:
|
|
|
|
// [3]
|
2013-04-27 21:14:05 +00:00
|
|
|
value = [NSString stringWithFormat:@"[%lu]", (unsigned long)[(NSArray *)rawValue count]];
|
2012-08-25 10:38:29 +00:00
|
|
|
} else if ([rawValue isKindOfClass:[NSString class]]) {
|
|
|
|
// for strings, show the string in quotes:
|
|
|
|
// "Hi mom."
|
|
|
|
value = [NSString stringWithFormat:@"\"%@\"", rawValue];
|
|
|
|
} else {
|
|
|
|
// for numbers, show just the number
|
|
|
|
value = [rawValue description];
|
|
|
|
}
|
|
|
|
[descStr appendFormat:@"%@%@%@:%@", spacer, key, qmark, value];
|
|
|
|
spacer = @" ";
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString *str = [NSString stringWithFormat:@"%@ %p: {%@}",
|
|
|
|
[self class], self, descStr];
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Class Registration
|
|
|
|
|
|
|
|
static NSMutableDictionary *gKindMap = nil;
|
|
|
|
|
|
|
|
+ (Class)registeredObjectClassForKind:(NSString *)kind {
|
|
|
|
Class resultClass = [gKindMap objectForKey:kind];
|
|
|
|
return resultClass;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (void)registerObjectClassForKind:(NSString *)kind {
|
|
|
|
// there's no autorelease pool in place at +load time, so we'll create our own
|
|
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
|
|
|
|
|
|
if (gKindMap == nil) {
|
|
|
|
gKindMap = [GTLUtilities newStaticDictionary];
|
|
|
|
}
|
|
|
|
|
|
|
|
Class selfClass = [self class];
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
// ensure this is a unique registration
|
|
|
|
if ([gKindMap objectForKey:kind] != nil ) {
|
|
|
|
GTL_DEBUG_LOG(@"%@ (%@) registration conflicts with %@",
|
|
|
|
selfClass, kind, [gKindMap objectForKey:kind]);
|
|
|
|
}
|
|
|
|
if ([[gKindMap allKeysForObject:selfClass] count] != 0) {
|
|
|
|
GTL_DEBUG_LOG(@"%@ (%@) registration conflicts with %@",
|
|
|
|
selfClass, kind, [gKindMap allKeysForObject:selfClass]);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
[gKindMap setValue:selfClass forKey:kind];
|
|
|
|
|
|
|
|
// we drain here to keep the clang static analyzer quiet
|
|
|
|
[pool drain];
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Object Instantiation
|
|
|
|
|
|
|
|
+ (GTLObject *)objectForJSON:(NSMutableDictionary *)json
|
|
|
|
defaultClass:(Class)defaultClass
|
|
|
|
surrogates:(NSDictionary *)surrogates
|
|
|
|
batchClassMap:(NSDictionary *)batchClassMap {
|
2013-01-31 21:22:37 +00:00
|
|
|
if ([json count] == 0 || [json isEqual:[NSNull null]]) {
|
2012-08-25 10:38:29 +00:00
|
|
|
// no actual result, such as the response from a delete
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the class to instantiate, based on the original fetch
|
|
|
|
// request or by looking up "kind" string from the registration at
|
|
|
|
// +load time of GTLObject subclasses
|
|
|
|
//
|
|
|
|
// We're letting the dynamic kind override the default class so
|
|
|
|
// feeds of heterogenous entries can use the defaultClass as a
|
|
|
|
// fallback
|
|
|
|
Class classToCreate = defaultClass;
|
|
|
|
NSString *kind = nil;
|
|
|
|
if ([json isKindOfClass:[NSDictionary class]]) {
|
|
|
|
kind = [json valueForKey:@"kind"];
|
|
|
|
if ([kind isKindOfClass:[NSString class]] && [kind length] > 0) {
|
|
|
|
Class dynamicClass = [GTLObject registeredObjectClassForKind:kind];
|
|
|
|
if (dynamicClass) {
|
|
|
|
classToCreate = dynamicClass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Warn the developer that no specific class of GTLObject
|
|
|
|
// was requested with the fetch call, and no class is found
|
|
|
|
// compiled in to match the "kind" attribute of the JSON
|
|
|
|
// returned by the server
|
|
|
|
GTL_ASSERT(classToCreate != nil,
|
|
|
|
@"Could not find registered GTLObject subclass to "
|
|
|
|
"match JSON with kind \"%@\"", kind);
|
|
|
|
|
|
|
|
if (classToCreate == nil) {
|
|
|
|
classToCreate = [self class];
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if the top-level class for the JSON is listed in the surrogates;
|
|
|
|
// if so, instantiate the surrogate class instead
|
|
|
|
Class baseSurrogate = [surrogates objectForKey:classToCreate];
|
|
|
|
if (baseSurrogate) {
|
|
|
|
classToCreate = baseSurrogate;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now instantiate the GTLObject
|
|
|
|
GTLObject *parsedObject = [classToCreate object];
|
|
|
|
|
|
|
|
parsedObject.surrogates = surrogates;
|
|
|
|
parsedObject.JSON = json;
|
|
|
|
|
|
|
|
// it's time to instantiate inner items
|
|
|
|
if ([parsedObject conformsToProtocol:@protocol(GTLBatchItemCreationProtocol)]) {
|
|
|
|
id <GTLBatchItemCreationProtocol> batch =
|
|
|
|
(id <GTLBatchItemCreationProtocol>) parsedObject;
|
|
|
|
[batch createItemsWithClassMap:batchClassMap];
|
|
|
|
}
|
|
|
|
|
|
|
|
return parsedObject;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Runtime Utilities
|
|
|
|
|
|
|
|
static NSMutableDictionary *gJSONKeyMapCache = nil;
|
|
|
|
static NSMutableDictionary *gArrayPropertyToClassMapCache = nil;
|
|
|
|
|
|
|
|
+ (void)initialize {
|
|
|
|
// Note that initialize is guaranteed by the runtime to be called in a
|
|
|
|
// thread-safe manner
|
|
|
|
if (gJSONKeyMapCache == nil) {
|
|
|
|
gJSONKeyMapCache = [GTLUtilities newStaticDictionary];
|
|
|
|
}
|
|
|
|
if (gArrayPropertyToClassMapCache == nil) {
|
|
|
|
gArrayPropertyToClassMapCache = [GTLUtilities newStaticDictionary];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSDictionary *)propertyToJSONKeyMapForClass:(Class<GTLRuntimeCommon>)aClass {
|
|
|
|
NSDictionary *resultMap =
|
|
|
|
[GTLUtilities mergedClassDictionaryForSelector:@selector(propertyToJSONKeyMap)
|
|
|
|
startClass:aClass
|
|
|
|
ancestorClass:[GTLObject class]
|
|
|
|
cache:gJSONKeyMapCache];
|
|
|
|
return resultMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (NSDictionary *)arrayPropertyToClassMapForClass:(Class<GTLRuntimeCommon>)aClass {
|
|
|
|
NSDictionary *resultMap =
|
|
|
|
[GTLUtilities mergedClassDictionaryForSelector:@selector(arrayPropertyToClassMap)
|
|
|
|
startClass:aClass
|
|
|
|
ancestorClass:[GTLObject class]
|
|
|
|
cache:gArrayPropertyToClassMapCache];
|
|
|
|
return resultMap;
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark Runtime Support
|
|
|
|
|
|
|
|
+ (Class<GTLRuntimeCommon>)ancestorClass {
|
|
|
|
return [GTLObject class];
|
|
|
|
}
|
|
|
|
|
|
|
|
+ (BOOL)resolveInstanceMethod:(SEL)sel {
|
|
|
|
BOOL resolved = [GTLRuntimeCommon resolveInstanceMethod:sel onClass:self];
|
|
|
|
if (resolved)
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
return [super resolveInstanceMethod:sel];
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GTLCollectionObject
|
|
|
|
// Subclasses must implement the items method dynamically.
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
- (void)dealloc {
|
|
|
|
[identifierMap_ release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
2012-08-25 10:38:29 +00:00
|
|
|
- (id)itemAtIndex:(NSUInteger)idx {
|
|
|
|
NSArray *items = [self performSelector:@selector(items)];
|
|
|
|
if (idx < [items count]) {
|
|
|
|
return [items objectAtIndex:idx];
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2013-01-31 21:22:37 +00:00
|
|
|
- (id)objectAtIndexedSubscript:(NSInteger)idx {
|
|
|
|
if (idx >= 0) {
|
|
|
|
return [self itemAtIndex:(NSUInteger)idx];
|
|
|
|
}
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)itemForIdentifier:(NSString *)key {
|
|
|
|
if (identifierMap_ == nil) {
|
|
|
|
NSArray *items = [self performSelector:@selector(items)];
|
|
|
|
NSMutableDictionary *dict =
|
|
|
|
[NSMutableDictionary dictionaryWithCapacity:[items count]];
|
|
|
|
for (id item in items) {
|
|
|
|
id identifier = [item valueForKey:@"identifier"];
|
|
|
|
if (identifier != nil && identifier != [NSNull null]) {
|
|
|
|
if ([dict objectForKey:identifier] == nil) {
|
|
|
|
[dict setObject:item forKey:identifier];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
identifierMap_ = [dict copy];
|
|
|
|
}
|
|
|
|
return [identifierMap_ objectForKey:key];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)resetIdentifierMap {
|
|
|
|
[identifierMap_ release];
|
|
|
|
identifierMap_ = nil;
|
|
|
|
}
|
|
|
|
|
2012-08-25 10:38:29 +00:00
|
|
|
// NSFastEnumeration protocol
|
|
|
|
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
|
|
|
|
objects:(id *)stackbuf
|
|
|
|
count:(NSUInteger)len {
|
|
|
|
NSArray *items = [self performSelector:@selector(items)];
|
|
|
|
NSUInteger result = [items countByEnumeratingWithState:state
|
|
|
|
objects:stackbuf
|
|
|
|
count:len];
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation GTLResultArray
|
|
|
|
|
|
|
|
- (NSArray *)itemsWithItemClass:(Class)itemClass {
|
|
|
|
// Return the cached array before creating on demand.
|
|
|
|
NSString *cacheKey = @"result_array_items";
|
|
|
|
NSMutableArray *cachedArray = [self cacheChildForKey:cacheKey];
|
|
|
|
if (cachedArray != nil) {
|
|
|
|
return cachedArray;
|
|
|
|
}
|
|
|
|
NSArray *result = nil;
|
|
|
|
NSArray *array = (NSArray *)[self JSON];
|
|
|
|
if (array != nil) {
|
|
|
|
if ([array isKindOfClass:[NSArray class]]) {
|
|
|
|
NSDictionary *surrogates = self.surrogates;
|
|
|
|
result = [GTLRuntimeCommon objectFromJSON:array
|
|
|
|
defaultClass:itemClass
|
|
|
|
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([self class]),
|
|
|
|
NSStringFromClass([array class]),
|
|
|
|
array);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
result = array;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[self setCacheChild:result forKey:cacheKey];
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|