text: // Protocol Buffers - Google's data interchange format
  // Copyright 2008 Google Inc.  All rights reserved.
  // https://developers.google.com/protocol-buffers/
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are
  // met:
  //
  //     * Redistributions of source code must retain the above copyright
  // notice, this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above
  // copyright notice, this list of conditions and the following disclaimer
  // in the documentation and/or other materials provided with the
  // distribution.
  //     * Neither the name of Google Inc. nor the names of its
  // contributors may be used to endorse or promote products derived from
  // this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  #import "GPBDescriptor_PackagePrivate.h"
  
  #import <objc/runtime.h>
  
  #import "GPBUtilities_PackagePrivate.h"
  #import "GPBWireFormat.h"
  #import "GPBMessage_PackagePrivate.h"
  
  // Direct access is use for speed, to avoid even internally declaring things
  // read/write, etc. The warning is enabled in the project to ensure code calling
  // protos can turn on -Wdirect-ivar-access without issues.
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wdirect-ivar-access"
  
  // The addresses of these variables are used as keys for objc_getAssociatedObject.
  static const char kTextFormatExtraValueKey = 0;
  static const char kParentClassValueKey = 0;
  static const char kClassNameSuffixKey = 0;
  
  // Utility function to generate selectors on the fly.
  static SEL SelFromStrings(const char *prefix, const char *middle,
                            const char *suffix, BOOL takesArg) {
    if (prefix == NULL && suffix == NULL && !takesArg) {
      return sel_getUid(middle);
    }
    const size_t prefixLen = prefix != NULL ? strlen(prefix) : 0;
    const size_t middleLen = strlen(middle);
    const size_t suffixLen = suffix != NULL ? strlen(suffix) : 0;
    size_t totalLen =
        prefixLen + middleLen + suffixLen + 1;  // include space for null on end.
    if (takesArg) {
      totalLen += 1;
    }
    char buffer[totalLen];
    if (prefix != NULL) {
      memcpy(buffer, prefix, prefixLen);
      memcpy(buffer + prefixLen, middle, middleLen);
      buffer[prefixLen] = (char)toupper(buffer[prefixLen]);
    } else {
      memcpy(buffer, middle, middleLen);
    }
    if (suffix != NULL) {
      memcpy(buffer + prefixLen + middleLen, suffix, suffixLen);
    }
    if (takesArg) {
      buffer[totalLen - 2] = ':';
    }
    // Always null terminate it.
    buffer[totalLen - 1] = 0;
  
    SEL result = sel_getUid(buffer);
    return result;
  }
  
  static NSArray *NewFieldsArrayForHasIndex(int hasIndex,
                                            NSArray *allMessageFields)
      __attribute__((ns_returns_retained));
  
  static NSArray *NewFieldsArrayForHasIndex(int hasIndex,
                                            NSArray *allMessageFields) {
    NSMutableArray *result = [[NSMutableArray alloc] init];
    for (GPBFieldDescriptor *fieldDesc in allMessageFields) {
      if (fieldDesc->description_->hasIndex == hasIndex) {
        [result addObject:fieldDesc];
      }
    }
    return result;
  }
  
  @implementation GPBDescriptor {
    Class messageClass_;
    GPBFileDescriptor *file_;
    BOOL wireFormat_;
  }
  
  @synthesize messageClass = messageClass_;
  @synthesize fields = fields_;
  @synthesize oneofs = oneofs_;
  @synthesize extensionRanges = extensionRanges_;
  @synthesize extensionRangesCount = extensionRangesCount_;
  @synthesize file = file_;
  @synthesize wireFormat = wireFormat_;
  
  + (instancetype)
      allocDescriptorForClass:(Class)messageClass
                    rootClass:(Class)rootClass
                         file:(GPBFileDescriptor *)file
                       fields:(void *)fieldDescriptions
                   fieldCount:(uint32_t)fieldCount
                  storageSize:(uint32_t)storageSize
                        flags:(GPBDescriptorInitializationFlags)flags {
    // The rootClass is no longer used, but it is passed in to ensure it
    // was started up during initialization also.
    (void)rootClass;
    NSMutableArray *fields = nil;
    GPBFileSyntax syntax = file.syntax;
    BOOL fieldsIncludeDefault =
        (flags & GPBDescriptorInitializationFlag_FieldsWithDefault) != 0;
    BOOL usesClassRefs =
        (flags & GPBDescriptorInitializationFlag_UsesClassRefs) != 0;
    BOOL proto3OptionalKnown =
        (flags & GPBDescriptorInitializationFlag_Proto3OptionalKnown) != 0;
  
    void *desc;
    for (uint32_t i = 0; i < fieldCount; ++i) {
      if (fields == nil) {
        fields = [[NSMutableArray alloc] initWithCapacity:fieldCount];
      }
      // Need correctly typed pointer for array indexing below to work.
      if (fieldsIncludeDefault) {
        GPBMessageFieldDescriptionWithDefault *fieldDescWithDefault = fieldDescriptions;
        desc = &(fieldDescWithDefault[i]);
      } else {
        GPBMessageFieldDescription *fieldDesc = fieldDescriptions;
        desc = &(fieldDesc[i]);
      }
      GPBFieldDescriptor *fieldDescriptor =
          [[GPBFieldDescriptor alloc] initWithFieldDescription:desc
                                               includesDefault:fieldsIncludeDefault
                                                 usesClassRefs:usesClassRefs
                                           proto3OptionalKnown:proto3OptionalKnown
                                                        syntax:syntax];
      [fields addObject:fieldDescriptor];
      [fieldDescriptor release];
    }
  
    BOOL wireFormat = (flags & GPBDescriptorInitializationFlag_WireFormat) != 0;
    GPBDescriptor *descriptor = [[self alloc] initWithClass:messageClass
                                                       file:file
                                                     fields:fields
                                                storageSize:storageSize
                                                 wireFormat:wireFormat];
    [fields release];
    return descriptor;
  }
  
  - (instancetype)initWithClass:(Class)messageClass
                           file:(GPBFileDescriptor *)file
                         fields:(NSArray *)fields
                    storageSize:(uint32_t)storageSize
                     wireFormat:(BOOL)wireFormat {
    if ((self = [super init])) {
      messageClass_ = messageClass;
      file_ = file;
      fields_ = [fields retain];
      storageSize_ = storageSize;
      wireFormat_ = wireFormat;
    }
    return self;
  }
  
  - (void)dealloc {
    [fields_ release];
    [oneofs_ release];
    [super dealloc];
  }
  
  - (void)setupOneofs:(const char **)oneofNames
                count:(uint32_t)count
        firstHasIndex:(int32_t)firstHasIndex {
    NSCAssert(firstHasIndex < 0, @"Should always be <0");
    NSMutableArray *oneofs = [[NSMutableArray alloc] initWithCapacity:count];
    for (uint32_t i = 0, hasIndex = firstHasIndex; i < count; ++i, --hasIndex) {
      const char *name = oneofNames[i];
      NSArray *fieldsForOneof = NewFieldsArrayForHasIndex(hasIndex, fields_);
      NSCAssert(fieldsForOneof.count > 0,
                @"No fields for this oneof? (%s:%d)", name, hasIndex);
      GPBOneofDescriptor *oneofDescriptor =
          [[GPBOneofDescriptor alloc] initWithName:name fields:fieldsForOneof];
      [oneofs addObject:oneofDescriptor];
      [oneofDescriptor release];
      [fieldsForOneof release];
    }
    oneofs_ = oneofs;
  }
  
  - (void)setupExtraTextInfo:(const char *)extraTextFormatInfo {
    // Extra info is a compile time option, so skip the work if not needed.
    if (extraTextFormatInfo) {
      NSValue *extraInfoValue = [NSValue valueWithPointer:extraTextFormatInfo];
      for (GPBFieldDescriptor *fieldDescriptor in fields_) {
        if (fieldDescriptor->description_->flags & GPBFieldTextFormatNameCustom) {
          objc_setAssociatedObject(fieldDescriptor, &kTextFormatExtraValueKey,
                                   extraInfoValue,
                                   OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
      }
    }
  }
  
  - (void)setupExtensionRanges:(const GPBExtensionRange *)ranges count:(int32_t)count {
    extensionRanges_ = ranges;
    extensionRangesCount_ = count;
  }
  
  - (void)setupContainingMessageClass:(Class)messageClass {
    objc_setAssociatedObject(self, &kParentClassValueKey,
                             messageClass,
                             OBJC_ASSOCIATION_ASSIGN);
  }
  
  - (void)setupContainingMessageClassName:(const char *)msgClassName {
    // Note: Only fetch the class here, can't send messages to it because
    // that could cause cycles back to this class within +initialize if
    // two messages have each other in fields (i.e. - they build a graph).
    Class clazz = objc_getClass(msgClassName);
    NSAssert(clazz, @"Class %s not defined", msgClassName);
    [self setupContainingMessageClass:clazz];
  }
  
  - (void)setupMessageClassNameSuffix:(NSString *)suffix {
    if (suffix.length) {
      objc_setAssociatedObject(self, &kClassNameSuffixKey,
                               suffix,
                               OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
  }
  
  - (NSString *)name {
    return NSStringFromClass(messageClass_);
  }
  
  - (GPBDescriptor *)containingType {
    Class parentClass = objc_getAssociatedObject(self, &kParentClassValueKey);
    return [parentClass descriptor];
  }
  
  - (NSString *)fullName {
    NSString *className = NSStringFromClass(self.messageClass);
    GPBFileDescriptor *file = self.file;
    NSString *objcPrefix = file.objcPrefix;
    if (objcPrefix && ![className hasPrefix:objcPrefix]) {
      NSAssert(0,
               @"Class didn't have correct prefix? (%@ - %@)",
               className, objcPrefix);
      return nil;
    }
    GPBDescriptor *parent = self.containingType;
  
    NSString *name = nil;
    if (parent) {
      NSString *parentClassName = NSStringFromClass(parent.messageClass);
      // The generator will add _Class to avoid reserved words, drop it.
      NSString *suffix = objc_getAssociatedObject(parent, &kClassNameSuffixKey);
      if (suffix) {
        if (![parentClassName hasSuffix:suffix]) {
          NSAssert(0,
                   @"ParentMessage class didn't have correct suffix? (%@ - %@)",
                   className, suffix);
          return nil;
        }
        parentClassName =
            [parentClassName substringToIndex:(parentClassName.length - suffix.length)];
      }
      NSString *parentPrefix = [parentClassName stringByAppendingString:@"_"];
      if (![className hasPrefix:parentPrefix]) {
        NSAssert(0,
                 @"Class didn't have the correct parent name prefix? (%@ - %@)",
                 parentPrefix, className);
        return nil;
      }
      name = [className substringFromIndex:parentPrefix.length];
    } else {
      name = [className substringFromIndex:objcPrefix.length];
    }
  
    // The generator will add _Class to avoid reserved words, drop it.
    NSString *suffix = objc_getAssociatedObject(self, &kClassNameSuffixKey);
    if (suffix) {
      if (![name hasSuffix:suffix]) {
        NSAssert(0,
                 @"Message class didn't have correct suffix? (%@ - %@)",
                 name, suffix);
        return nil;
      }
      name = [name substringToIndex:(name.length - suffix.length)];
    }
  
    NSString *prefix = (parent != nil ? parent.fullName : file.package);
    NSString *result;
    if (prefix.length > 0) {
      result = [NSString stringWithFormat:@"%@.%@", prefix, name];
    } else {
      result = name;
    }
    return result;
  }
  
  - (id)copyWithZone:(NSZone *)zone {
  #pragma unused(zone)
    return [self retain];
  }
  
  - (GPBFieldDescriptor *)fieldWithNumber:(uint32_t)fieldNumber {
    for (GPBFieldDescriptor *descriptor in fields_) {
      if (GPBFieldNumber(descriptor) == fieldNumber) {
        return descriptor;
      }
    }
    return nil;
  }
  
  - (GPBFieldDescriptor *)fieldWithName:(NSString *)name {
    for (GPBFieldDescriptor *descriptor in fields_) {
      if ([descriptor.name isEqual:name]) {
        return descriptor;
      }
    }
    return nil;
  }
  
  - (GPBOneofDescriptor *)oneofWithName:(NSString *)name {
    for (GPBOneofDescriptor *descriptor in oneofs_) {
      if ([descriptor.name isEqual:name]) {
        return descriptor;
      }
    }
    return nil;
  }
  
  @end
  
  @implementation GPBFileDescriptor {
    NSString *package_;
    NSString *objcPrefix_;
    GPBFileSyntax syntax_;
  }
  
  @synthesize package = package_;
  @synthesize objcPrefix = objcPrefix_;
  @synthesize syntax = syntax_;
  
  - (instancetype)initWithPackage:(NSString *)package
                       objcPrefix:(NSString *)objcPrefix
                           syntax:(GPBFileSyntax)syntax {
    self = [super init];
    if (self) {
      package_ = [package copy];
      objcPrefix_ = [objcPrefix copy];
      syntax_ = syntax;
    }
    return self;
  }
  
  - (instancetype)initWithPackage:(NSString *)package
                           syntax:(GPBFileSyntax)syntax {
    self = [super init];
    if (self) {
      package_ = [package copy];
      syntax_ = syntax;
    }
    return self;
  }
  
  - (void)dealloc {
    [package_ release];
    [objcPrefix_ release];
    [super dealloc];
  }
  
  @end
  
  @implementation GPBOneofDescriptor
  
  @synthesize fields = fields_;
  
  - (instancetype)initWithName:(const char *)name fields:(NSArray *)fields {
    self = [super init];
    if (self) {
      name_ = name;
      fields_ = [fields retain];
      for (GPBFieldDescriptor *fieldDesc in fields) {
        fieldDesc->containingOneof_ = self;
      }
  
      caseSel_ = SelFromStrings(NULL, name, "OneOfCase", NO);
    }
    return self;
  }
  
  - (void)dealloc {
    [fields_ release];
    [super dealloc];
  }
  
  - (NSString *)name {
    return (NSString * _Nonnull)@(name_);
  }
  
  - (GPBFieldDescriptor *)fieldWithNumber:(uint32_t)fieldNumber {
    for (GPBFieldDescriptor *descriptor in fields_) {
      if (GPBFieldNumber(descriptor) == fieldNumber) {
        return descriptor;
      }
    }
    return nil;
  }
  
  - (GPBFieldDescriptor *)fieldWithName:(NSString *)name {
    for (GPBFieldDescriptor *descriptor in fields_) {
      if ([descriptor.name isEqual:name]) {
        return descriptor;
      }
    }
    return nil;
  }
  
  @end
  
  uint32_t GPBFieldTag(GPBFieldDescriptor *self) {
    GPBMessageFieldDescription *description = self->description_;
    GPBWireFormat format;
    if ((description->flags & GPBFieldMapKeyMask) != 0) {
      // Maps are repeated messages on the wire.
      format = GPBWireFormatForType(GPBDataTypeMessage, NO);
    } else {
      format = GPBWireFormatForType(description->dataType,
                                    ((description->flags & GPBFieldPacked) != 0));
    }
    return GPBWireFormatMakeTag(description->number, format);
  }
  
  uint32_t GPBFieldAlternateTag(GPBFieldDescriptor *self) {
    GPBMessageFieldDescription *description = self->description_;
    NSCAssert((description->flags & GPBFieldRepeated) != 0,
              @"Only valid on repeated fields");
    GPBWireFormat format =
        GPBWireFormatForType(description->dataType,
                             ((description->flags & GPBFieldPacked) == 0));
    return GPBWireFormatMakeTag(description->number, format);
  }
  
  @implementation GPBFieldDescriptor {
    GPBGenericValue defaultValue_;
  
    // Message ivars
    Class msgClass_;
  
    // Enum ivars.
    // If protos are generated with GenerateEnumDescriptors on then it will
    // be a enumDescriptor, otherwise it will be a enumVerifier.
    union {
      GPBEnumDescriptor *enumDescriptor_;
      GPBEnumValidationFunc enumVerifier_;
    } enumHandling_;
  }
  
  @synthesize msgClass = msgClass_;
  @synthesize containingOneof = containingOneof_;
  
  - (instancetype)init {
    // Throw an exception if people attempt to not use the designated initializer.
    self = [super init];
    if (self != nil) {
      [self doesNotRecognizeSelector:_cmd];
      self = nil;
    }
    return self;
  }
  
  - (instancetype)initWithFieldDescription:(void *)description
                           includesDefault:(BOOL)includesDefault
                             usesClassRefs:(BOOL)usesClassRefs
                       proto3OptionalKnown:(BOOL)proto3OptionalKnown
                                    syntax:(GPBFileSyntax)syntax {
    if ((self = [super init])) {
      GPBMessageFieldDescription *coreDesc;
      if (includesDefault) {
        coreDesc = &(((GPBMessageFieldDescriptionWithDefault *)description)->core);
      } else {
        coreDesc = description;
      }
      description_ = coreDesc;
      getSel_ = sel_getUid(coreDesc->name);
      setSel_ = SelFromStrings("set", coreDesc->name, NULL, YES);
  
      GPBDataType dataType = coreDesc->dataType;
      BOOL isMessage = GPBDataTypeIsMessage(dataType);
      BOOL isMapOrArray = GPBFieldIsMapOrArray(self);
  
      // If proto3 optionals weren't known (i.e. generated code from an
      // older version), compute the flag for the rest of the runtime.
      if (!proto3OptionalKnown) {
        // If it was...
        //  - proto3 syntax
        //  - not repeated/map
        //  - not in a oneof (negative has index)
        //  - not a message (the flag doesn't make sense for messages)
        BOOL clearOnZero = ((syntax == GPBFileSyntaxProto3) &&
                            !isMapOrArray &&
                            (coreDesc->hasIndex >= 0) &&
                            !isMessage);
        if (clearOnZero) {
          coreDesc->flags |= GPBFieldClearHasIvarOnZero;
        }
      }
  
      if (isMapOrArray) {
        // map<>/repeated fields get a *Count property (inplace of a has*) to
        // support checking if there are any entries without triggering
        // autocreation.
        hasOrCountSel_ = SelFromStrings(NULL, coreDesc->name, "_Count", NO);
      } else {
        // It is a single field; it gets has/setHas selectors if...
        //  - not in a oneof (negative has index)
        //  - not clearing on zero
        if ((coreDesc->hasIndex >= 0) &&
            ((coreDesc->flags & GPBFieldClearHasIvarOnZero) == 0)) {
          hasOrCountSel_ = SelFromStrings("has", coreDesc->name, NULL, NO);
          setHasSel_ = SelFromStrings("setHas", coreDesc->name, NULL, YES);
        }
      }
  
      // Extra type specific data.
      if (isMessage) {
        // Note: Only fetch the class here, can't send messages to it because
        // that could cause cycles back to this class within +initialize if
        // two messages have each other in fields (i.e. - they build a graph).
        if (usesClassRefs) {
          msgClass_ = coreDesc->dataTypeSpecific.clazz;
        } else {
          // Backwards compatibility for sources generated with older protoc.
          const char *className = coreDesc->dataTypeSpecific.className;
          msgClass_ = objc_getClass(className);
          NSAssert(msgClass_, @"Class %s not defined", className);
        }
      } else if (dataType == GPBDataTypeEnum) {
        if ((coreDesc->flags & GPBFieldHasEnumDescriptor) != 0) {
          enumHandling_.enumDescriptor_ =
              coreDesc->dataTypeSpecific.enumDescFunc();
        } else {
          enumHandling_.enumVerifier_ =
              coreDesc->dataTypeSpecific.enumVerifier;
        }
      }
  
      // Non map<>/repeated fields can have defaults in proto2 syntax.
      if (!isMapOrArray && includesDefault) {
        defaultValue_ = ((GPBMessageFieldDescriptionWithDefault *)description)->defaultValue;
        if (dataType == GPBDataTypeBytes) {
          // Data stored as a length prefixed (network byte order) c-string in
          // descriptor structure.
          const uint8_t *bytes = (const uint8_t *)defaultValue_.valueData;
          if (bytes) {
            uint32_t length;
            memcpy(&length, bytes, sizeof(length));
            length = ntohl(length);
            bytes += sizeof(length);
            defaultValue_.valueData =
                [[NSData alloc] initWithBytes:bytes length:length];
          }
        }
      }
    }
    return self;
  }
  
  - (void)dealloc {
    if (description_->dataType == GPBDataTypeBytes &&
        !(description_->flags & GPBFieldRepeated)) {
      [defaultValue_.valueData release];
    }
    [super dealloc];
  }
  
  - (GPBDataType)dataType {
    return description_->dataType;
  }
  
  - (BOOL)hasDefaultValue {
    return (description_->flags & GPBFieldHasDefaultValue) != 0;
  }
  
  - (uint32_t)number {
    return description_->number;
  }
  
  - (NSString *)name {
    return (NSString * _Nonnull)@(description_->name);
  }
  
  - (BOOL)isRequired {
    return (description_->flags & GPBFieldRequired) != 0;
  }
  
  - (BOOL)isOptional {
    return (description_->flags & GPBFieldOptional) != 0;
  }
  
  - (GPBFieldType)fieldType {
    GPBFieldFlags flags = description_->flags;
    if ((flags & GPBFieldRepeated) != 0) {
      return GPBFieldTypeRepeated;
    } else if ((flags & GPBFieldMapKeyMask) != 0) {
      return GPBFieldTypeMap;
    } else {
      return GPBFieldTypeSingle;
    }
  }
  
  - (GPBDataType)mapKeyDataType {
    switch (description_->flags & GPBFieldMapKeyMask) {
      case GPBFieldMapKeyInt32:
        return GPBDataTypeInt32;
      case GPBFieldMapKeyInt64:
        return GPBDataTypeInt64;
      case GPBFieldMapKeyUInt32:
        return GPBDataTypeUInt32;
      case GPBFieldMapKeyUInt64:
        return GPBDataTypeUInt64;
      case GPBFieldMapKeySInt32:
        return GPBDataTypeSInt32;
      case GPBFieldMapKeySInt64:
        return GPBDataTypeSInt64;
      case GPBFieldMapKeyFixed32:
        return GPBDataTypeFixed32;
      case GPBFieldMapKeyFixed64:
        return GPBDataTypeFixed64;
      case GPBFieldMapKeySFixed32:
        return GPBDataTypeSFixed32;
      case GPBFieldMapKeySFixed64:
        return GPBDataTypeSFixed64;
      case GPBFieldMapKeyBool:
        return GPBDataTypeBool;
      case GPBFieldMapKeyString:
        return GPBDataTypeString;
  
      default:
        NSAssert(0, @"Not a map type");
        return GPBDataTypeInt32;  // For lack of anything better.
    }
  }
  
  - (BOOL)isPackable {
    return (description_->flags & GPBFieldPacked) != 0;
  }
  
  - (BOOL)isValidEnumValue:(int32_t)value {
    NSAssert(description_->dataType == GPBDataTypeEnum,
             @"Field Must be of type GPBDataTypeEnum");
    if (description_->flags & GPBFieldHasEnumDescriptor) {
      return enumHandling_.enumDescriptor_.enumVerifier(value);
    } else {
      return enumHandling_.enumVerifier_(value);
    }
  }
  
  - (GPBEnumDescriptor *)enumDescriptor {
    if (description_->flags & GPBFieldHasEnumDescriptor) {
      return enumHandling_.enumDescriptor_;
    } else {
      return nil;
    }
  }
  
  - (GPBGenericValue)defaultValue {
    // Depends on the fact that defaultValue_ is initialized either to "0/nil" or
    // to an actual defaultValue in our initializer.
    GPBGenericValue value = defaultValue_;
  
    if (!(description_->flags & GPBFieldRepeated)) {
      // We special handle data and strings. If they are nil, we replace them
      // with empty string/empty data.
      GPBDataType type = description_->dataType;
      if (type == GPBDataTypeBytes && value.valueData == nil) {
        value.valueData = GPBEmptyNSData();
      } else if (type == GPBDataTypeString && value.valueString == nil) {
        value.valueString = @"";
      }
    }
    return value;
  }
  
  - (NSString *)textFormatName {
    if ((description_->flags & GPBFieldTextFormatNameCustom) != 0) {
      NSValue *extraInfoValue =
          objc_getAssociatedObject(self, &kTextFormatExtraValueKey);
      // Support can be left out at generation time.
      if (!extraInfoValue) {
        return nil;
      }
      const uint8_t *extraTextFormatInfo = [extraInfoValue pointerValue];
      return GPBDecodeTextFormatName(extraTextFormatInfo, GPBFieldNumber(self),
                                     self.name);
    }
  
    // The logic here has to match SetCommonFieldVariables() from
    // objectivec_field.cc in the proto compiler.
    NSString *name = self.name;
    NSUInteger len = [name length];
  
    // Remove the "_p" added to reserved names.
    if ([name hasSuffix:@"_p"]) {
      name = [name substringToIndex:(len - 2)];
      len = [name length];
    }
  
    // Remove "Array" from the end for repeated fields.
    if (((description_->flags & GPBFieldRepeated) != 0) &&
        [name hasSuffix:@"Array"]) {
      name = [name substringToIndex:(len - 5)];
      len = [name length];
    }
  
    // Groups vs. other fields.
    if (description_->dataType == GPBDataTypeGroup) {
      // Just capitalize the first letter.
      unichar firstChar = [name characterAtIndex:0];
      if (firstChar >= 'a' && firstChar <= 'z') {
        NSString *firstCharString =
            [NSString stringWithFormat:@"%C", (unichar)(firstChar - 'a' + 'A')];
        NSString *result =
            [name stringByReplacingCharactersInRange:NSMakeRange(0, 1)
                                          withString:firstCharString];
        return result;
      }
      return name;
  
    } else {
      // Undo the CamelCase.
      NSMutableString *result = [NSMutableString stringWithCapacity:len];
      for (uint32_t i = 0; i < len; i++) {
        unichar c = [name characterAtIndex:i];
        if (c >= 'A' && c <= 'Z') {
          if (i > 0) {
            [result appendFormat:@"_%C", (unichar)(c - 'A' + 'a')];
          } else {
            [result appendFormat:@"%C", c];
          }
        } else {
          [result appendFormat:@"%C", c];
        }
      }
      return result;
    }
  }
  
  @end
  
  @implementation GPBEnumDescriptor {
    NSString *name_;
    // valueNames_ is a single c string with all of the value names appended
    // together, each null terminated.  -calcValueNameOffsets fills in
    // nameOffsets_ with the offsets to allow quicker access to the individual
    // names.
    const char *valueNames_;
    const int32_t *values_;
    GPBEnumValidationFunc enumVerifier_;
    const uint8_t *extraTextFormatInfo_;
    uint32_t *nameOffsets_;
    uint32_t valueCount_;
  }
  
  @synthesize name = name_;
  @synthesize enumVerifier = enumVerifier_;
  
  + (instancetype)
      allocDescriptorForName:(NSString *)name
                  valueNames:(const char *)valueNames
                      values:(const int32_t *)values
                       count:(uint32_t)valueCount
                enumVerifier:(GPBEnumValidationFunc)enumVerifier {
    GPBEnumDescriptor *descriptor = [[self alloc] initWithName:name
                                                    valueNames:valueNames
                                                        values:values
                                                         count:valueCount
                                                  enumVerifier:enumVerifier];
    return descriptor;
  }
  
  + (instancetype)
      allocDescriptorForName:(NSString *)name
                  valueNames:(const char *)valueNames
                      values:(const int32_t *)values
                       count:(uint32_t)valueCount
                enumVerifier:(GPBEnumValidationFunc)enumVerifier
         extraTextFormatInfo:(const char *)extraTextFormatInfo {
    // Call the common case.
    GPBEnumDescriptor *descriptor = [self allocDescriptorForName:name
                                                      valueNames:valueNames
                                                          values:values
                                                           count:valueCount
                                                    enumVerifier:enumVerifier];
    // Set the extra info.
    descriptor->extraTextFormatInfo_ = (const uint8_t *)extraTextFormatInfo;
    return descriptor;
  }
  
  - (instancetype)initWithName:(NSString *)name
                    valueNames:(const char *)valueNames
                        values:(const int32_t *)values
                         count:(uint32_t)valueCount
                  enumVerifier:(GPBEnumValidationFunc)enumVerifier {
    if ((self = [super init])) {
      name_ = [name copy];
      valueNames_ = valueNames;
      values_ = values;
      valueCount_ = valueCount;
      enumVerifier_ = enumVerifier;
    }
    return self;
  }
  
  - (void)dealloc {
    [name_ release];
    if (nameOffsets_) free(nameOffsets_);
    [super dealloc];
  }
  
  - (void)calcValueNameOffsets {
    @synchronized(self) {
      if (nameOffsets_ != NULL) {
        return;
      }
      uint32_t *offsets = malloc(valueCount_ * sizeof(uint32_t));
      if (!offsets) return;
      const char *scan = valueNames_;
      for (uint32_t i = 0; i < valueCount_; ++i) {
        offsets[i] = (uint32_t)(scan - valueNames_);
        while (*scan != '\0') ++scan;
        ++scan;  // Step over the null.
      }
      nameOffsets_ = offsets;
    }
  }
  
  - (NSString *)enumNameForValue:(int32_t)number {
    for (uint32_t i = 0; i < valueCount_; ++i) {
      if (values_[i] == number) {
        return [self getEnumNameForIndex:i];
      }
    }
    return nil;
  }
  
  - (BOOL)getValue:(int32_t *)outValue forEnumName:(NSString *)name {
    // Must have the prefix.
    NSUInteger prefixLen = name_.length + 1;
    if ((name.length <= prefixLen) || ![name hasPrefix:name_] ||
        ([name characterAtIndex:prefixLen - 1] != '_')) {
      return NO;
    }
  
    // Skip over the prefix.
    const char *nameAsCStr = [name UTF8String];
    nameAsCStr += prefixLen;
  
    if (nameOffsets_ == NULL) [self calcValueNameOffsets];
    if (nameOffsets_ == NULL) return NO;
  
    // Find it.
    for (uint32_t i = 0; i < valueCount_; ++i) {
      const char *valueName = valueNames_ + nameOffsets_[i];
      if (strcmp(nameAsCStr, valueName) == 0) {
        if (outValue) {
          *outValue = values_[i];
        }
        return YES;
      }
    }
    return NO;
  }
  
  - (BOOL)getValue:(int32_t *)outValue forEnumTextFormatName:(NSString *)textFormatName {
      if (nameOffsets_ == NULL) [self calcValueNameOffsets];
      if (nameOffsets_ == NULL) return NO;
  
      for (uint32_t i = 0; i < valueCount_; ++i) {
          NSString *valueTextFormatName = [self getEnumTextFormatNameForIndex:i];
          if ([valueTextFormatName isEqual:textFormatName]) {
              if (outValue) {
                  *outValue = values_[i];
              }
              return YES;
          }
      }
      return NO;
  }
  
  - (NSString *)textFormatNameForValue:(int32_t)number {
    // Find the EnumValue descriptor and its index.
    BOOL foundIt = NO;
    uint32_t valueDescriptorIndex;
    for (valueDescriptorIndex = 0; valueDescriptorIndex < valueCount_;
         ++valueDescriptorIndex) {
      if (values_[valueDescriptorIndex] == number) {
        foundIt = YES;
        break;
      }
    }
  
    if (!foundIt) {
      return nil;
    }
    return [self getEnumTextFormatNameForIndex:valueDescriptorIndex];
  }
  
  - (uint32_t)enumNameCount {
    return valueCount_;
  }
  
  - (NSString *)getEnumNameForIndex:(uint32_t)index {
    if (nameOffsets_ == NULL) [self calcValueNameOffsets];
    if (nameOffsets_ == NULL) return nil;
  
    if (index >= valueCount_) {
      return nil;
    }
    const char *valueName = valueNames_ + nameOffsets_[index];
    NSString *fullName = [NSString stringWithFormat:@"%@_%s", name_, valueName];
    return fullName;
  }
  
  - (NSString *)getEnumTextFormatNameForIndex:(uint32_t)index {
    if (nameOffsets_ == NULL) [self calcValueNameOffsets];
    if (nameOffsets_ == NULL) return nil;
  
    if (index >= valueCount_) {
      return nil;
    }
    NSString *result = nil;
    // Naming adds an underscore between enum name and value name, skip that also.
    const char *valueName = valueNames_ + nameOffsets_[index];
    NSString *shortName = @(valueName);
  
    // See if it is in the map of special format handling.
    if (extraTextFormatInfo_) {
      result = GPBDecodeTextFormatName(extraTextFormatInfo_,
                                       (int32_t)index, shortName);
    }
    // Logic here needs to match what objectivec_enum.cc does in the proto
    // compiler.
    if (result == nil) {
      NSUInteger len = [shortName length];
      NSMutableString *worker = [NSMutableString stringWithCapacity:len];
      for (NSUInteger i = 0; i < len; i++) {
        unichar c = [shortName characterAtIndex:i];
        if (i > 0 && c >= 'A' && c <= 'Z') {
          [worker appendString:@"_"];
        }
        [worker appendFormat:@"%c", toupper((char)c)];
      }
      result = worker;
    }
    return result;
  }
  
  @end
  
  @implementation GPBExtensionDescriptor {
    GPBGenericValue defaultValue_;
  }
  
  - (instancetype)initWithExtensionDescription:(GPBExtensionDescription *)desc
                                 usesClassRefs:(BOOL)usesClassRefs {
    if ((self = [super init])) {
      description_ = desc;
      if (!usesClassRefs) {
        // Legacy without class ref support.
        const char *className = description_->messageOrGroupClass.name;
        if (className) {
          Class clazz = objc_lookUpClass(className);
          NSAssert(clazz != Nil, @"Class %s not defined", className);
          description_->messageOrGroupClass.clazz = clazz;
        }
  
        const char *extendedClassName = description_->extendedClass.name;
        if (extendedClassName) {
          Class clazz = objc_lookUpClass(extendedClassName);
          NSAssert(clazz, @"Class %s not defined", extendedClassName);
          description_->extendedClass.clazz = clazz;
        }
      }
  
      GPBDataType type = description_->dataType;
      if (type == GPBDataTypeBytes) {
        // Data stored as a length prefixed c-string in descriptor records.
        const uint8_t *bytes =
            (const uint8_t *)description_->defaultValue.valueData;
        if (bytes) {
          uint32_t length;
          memcpy(&length, bytes, sizeof(length));
          // The length is stored in network byte order.
          length = ntohl(length);
          bytes += sizeof(length);
          defaultValue_.valueData =
              [[NSData alloc] initWithBytes:bytes length:length];
        }
      } else if (type == GPBDataTypeMessage || type == GPBDataTypeGroup) {
        // The default is looked up in -defaultValue instead since extensions
        // aren't common, we avoid the hit startup hit and it avoid initialization
        // order issues.
      } else {
        defaultValue_ = description_->defaultValue;
      }
    }
    return self;
  }
  
  - (instancetype)initWithExtensionDescription:(GPBExtensionDescription *)desc {
    return [self initWithExtensionDescription:desc usesClassRefs:NO];
  }
  
  - (void)dealloc {
    if ((description_->dataType == GPBDataTypeBytes) &&
        !GPBExtensionIsRepeated(description_)) {
      [defaultValue_.valueData release];
    }
    [super dealloc];
  }
  
  - (instancetype)copyWithZone:(NSZone *)zone {
  #pragma unused(zone)
    // Immutable.
    return [self retain];
  }
  
  - (NSString *)singletonName {
    return (NSString * _Nonnull)@(description_->singletonName);
  }
  
  - (const char *)singletonNameC {
    return description_->singletonName;
  }
  
  - (uint32_t)fieldNumber {
    return description_->fieldNumber;
  }
  
  - (GPBDataType)dataType {
    return description_->dataType;
  }
  
  - (GPBWireFormat)wireType {
    return GPBWireFormatForType(description_->dataType,
                                GPBExtensionIsPacked(description_));
  }
  
  - (GPBWireFormat)alternateWireType {
    NSAssert(GPBExtensionIsRepeated(description_),
             @"Only valid on repeated extensions");
    return GPBWireFormatForType(description_->dataType,
                                !GPBExtensionIsPacked(description_));
  }
  
  - (BOOL)isRepeated {
    return GPBExtensionIsRepeated(description_);
  }
  
  - (BOOL)isPackable {
    return GPBExtensionIsPacked(description_);
  }
  
  - (Class)msgClass {
    return description_->messageOrGroupClass.clazz;
  }
  
  - (Class)containingMessageClass {
    return description_->extendedClass.clazz;
  }
  
  - (GPBEnumDescriptor *)enumDescriptor {
    if (description_->dataType == GPBDataTypeEnum) {
      GPBEnumDescriptor *enumDescriptor = description_->enumDescriptorFunc();
      return enumDescriptor;
    }
    return nil;
  }
  
  - (id)defaultValue {
    if (GPBExtensionIsRepeated(description_)) {
      return nil;
    }
  
    switch (description_->dataType) {
      case GPBDataTypeBool:
        return @(defaultValue_.valueBool);
      case GPBDataTypeFloat:
        return @(defaultValue_.valueFloat);
      case GPBDataTypeDouble:
        return @(defaultValue_.valueDouble);
      case GPBDataTypeInt32:
      case GPBDataTypeSInt32:
      case GPBDataTypeEnum:
      case GPBDataTypeSFixed32:
        return @(defaultValue_.valueInt32);
      case GPBDataTypeInt64:
      case GPBDataTypeSInt64:
      case GPBDataTypeSFixed64:
        return @(defaultValue_.valueInt64);
      case GPBDataTypeUInt32:
      case GPBDataTypeFixed32:
        return @(defaultValue_.valueUInt32);
      case GPBDataTypeUInt64:
      case GPBDataTypeFixed64:
        return @(defaultValue_.valueUInt64);
      case GPBDataTypeBytes:
        // Like message fields, the default is zero length data.
        return (defaultValue_.valueData ? defaultValue_.valueData
                                        : GPBEmptyNSData());
      case GPBDataTypeString:
        // Like message fields, the default is zero length string.
        return (defaultValue_.valueString ? defaultValue_.valueString : @"");
      case GPBDataTypeGroup:
      case GPBDataTypeMessage:
        return nil;
    }
  }
  
  - (NSComparisonResult)compareByFieldNumber:(GPBExtensionDescriptor *)other {
    int32_t selfNumber = description_->fieldNumber;
    int32_t otherNumber = other->description_->fieldNumber;
    if (selfNumber < otherNumber) {
      return NSOrderedAscending;
    } else if (selfNumber == otherNumber) {
      return NSOrderedSame;
    } else {
      return NSOrderedDescending;
    }
  }
  
  @end
  
  #pragma clang diagnostic pop
  