PageRenderTime 228ms CodeModel.GetById 29ms app.highlight 190ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/google-toolbox-for-mac/AddressBook/GTMABAddressBook.m

http://macfuse.googlecode.com/
Objective C | 1241 lines | 1014 code | 105 blank | 122 comment | 147 complexity | f4bb39731b724e82dbd03673f93d8cae MD5 | raw file
   1//
   2//  GTMAddressBook.m
   3//
   4//  Copyright 2008 Google Inc.
   5//
   6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
   7//  use this file except in compliance with the License.  You may obtain a copy
   8//  of the License at
   9// 
  10//  http://www.apache.org/licenses/LICENSE-2.0
  11// 
  12//  Unless required by applicable law or agreed to in writing, software
  13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
  15//  License for the specific language governing permissions and limitations under
  16//  the License.
  17//
  18
  19#import "GTMABAddressBook.h"
  20#import "GTMTypeCasting.h"
  21
  22#if GTM_IPHONE_SDK
  23#import <UIKit/UIKit.h>
  24#else  // GTM_IPHONE_SDK
  25#import <Cocoa/Cocoa.h>
  26#endif  // GTM_IPHONE_SDK
  27
  28#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
  29// Tiger does not have this functionality, so we just set them to 0
  30// as they are "or'd" in. This does change the functionality slightly.
  31enum {
  32  NSDiacriticInsensitiveSearch = 0,
  33  NSWidthInsensitiveSearch = 0
  34};
  35#endif
  36
  37NSString *const kGTMABUnknownPropertyName = @"UNKNOWN_PROPERTY";
  38
  39typedef struct {
  40  GTMABPropertyType pType;
  41  Class class;
  42} TypeClassNameMap;
  43
  44@interface GTMABMultiValue ()
  45- (unsigned long*)mutations;
  46@end
  47
  48@interface GTMABMutableMultiValue ()
  49// Checks to see if a value is a valid type to be stored in this multivalue
  50- (BOOL)checkValueType:(id)value;
  51@end
  52
  53@interface GTMABMultiValueEnumerator : NSEnumerator {
  54 @private
  55  __weak ABMultiValueRef ref_;  // ref_ cached from enumeree_
  56  GTMABMultiValue *enumeree_;
  57  unsigned long mutations_;
  58  NSUInteger count_;
  59  NSUInteger index_;
  60  BOOL useLabels_;
  61}
  62+ (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree;
  63+ (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree;
  64- (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels;
  65#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  66- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state 
  67                                  objects:(id *)stackbuf 
  68                                    count:(NSUInteger)len;
  69#endif  // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  70@end
  71
  72@implementation GTMABAddressBook
  73+ (GTMABAddressBook *)addressBook {
  74  return [[[self alloc] init] autorelease];
  75}
  76
  77- (id)init {
  78  if ((self = [super init])) {
  79#if GTM_IPHONE_SDK
  80    CFErrorRef error = nil;
  81    addressBook_ = ABAddressBookCreateWithOptions(NULL, &error);
  82    if (error) {
  83      _GTMDevLog(@"ABAddressBookCreate: %@", error);
  84      CFRelease(error);
  85    }
  86#else  // GTM_IPHONE_SDK
  87    addressBook_ = ABGetSharedAddressBook();
  88    CFRetain(addressBook_);
  89#endif  // GTM_IPHONE_SDK
  90    if (!addressBook_) {
  91      // COV_NF_START
  92      [self release];
  93      self = nil;
  94      // COV_NF_END
  95    }
  96  }
  97  return self;
  98}
  99
 100- (void)dealloc {
 101  if (addressBook_) {
 102    CFRelease(addressBook_);
 103  }
 104  [super dealloc];
 105}
 106
 107- (BOOL)save {
 108#if GTM_IPHONE_SDK
 109  CFErrorRef cfError = NULL;
 110  bool wasGood = ABAddressBookSave(addressBook_, &cfError);
 111  if (!wasGood) {
 112    _GTMDevLog(@"Error in [%@ %@]: %@", 
 113               [self class], NSStringFromSelector(_cmd), cfError);
 114    CFRelease(cfError);
 115  }
 116#else  // GTM_IPHONE_SDK
 117  bool wasGood = ABSave(addressBook_);
 118#endif  // GTM_IPHONE_SDK
 119  return wasGood ? YES : NO;
 120}
 121
 122- (BOOL)hasUnsavedChanges {
 123  bool hasUnsavedChanges;
 124#if GTM_IPHONE_SDK
 125  hasUnsavedChanges = ABAddressBookHasUnsavedChanges(addressBook_);
 126#else  // GTM_IPHONE_SDK
 127  hasUnsavedChanges = ABHasUnsavedChanges(addressBook_);
 128#endif  // GTM_IPHONE_SDK
 129  return hasUnsavedChanges ? YES : NO;
 130}
 131
 132- (BOOL)addRecord:(GTMABRecord *)record {
 133  // Note: we check for bad data here because of radar
 134  // 6201258 Adding a NULL record using ABAddressBookAddRecord crashes
 135  if (!record) return NO;
 136#if GTM_IPHONE_SDK
 137  CFErrorRef cfError = NULL;
 138  bool wasGood = ABAddressBookAddRecord(addressBook_, 
 139                                        [record recordRef], &cfError);
 140  if (cfError) {
 141    // COV_NF_START
 142    _GTMDevLog(@"Error in [%@ %@]: %@", 
 143               [self class], NSStringFromSelector(_cmd), cfError);
 144    CFRelease(cfError);  
 145    // COV_NF_END
 146  }
 147#else  // GTM_IPHONE_SDK
 148  bool wasGood = ABAddRecord(addressBook_, [record recordRef]);
 149#endif  // GTM_IPHONE_SDK
 150  return wasGood ? YES : NO;
 151}
 152
 153- (BOOL)removeRecord:(GTMABRecord *)record {
 154  // Note: we check for bad data here because of radar
 155  // 6201276 Removing a NULL record using ABAddressBookRemoveRecord crashes
 156  if (!record) return NO;
 157#if GTM_IPHONE_SDK
 158  CFErrorRef cfError = NULL;
 159  bool wasGood = ABAddressBookRemoveRecord(addressBook_, 
 160                                           [record recordRef], &cfError);
 161  if (cfError) {
 162    // COV_NF_START
 163    _GTMDevLog(@"Error in [%@ %@]: %@", 
 164               [self class], NSStringFromSelector(_cmd), cfError);
 165    CFRelease(cfError);
 166    // COV_NF_END
 167  }
 168#else  // GTM_IPHONE_SDK
 169  GTMABRecordID recID = [record recordID];
 170  ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, (CFStringRef)recID);
 171  bool wasGood = NO;
 172  if (ref) {
 173    wasGood = ABRemoveRecord(addressBook_, [record recordRef]);
 174    CFRelease(ref);
 175  }
 176#endif  // GTM_IPHONE_SDK
 177  return wasGood ? YES : NO;
 178}  
 179
 180- (NSArray *)people {
 181#if GTM_IPHONE_SDK
 182  NSArray *people 
 183    = GTMCFAutorelease(ABAddressBookCopyArrayOfAllPeople(addressBook_));
 184#else  // GTM_IPHONE_SDK
 185  NSArray *people 
 186    = GTMCFAutorelease(ABCopyArrayOfAllPeople(addressBook_));
 187#endif  // GTM_IPHONE_SDK  
 188  NSMutableArray *result = [NSMutableArray arrayWithCapacity:[people count]];
 189  id person;
 190  GTM_FOREACH_OBJECT(person, people) {
 191    [result addObject:[GTMABPerson recordWithRecord:person]];
 192  }
 193  return result;
 194}
 195
 196- (NSArray *)groups {
 197#if GTM_IPHONE_SDK
 198  NSArray *groups 
 199    = GTMCFAutorelease(ABAddressBookCopyArrayOfAllGroups(addressBook_));
 200#else  // GTM_IPHONE_SDK
 201  NSArray *groups 
 202    = GTMCFAutorelease(ABCopyArrayOfAllGroups(addressBook_));
 203#endif  // GTM_IPHONE_SDK  
 204  NSMutableArray *result = [NSMutableArray arrayWithCapacity:[groups count]];
 205  id group;
 206  GTM_FOREACH_OBJECT(group, groups) {
 207    [result addObject:[GTMABGroup recordWithRecord:group]];
 208  }
 209  return result;
 210}
 211
 212- (ABAddressBookRef)addressBookRef {
 213  return addressBook_;
 214}
 215
 216- (GTMABPerson *)personForId:(GTMABRecordID)uniqueId {
 217  GTMABPerson *person = nil;
 218#if GTM_IPHONE_SDK
 219  ABRecordRef ref = ABAddressBookGetPersonWithRecordID(addressBook_, uniqueId);
 220#else  // GTM_IPHONE_SDK
 221  ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, 
 222                                            (CFStringRef)uniqueId);
 223#endif  // GTM_IPHONE_SDK
 224  if (ref) {
 225    person = [GTMABPerson recordWithRecord:ref];
 226  }
 227  return person;
 228}
 229
 230- (GTMABGroup *)groupForId:(GTMABRecordID)uniqueId {
 231  GTMABGroup *group = nil;
 232#if GTM_IPHONE_SDK
 233  ABRecordRef ref = ABAddressBookGetGroupWithRecordID(addressBook_, uniqueId);
 234#else  // GTM_IPHONE_SDK
 235  ABRecordRef ref = ABCopyRecordForUniqueId(addressBook_, 
 236                                            (CFStringRef)uniqueId);
 237#endif  // GTM_IPHONE_SDK
 238  if (ref) {
 239    group = [GTMABGroup recordWithRecord:ref];
 240  }
 241  return group;
 242}
 243
 244// Performs a prefix search on the composite names of people in an address book 
 245// and returns an array of persons that match the search criteria.
 246- (NSArray *)peopleWithCompositeNameWithPrefix:(NSString *)prefix {
 247#if GTM_IPHONE_SDK
 248  NSArray *people = 
 249    GTMCFAutorelease(ABAddressBookCopyPeopleWithName(addressBook_,
 250                                                     (CFStringRef)prefix));
 251  NSMutableArray *gtmPeople = [NSMutableArray arrayWithCapacity:[people count]];
 252  id person;
 253  GTM_FOREACH_OBJECT(person, people) {
 254    GTMABPerson *gtmPerson = [GTMABPerson recordWithRecord:person];
 255    [gtmPeople addObject:gtmPerson];
 256  }
 257  return gtmPeople;
 258#else
 259  // TODO(dmaclach): Change over to recordsMatchingSearchElement as an
 260  // optimization?
 261  // TODO(dmaclach): Make this match the way that the iPhone does it (by
 262  // checking both first and last names) and adding unittests for all this.
 263  NSArray *people = [self people];
 264  NSMutableArray *foundPeople = [NSMutableArray array];
 265  GTMABPerson *person;
 266  GTM_FOREACH_OBJECT(person, people) {
 267    NSString *compositeName = [person compositeName];
 268    NSRange range = [compositeName rangeOfString:prefix
 269                                         options:(NSCaseInsensitiveSearch 
 270                                                  | NSDiacriticInsensitiveSearch
 271                                                  | NSWidthInsensitiveSearch
 272                                                  | NSAnchoredSearch)];
 273    if (range.location != NSNotFound) {
 274      [foundPeople addObject:person];
 275    }
 276  }
 277  return foundPeople;
 278#endif
 279}
 280
 281// Performs a prefix search on the composite names of groups in an address book 
 282// and returns an array of groups that match the search criteria.
 283- (NSArray *)groupsWithCompositeNameWithPrefix:(NSString *)prefix {
 284  NSArray *groups = [self groups];
 285  NSMutableArray *foundGroups = [NSMutableArray array];
 286  GTMABGroup *group;
 287  GTM_FOREACH_OBJECT(group, groups) {
 288    NSString *compositeName = [group compositeName];
 289    NSRange range = [compositeName rangeOfString:prefix
 290                                         options:(NSCaseInsensitiveSearch 
 291                                                  | NSDiacriticInsensitiveSearch
 292                                                  | NSWidthInsensitiveSearch
 293                                                  | NSAnchoredSearch)];
 294    if (range.location != NSNotFound) {
 295      [foundGroups addObject:group];
 296    }
 297  }
 298  return foundGroups;
 299}  
 300
 301+ (NSString *)localizedLabel:(NSString *)label {
 302#if GTM_IPHONE_SDK
 303  return GTMCFAutorelease(ABAddressBookCopyLocalizedLabel((CFStringRef)label));
 304#else  // GTM_IPHONE_SDK
 305  return GTMCFAutorelease(ABCopyLocalizedPropertyOrLabel((CFStringRef)label));
 306#endif  // GTM_IPHONE_SDK
 307}
 308
 309@end
 310
 311@implementation GTMABRecord
 312+ (id)recordWithRecord:(ABRecordRef)record {
 313  return [[[self alloc] initWithRecord:record] autorelease];
 314}
 315
 316- (id)initWithRecord:(ABRecordRef)record {
 317  if ((self = [super init])) {
 318    if ([self class] == [GTMABRecord class]) {
 319      [self autorelease];
 320      [self doesNotRecognizeSelector:_cmd];
 321    }
 322    if (!record) {
 323      [self release];
 324      self = nil;
 325    } else {
 326      record_ = (ABRecordRef)CFRetain(record);
 327    }
 328  }
 329  return self;
 330}
 331
 332- (NSUInteger)hash {
 333  // This really isn't completely valid due to
 334  // 6203836 ABRecords hash to their address
 335  // but it's the best we can do without knowing what properties
 336  // are in a record, and we don't have an API for that.
 337  return CFHash(record_);
 338}
 339
 340- (BOOL)isEqual:(id)object {
 341  // This really isn't completely valid due to
 342  // 6203836 ABRecords hash to their address
 343  // but it's the best we can do without knowing what properties
 344  // are in a record, and we don't have an API for that.
 345  return [object respondsToSelector:@selector(recordRef)] 
 346    && CFEqual(record_, [object recordRef]);
 347}
 348
 349- (void)dealloc {
 350  if (record_) {
 351    CFRelease(record_);
 352  }
 353  [super dealloc];
 354}
 355
 356- (ABRecordRef)recordRef {
 357  return record_;
 358}
 359
 360- (GTMABRecordID)recordID {
 361#if GTM_IPHONE_SDK
 362  return ABRecordGetRecordID(record_);
 363#else  // GTM_IPHONE_SDK
 364  return GTMCFAutorelease(ABRecordCopyUniqueId(record_));
 365#endif  // GTM_IPHONE_SDK
 366}
 367
 368- (id)valueForProperty:(GTMABPropertyID)property {
 369#if GTM_IPHONE_SDK
 370  id value = GTMCFAutorelease(ABRecordCopyValue(record_, property));
 371#else  // GTM_IPHONE_SDK 
 372  id value = GTMCFAutorelease(ABRecordCopyValue(record_, (CFStringRef)property));
 373#endif  // GTM_IPHONE_SDK
 374  if (value) {
 375    if ([[self class] typeOfProperty:property] & kABMultiValueMask) {
 376      value = [[[GTMABMultiValue alloc] 
 377                initWithMultiValue:(ABMultiValueRef)value] autorelease];
 378    }
 379  }
 380  return value;
 381}
 382
 383- (BOOL)setValue:(id)value forProperty:(GTMABPropertyID)property {
 384  if (!value) return NO;
 385  // We check the type here because of
 386  // Radar 6201046 ABRecordSetValue returns true even if you pass in a bad type
 387  //               for a value  
 388  TypeClassNameMap fullTypeMap[] = {
 389    { kGTMABStringPropertyType, [NSString class] },
 390    { kGTMABIntegerPropertyType, [NSNumber class] },
 391    { kGTMABRealPropertyType, [NSNumber class] },
 392    { kGTMABDateTimePropertyType, [NSDate class] },
 393    { kGTMABDictionaryPropertyType, [NSDictionary class] },
 394    { kGTMABMultiStringPropertyType, [GTMABMultiValue class] },
 395    { kGTMABMultiRealPropertyType, [GTMABMultiValue class] },
 396    { kGTMABMultiDateTimePropertyType, [GTMABMultiValue class] },
 397    { kGTMABMultiDictionaryPropertyType, [GTMABMultiValue class] }
 398  };
 399  GTMABPropertyType type = [[self class] typeOfProperty:property];
 400  BOOL wasFound = NO;
 401  for (size_t i = 0; i < sizeof(fullTypeMap) / sizeof(TypeClassNameMap); ++i) {
 402    if (fullTypeMap[i].pType == type) {
 403      wasFound = YES;
 404      if (![[value class] isSubclassOfClass:fullTypeMap[i].class]) {
 405        return NO;
 406      }
 407    }
 408  }
 409  if (!wasFound) {
 410    return NO;
 411  }
 412  if (type & kABMultiValueMask) {
 413    value = (id)[value multiValueRef];
 414  }
 415#if GTM_IPHONE_SDK
 416  CFErrorRef cfError = nil;
 417  bool wasGood = ABRecordSetValue(record_, property, 
 418                                  (CFTypeRef)value, &cfError);
 419  if (cfError) {
 420    // COV_NF_START
 421    _GTMDevLog(@"Error in [%@ %@]: %@", 
 422               [self class], NSStringFromSelector(_cmd), cfError);
 423    CFRelease(cfError);
 424    // COV_NF_END
 425  }
 426#else  // GTM_IPHONE_SDK
 427  bool wasGood = ABRecordSetValue(record_, (CFStringRef)property, (CFTypeRef)value);
 428#endif  // GTM_IPHONE_SDK
 429  return wasGood ? YES : NO;
 430}
 431
 432- (BOOL)removeValueForProperty:(GTMABPropertyID)property {
 433#if GTM_IPHONE_SDK
 434  CFErrorRef cfError = nil;
 435  // We check to see if the value is in the property because of:
 436  // Radar 6201005 ABRecordRemoveValue returns true for value that aren't 
 437  //               in the record
 438  id value = [self valueForProperty:property];
 439  bool wasGood = value && ABRecordRemoveValue(record_, property, &cfError);
 440  if (cfError) {
 441    // COV_NF_START
 442    _GTMDevLog(@"Error in [%@ %@]: %@", 
 443               [self class], NSStringFromSelector(_cmd), cfError);
 444    CFRelease(cfError);
 445    // COV_NF_END
 446  }
 447#else  // GTM_IPHONE_SDK
 448  id value = [self valueForProperty:property];
 449  bool wasGood = value && ABRecordRemoveValue(record_, (CFStringRef)property);
 450#endif  // GTM_IPHONE_SDK
 451  return wasGood ? YES : NO;
 452}
 453
 454// COV_NF_START
 455// All of these methods are to be overridden by their subclasses
 456
 457- (NSString *)compositeName {
 458  [self doesNotRecognizeSelector:_cmd];
 459  return nil;
 460}
 461
 462+ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property {
 463  [self doesNotRecognizeSelector:_cmd];
 464  return kGTMABInvalidPropertyType;
 465}
 466
 467+ (NSString *)localizedPropertyName:(GTMABPropertyID)property {
 468  [self doesNotRecognizeSelector:_cmd];
 469  return nil; 
 470}
 471// COV_NF_END
 472@end
 473
 474@implementation GTMABPerson
 475
 476+ (GTMABPerson *)personWithFirstName:(NSString *)first 
 477                            lastName:(NSString *)last {
 478  GTMABPerson *person = [[[self alloc] init] autorelease];
 479  if (person) {
 480    BOOL isGood = YES;
 481    if (first) {
 482      isGood = [person setValue:first 
 483                    forProperty:kGTMABPersonFirstNameProperty];
 484    }
 485    if (isGood && last) {
 486      isGood = [person setValue:last forProperty:kGTMABPersonLastNameProperty];
 487    }
 488    if (!isGood) {
 489      // COV_NF_START
 490      // Marked as NF because I don't know how to force an error
 491      person = nil;
 492      // COV_NF_END
 493    }
 494  }
 495  return person;
 496}
 497
 498- (id)init {
 499  ABRecordRef person = ABPersonCreate();
 500  self = [super initWithRecord:person];
 501  if (person) {
 502    CFRelease(person);
 503  } 
 504  return self;
 505}
 506
 507- (BOOL)setImageData:(NSData *)data {
 508#if GTM_IPHONE_SDK
 509  CFErrorRef cfError = NULL;
 510  bool wasGood = NO;
 511  if (!data) {
 512    wasGood = ABPersonRemoveImageData([self recordRef], &cfError);
 513  } else {
 514    // We verify that the data is good because of:
 515    // Radar 6202868 ABPersonSetImageData should validate image data
 516    UIImage *image = [UIImage imageWithData:data];
 517    wasGood = image && ABPersonSetImageData([self recordRef], 
 518                                            (CFDataRef)data, &cfError);
 519  }
 520  if (cfError) {
 521    // COV_NF_START
 522    _GTMDevLog(@"Error in [%@ %@]: %@", 
 523               [self class], NSStringFromSelector(_cmd), cfError);
 524    CFRelease(cfError);
 525    // COV_NF_END
 526  }
 527#else  // GTM_IPHONE_SDK
 528  bool wasGood = YES;
 529  if (data) {
 530    NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
 531    wasGood = image != nil;
 532  }
 533  wasGood = wasGood && ABPersonSetImageData([self recordRef], (CFDataRef)data);
 534#endif  // GTM_IPHONE_SDK
 535  return wasGood ? YES : NO;
 536}
 537
 538- (GTMABImage *)image {
 539  NSData *data = [self imageData];
 540#if GTM_IPHONE_SDK
 541  return [UIImage imageWithData:data];
 542#else  // GTM_IPHONE_SDK
 543  return [[[NSImage alloc] initWithData:data] autorelease];
 544#endif  // GTM_IPHONE_SDK
 545}
 546
 547- (BOOL)setImage:(GTMABImage *)image {
 548#if GTM_IPHONE_SDK
 549  NSData *data = UIImagePNGRepresentation(image);
 550#else  // GTM_IPHONE_SDK
 551  NSData *data = [image TIFFRepresentation];
 552#endif  // GTM_IPHONE_SDK
 553  return [self setImageData:data];
 554}
 555
 556- (NSData *)imageData {
 557  return GTMCFAutorelease(ABPersonCopyImageData([self recordRef]));
 558}
 559
 560- (NSString *)compositeName {
 561#if GTM_IPHONE_SDK
 562  return GTMCFAutorelease(ABRecordCopyCompositeName([self recordRef]));
 563#else  // GTM_IPHONE_SDK
 564  NSNumber *nsFlags = [self valueForProperty:kABPersonFlags];
 565  NSInteger flags = [nsFlags longValue];
 566  NSString *compositeName = nil;
 567  if (flags & kABShowAsCompany) {
 568    compositeName = [self valueForProperty:kABOrganizationProperty];
 569  } else {
 570    NSString *firstName = [self valueForProperty:kGTMABPersonFirstNameProperty];
 571    NSString *lastName = [self valueForProperty:kGTMABPersonLastNameProperty];
 572    
 573    if (firstName && lastName) {
 574      GTMABPersonCompositeNameFormat format;
 575      if (flags & kABFirstNameFirst) {
 576        format = kABPersonCompositeNameFormatFirstNameFirst;
 577      } else if (flags & kABLastNameFirst) {
 578        format = kABPersonCompositeNameFormatLastNameFirst;
 579      } else {
 580        format = [[self class] compositeNameFormat];
 581      }
 582      if (format == kABPersonCompositeNameFormatLastNameFirst) {
 583        NSString *tempStr = lastName;
 584        lastName = firstName;
 585        firstName = tempStr;
 586      }
 587      compositeName = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
 588    } else if (firstName) {
 589      compositeName = firstName;
 590    } else if (lastName) {
 591      compositeName = lastName;
 592    } else {
 593      compositeName = @"";
 594    }
 595  }
 596    
 597  return compositeName;
 598#endif  // GTM_IPHONE_SDK
 599}
 600
 601- (NSString *)description {
 602#if GTM_IPHONE_SDK
 603  return [NSString stringWithFormat:@"%@ %@ %@ %d",
 604          [self class], 
 605          [self valueForProperty:kGTMABPersonFirstNameProperty],
 606          [self valueForProperty:kGTMABPersonLastNameProperty],
 607          [self recordID]]; 
 608#else  // GTM_IPHONE_SDK
 609  return [NSString stringWithFormat:@"%@ %@ %@ %@",
 610          [self class], 
 611          [self valueForProperty:kGTMABPersonFirstNameProperty],
 612          [self valueForProperty:kGTMABPersonLastNameProperty],
 613          [self recordID]];
 614#endif  // GTM_IPHONE_SDK
 615}
 616
 617+ (NSString *)localizedPropertyName:(GTMABPropertyID)property {
 618#if GTM_IPHONE_SDK
 619  return GTMCFAutorelease(ABPersonCopyLocalizedPropertyName(property)); 
 620#else  // GTM_IPHONE_SDK
 621  return ABLocalizedPropertyOrLabel(property);
 622#endif  // GTM_IPHONE_SDK
 623}
 624
 625+ (GTMABPersonCompositeNameFormat)compositeNameFormat {
 626#if GTM_IPHONE_SDK
 627  return ABPersonGetCompositeNameFormat();
 628#else  // GTM_IPHONE_SDK
 629  NSInteger nameOrdering 
 630    = [[ABAddressBook sharedAddressBook] defaultNameOrdering];
 631  return nameOrdering == kABFirstNameFirst ? 
 632    kABPersonCompositeNameFormatFirstNameFirst :
 633    kABPersonCompositeNameFormatLastNameFirst;
 634#endif  // GTM_IPHONE_SDK
 635}
 636
 637+ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property {
 638#if GTM_IPHONE_SDK
 639  return ABPersonGetTypeOfProperty(property);
 640#else  // GTM_IPHONE_SDK
 641  return ABTypeOfProperty([[GTMABAddressBook addressBook] addressBookRef], 
 642                          (CFStringRef)kABPersonRecordType, 
 643                          (CFStringRef)property);
 644#endif  // GTM_IPHONE_SDK
 645}
 646@end
 647
 648@implementation GTMABGroup
 649
 650+ (GTMABGroup *)groupNamed:(NSString *)name {
 651  GTMABGroup *group = [[[self alloc] init] autorelease];
 652  if (group) {
 653    if (![group setValue:name forProperty:kABGroupNameProperty]) {
 654      // COV_NF_START
 655      // Can't get setValue to fail for me
 656      group = nil;
 657      // COV_NF_END
 658    }
 659  }
 660  return group;
 661}
 662
 663- (id)init {
 664  ABRecordRef group = ABGroupCreate();
 665  self = [super initWithRecord:group];
 666  if (group) {
 667    CFRelease(group);
 668  } 
 669  return self;
 670}
 671
 672- (NSArray *)members {
 673  NSArray *people 
 674    = GTMCFAutorelease(ABGroupCopyArrayOfAllMembers([self recordRef]));
 675  NSMutableArray *gtmPeople = [NSMutableArray arrayWithCapacity:[people count]];
 676  id person;
 677  GTM_FOREACH_OBJECT(person, people) {
 678    [gtmPeople addObject:[GTMABPerson recordWithRecord:(ABRecordRef)person]];
 679  }
 680  return gtmPeople;
 681}  
 682
 683- (BOOL)addMember:(GTMABPerson *)person {
 684#if GTM_IPHONE_SDK
 685  CFErrorRef cfError = nil;
 686  // We check for person because of
 687  // Radar 6202860 Passing nil person into ABGroupAddMember crashes
 688  bool wasGood = person && ABGroupAddMember([self recordRef], 
 689                                            [person recordRef], &cfError);
 690  if (cfError) {
 691    // COV_NF_START
 692    _GTMDevLog(@"Error in [%@ %@]: %@", 
 693               [self class], NSStringFromSelector(_cmd), cfError);
 694    CFRelease(cfError);
 695    // COV_NF_END
 696  }
 697#else  // GTM_IPHONE_SDK
 698  bool wasGood = person && ABGroupAddMember([self recordRef], 
 699                                            [person recordRef]);
 700#endif  // GTM_IPHONE_SDK
 701  return wasGood ? YES : NO;
 702}  
 703
 704- (BOOL)removeMember:(GTMABPerson *)person {
 705#if GTM_IPHONE_SDK
 706  CFErrorRef cfError = nil;
 707  // We check for person because of
 708  // Radar 6202860 Passing nil person into ABGroupAddMember crashes
 709  // (I know this is remove, but it crashes there too)
 710  bool wasGood = person && ABGroupRemoveMember([self recordRef], 
 711                                               [person recordRef], &cfError);
 712  if (cfError) {
 713    // COV_NF_START
 714    _GTMDevLog(@"Error in [%@ %@]: %@", 
 715               [self class], NSStringFromSelector(_cmd), cfError);
 716    CFRelease(cfError);
 717    // COV_NF_END
 718  }
 719#else  // GTM_IPHONE_SDK
 720  bool wasGood = person != nil;
 721  if (wasGood) {
 722    NSArray *array = GTMCFAutorelease(ABPersonCopyParentGroups([person recordRef]));
 723    if ([array containsObject:[self recordRef]]) {
 724      wasGood = ABGroupRemoveMember([self recordRef], 
 725                                    [person recordRef]);
 726    } else {
 727      wasGood = NO;
 728    }
 729  }
 730#endif  // GTM_IPHONE_SDK
 731  return wasGood ? YES : NO;
 732}  
 733
 734- (NSString *)compositeName {
 735#if GTM_IPHONE_SDK
 736  return GTMCFAutorelease(ABRecordCopyCompositeName([self recordRef]));
 737#else  // GTM_IPHONE_SDK
 738  return [self valueForProperty:kGTMABGroupNameProperty];
 739#endif  // GTM_IPHONE_SDK
 740}
 741
 742+ (GTMABPropertyType)typeOfProperty:(GTMABPropertyID)property {
 743  GTMABPropertyType type = kGTMABInvalidPropertyType;
 744  if (property == kABGroupNameProperty) {
 745    type = kGTMABStringPropertyType;
 746  } 
 747  return type;
 748}
 749
 750+ (NSString *)localizedPropertyName:(GTMABPropertyID)property {
 751  NSString *name = kGTMABUnknownPropertyName;
 752  if (property == kABGroupNameProperty) {
 753    name = NSLocalizedStringFromTable(@"Name",
 754                                      @"GTMABAddressBook", 
 755                                      @"name property");
 756  }
 757  return name;
 758}
 759
 760- (NSString *)description {
 761#if GTM_IPHONE_SDK
 762  return [NSString stringWithFormat:@"%@ %@ %d", 
 763          [self class], 
 764          [self valueForProperty:kABGroupNameProperty],
 765          [self recordID]];
 766#else  // GTM_IPHONE_SDK
 767  return [NSString stringWithFormat:@"%@ %@ %@", 
 768          [self class], 
 769          [self valueForProperty:kABGroupNameProperty],
 770          [self recordID]];
 771#endif  // GTM_IPHONE_SDK
 772}
 773@end
 774
 775@implementation GTMABMultiValue
 776- (id)init {
 777  // Call super init and release so we don't leak
 778  [[super init] autorelease];
 779  [self doesNotRecognizeSelector:_cmd];
 780  return nil;  // COV_NF_LINE
 781}
 782
 783- (id)initWithMultiValue:(ABMultiValueRef)multiValue {
 784  if ((self = [super init])) {
 785    if (!multiValue) {
 786      [self release];
 787      self = nil;
 788    } else {
 789      multiValue_ = CFRetain(multiValue);
 790    }
 791  }
 792  return self;
 793}
 794
 795- (id)copyWithZone:(NSZone *)zone {
 796  return [[GTMABMultiValue alloc] initWithMultiValue:multiValue_];
 797}
 798
 799- (id)mutableCopyWithZone:(NSZone *)zone {
 800  return [[GTMABMutableMultiValue alloc] initWithMultiValue:multiValue_];
 801}
 802
 803- (NSUInteger)hash {
 804  // I'm implementing hash instead of using CFHash(multiValue_) because
 805  // 6203854 ABMultiValues hash to their address
 806  NSUInteger count = [self count];
 807  NSUInteger hash = 0;
 808  for (NSUInteger i = 0; i < count;  ++i) {
 809    NSString *label = [self labelAtIndex:i];
 810    id value = [self valueAtIndex:i];
 811    hash += [label hash];
 812    hash += [value hash];
 813  }
 814  return hash;
 815}
 816
 817- (BOOL)isEqual:(id)object {
 818  // I'm implementing isEqual instea of using CFEquals(multiValue,...) because
 819  // 6203854 ABMultiValues hash to their address
 820  // and it appears CFEquals just calls through to hash to compare them.
 821  BOOL isEqual = NO;
 822  if ([object respondsToSelector:@selector(multiValueRef)]) { 
 823    isEqual = multiValue_ == [object multiValueRef];
 824    if (!isEqual) {
 825      NSUInteger count = [self count];
 826      NSUInteger objCount = [object count];
 827      isEqual = count == objCount;
 828      for (NSUInteger i = 0; isEqual && i < count;  ++i) {
 829        NSString *label = [self labelAtIndex:i];
 830        NSString *objLabel = [object labelAtIndex:i];
 831        isEqual = [label isEqual:objLabel];
 832        if (isEqual) {
 833          id value = [self valueAtIndex:i];
 834          GTMABMultiValue *multiValueObject 
 835            = GTM_STATIC_CAST(GTMABMultiValue, object);
 836          id objValue = [multiValueObject valueAtIndex:i];
 837          isEqual = [value isEqual:objValue];
 838        }
 839      }
 840    }
 841  }
 842  return isEqual;
 843}
 844
 845- (void)dealloc {
 846  if (multiValue_) {
 847    CFRelease(multiValue_);
 848  }
 849  [super dealloc];
 850}
 851
 852- (ABMultiValueRef)multiValueRef {
 853  return multiValue_;
 854}
 855
 856- (NSUInteger)count {
 857#if GTM_IPHONE_SDK
 858  return ABMultiValueGetCount(multiValue_);
 859#else  // GTM_IPHONE_SDK
 860  return ABMultiValueCount(multiValue_);
 861#endif  // GTM_IPHONE_SDK
 862}
 863
 864- (id)valueAtIndex:(NSUInteger)idx {
 865  id value = nil;
 866  if (idx < [self count]) {
 867    value = GTMCFAutorelease(ABMultiValueCopyValueAtIndex(multiValue_, idx));
 868    ABPropertyType type = [self propertyType];
 869    if (type == kGTMABIntegerPropertyType 
 870        || type == kGTMABRealPropertyType
 871        || type == kGTMABDictionaryPropertyType) {
 872      // This is because of
 873      // 6208390 Integer and real values don't work in ABMultiValueRefs
 874      // Apparently they forget to add a ref count on int, real and 
 875      // dictionary values in ABMultiValueCopyValueAtIndex, although they do 
 876      // remember them for all other types.
 877      // Once they fix this, this will lead to a leak, but I figure the leak
 878      // is better than the crash. Our unittests will test to make sure that
 879      // this is the case, and once we find a system that has this fixed, we
 880      // can conditionalize this code. Look for testRadar6208390 in
 881      // GTMABAddressBookTest.m
 882      // Also, search for 6208390 below and fix the fast enumerator to actually
 883      // be somewhat performant when this is fixed.
 884#ifndef __clang_analyzer__
 885      [value retain];
 886#endif  // __clang_analyzer__
 887    }
 888  }
 889  return value;
 890}
 891
 892- (NSString *)labelAtIndex:(NSUInteger)idx {
 893  NSString *label = nil;
 894  if (idx < [self count]) {
 895    label = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(multiValue_, idx));
 896  }
 897  return label;
 898}
 899
 900- (GTMABMultiValueIdentifier)identifierAtIndex:(NSUInteger)idx {
 901  GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier;
 902  if (idx < [self count]) {
 903#if GTM_IPHONE_SDK
 904    identifier = ABMultiValueGetIdentifierAtIndex(multiValue_, idx);
 905#else  // GTM_IPHONE_SDK
 906    identifier = GTMCFAutorelease(ABMultiValueCopyIdentifierAtIndex(multiValue_, 
 907                                                                  idx));
 908#endif  // GTM_IPHONE_SDK
 909  }
 910  return identifier;
 911}
 912
 913- (NSUInteger)indexForIdentifier:(GTMABMultiValueIdentifier)identifier {
 914#if GTM_IPHONE_SDK
 915  NSUInteger idx = ABMultiValueGetIndexForIdentifier(multiValue_, identifier);
 916#else  // GTM_IPHONE_SDK
 917  NSUInteger idx = ABMultiValueIndexForIdentifier(multiValue_, 
 918                                                  (CFStringRef)identifier);
 919#endif  // GTM_IPHONE_SDK
 920  return idx == (NSUInteger)kCFNotFound ? (NSUInteger)NSNotFound : idx;
 921}
 922
 923- (GTMABPropertyType)propertyType {
 924#if GTM_IPHONE_SDK
 925  return ABMultiValueGetPropertyType(multiValue_);
 926#else  // GTM_IPHONE_SDK
 927  return ABMultiValuePropertyType(multiValue_);
 928#endif  // GTM_IPHONE_SDK
 929}
 930
 931- (id)valueForIdentifier:(GTMABMultiValueIdentifier)identifier {
 932  return [self valueAtIndex:[self indexForIdentifier:identifier]];
 933}
 934
 935- (NSString *)labelForIdentifier:(GTMABMultiValueIdentifier)identifier {
 936  return [self labelAtIndex:[self indexForIdentifier:identifier]];
 937}
 938
 939- (unsigned long*)mutations {
 940  // We just need some constant non-zero value here so fast enumeration works.
 941  // Dereferencing self should give us the isa which will stay constant
 942  // over the enumeration.
 943  return (unsigned long*)self;
 944}
 945
 946- (NSEnumerator *)valueEnumerator {
 947  return [GTMABMultiValueEnumerator valueEnumeratorFor:self];
 948}
 949
 950- (NSEnumerator *)labelEnumerator {
 951  return [GTMABMultiValueEnumerator labelEnumeratorFor:self];
 952}
 953
 954@end
 955
 956@implementation GTMABMutableMultiValue
 957+ (id)valueWithPropertyType:(GTMABPropertyType)type {
 958  return [[[self alloc] initWithPropertyType:type] autorelease];
 959}
 960
 961- (id)initWithPropertyType:(GTMABPropertyType)type {
 962  ABMutableMultiValueRef ref = nil;
 963  if (type != kGTMABInvalidPropertyType) {
 964#if GTM_IPHONE_SDK
 965    ref = ABMultiValueCreateMutable(type);
 966#else  // GTM_IPHONE_SDK
 967    ref = ABMultiValueCreateMutable();
 968#endif  // GTM_IPHONE_SDK
 969  }
 970  self = [super initWithMultiValue:ref];
 971  if (ref) {
 972    CFRelease(ref);
 973  } 
 974  return self;
 975}
 976
 977- (id)initWithMultiValue:(ABMultiValueRef)multiValue {
 978  ABMutableMultiValueRef ref = nil;
 979  if (multiValue) {
 980    ref = ABMultiValueCreateMutableCopy(multiValue);
 981  }
 982  self = [super initWithMultiValue:ref];
 983  if (ref) {
 984    CFRelease(ref);
 985  } 
 986  return self;
 987}
 988
 989- (id)initWithMutableMultiValue:(ABMutableMultiValueRef)multiValue {
 990  return [super initWithMultiValue:multiValue];
 991}
 992
 993- (BOOL)checkValueType:(id)value {
 994  BOOL isGood = NO;
 995  if (value) {
 996    TypeClassNameMap singleValueTypeMap[] = {
 997      { kGTMABStringPropertyType, [NSString class] },
 998      { kGTMABIntegerPropertyType, [NSNumber class] },
 999      { kGTMABRealPropertyType, [NSNumber class] },
1000      { kGTMABDateTimePropertyType, [NSDate class] },
1001      { kGTMABDictionaryPropertyType, [NSDictionary class] },
1002    };
1003    GTMABPropertyType type = [self propertyType] & ~kABMultiValueMask;
1004#if GTM_MACOS_SDK
1005    // Since on the desktop mutables don't have a type UNTIL they have 
1006    // something in them, return YES if it's empty.
1007    if ((type == 0) && ([self count] == 0)) return YES;
1008#endif  // GTM_MACOS_SDK
1009    for (size_t i = 0; 
1010         i < sizeof(singleValueTypeMap) / sizeof(TypeClassNameMap); ++i) {
1011      if (singleValueTypeMap[i].pType == type) {
1012        if ([[value class] isSubclassOfClass:singleValueTypeMap[i].class]) {
1013          isGood = YES;
1014          break;
1015        }
1016      }
1017    }
1018  }
1019  return isGood;
1020}
1021
1022- (GTMABMultiValueIdentifier)addValue:(id)value withLabel:(CFStringRef)label {
1023  GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier;
1024  // We check label and value here because of
1025  // radar 6202827  Passing nil info ABMultiValueAddValueAndLabel causes crash
1026  bool wasGood = label && [self checkValueType:value];
1027  if (wasGood) {
1028#if GTM_IPHONE_SDK
1029    wasGood = ABMultiValueAddValueAndLabel(multiValue_, 
1030                                           value, 
1031                                           label, 
1032                                           &identifier);
1033#else  // GTM_IPHONE_SDK 
1034    wasGood = ABMultiValueAdd((ABMutableMultiValueRef)multiValue_, 
1035                              value, 
1036                              label, 
1037                              (CFStringRef *)&identifier);
1038#endif  // GTM_IPHONE_SDK
1039  }
1040  if (!wasGood) {
1041    identifier = kGTMABMultiValueInvalidIdentifier;
1042  } else {
1043    mutations_++;
1044  }
1045  return identifier;
1046}
1047
1048- (GTMABMultiValueIdentifier)insertValue:(id)value 
1049                               withLabel:(CFStringRef)label 
1050                                 atIndex:(NSUInteger)idx {
1051  GTMABMultiValueIdentifier identifier = kGTMABMultiValueInvalidIdentifier;
1052  // We perform a check here to ensure that we don't get bitten by
1053  // Radar 6202807 ABMultiValueInsertValueAndLabelAtIndex allows you to insert 
1054  //               values past end
1055  NSUInteger count = [self count];
1056  // We check label and value here because of
1057  // radar 6202827  Passing nil info ABMultiValueAddValueAndLabel causes crash
1058  bool wasGood = idx <= count && label && [self checkValueType:value];
1059  if (wasGood) {
1060#if GTM_IPHONE_SDK
1061    wasGood = ABMultiValueInsertValueAndLabelAtIndex(multiValue_, 
1062                                                     value, 
1063                                                     label, 
1064                                                     idx, 
1065                                                     &identifier);
1066#else  // GTM_IPHONE_SDK
1067    wasGood = ABMultiValueInsert((ABMutableMultiValueRef)multiValue_, 
1068                                 value, 
1069                                 label, 
1070                                 idx, 
1071                                 (CFStringRef *)&identifier);
1072#endif  // GTM_IPHONE_SDK
1073  }
1074  if (!wasGood) {
1075    identifier = kGTMABMultiValueInvalidIdentifier;
1076  } else {
1077    mutations_++;
1078  }
1079  return identifier;
1080}
1081
1082- (BOOL)removeValueAndLabelAtIndex:(NSUInteger)idx {
1083  BOOL isGood = NO;
1084  NSUInteger count = [self count];
1085  if (idx < count) {
1086#if GTM_IPHONE_SDK
1087    bool wasGood = ABMultiValueRemoveValueAndLabelAtIndex(multiValue_, 
1088                                                          idx);
1089#else  // GTM_IPHONE_SDK
1090    bool wasGood = ABMultiValueRemove((ABMutableMultiValueRef)multiValue_,
1091                                      idx);
1092#endif  // GTM_IPHONE_SDK
1093    if (wasGood) {
1094      mutations_++;
1095      isGood = YES;
1096    }
1097  }
1098  return isGood; 
1099}
1100
1101- (BOOL)replaceValueAtIndex:(NSUInteger)idx withValue:(id)value {
1102  BOOL isGood = NO;
1103  NSUInteger count = [self count];
1104  if (idx < count && [self checkValueType:value]) {
1105#if GTM_IPHONE_SDK
1106    bool goodReplace = ABMultiValueReplaceValueAtIndex(multiValue_, 
1107                                                       value, idx);
1108#else  // GTM_IPHONE_SDK
1109    bool goodReplace 
1110      = ABMultiValueReplaceValue((ABMutableMultiValueRef)multiValue_, 
1111                                 (CFTypeRef)value, idx);
1112#endif  // GTM_IPHONE_SDK
1113    if (goodReplace) {
1114      mutations_++;
1115      isGood = YES;
1116    }
1117  }
1118  return isGood; 
1119}
1120
1121- (BOOL)replaceLabelAtIndex:(NSUInteger)idx withLabel:(CFStringRef)label {
1122  BOOL isGood = NO;
1123  NSUInteger count = [self count];
1124  if (idx < count) {
1125#if GTM_IPHONE_SDK
1126    bool goodReplace = ABMultiValueReplaceLabelAtIndex(multiValue_, 
1127                                                       label, idx);
1128#else  // GTM_IPHONE_SDK
1129    bool goodReplace 
1130      = ABMultiValueReplaceLabel((ABMutableMultiValueRef)multiValue_, 
1131                                 (CFTypeRef)label, idx);
1132#endif  // GTM_IPHONE_SDK
1133    if (goodReplace) {
1134      mutations_++;
1135      isGood = YES;
1136    }
1137  }
1138  return isGood; 
1139}
1140      
1141- (unsigned long*)mutations {
1142  return &mutations_;
1143}
1144@end
1145      
1146
1147@implementation GTMABMultiValueEnumerator
1148
1149+ (id)valueEnumeratorFor:(GTMABMultiValue*)enumeree {
1150  return [[[self alloc] initWithEnumeree:enumeree useLabels:NO] autorelease];
1151}
1152
1153+ (id)labelEnumeratorFor:(GTMABMultiValue*)enumeree  {
1154  return [[[self alloc] initWithEnumeree:enumeree useLabels:YES] autorelease];
1155}
1156
1157- (id)initWithEnumeree:(GTMABMultiValue*)enumeree useLabels:(BOOL)useLabels {
1158  if ((self = [super init])) {
1159    if (enumeree) {
1160      enumeree_ = [enumeree retain];
1161      useLabels_ = useLabels;
1162    } else {
1163      // COV_NF_START
1164      // Since this is a private class where the enumeree creates us
1165      // there is no way we should ever get here.
1166      [self release];
1167      self = nil;
1168      // COV_NF_END
1169    }
1170  }
1171  return self;
1172}
1173
1174- (void)dealloc {
1175  [enumeree_ release];
1176  [super dealloc];
1177}
1178
1179#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
1180- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state 
1181                                  objects:(id *)stackbuf 
1182                                    count:(NSUInteger)len {
1183  NSUInteger i;
1184  if (!ref_) {
1185    count_ = [enumeree_ count];
1186    ref_ = [enumeree_ multiValueRef];
1187  }
1188  
1189  for (i = 0; state->state < count_ && i < len; ++i, ++state->state) {
1190    if (useLabels_) {
1191      stackbuf[i] = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_, 
1192                                                                  state->state));
1193    } else {
1194      // TODO(dmaclach) Check this on Mac Desktop and use fast path if we can
1195      // Yes this is slow, but necessary in light of radar 6208390
1196      // Once this is fixed we can go to something similar to the label
1197      // case which should speed stuff up again. Hopefully anybody who wants
1198      // real performance is willing to move down to the C API anyways.
1199      stackbuf[i] = [enumeree_ valueAtIndex:state->state];
1200    }
1201  }
1202    
1203  state->itemsPtr = stackbuf;
1204  state->mutationsPtr = [enumeree_ mutations];
1205  return i;
1206}
1207#endif  // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
1208
1209- (id)nextObject {
1210  id value = nil;
1211  if (!ref_) {
1212    count_ = [enumeree_ count];
1213    mutations_ = *[enumeree_ mutations];
1214    ref_ = [enumeree_ multiValueRef];
1215
1216  }
1217  if (mutations_ != *[enumeree_ mutations]) {
1218    NSString *reason = [NSString stringWithFormat:@"*** Collection <%@> was "
1219                        "mutated while being enumerated", enumeree_];
1220    [[NSException exceptionWithName:NSGenericException
1221                             reason:reason
1222                           userInfo:nil] raise];
1223  }
1224  if (index_ < count_) {
1225    if (useLabels_) {
1226      value = GTMCFAutorelease(ABMultiValueCopyLabelAtIndex(ref_, 
1227                                                            index_));
1228    } else {
1229      // TODO(dmaclach) Check this on Mac Desktop and use fast path if we can
1230      // Yes this is slow, but necessary in light of radar 6208390
1231      // Once this is fixed we can go to something similar to the label
1232      // case which should speed stuff up again. Hopefully anybody who wants
1233      // real performance is willing to move down to the C API anyways.
1234      value = [enumeree_ valueAtIndex:index_];
1235    }
1236    index_ += 1;
1237  }
1238  return value;
1239}
1240@end
1241