// 
// Decompiled by Procyon v0.6.0
// 

package com.google.protobuf;

import java.util.IdentityHashMap;
import java.util.Set;
import java.util.Comparator;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntFunction;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

@CheckReturnValue
public final class Descriptors
{
    private static final Logger logger;
    private static final int[] EMPTY_INT_ARRAY;
    private static final Descriptor[] EMPTY_DESCRIPTORS;
    private static final FieldDescriptor[] EMPTY_FIELD_DESCRIPTORS;
    private static final EnumDescriptor[] EMPTY_ENUM_DESCRIPTORS;
    private static final ServiceDescriptor[] EMPTY_SERVICE_DESCRIPTORS;
    private static final OneofDescriptor[] EMPTY_ONEOF_DESCRIPTORS;
    private static final ConcurrentHashMap<DescriptorProtos.FeatureSet, DescriptorProtos.FeatureSet> FEATURE_CACHE;
    private static volatile DescriptorProtos.FeatureSetDefaults javaEditionDefaults;
    
    static void setTestJavaEditionDefaults(final DescriptorProtos.FeatureSetDefaults defaults) {
        Descriptors.javaEditionDefaults = defaults;
    }
    
    static DescriptorProtos.FeatureSetDefaults getJavaEditionDefaults() {
        final Descriptor unused1 = DescriptorProtos.FeatureSetDefaults.getDescriptor();
        final FileDescriptor unused2 = JavaFeaturesProto.getDescriptor();
        if (Descriptors.javaEditionDefaults == null) {
            synchronized (Descriptors.class) {
                if (Descriptors.javaEditionDefaults == null) {
                    try {
                        final ExtensionRegistry registry = ExtensionRegistry.newInstance();
                        registry.add(JavaFeaturesProto.java_);
                        setTestJavaEditionDefaults(DescriptorProtos.FeatureSetDefaults.parseFrom("\n'\u0018\u0084\u0007\"\u0003\u00ca>\u0000*\u001d\b\u0001\u0010\u0002\u0018\u0002 \u0003(\u00010\u00028\u0002@\u0001\u00ca>\n\b\u0001\u0010\u0001\u0018\u0000 \u0001(\u0003\n'\u0018\u00e7\u0007\"\u0003\u00ca>\u0000*\u001d\b\u0002\u0010\u0001\u0018\u0001 \u0002(\u00010\u00018\u0002@\u0001\u00ca>\n\b\u0000\u0010\u0001\u0018\u0000 \u0001(\u0003\n'\u0018\u00e8\u0007\"\u0013\b\u0001\u0010\u0001\u0018\u0001 \u0002(\u00010\u0001\u00ca>\u0004\b\u0000\u0010\u0001*\r8\u0002@\u0001\u00ca>\u0006\u0018\u0000 \u0001(\u0003\n'\u0018\u00e9\u0007\"\u001b\b\u0001\u0010\u0001\u0018\u0001 \u0002(\u00010\u00018\u0001@\u0002\u00ca>\b\b\u0000\u0010\u0001\u0018\u0000(\u0001*\u0005\u00ca>\u0002 \u0000 \u00e6\u0007(\u00e9\u0007".getBytes(Internal.ISO_8859_1), registry));
                    }
                    catch (final Exception e) {
                        throw new AssertionError((Object)e);
                    }
                }
            }
        }
        return Descriptors.javaEditionDefaults;
    }
    
    static DescriptorProtos.FeatureSet getEditionDefaults(final DescriptorProtos.Edition edition) {
        final DescriptorProtos.FeatureSetDefaults javaEditionDefaults = getJavaEditionDefaults();
        if (edition.getNumber() < javaEditionDefaults.getMinimumEdition().getNumber()) {
            throw new IllegalArgumentException("Edition " + edition + " is lower than the minimum supported edition " + javaEditionDefaults.getMinimumEdition() + "!");
        }
        if (edition.getNumber() > javaEditionDefaults.getMaximumEdition().getNumber()) {
            throw new IllegalArgumentException("Edition " + edition + " is greater than the maximum supported edition " + javaEditionDefaults.getMaximumEdition() + "!");
        }
        DescriptorProtos.FeatureSetDefaults.FeatureSetEditionDefault found = null;
        for (final DescriptorProtos.FeatureSetDefaults.FeatureSetEditionDefault editionDefault : javaEditionDefaults.getDefaultsList()) {
            if (editionDefault.getEdition().getNumber() > edition.getNumber()) {
                break;
            }
            found = editionDefault;
        }
        if (found == null) {
            throw new IllegalArgumentException("Edition " + edition + " does not have a valid default FeatureSet!");
        }
        return found.getFixedFeatures().toBuilder().mergeFrom(found.getOverridableFeatures()).build();
    }
    
    private static DescriptorProtos.FeatureSet internFeatures(final DescriptorProtos.FeatureSet features) {
        final DescriptorProtos.FeatureSet cached = Descriptors.FEATURE_CACHE.putIfAbsent(features, features);
        if (cached == null) {
            return features;
        }
        return cached;
    }
    
    private static String computeFullName(final FileDescriptor file, final Descriptor parent, final String name) {
        if (parent != null) {
            return parent.getFullName() + '.' + name;
        }
        final String packageName = file.getPackage();
        if (!packageName.isEmpty()) {
            return packageName + '.' + name;
        }
        return name;
    }
    
    private static <T> T binarySearch(final T[] array, final int size, final ToIntFunction<T> getter, final int number) {
        int left = 0;
        int right = size - 1;
        while (left <= right) {
            final int mid = (left + right) / 2;
            final T midValue = array[mid];
            final int midValueNumber = getter.applyAsInt(midValue);
            if (number < midValueNumber) {
                right = mid - 1;
            }
            else {
                if (number <= midValueNumber) {
                    return midValue;
                }
                left = mid + 1;
            }
        }
        return null;
    }
    
    static {
        logger = Logger.getLogger(Descriptors.class.getName());
        EMPTY_INT_ARRAY = new int[0];
        EMPTY_DESCRIPTORS = new Descriptor[0];
        EMPTY_FIELD_DESCRIPTORS = new FieldDescriptor[0];
        EMPTY_ENUM_DESCRIPTORS = new EnumDescriptor[0];
        EMPTY_SERVICE_DESCRIPTORS = new ServiceDescriptor[0];
        EMPTY_ONEOF_DESCRIPTORS = new OneofDescriptor[0];
        FEATURE_CACHE = new ConcurrentHashMap<DescriptorProtos.FeatureSet, DescriptorProtos.FeatureSet>();
        Descriptors.javaEditionDefaults = null;
    }
    
    public static final class FileDescriptor extends GenericDescriptor
    {
        private DescriptorProtos.FileDescriptorProto proto;
        private volatile DescriptorProtos.FileOptions options;
        private final Descriptor[] messageTypes;
        private final EnumDescriptor[] enumTypes;
        private final ServiceDescriptor[] services;
        private final FieldDescriptor[] extensions;
        private final FileDescriptor[] dependencies;
        private final FileDescriptor[] publicDependencies;
        private final FileDescriptorTables tables;
        private final boolean placeholder;
        private volatile boolean featuresResolved;
        
        @Override
        public DescriptorProtos.FileDescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public FileDescriptor getFile() {
            return this;
        }
        
        @Override
        GenericDescriptor getParent() {
            return null;
        }
        
        public boolean isPlaceholder() {
            return this.placeholder;
        }
        
        @Override
        public String getFullName() {
            return this.proto.getName();
        }
        
        public String getPackage() {
            return this.proto.getPackage();
        }
        
        public DescriptorProtos.FileOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.FileOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        public List<Descriptor> getMessageTypes() {
            return Collections.unmodifiableList((List<? extends Descriptor>)Arrays.asList((T[])this.messageTypes));
        }
        
        public int getMessageTypeCount() {
            return this.messageTypes.length;
        }
        
        public Descriptor getMessageType(final int index) {
            return this.messageTypes[index];
        }
        
        public List<EnumDescriptor> getEnumTypes() {
            return Collections.unmodifiableList((List<? extends EnumDescriptor>)Arrays.asList((T[])this.enumTypes));
        }
        
        public int getEnumTypeCount() {
            return this.enumTypes.length;
        }
        
        public EnumDescriptor getEnumType(final int index) {
            return this.enumTypes[index];
        }
        
        public List<ServiceDescriptor> getServices() {
            return Collections.unmodifiableList((List<? extends ServiceDescriptor>)Arrays.asList((T[])this.services));
        }
        
        public int getServiceCount() {
            return this.services.length;
        }
        
        public ServiceDescriptor getService(final int index) {
            return this.services[index];
        }
        
        public List<FieldDescriptor> getExtensions() {
            return Collections.unmodifiableList((List<? extends FieldDescriptor>)Arrays.asList((T[])this.extensions));
        }
        
        public int getExtensionCount() {
            return this.extensions.length;
        }
        
        public FieldDescriptor getExtension(final int index) {
            return this.extensions[index];
        }
        
        public List<FileDescriptor> getDependencies() {
            return Collections.unmodifiableList((List<? extends FileDescriptor>)Arrays.asList((T[])this.dependencies));
        }
        
        public List<FileDescriptor> getPublicDependencies() {
            return Collections.unmodifiableList((List<? extends FileDescriptor>)Arrays.asList((T[])this.publicDependencies));
        }
        
        DescriptorProtos.Edition getEdition() {
            final String syntax = this.proto.getSyntax();
            switch (syntax) {
                case "editions": {
                    return this.proto.getEdition();
                }
                case "proto3": {
                    return DescriptorProtos.Edition.EDITION_PROTO3;
                }
                default: {
                    return DescriptorProtos.Edition.EDITION_PROTO2;
                }
            }
        }
        
        public void copyHeadingTo(final DescriptorProtos.FileDescriptorProto.Builder protoBuilder) {
            protoBuilder.setName(this.getName()).setSyntax(this.proto.getSyntax());
            if (!this.getPackage().isEmpty()) {
                protoBuilder.setPackage(this.getPackage());
            }
            if (this.proto.getSyntax().equals("editions")) {
                protoBuilder.setEdition(this.proto.getEdition());
            }
            if (this.proto.hasOptions() && !this.proto.getOptions().equals(DescriptorProtos.FileOptions.getDefaultInstance())) {
                protoBuilder.setOptions(this.proto.getOptions());
            }
        }
        
        public Descriptor findMessageTypeByName(String name) {
            if (name.indexOf(46) != -1) {
                return null;
            }
            final String packageName = this.getPackage();
            if (!packageName.isEmpty()) {
                name = packageName + '.' + name;
            }
            final GenericDescriptor result = this.tables.findSymbol(name);
            if (result instanceof Descriptor && result.getFile() == this) {
                return (Descriptor)result;
            }
            return null;
        }
        
        public EnumDescriptor findEnumTypeByName(String name) {
            if (name.indexOf(46) != -1) {
                return null;
            }
            final String packageName = this.getPackage();
            if (!packageName.isEmpty()) {
                name = packageName + '.' + name;
            }
            final GenericDescriptor result = this.tables.findSymbol(name);
            if (result instanceof EnumDescriptor && result.getFile() == this) {
                return (EnumDescriptor)result;
            }
            return null;
        }
        
        public ServiceDescriptor findServiceByName(String name) {
            if (name.indexOf(46) != -1) {
                return null;
            }
            final String packageName = this.getPackage();
            if (!packageName.isEmpty()) {
                name = packageName + '.' + name;
            }
            final GenericDescriptor result = this.tables.findSymbol(name);
            if (result instanceof ServiceDescriptor && result.getFile() == this) {
                return (ServiceDescriptor)result;
            }
            return null;
        }
        
        public FieldDescriptor findExtensionByName(String name) {
            if (name.indexOf(46) != -1) {
                return null;
            }
            final String packageName = this.getPackage();
            if (!packageName.isEmpty()) {
                name = packageName + '.' + name;
            }
            final GenericDescriptor result = this.tables.findSymbol(name);
            if (result instanceof FieldDescriptor && result.getFile() == this) {
                return (FieldDescriptor)result;
            }
            return null;
        }
        
        public static FileDescriptor buildFrom(final DescriptorProtos.FileDescriptorProto proto, final FileDescriptor[] dependencies) throws DescriptorValidationException {
            return buildFrom(proto, dependencies, false);
        }
        
        public static FileDescriptor buildFrom(final DescriptorProtos.FileDescriptorProto proto, final FileDescriptor[] dependencies, final boolean allowUnknownDependencies) throws DescriptorValidationException {
            return buildFrom(proto, dependencies, allowUnknownDependencies, false);
        }
        
        private static FileDescriptor buildFrom(final DescriptorProtos.FileDescriptorProto proto, final FileDescriptor[] dependencies, final boolean allowUnknownDependencies, final boolean allowUnresolvedFeatures) throws DescriptorValidationException {
            final FileDescriptorTables tables = new FileDescriptorTables(dependencies, allowUnknownDependencies);
            final FileDescriptor result = new FileDescriptor(proto, dependencies, tables, allowUnknownDependencies);
            result.crossLink();
            if (!allowUnresolvedFeatures) {
                result.resolveAllFeaturesInternal();
            }
            return result;
        }
        
        private static byte[] latin1Cat(final String[] strings) {
            if (strings.length == 1) {
                return strings[0].getBytes(Internal.ISO_8859_1);
            }
            final StringBuilder descriptorData = new StringBuilder();
            for (final String part : strings) {
                descriptorData.append(part);
            }
            return descriptorData.toString().getBytes(Internal.ISO_8859_1);
        }
        
        private static FileDescriptor[] findDescriptors(final Class<?> descriptorOuterClass, final String[] dependencyClassNames, final String[] dependencyFileNames) {
            final List<FileDescriptor> descriptors = new ArrayList<FileDescriptor>();
            for (int i = 0; i < dependencyClassNames.length; ++i) {
                try {
                    final Class<?> clazz = descriptorOuterClass.getClassLoader().loadClass(dependencyClassNames[i]);
                    descriptors.add((FileDescriptor)clazz.getField("descriptor").get(null));
                }
                catch (final Exception e) {
                    Descriptors.logger.warning("Descriptors for \"" + dependencyFileNames[i] + "\" can not be found.");
                }
            }
            return descriptors.toArray(new FileDescriptor[0]);
        }
        
        @Deprecated
        public static void internalBuildGeneratedFileFrom(final String[] descriptorDataParts, final FileDescriptor[] dependencies, final InternalDescriptorAssigner descriptorAssigner) {
            final byte[] descriptorBytes = latin1Cat(descriptorDataParts);
            DescriptorProtos.FileDescriptorProto proto;
            try {
                proto = DescriptorProtos.FileDescriptorProto.parseFrom(descriptorBytes);
            }
            catch (final InvalidProtocolBufferException e) {
                throw new IllegalArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
            }
            FileDescriptor result;
            try {
                result = buildFrom(proto, dependencies, true);
            }
            catch (final DescriptorValidationException e2) {
                throw new IllegalArgumentException("Invalid embedded descriptor for \"" + proto.getName() + "\".", e2);
            }
            final ExtensionRegistry registry = descriptorAssigner.assignDescriptors(result);
            if (registry != null) {
                throw new RuntimeException("assignDescriptors must return null");
            }
        }
        
        public static FileDescriptor internalBuildGeneratedFileFrom(final String[] descriptorDataParts, final FileDescriptor[] dependencies) {
            final byte[] descriptorBytes = latin1Cat(descriptorDataParts);
            DescriptorProtos.FileDescriptorProto proto;
            try {
                proto = DescriptorProtos.FileDescriptorProto.parseFrom(descriptorBytes);
            }
            catch (final InvalidProtocolBufferException e) {
                throw new IllegalArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
            }
            try {
                return buildFrom(proto, dependencies, true, true);
            }
            catch (final DescriptorValidationException e2) {
                throw new IllegalArgumentException("Invalid embedded descriptor for \"" + proto.getName() + "\".", e2);
            }
        }
        
        public static FileDescriptor internalBuildGeneratedFileFrom(final String[] descriptorDataParts, final Class<?> descriptorOuterClass, final String[] dependencyClassNames, final String[] dependencyFileNames) {
            final FileDescriptor[] dependencies = findDescriptors(descriptorOuterClass, dependencyClassNames, dependencyFileNames);
            return internalBuildGeneratedFileFrom(descriptorDataParts, dependencies);
        }
        
        public static void internalUpdateFileDescriptor(final FileDescriptor descriptor, final ExtensionRegistry registry) {
            final ByteString bytes = descriptor.proto.toByteString();
            try {
                final DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.parseFrom(bytes, registry);
                descriptor.setProto(proto);
            }
            catch (final InvalidProtocolBufferException e) {
                throw new IllegalArgumentException("Failed to parse protocol buffer descriptor for generated code.", e);
            }
        }
        
        private FileDescriptor(final DescriptorProtos.FileDescriptorProto proto, final FileDescriptor[] dependencies, final FileDescriptorTables tables, final boolean allowUnknownDependencies) throws DescriptorValidationException {
            this.tables = tables;
            this.proto = proto;
            this.dependencies = dependencies.clone();
            this.featuresResolved = false;
            final HashMap<String, FileDescriptor> nameToFileMap = new HashMap<String, FileDescriptor>();
            for (final FileDescriptor file : dependencies) {
                nameToFileMap.put(file.getName(), file);
            }
            final List<FileDescriptor> publicDependencies = new ArrayList<FileDescriptor>();
            for (int i = 0; i < proto.getPublicDependencyCount(); ++i) {
                final int index = proto.getPublicDependency(i);
                if (index < 0 || index >= proto.getDependencyCount()) {
                    throw new DescriptorValidationException(this, "Invalid public dependency index.");
                }
                final String name = proto.getDependency(index);
                final FileDescriptor file2 = nameToFileMap.get(name);
                if (file2 == null) {
                    if (!allowUnknownDependencies) {
                        throw new DescriptorValidationException(this, "Invalid public dependency: " + name);
                    }
                }
                else {
                    publicDependencies.add(file2);
                }
            }
            publicDependencies.toArray(this.publicDependencies = new FileDescriptor[publicDependencies.size()]);
            this.placeholder = false;
            tables.addPackage(this.getPackage(), this);
            this.messageTypes = ((proto.getMessageTypeCount() > 0) ? new Descriptor[proto.getMessageTypeCount()] : Descriptors.EMPTY_DESCRIPTORS);
            for (int i = 0; i < proto.getMessageTypeCount(); ++i) {
                this.messageTypes[i] = new Descriptor(proto.getMessageType(i), this, (Descriptor)null, i);
            }
            this.enumTypes = ((proto.getEnumTypeCount() > 0) ? new EnumDescriptor[proto.getEnumTypeCount()] : Descriptors.EMPTY_ENUM_DESCRIPTORS);
            for (int i = 0; i < proto.getEnumTypeCount(); ++i) {
                this.enumTypes[i] = new EnumDescriptor(proto.getEnumType(i), this, (Descriptor)null, i);
            }
            this.services = ((proto.getServiceCount() > 0) ? new ServiceDescriptor[proto.getServiceCount()] : Descriptors.EMPTY_SERVICE_DESCRIPTORS);
            for (int i = 0; i < proto.getServiceCount(); ++i) {
                this.services[i] = new ServiceDescriptor(proto.getService(i), this, i);
            }
            this.extensions = ((proto.getExtensionCount() > 0) ? new FieldDescriptor[proto.getExtensionCount()] : Descriptors.EMPTY_FIELD_DESCRIPTORS);
            for (int i = 0; i < proto.getExtensionCount(); ++i) {
                this.extensions[i] = new FieldDescriptor(proto.getExtension(i), this, (Descriptor)null, i, true);
            }
        }
        
        FileDescriptor(final String packageName, final Descriptor message) throws DescriptorValidationException {
            this.tables = new FileDescriptorTables(new FileDescriptor[0], true);
            this.proto = DescriptorProtos.FileDescriptorProto.newBuilder().setName(message.getFullName() + ".placeholder.proto").setPackage(packageName).addMessageType(message.toProto()).build();
            this.dependencies = new FileDescriptor[0];
            this.publicDependencies = new FileDescriptor[0];
            this.featuresResolved = false;
            this.messageTypes = new Descriptor[] { message };
            this.enumTypes = Descriptors.EMPTY_ENUM_DESCRIPTORS;
            this.services = Descriptors.EMPTY_SERVICE_DESCRIPTORS;
            this.extensions = Descriptors.EMPTY_FIELD_DESCRIPTORS;
            this.placeholder = true;
            this.tables.addPackage(packageName, this);
            this.tables.addSymbol(message);
        }
        
        public void resolveAllFeaturesImmutable() {
            try {
                this.resolveAllFeaturesInternal();
            }
            catch (final DescriptorValidationException e) {
                throw new IllegalArgumentException("Invalid features for \"" + this.proto.getName() + "\".", e);
            }
        }
        
        private void resolveAllFeaturesInternal() throws DescriptorValidationException {
            if (this.featuresResolved) {
                return;
            }
            synchronized (this) {
                if (this.featuresResolved) {
                    return;
                }
                this.resolveFeatures(this.proto.getOptions().getFeatures());
                for (final Descriptor messageType : this.messageTypes) {
                    messageType.resolveAllFeatures();
                }
                for (final EnumDescriptor enumType : this.enumTypes) {
                    enumType.resolveAllFeatures();
                }
                for (final ServiceDescriptor service : this.services) {
                    service.resolveAllFeatures();
                }
                for (final FieldDescriptor extension : this.extensions) {
                    extension.resolveAllFeatures();
                }
                this.featuresResolved = true;
            }
        }
        
        @Override
        DescriptorProtos.FeatureSet inferLegacyProtoFeatures() {
            if (this.getEdition().getNumber() >= DescriptorProtos.Edition.EDITION_2023.getNumber()) {
                return DescriptorProtos.FeatureSet.getDefaultInstance();
            }
            DescriptorProtos.FeatureSet.Builder features = null;
            if (this.getEdition() == DescriptorProtos.Edition.EDITION_PROTO2 && this.proto.getOptions().getJavaStringCheckUtf8()) {
                features = DescriptorProtos.FeatureSet.newBuilder();
                features.setExtension(JavaFeaturesProto.java_, JavaFeaturesProto.JavaFeatures.newBuilder().setUtf8Validation(JavaFeaturesProto.JavaFeatures.Utf8Validation.VERIFY).build());
            }
            return (features != null) ? features.build() : DescriptorProtos.FeatureSet.getDefaultInstance();
        }
        
        private void crossLink() throws DescriptorValidationException {
            for (final Descriptor messageType : this.messageTypes) {
                messageType.crossLink();
            }
            for (final ServiceDescriptor service : this.services) {
                service.crossLink();
            }
            for (final FieldDescriptor extension : this.extensions) {
                extension.crossLink();
            }
        }
        
        private synchronized void setProto(final DescriptorProtos.FileDescriptorProto proto) {
            this.proto = proto;
            this.options = null;
            try {
                this.resolveFeatures(proto.getOptions().getFeatures());
                for (int i = 0; i < this.messageTypes.length; ++i) {
                    this.messageTypes[i].setProto(proto.getMessageType(i));
                }
                for (int i = 0; i < this.enumTypes.length; ++i) {
                    this.enumTypes[i].setProto(proto.getEnumType(i));
                }
                for (int i = 0; i < this.services.length; ++i) {
                    this.services[i].setProto(proto.getService(i));
                }
                for (int i = 0; i < this.extensions.length; ++i) {
                    this.extensions[i].setProto(proto.getExtension(i));
                }
            }
            catch (final DescriptorValidationException e) {
                throw new IllegalArgumentException("Invalid features for \"" + proto.getName() + "\".", e);
            }
        }
        
        @Deprecated
        public interface InternalDescriptorAssigner
        {
            ExtensionRegistry assignDescriptors(final FileDescriptor root);
        }
    }
    
    public static final class Descriptor extends GenericDescriptor
    {
        private final int index;
        private DescriptorProtos.DescriptorProto proto;
        private volatile DescriptorProtos.MessageOptions options;
        private final String fullName;
        private final GenericDescriptor parent;
        private final Descriptor[] nestedTypes;
        private final EnumDescriptor[] enumTypes;
        private final FieldDescriptor[] fields;
        private final FieldDescriptor[] fieldsSortedByNumber;
        private final FieldDescriptor[] extensions;
        private final OneofDescriptor[] oneofs;
        private final int realOneofCount;
        private final int[] extensionRangeLowerBounds;
        private final int[] extensionRangeUpperBounds;
        private final boolean placeholder;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public DescriptorProtos.DescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.parent.getFile();
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.parent;
        }
        
        public boolean isPlaceholder() {
            return this.placeholder;
        }
        
        public Descriptor getContainingType() {
            if (this.parent instanceof Descriptor) {
                return (Descriptor)this.parent;
            }
            return null;
        }
        
        public DescriptorProtos.MessageOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.MessageOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        public List<FieldDescriptor> getFields() {
            return Collections.unmodifiableList((List<? extends FieldDescriptor>)Arrays.asList((T[])this.fields));
        }
        
        public int getFieldCount() {
            return this.fields.length;
        }
        
        public FieldDescriptor getField(final int index) {
            return this.fields[index];
        }
        
        public List<OneofDescriptor> getOneofs() {
            return Collections.unmodifiableList((List<? extends OneofDescriptor>)Arrays.asList((T[])this.oneofs));
        }
        
        public int getOneofCount() {
            return this.oneofs.length;
        }
        
        public OneofDescriptor getOneof(final int index) {
            return this.oneofs[index];
        }
        
        public List<OneofDescriptor> getRealOneofs() {
            return Collections.unmodifiableList((List<? extends OneofDescriptor>)Arrays.asList(this.oneofs).subList(0, this.realOneofCount));
        }
        
        public int getRealOneofCount() {
            return this.realOneofCount;
        }
        
        public OneofDescriptor getRealOneof(final int index) {
            if (index >= this.realOneofCount) {
                throw new ArrayIndexOutOfBoundsException(index);
            }
            return this.oneofs[index];
        }
        
        public List<FieldDescriptor> getExtensions() {
            return Collections.unmodifiableList((List<? extends FieldDescriptor>)Arrays.asList((T[])this.extensions));
        }
        
        public int getExtensionCount() {
            return this.extensions.length;
        }
        
        public FieldDescriptor getExtension(final int index) {
            return this.extensions[index];
        }
        
        public List<Descriptor> getNestedTypes() {
            return Collections.unmodifiableList((List<? extends Descriptor>)Arrays.asList((T[])this.nestedTypes));
        }
        
        public int getNestedTypeCount() {
            return this.nestedTypes.length;
        }
        
        public Descriptor getNestedType(final int index) {
            return this.nestedTypes[index];
        }
        
        public List<EnumDescriptor> getEnumTypes() {
            return Collections.unmodifiableList((List<? extends EnumDescriptor>)Arrays.asList((T[])this.enumTypes));
        }
        
        public int getEnumTypeCount() {
            return this.enumTypes.length;
        }
        
        public EnumDescriptor getEnumType(final int index) {
            return this.enumTypes[index];
        }
        
        public boolean isExtensionNumber(final int number) {
            int index = Arrays.binarySearch(this.extensionRangeLowerBounds, number);
            if (index < 0) {
                index = ~index - 1;
            }
            return index >= 0 && number < this.extensionRangeUpperBounds[index];
        }
        
        public boolean isReservedNumber(final int number) {
            for (final DescriptorProtos.DescriptorProto.ReservedRange range : this.proto.getReservedRangeList()) {
                if (range.getStart() <= number && number < range.getEnd()) {
                    return true;
                }
            }
            return false;
        }
        
        public boolean isReservedName(final String name) {
            Internal.checkNotNull(name);
            for (final String reservedName : this.proto.getReservedNameList()) {
                if (reservedName.equals(name)) {
                    return true;
                }
            }
            return false;
        }
        
        public boolean isExtendable() {
            return !this.proto.getExtensionRangeList().isEmpty();
        }
        
        public FieldDescriptor findFieldByName(final String name) {
            final GenericDescriptor result = this.getFile().tables.findSymbol(this.fullName + '.' + name);
            if (result instanceof FieldDescriptor) {
                return (FieldDescriptor)result;
            }
            return null;
        }
        
        public FieldDescriptor findFieldByNumber(final int number) {
            return (FieldDescriptor)binarySearch(this.fieldsSortedByNumber, this.fieldsSortedByNumber.length, FieldDescriptor.NUMBER_GETTER, number);
        }
        
        public Descriptor findNestedTypeByName(final String name) {
            final GenericDescriptor result = this.getFile().tables.findSymbol(this.fullName + '.' + name);
            if (result instanceof Descriptor) {
                return (Descriptor)result;
            }
            return null;
        }
        
        public EnumDescriptor findEnumTypeByName(final String name) {
            final GenericDescriptor result = this.getFile().tables.findSymbol(this.fullName + '.' + name);
            if (result instanceof EnumDescriptor) {
                return (EnumDescriptor)result;
            }
            return null;
        }
        
        Descriptor(final String fullname) throws DescriptorValidationException {
            String name = fullname;
            String packageName = "";
            final int pos = fullname.lastIndexOf(46);
            if (pos != -1) {
                name = fullname.substring(pos + 1);
                packageName = fullname.substring(0, pos);
            }
            this.index = 0;
            this.proto = DescriptorProtos.DescriptorProto.newBuilder().setName(name).addExtensionRange(DescriptorProtos.DescriptorProto.ExtensionRange.newBuilder().setStart(1).setEnd(536870912).build()).build();
            this.fullName = fullname;
            this.nestedTypes = Descriptors.EMPTY_DESCRIPTORS;
            this.enumTypes = Descriptors.EMPTY_ENUM_DESCRIPTORS;
            this.fields = Descriptors.EMPTY_FIELD_DESCRIPTORS;
            this.fieldsSortedByNumber = Descriptors.EMPTY_FIELD_DESCRIPTORS;
            this.extensions = Descriptors.EMPTY_FIELD_DESCRIPTORS;
            this.oneofs = Descriptors.EMPTY_ONEOF_DESCRIPTORS;
            this.realOneofCount = 0;
            this.parent = new FileDescriptor(packageName, this);
            this.extensionRangeLowerBounds = new int[] { 1 };
            this.extensionRangeUpperBounds = new int[] { 536870912 };
            this.placeholder = true;
        }
        
        private Descriptor(final DescriptorProtos.DescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index) throws DescriptorValidationException {
            if (parent == null) {
                this.parent = file;
            }
            else {
                this.parent = parent;
            }
            this.index = index;
            this.proto = proto;
            this.fullName = computeFullName(file, parent, proto.getName());
            this.oneofs = ((proto.getOneofDeclCount() > 0) ? new OneofDescriptor[proto.getOneofDeclCount()] : Descriptors.EMPTY_ONEOF_DESCRIPTORS);
            for (int i = 0; i < proto.getOneofDeclCount(); ++i) {
                this.oneofs[i] = new OneofDescriptor(proto.getOneofDecl(i), this, i);
            }
            this.nestedTypes = ((proto.getNestedTypeCount() > 0) ? new Descriptor[proto.getNestedTypeCount()] : Descriptors.EMPTY_DESCRIPTORS);
            for (int i = 0; i < proto.getNestedTypeCount(); ++i) {
                this.nestedTypes[i] = new Descriptor(proto.getNestedType(i), file, this, i);
            }
            this.enumTypes = ((proto.getEnumTypeCount() > 0) ? new EnumDescriptor[proto.getEnumTypeCount()] : Descriptors.EMPTY_ENUM_DESCRIPTORS);
            for (int i = 0; i < proto.getEnumTypeCount(); ++i) {
                this.enumTypes[i] = new EnumDescriptor(proto.getEnumType(i), file, this, i);
            }
            this.fields = ((proto.getFieldCount() > 0) ? new FieldDescriptor[proto.getFieldCount()] : Descriptors.EMPTY_FIELD_DESCRIPTORS);
            for (int i = 0; i < proto.getFieldCount(); ++i) {
                this.fields[i] = new FieldDescriptor(proto.getField(i), file, this, i, false);
            }
            this.fieldsSortedByNumber = ((proto.getFieldCount() > 0) ? this.fields.clone() : Descriptors.EMPTY_FIELD_DESCRIPTORS);
            this.extensions = ((proto.getExtensionCount() > 0) ? new FieldDescriptor[proto.getExtensionCount()] : Descriptors.EMPTY_FIELD_DESCRIPTORS);
            for (int i = 0; i < proto.getExtensionCount(); ++i) {
                this.extensions[i] = new FieldDescriptor(proto.getExtension(i), file, this, i, true);
            }
            for (int i = 0; i < proto.getOneofDeclCount(); ++i) {
                this.oneofs[i].fields = new FieldDescriptor[this.oneofs[i].getFieldCount()];
                this.oneofs[i].fieldCount = 0;
            }
            for (int i = 0; i < proto.getFieldCount(); ++i) {
                final OneofDescriptor oneofDescriptor = this.fields[i].getContainingOneof();
                if (oneofDescriptor != null) {
                    oneofDescriptor.fields[oneofDescriptor.fieldCount++] = this.fields[i];
                }
            }
            int syntheticOneofCount = 0;
            for (final OneofDescriptor oneof : this.oneofs) {
                if (oneof.isSynthetic()) {
                    ++syntheticOneofCount;
                }
                else if (syntheticOneofCount > 0) {
                    throw new DescriptorValidationException((GenericDescriptor)this, "Synthetic oneofs must come last.");
                }
            }
            this.realOneofCount = this.oneofs.length - syntheticOneofCount;
            this.placeholder = false;
            file.tables.addSymbol(this);
            if (proto.getExtensionRangeCount() > 0) {
                this.extensionRangeLowerBounds = new int[proto.getExtensionRangeCount()];
                this.extensionRangeUpperBounds = new int[proto.getExtensionRangeCount()];
                int j = 0;
                for (final DescriptorProtos.DescriptorProto.ExtensionRange range : proto.getExtensionRangeList()) {
                    this.extensionRangeLowerBounds[j] = range.getStart();
                    this.extensionRangeUpperBounds[j] = range.getEnd();
                    ++j;
                }
                Arrays.sort(this.extensionRangeLowerBounds);
                Arrays.sort(this.extensionRangeUpperBounds);
            }
            else {
                this.extensionRangeLowerBounds = Descriptors.EMPTY_INT_ARRAY;
                this.extensionRangeUpperBounds = Descriptors.EMPTY_INT_ARRAY;
            }
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
            for (final Descriptor nestedType : this.nestedTypes) {
                nestedType.resolveAllFeatures();
            }
            for (final EnumDescriptor enumType : this.enumTypes) {
                enumType.resolveAllFeatures();
            }
            for (final OneofDescriptor oneof : this.oneofs) {
                oneof.resolveAllFeatures();
            }
            for (final FieldDescriptor field : this.fields) {
                field.resolveAllFeatures();
            }
            for (final FieldDescriptor extension : this.extensions) {
                extension.resolveAllFeatures();
            }
        }
        
        private void crossLink() throws DescriptorValidationException {
            for (final Descriptor nestedType : this.nestedTypes) {
                nestedType.crossLink();
            }
            for (final FieldDescriptor field : this.fields) {
                field.crossLink();
            }
            Arrays.sort(this.fieldsSortedByNumber);
            this.validateNoDuplicateFieldNumbers();
            for (final FieldDescriptor extension : this.extensions) {
                extension.crossLink();
            }
        }
        
        private void validateNoDuplicateFieldNumbers() throws DescriptorValidationException {
            for (int i = 0; i + 1 < this.fieldsSortedByNumber.length; ++i) {
                final FieldDescriptor old = this.fieldsSortedByNumber[i];
                final FieldDescriptor field = this.fieldsSortedByNumber[i + 1];
                if (old.getNumber() == field.getNumber()) {
                    throw new DescriptorValidationException((GenericDescriptor)field, "Field number " + field.getNumber() + " has already been used in \"" + field.getContainingType().getFullName() + "\" by field \"" + old.getName() + "\".");
                }
            }
        }
        
        private void setProto(final DescriptorProtos.DescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
            for (int i = 0; i < this.nestedTypes.length; ++i) {
                this.nestedTypes[i].setProto(proto.getNestedType(i));
            }
            for (int i = 0; i < this.oneofs.length; ++i) {
                this.oneofs[i].setProto(proto.getOneofDecl(i));
            }
            for (int i = 0; i < this.enumTypes.length; ++i) {
                this.enumTypes[i].setProto(proto.getEnumType(i));
            }
            for (int i = 0; i < this.fields.length; ++i) {
                this.fields[i].setProto(proto.getField(i));
            }
            for (int i = 0; i < this.extensions.length; ++i) {
                this.extensions[i].setProto(proto.getExtension(i));
            }
        }
    }
    
    public static final class FieldDescriptor extends GenericDescriptor implements Comparable<FieldDescriptor>, FieldSet.FieldDescriptorLite<FieldDescriptor>
    {
        private static final ToIntFunction<FieldDescriptor> NUMBER_GETTER;
        private static final WireFormat.FieldType[] table;
        private final int index;
        private DescriptorProtos.FieldDescriptorProto proto;
        private volatile DescriptorProtos.FieldOptions options;
        private final String fullName;
        private String jsonName;
        private final GenericDescriptor parent;
        private final Descriptor extensionScope;
        private final boolean isProto3Optional;
        private volatile RedactionState redactionState;
        private Type type;
        private Descriptor containingType;
        private OneofDescriptor containingOneof;
        private GenericDescriptor typeDescriptor;
        private Object defaultValue;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public DescriptorProtos.FieldDescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public int getNumber() {
            return this.proto.getNumber();
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        public String getJsonName() {
            final String result = this.jsonName;
            if (result != null) {
                return result;
            }
            if (this.proto.hasJsonName()) {
                return this.jsonName = this.proto.getJsonName();
            }
            return this.jsonName = fieldNameToJsonName(this.proto.getName());
        }
        
        public JavaType getJavaType() {
            return this.getType().getJavaType();
        }
        
        @Override
        public WireFormat.JavaType getLiteJavaType() {
            return this.getLiteType().getJavaType();
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.parent.getFile();
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.parent;
        }
        
        public Type getType() {
            if (this.type == Type.MESSAGE && (this.typeDescriptor == null || !((Descriptor)this.typeDescriptor).toProto().getOptions().getMapEntry()) && (this.containingType == null || !this.containingType.toProto().getOptions().getMapEntry()) && this.features != null && this.getFeatures().getMessageEncoding() == DescriptorProtos.FeatureSet.MessageEncoding.DELIMITED) {
                return Type.GROUP;
            }
            return this.type;
        }
        
        @Override
        public WireFormat.FieldType getLiteType() {
            return FieldDescriptor.table[this.getType().ordinal()];
        }
        
        public boolean needsUtf8Check() {
            return this.getType() == Type.STRING && (this.getContainingType().toProto().getOptions().getMapEntry() || this.getFeatures().getExtension(JavaFeaturesProto.java_).getUtf8Validation().equals(JavaFeaturesProto.JavaFeatures.Utf8Validation.VERIFY) || this.getFeatures().getUtf8Validation().equals(DescriptorProtos.FeatureSet.Utf8Validation.VERIFY));
        }
        
        public boolean isMapField() {
            return this.getType() == Type.MESSAGE && this.isRepeated() && this.getMessageType().toProto().getOptions().getMapEntry();
        }
        
        public boolean isRequired() {
            return this.getFeatures().getFieldPresence() == DescriptorProtos.FeatureSet.FieldPresence.LEGACY_REQUIRED;
        }
        
        @Deprecated
        public boolean isOptional() {
            return this.proto.getLabel() == DescriptorProtos.FieldDescriptorProto.Label.LABEL_OPTIONAL && this.getFeatures().getFieldPresence() != DescriptorProtos.FeatureSet.FieldPresence.LEGACY_REQUIRED;
        }
        
        @Override
        public boolean isRepeated() {
            return this.proto.getLabel() == DescriptorProtos.FieldDescriptorProto.Label.LABEL_REPEATED;
        }
        
        @Override
        public boolean isPacked() {
            return this.isPackable() && this.getFeatures().getRepeatedFieldEncoding().equals(DescriptorProtos.FeatureSet.RepeatedFieldEncoding.PACKED);
        }
        
        public boolean isPackable() {
            return this.isRepeated() && this.getLiteType().isPackable();
        }
        
        public boolean hasDefaultValue() {
            return this.proto.hasDefaultValue();
        }
        
        public Object getDefaultValue() {
            if (this.getJavaType() == JavaType.MESSAGE) {
                throw new UnsupportedOperationException("FieldDescriptor.getDefaultValue() called on an embedded message field.");
            }
            return this.defaultValue;
        }
        
        public DescriptorProtos.FieldOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.FieldOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        public boolean isExtension() {
            return this.proto.hasExtendee();
        }
        
        public Descriptor getContainingType() {
            return this.containingType;
        }
        
        public OneofDescriptor getContainingOneof() {
            return this.containingOneof;
        }
        
        public OneofDescriptor getRealContainingOneof() {
            return (this.containingOneof != null && !this.containingOneof.isSynthetic()) ? this.containingOneof : null;
        }
        
        boolean hasOptionalKeyword() {
            return this.isProto3Optional || (this.getFile().getEdition() == DescriptorProtos.Edition.EDITION_PROTO2 && !this.isRequired() && !this.isRepeated() && this.getContainingOneof() == null);
        }
        
        public boolean hasPresence() {
            return !this.isRepeated() && (this.isProto3Optional || this.getType() == Type.MESSAGE || this.getType() == Type.GROUP || this.isExtension() || this.getContainingOneof() != null || this.getFeatures().getFieldPresence() != DescriptorProtos.FeatureSet.FieldPresence.IMPLICIT);
        }
        
        boolean isGroupLike() {
            return this.getType() == Type.GROUP && this.getMessageType().getName().toLowerCase().equals(this.getName()) && this.getMessageType().getFile() == this.getFile() && (this.isExtension() ? (this.getMessageType().getContainingType() == this.getExtensionScope()) : (this.getMessageType().getContainingType() == this.getContainingType()));
        }
        
        public Descriptor getExtensionScope() {
            if (!this.isExtension()) {
                throw new UnsupportedOperationException(String.format("This field is not an extension. (%s)", this.fullName));
            }
            return this.extensionScope;
        }
        
        public Descriptor getMessageType() {
            if (this.getJavaType() != JavaType.MESSAGE) {
                throw new UnsupportedOperationException(String.format("This field is not of message type. (%s)", this.fullName));
            }
            return (Descriptor)this.typeDescriptor;
        }
        
        @Override
        public EnumDescriptor getEnumType() {
            if (this.getJavaType() != JavaType.ENUM) {
                throw new UnsupportedOperationException(String.format("This field is not of enum type. (%s)", this.fullName));
            }
            return (EnumDescriptor)this.typeDescriptor;
        }
        
        public boolean legacyEnumFieldTreatedAsClosed() {
            if (this.getFile().getDependencies().isEmpty()) {
                return this.getType() == Type.ENUM && this.getEnumType().isClosed();
            }
            return this.getType() == Type.ENUM && (this.getFeatures().getExtension(JavaFeaturesProto.java_).getLegacyClosedEnum() || this.getEnumType().isClosed());
        }
        
        @Override
        public int compareTo(final FieldDescriptor other) {
            if (other.containingType != this.containingType) {
                throw new IllegalArgumentException("FieldDescriptors can only be compared to other FieldDescriptors for fields of the same message type.");
            }
            return this.getNumber() - other.getNumber();
        }
        
        @Override
        public String toString() {
            return this.getFullName();
        }
        
        private static String fieldNameToJsonName(final String name) {
            final int length = name.length();
            final StringBuilder result = new StringBuilder(length);
            boolean isNextUpperCase = false;
            for (int i = 0; i < length; ++i) {
                char ch = name.charAt(i);
                if (ch == '_') {
                    isNextUpperCase = true;
                }
                else if (isNextUpperCase) {
                    if ('a' <= ch && ch <= 'z') {
                        ch = (char)(ch - 'a' + 65);
                    }
                    result.append(ch);
                    isNextUpperCase = false;
                }
                else {
                    result.append(ch);
                }
            }
            return result.toString();
        }
        
        private FieldDescriptor(final DescriptorProtos.FieldDescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index, final boolean isExtension) throws DescriptorValidationException {
            this.index = index;
            this.proto = proto;
            this.fullName = computeFullName(file, parent, proto.getName());
            if (proto.hasType()) {
                this.type = Type.valueOf(proto.getType());
            }
            this.isProto3Optional = proto.getProto3Optional();
            if (this.getNumber() <= 0) {
                throw new DescriptorValidationException((GenericDescriptor)this, "Field numbers must be positive integers.");
            }
            if (isExtension) {
                if (!proto.hasExtendee()) {
                    throw new DescriptorValidationException((GenericDescriptor)this, "FieldDescriptorProto.extendee not set for extension field.");
                }
                this.containingType = null;
                if (parent != null) {
                    this.extensionScope = parent;
                    this.parent = parent;
                }
                else {
                    this.extensionScope = null;
                    this.parent = Internal.checkNotNull(file);
                }
                if (proto.hasOneofIndex()) {
                    throw new DescriptorValidationException((GenericDescriptor)this, "FieldDescriptorProto.oneof_index set for extension field.");
                }
                this.containingOneof = null;
            }
            else {
                if (proto.hasExtendee()) {
                    throw new DescriptorValidationException((GenericDescriptor)this, "FieldDescriptorProto.extendee set for non-extension field.");
                }
                this.containingType = parent;
                if (proto.hasOneofIndex()) {
                    if (proto.getOneofIndex() < 0 || proto.getOneofIndex() >= parent.toProto().getOneofDeclCount()) {
                        throw new DescriptorValidationException((GenericDescriptor)this, "FieldDescriptorProto.oneof_index is out of range for type " + parent.getName());
                    }
                    (this.containingOneof = parent.getOneofs().get(proto.getOneofIndex())).fieldCount++;
                    this.parent = Internal.checkNotNull(this.containingOneof);
                }
                else {
                    this.containingOneof = null;
                    this.parent = Internal.checkNotNull(parent);
                }
                this.extensionScope = null;
            }
            file.tables.addSymbol(this);
        }
        
        private static RedactionState isOptionSensitive(final FieldDescriptor field, final Object value) {
            if (field.getType() == Type.ENUM) {
                if (field.isRepeated()) {
                    for (final EnumValueDescriptor v : (List)value) {
                        if (v.getOptions().getDebugRedact()) {
                            return of(true, false);
                        }
                    }
                }
                else if (((EnumValueDescriptor)value).getOptions().getDebugRedact()) {
                    return of(true, false);
                }
            }
            else if (field.getJavaType() == JavaType.MESSAGE) {
                if (field.isRepeated()) {
                    for (final Message m : (List)value) {
                        for (final Map.Entry<FieldDescriptor, Object> entry : m.getAllFields().entrySet()) {
                            final RedactionState state = isOptionSensitive(entry.getKey(), entry.getValue());
                            if (state.redact) {
                                return state;
                            }
                        }
                    }
                }
                else {
                    for (final Map.Entry<FieldDescriptor, Object> entry2 : ((Message)value).getAllFields().entrySet()) {
                        final RedactionState state2 = isOptionSensitive(entry2.getKey(), entry2.getValue());
                        if (state2.redact) {
                            return state2;
                        }
                    }
                }
            }
            return of(false);
        }
        
        RedactionState getRedactionState() {
            RedactionState state = this.redactionState;
            if (state == null) {
                synchronized (this) {
                    state = this.redactionState;
                    if (state == null) {
                        final DescriptorProtos.FieldOptions options = this.getOptions();
                        state = of(options.getDebugRedact());
                        for (final Map.Entry<FieldDescriptor, Object> entry : options.getAllFields().entrySet()) {
                            state = combine(state, isOptionSensitive(entry.getKey(), entry.getValue()));
                            if (state.redact) {
                                break;
                            }
                        }
                        this.redactionState = state;
                    }
                }
            }
            return state;
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
        }
        
        @Override
        DescriptorProtos.FeatureSet inferLegacyProtoFeatures() {
            if (this.getFile().getEdition().getNumber() >= DescriptorProtos.Edition.EDITION_2023.getNumber()) {
                return DescriptorProtos.FeatureSet.getDefaultInstance();
            }
            DescriptorProtos.FeatureSet.Builder features = null;
            if (this.proto.getLabel() == DescriptorProtos.FieldDescriptorProto.Label.LABEL_REQUIRED) {
                features = DescriptorProtos.FeatureSet.newBuilder();
                features.setFieldPresence(DescriptorProtos.FeatureSet.FieldPresence.LEGACY_REQUIRED);
            }
            if (this.proto.getType() == DescriptorProtos.FieldDescriptorProto.Type.TYPE_GROUP) {
                if (features == null) {
                    features = DescriptorProtos.FeatureSet.newBuilder();
                }
                features.setMessageEncoding(DescriptorProtos.FeatureSet.MessageEncoding.DELIMITED);
            }
            if (this.getFile().getEdition() == DescriptorProtos.Edition.EDITION_PROTO2 && this.proto.getOptions().getPacked()) {
                if (features == null) {
                    features = DescriptorProtos.FeatureSet.newBuilder();
                }
                features.setRepeatedFieldEncoding(DescriptorProtos.FeatureSet.RepeatedFieldEncoding.PACKED);
            }
            if (this.getFile().getEdition() == DescriptorProtos.Edition.EDITION_PROTO3 && this.proto.getOptions().hasPacked() && !this.proto.getOptions().getPacked()) {
                if (features == null) {
                    features = DescriptorProtos.FeatureSet.newBuilder();
                }
                features.setRepeatedFieldEncoding(DescriptorProtos.FeatureSet.RepeatedFieldEncoding.EXPANDED);
            }
            return (features != null) ? features.build() : DescriptorProtos.FeatureSet.getDefaultInstance();
        }
        
        @Override
        void validateFeatures() throws DescriptorValidationException {
            if (this.containingType != null && this.containingType.toProto().getOptions().getMessageSetWireFormat() && this.isExtension() && (this.isRequired() || this.isRepeated() || this.getType() != Type.MESSAGE)) {
                throw new DescriptorValidationException((GenericDescriptor)this, "Extensions of MessageSets may not be required or repeated messages.");
            }
        }
        
        private void crossLink() throws DescriptorValidationException {
            if (this.proto.hasExtendee()) {
                final GenericDescriptor extendee = this.getFile().tables.lookupSymbol(this.proto.getExtendee(), this, FileDescriptorTables.SearchFilter.TYPES_ONLY);
                if (!(extendee instanceof Descriptor)) {
                    throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.proto.getExtendee() + "\" is not a message type.");
                }
                this.containingType = (Descriptor)extendee;
                if (!this.getContainingType().isExtensionNumber(this.getNumber())) {
                    throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.getContainingType().getFullName() + "\" does not declare " + this.getNumber() + " as an extension number.");
                }
            }
            if (this.proto.hasTypeName()) {
                final GenericDescriptor typeDescriptor = this.getFile().tables.lookupSymbol(this.proto.getTypeName(), this, FileDescriptorTables.SearchFilter.TYPES_ONLY);
                if (!this.proto.hasType()) {
                    if (typeDescriptor instanceof Descriptor) {
                        this.type = Type.MESSAGE;
                    }
                    else {
                        if (!(typeDescriptor instanceof EnumDescriptor)) {
                            throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.proto.getTypeName() + "\" is not a type.");
                        }
                        this.type = Type.ENUM;
                    }
                }
                if (this.type.getJavaType() == JavaType.MESSAGE) {
                    if (!(typeDescriptor instanceof Descriptor)) {
                        throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.proto.getTypeName() + "\" is not a message type.");
                    }
                    this.typeDescriptor = typeDescriptor;
                    if (this.proto.hasDefaultValue()) {
                        throw new DescriptorValidationException((GenericDescriptor)this, "Messages can't have default values.");
                    }
                }
                else {
                    if (this.type.getJavaType() != JavaType.ENUM) {
                        throw new DescriptorValidationException((GenericDescriptor)this, "Field with primitive type has type_name.");
                    }
                    if (!(typeDescriptor instanceof EnumDescriptor)) {
                        throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.proto.getTypeName() + "\" is not an enum type.");
                    }
                    this.typeDescriptor = typeDescriptor;
                }
            }
            else if (this.type.getJavaType() == JavaType.MESSAGE || this.type.getJavaType() == JavaType.ENUM) {
                throw new DescriptorValidationException((GenericDescriptor)this, "Field with message or enum type missing type_name.");
            }
            if (this.proto.getOptions().getPacked() && !this.isPackable()) {
                throw new DescriptorValidationException((GenericDescriptor)this, "[packed = true] can only be specified for repeated primitive fields.");
            }
            if (this.proto.hasDefaultValue()) {
                if (this.isRepeated()) {
                    throw new DescriptorValidationException((GenericDescriptor)this, "Repeated fields cannot have default values.");
                }
                try {
                    switch (this.type.ordinal()) {
                        case 4:
                        case 14:
                        case 16: {
                            this.defaultValue = TextFormat.parseInt32(this.proto.getDefaultValue());
                            break;
                        }
                        case 6:
                        case 12: {
                            this.defaultValue = TextFormat.parseUInt32(this.proto.getDefaultValue());
                            break;
                        }
                        case 2:
                        case 15:
                        case 17: {
                            this.defaultValue = TextFormat.parseInt64(this.proto.getDefaultValue());
                            break;
                        }
                        case 3:
                        case 5: {
                            this.defaultValue = TextFormat.parseUInt64(this.proto.getDefaultValue());
                            break;
                        }
                        case 1: {
                            if (this.proto.getDefaultValue().equals("inf")) {
                                this.defaultValue = Float.POSITIVE_INFINITY;
                                break;
                            }
                            if (this.proto.getDefaultValue().equals("-inf")) {
                                this.defaultValue = Float.NEGATIVE_INFINITY;
                                break;
                            }
                            if (this.proto.getDefaultValue().equals("nan")) {
                                this.defaultValue = Float.NaN;
                                break;
                            }
                            this.defaultValue = Float.valueOf(this.proto.getDefaultValue());
                            break;
                        }
                        case 0: {
                            if (this.proto.getDefaultValue().equals("inf")) {
                                this.defaultValue = Double.POSITIVE_INFINITY;
                                break;
                            }
                            if (this.proto.getDefaultValue().equals("-inf")) {
                                this.defaultValue = Double.NEGATIVE_INFINITY;
                                break;
                            }
                            if (this.proto.getDefaultValue().equals("nan")) {
                                this.defaultValue = Double.NaN;
                                break;
                            }
                            this.defaultValue = Double.valueOf(this.proto.getDefaultValue());
                            break;
                        }
                        case 7: {
                            this.defaultValue = Boolean.valueOf(this.proto.getDefaultValue());
                            break;
                        }
                        case 8: {
                            this.defaultValue = this.proto.getDefaultValue();
                            break;
                        }
                        case 11: {
                            try {
                                this.defaultValue = TextFormat.unescapeBytes(this.proto.getDefaultValue());
                                break;
                            }
                            catch (final TextFormat.InvalidEscapeSequenceException e) {
                                throw new DescriptorValidationException((GenericDescriptor)this, "Couldn't parse default value: " + e.getMessage(), (Throwable)e);
                            }
                        }
                        case 13: {
                            this.defaultValue = this.getEnumType().findValueByName(this.proto.getDefaultValue());
                            if (this.defaultValue == null) {
                                throw new DescriptorValidationException((GenericDescriptor)this, "Unknown enum default value: \"" + this.proto.getDefaultValue() + '\"');
                            }
                            break;
                        }
                        case 9:
                        case 10: {
                            throw new DescriptorValidationException((GenericDescriptor)this, "Message type had default value.");
                        }
                    }
                    return;
                }
                catch (final NumberFormatException e2) {
                    throw new DescriptorValidationException((GenericDescriptor)this, "Could not parse default value: \"" + this.proto.getDefaultValue() + '\"', (Throwable)e2);
                }
            }
            if (this.isRepeated()) {
                this.defaultValue = Collections.emptyList();
            }
            else {
                switch (this.type.getJavaType().ordinal()) {
                    case 7: {
                        this.defaultValue = this.getEnumType().getValue(0);
                        break;
                    }
                    case 8: {
                        this.defaultValue = null;
                        break;
                    }
                    default: {
                        this.defaultValue = this.type.getJavaType().defaultDefault;
                        break;
                    }
                }
            }
        }
        
        private void setProto(final DescriptorProtos.FieldDescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
        }
        
        @Override
        public boolean internalMessageIsImmutable(final Object message) {
            return message instanceof MessageLite;
        }
        
        @Override
        public void internalMergeFrom(final Object to, final Object from) {
            ((Message.Builder)to).mergeFrom((Message)from);
        }
        
        static {
            NUMBER_GETTER = FieldDescriptor::getNumber;
            table = WireFormat.FieldType.values();
            if (Type.types.length != DescriptorProtos.FieldDescriptorProto.Type.values().length) {
                throw new RuntimeException("descriptor.proto has a new declared type but Descriptors.java wasn't updated.");
            }
        }
        
        static final class RedactionState
        {
            private static final RedactionState FALSE_FALSE;
            private static final RedactionState FALSE_TRUE;
            private static final RedactionState TRUE_FALSE;
            private static final RedactionState TRUE_TRUE;
            final boolean redact;
            final boolean report;
            
            private RedactionState(final boolean redact, final boolean report) {
                this.redact = redact;
                this.report = report;
            }
            
            private static RedactionState of(final boolean redact) {
                return of(redact, false);
            }
            
            private static RedactionState of(final boolean redact, final boolean report) {
                if (redact) {
                    return report ? RedactionState.TRUE_TRUE : RedactionState.TRUE_FALSE;
                }
                return report ? RedactionState.FALSE_TRUE : RedactionState.FALSE_FALSE;
            }
            
            private static RedactionState combine(final RedactionState lhs, final RedactionState rhs) {
                return of(lhs.redact || rhs.redact, rhs.report);
            }
            
            static {
                FALSE_FALSE = new RedactionState(false, false);
                FALSE_TRUE = new RedactionState(false, true);
                TRUE_FALSE = new RedactionState(true, false);
                TRUE_TRUE = new RedactionState(true, true);
            }
        }
        
        public enum Type
        {
            DOUBLE(JavaType.DOUBLE), 
            FLOAT(JavaType.FLOAT), 
            INT64(JavaType.LONG), 
            UINT64(JavaType.LONG), 
            INT32(JavaType.INT), 
            FIXED64(JavaType.LONG), 
            FIXED32(JavaType.INT), 
            BOOL(JavaType.BOOLEAN), 
            STRING(JavaType.STRING), 
            GROUP(JavaType.MESSAGE), 
            MESSAGE(JavaType.MESSAGE), 
            BYTES(JavaType.BYTE_STRING), 
            UINT32(JavaType.INT), 
            ENUM(JavaType.ENUM), 
            SFIXED32(JavaType.INT), 
            SFIXED64(JavaType.LONG), 
            SINT32(JavaType.INT), 
            SINT64(JavaType.LONG);
            
            private static final Type[] types;
            private final JavaType javaType;
            
            private Type(final JavaType javaType) {
                this.javaType = javaType;
            }
            
            public DescriptorProtos.FieldDescriptorProto.Type toProto() {
                return DescriptorProtos.FieldDescriptorProto.Type.forNumber(this.ordinal() + 1);
            }
            
            public JavaType getJavaType() {
                return this.javaType;
            }
            
            public static Type valueOf(final DescriptorProtos.FieldDescriptorProto.Type type) {
                return Type.types[type.getNumber() - 1];
            }
            
            static {
                types = values();
            }
        }
        
        public enum JavaType
        {
            INT((Object)0), 
            LONG((Object)0L), 
            FLOAT((Object)0.0f), 
            DOUBLE((Object)0.0), 
            BOOLEAN((Object)false), 
            STRING((Object)""), 
            BYTE_STRING((Object)ByteString.EMPTY), 
            ENUM((Object)null), 
            MESSAGE((Object)null);
            
            private final Object defaultDefault;
            
            private JavaType(final Object defaultDefault) {
                this.defaultDefault = defaultDefault;
            }
        }
    }
    
    public static final class EnumDescriptor extends GenericDescriptor implements Internal.EnumLiteMap<EnumValueDescriptor>
    {
        private final int index;
        private DescriptorProtos.EnumDescriptorProto proto;
        private volatile DescriptorProtos.EnumOptions options;
        private final String fullName;
        private final GenericDescriptor parent;
        private final EnumValueDescriptor[] values;
        private final EnumValueDescriptor[] valuesSortedByNumber;
        private final int distinctNumbers;
        private Map<Integer, WeakReference<EnumValueDescriptor>> unknownValues;
        private ReferenceQueue<EnumValueDescriptor> cleanupQueue;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public DescriptorProtos.EnumDescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.parent.getFile();
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.parent;
        }
        
        public boolean isPlaceholder() {
            return false;
        }
        
        public boolean isClosed() {
            return this.getFeatures().getEnumType() == DescriptorProtos.FeatureSet.EnumType.CLOSED;
        }
        
        public Descriptor getContainingType() {
            if (this.parent instanceof Descriptor) {
                return (Descriptor)this.parent;
            }
            return null;
        }
        
        public DescriptorProtos.EnumOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.EnumOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        public List<EnumValueDescriptor> getValues() {
            return Collections.unmodifiableList((List<? extends EnumValueDescriptor>)Arrays.asList((T[])this.values));
        }
        
        public int getValueCount() {
            return this.values.length;
        }
        
        public EnumValueDescriptor getValue(final int index) {
            return this.values[index];
        }
        
        public boolean isReservedNumber(final int number) {
            for (final DescriptorProtos.EnumDescriptorProto.EnumReservedRange range : this.proto.getReservedRangeList()) {
                if (range.getStart() <= number && number <= range.getEnd()) {
                    return true;
                }
            }
            return false;
        }
        
        public boolean isReservedName(final String name) {
            Internal.checkNotNull(name);
            for (final String reservedName : this.proto.getReservedNameList()) {
                if (reservedName.equals(name)) {
                    return true;
                }
            }
            return false;
        }
        
        public EnumValueDescriptor findValueByName(final String name) {
            final GenericDescriptor result = this.getFile().tables.findSymbol(this.fullName + '.' + name);
            if (result instanceof EnumValueDescriptor) {
                return (EnumValueDescriptor)result;
            }
            return null;
        }
        
        @Override
        public EnumValueDescriptor findValueByNumber(final int number) {
            return (EnumValueDescriptor)binarySearch(this.valuesSortedByNumber, this.distinctNumbers, EnumValueDescriptor.NUMBER_GETTER, number);
        }
        
        public EnumValueDescriptor findValueByNumberCreatingIfUnknown(final int number) {
            EnumValueDescriptor result = this.findValueByNumber(number);
            if (result != null) {
                return result;
            }
            synchronized (this) {
                if (this.cleanupQueue == null) {
                    this.cleanupQueue = new ReferenceQueue<EnumValueDescriptor>();
                    this.unknownValues = new HashMap<Integer, WeakReference<EnumValueDescriptor>>();
                }
                else {
                    while (true) {
                        final UnknownEnumValueReference toClean = (UnknownEnumValueReference)this.cleanupQueue.poll();
                        if (toClean == null) {
                            break;
                        }
                        this.unknownValues.remove(toClean.number);
                    }
                }
                final WeakReference<EnumValueDescriptor> reference = this.unknownValues.get(number);
                result = ((reference == null) ? null : reference.get());
                if (result == null) {
                    result = new EnumValueDescriptor(this, Integer.valueOf(number));
                    this.unknownValues.put(number, new UnknownEnumValueReference(number, result));
                }
            }
            return result;
        }
        
        int getUnknownEnumValueDescriptorCount() {
            return this.unknownValues.size();
        }
        
        private EnumDescriptor(final DescriptorProtos.EnumDescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index) throws DescriptorValidationException {
            this.unknownValues = null;
            this.cleanupQueue = null;
            if (parent == null) {
                this.parent = file;
            }
            else {
                this.parent = parent;
            }
            this.index = index;
            this.proto = proto;
            this.fullName = computeFullName(file, parent, proto.getName());
            if (proto.getValueCount() == 0) {
                throw new DescriptorValidationException((GenericDescriptor)this, "Enums must contain at least one value.");
            }
            this.values = new EnumValueDescriptor[proto.getValueCount()];
            for (int i = 0; i < proto.getValueCount(); ++i) {
                this.values[i] = new EnumValueDescriptor(proto.getValue(i), this, i);
            }
            Arrays.sort(this.valuesSortedByNumber = this.values.clone(), EnumValueDescriptor.BY_NUMBER);
            int j = 0;
            for (int k = 1; k < proto.getValueCount(); ++k) {
                final EnumValueDescriptor oldValue = this.valuesSortedByNumber[j];
                final EnumValueDescriptor newValue = this.valuesSortedByNumber[k];
                if (oldValue.getNumber() != newValue.getNumber()) {
                    this.valuesSortedByNumber[++j] = newValue;
                }
            }
            this.distinctNumbers = j + 1;
            Arrays.fill(this.valuesSortedByNumber, this.distinctNumbers, proto.getValueCount(), null);
            file.tables.addSymbol(this);
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
            for (final EnumValueDescriptor value : this.values) {
                value.resolveAllFeatures();
            }
        }
        
        private void setProto(final DescriptorProtos.EnumDescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
            for (int i = 0; i < this.values.length; ++i) {
                this.values[i].setProto(proto.getValue(i));
            }
        }
        
        private static class UnknownEnumValueReference extends WeakReference<EnumValueDescriptor>
        {
            private final int number;
            
            private UnknownEnumValueReference(final int number, final EnumValueDescriptor descriptor) {
                super(descriptor);
                this.number = number;
            }
        }
    }
    
    public static final class EnumValueDescriptor extends GenericDescriptor implements Internal.EnumLite
    {
        static final Comparator<EnumValueDescriptor> BY_NUMBER;
        static final ToIntFunction<EnumValueDescriptor> NUMBER_GETTER;
        private final int index;
        private DescriptorProtos.EnumValueDescriptorProto proto;
        private volatile DescriptorProtos.EnumValueOptions options;
        private final String fullName;
        private final EnumDescriptor type;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public DescriptorProtos.EnumValueDescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public int getNumber() {
            return this.proto.getNumber();
        }
        
        @Override
        public String toString() {
            return this.proto.getName();
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.type.getFile();
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.type;
        }
        
        public EnumDescriptor getType() {
            return this.type;
        }
        
        public DescriptorProtos.EnumValueOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.EnumValueOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        private EnumValueDescriptor(final DescriptorProtos.EnumValueDescriptorProto proto, final EnumDescriptor parent, final int index) throws DescriptorValidationException {
            this.index = index;
            this.proto = proto;
            this.type = parent;
            this.fullName = parent.getFullName() + '.' + proto.getName();
            this.type.getFile().tables.addSymbol(this);
        }
        
        private EnumValueDescriptor(final EnumDescriptor parent, final Integer number) {
            final String name = "UNKNOWN_ENUM_VALUE_" + parent.getName() + "_" + number;
            final DescriptorProtos.EnumValueDescriptorProto proto = DescriptorProtos.EnumValueDescriptorProto.newBuilder().setName(name).setNumber(number).build();
            this.index = -1;
            this.proto = proto;
            this.type = parent;
            this.fullName = parent.getFullName() + '.' + proto.getName();
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
        }
        
        private void setProto(final DescriptorProtos.EnumValueDescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
        }
        
        static {
            BY_NUMBER = new Comparator<EnumValueDescriptor>() {
                @Override
                public int compare(final EnumValueDescriptor o1, final EnumValueDescriptor o2) {
                    return Integer.compare(o1.getNumber(), o2.getNumber());
                }
            };
            NUMBER_GETTER = EnumValueDescriptor::getNumber;
        }
    }
    
    public static final class ServiceDescriptor extends GenericDescriptor
    {
        private final int index;
        private DescriptorProtos.ServiceDescriptorProto proto;
        private volatile DescriptorProtos.ServiceOptions options;
        private final String fullName;
        private final FileDescriptor file;
        private MethodDescriptor[] methods;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public DescriptorProtos.ServiceDescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.file;
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.file;
        }
        
        public DescriptorProtos.ServiceOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.ServiceOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        public List<MethodDescriptor> getMethods() {
            return Collections.unmodifiableList((List<? extends MethodDescriptor>)Arrays.asList((T[])this.methods));
        }
        
        public int getMethodCount() {
            return this.methods.length;
        }
        
        public MethodDescriptor getMethod(final int index) {
            return this.methods[index];
        }
        
        public MethodDescriptor findMethodByName(final String name) {
            final GenericDescriptor result = this.file.tables.findSymbol(this.fullName + '.' + name);
            if (result instanceof MethodDescriptor) {
                return (MethodDescriptor)result;
            }
            return null;
        }
        
        private ServiceDescriptor(final DescriptorProtos.ServiceDescriptorProto proto, final FileDescriptor file, final int index) throws DescriptorValidationException {
            this.index = index;
            this.proto = proto;
            this.fullName = computeFullName(file, null, proto.getName());
            this.file = file;
            this.methods = new MethodDescriptor[proto.getMethodCount()];
            for (int i = 0; i < proto.getMethodCount(); ++i) {
                this.methods[i] = new MethodDescriptor(proto.getMethod(i), this, i);
            }
            file.tables.addSymbol(this);
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
            for (final MethodDescriptor method : this.methods) {
                method.resolveAllFeatures();
            }
        }
        
        private void crossLink() throws DescriptorValidationException {
            for (final MethodDescriptor method : this.methods) {
                method.crossLink();
            }
        }
        
        private void setProto(final DescriptorProtos.ServiceDescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
            for (int i = 0; i < this.methods.length; ++i) {
                this.methods[i].setProto(proto.getMethod(i));
            }
        }
    }
    
    public static final class MethodDescriptor extends GenericDescriptor
    {
        private final int index;
        private DescriptorProtos.MethodDescriptorProto proto;
        private volatile DescriptorProtos.MethodOptions options;
        private final String fullName;
        private final ServiceDescriptor service;
        private Descriptor inputType;
        private Descriptor outputType;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public DescriptorProtos.MethodDescriptorProto toProto() {
            return this.proto;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.service.file;
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.service;
        }
        
        public ServiceDescriptor getService() {
            return this.service;
        }
        
        public Descriptor getInputType() {
            return this.inputType;
        }
        
        public Descriptor getOutputType() {
            return this.outputType;
        }
        
        public boolean isClientStreaming() {
            return this.proto.getClientStreaming();
        }
        
        public boolean isServerStreaming() {
            return this.proto.getServerStreaming();
        }
        
        public DescriptorProtos.MethodOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.MethodOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        private MethodDescriptor(final DescriptorProtos.MethodDescriptorProto proto, final ServiceDescriptor parent, final int index) throws DescriptorValidationException {
            this.index = index;
            this.proto = proto;
            this.service = parent;
            this.fullName = parent.getFullName() + '.' + proto.getName();
            this.service.file.tables.addSymbol(this);
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
        }
        
        private void crossLink() throws DescriptorValidationException {
            final GenericDescriptor input = this.getFile().tables.lookupSymbol(this.proto.getInputType(), this, FileDescriptorTables.SearchFilter.TYPES_ONLY);
            if (!(input instanceof Descriptor)) {
                throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.proto.getInputType() + "\" is not a message type.");
            }
            this.inputType = (Descriptor)input;
            final GenericDescriptor output = this.getFile().tables.lookupSymbol(this.proto.getOutputType(), this, FileDescriptorTables.SearchFilter.TYPES_ONLY);
            if (!(output instanceof Descriptor)) {
                throw new DescriptorValidationException((GenericDescriptor)this, '\"' + this.proto.getOutputType() + "\" is not a message type.");
            }
            this.outputType = (Descriptor)output;
        }
        
        private void setProto(final DescriptorProtos.MethodDescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
        }
    }
    
    public abstract static class GenericDescriptor
    {
        volatile DescriptorProtos.FeatureSet features;
        
        private GenericDescriptor() {
        }
        
        public abstract Message toProto();
        
        public abstract String getName();
        
        public abstract String getFullName();
        
        public abstract FileDescriptor getFile();
        
        abstract GenericDescriptor getParent();
        
        void resolveFeatures(DescriptorProtos.FeatureSet unresolvedFeatures) throws DescriptorValidationException {
            final GenericDescriptor parent = this.getParent();
            DescriptorProtos.FeatureSet inferredLegacyFeatures = null;
            if (parent != null && unresolvedFeatures.equals(DescriptorProtos.FeatureSet.getDefaultInstance()) && (inferredLegacyFeatures = this.inferLegacyProtoFeatures()).equals(DescriptorProtos.FeatureSet.getDefaultInstance())) {
                this.features = parent.features;
                this.validateFeatures();
                return;
            }
            boolean hasPossibleCustomJavaFeature = false;
            for (final FieldDescriptor f : unresolvedFeatures.getExtensionFields().keySet()) {
                if (f.getNumber() == JavaFeaturesProto.java_.getNumber() && f != JavaFeaturesProto.java_.getDescriptor()) {
                    hasPossibleCustomJavaFeature = true;
                    break;
                }
            }
            final boolean hasPossibleUnknownJavaFeature = !unresolvedFeatures.getUnknownFields().isEmpty() && unresolvedFeatures.getUnknownFields().hasField(JavaFeaturesProto.java_.getNumber());
            if (hasPossibleCustomJavaFeature || hasPossibleUnknownJavaFeature) {
                final ExtensionRegistry registry = ExtensionRegistry.newInstance();
                registry.add(JavaFeaturesProto.java_);
                final ByteString bytes = unresolvedFeatures.toByteString();
                try {
                    unresolvedFeatures = DescriptorProtos.FeatureSet.parseFrom(bytes, registry);
                }
                catch (final InvalidProtocolBufferException e) {
                    throw new DescriptorValidationException(this, "Failed to parse features with Java feature extension registry.", (Throwable)e);
                }
            }
            DescriptorProtos.FeatureSet.Builder features;
            if (parent == null) {
                final DescriptorProtos.Edition edition = this.getFile().getEdition();
                features = Descriptors.getEditionDefaults(edition).toBuilder();
            }
            else {
                features = parent.features.toBuilder();
            }
            if (inferredLegacyFeatures == null) {
                inferredLegacyFeatures = this.inferLegacyProtoFeatures();
            }
            features.mergeFrom(inferredLegacyFeatures);
            features.mergeFrom(unresolvedFeatures);
            this.features = internFeatures(features.build());
            this.validateFeatures();
        }
        
        DescriptorProtos.FeatureSet inferLegacyProtoFeatures() {
            return DescriptorProtos.FeatureSet.getDefaultInstance();
        }
        
        void validateFeatures() throws DescriptorValidationException {
        }
        
        DescriptorProtos.FeatureSet getFeatures() {
            if (this.features == null && (this.getFile().getEdition() == DescriptorProtos.Edition.EDITION_PROTO2 || this.getFile().getEdition() == DescriptorProtos.Edition.EDITION_PROTO3)) {
                this.getFile().resolveAllFeaturesImmutable();
            }
            if (this.features == null) {
                throw new NullPointerException(String.format("Features not yet loaded for %s.", this.getFullName()));
            }
            return this.features;
        }
    }
    
    public static class DescriptorValidationException extends Exception
    {
        private static final long serialVersionUID = 5750205775490483148L;
        private final String name;
        private final Message proto;
        private final String description;
        
        public String getProblemSymbolName() {
            return this.name;
        }
        
        public Message getProblemProto() {
            return this.proto;
        }
        
        public String getDescription() {
            return this.description;
        }
        
        private DescriptorValidationException(final GenericDescriptor problemDescriptor, final String description) {
            super(problemDescriptor.getFullName() + ": " + description);
            this.name = problemDescriptor.getFullName();
            this.proto = problemDescriptor.toProto();
            this.description = description;
        }
        
        private DescriptorValidationException(final GenericDescriptor problemDescriptor, final String description, final Throwable cause) {
            this(problemDescriptor, description);
            this.initCause(cause);
        }
        
        private DescriptorValidationException(final FileDescriptor problemDescriptor, final String description) {
            super(problemDescriptor.getName() + ": " + description);
            this.name = problemDescriptor.getName();
            this.proto = problemDescriptor.toProto();
            this.description = description;
        }
    }
    
    private static final class FileDescriptorTables
    {
        private final Set<FileDescriptor> dependencies;
        private final boolean allowUnknownDependencies;
        private final Map<String, GenericDescriptor> descriptorsByName;
        
        FileDescriptorTables(final FileDescriptor[] dependencies, final boolean allowUnknownDependencies) {
            this.descriptorsByName = new HashMap<String, GenericDescriptor>();
            this.dependencies = Collections.newSetFromMap(new IdentityHashMap<FileDescriptor, Boolean>(dependencies.length));
            this.allowUnknownDependencies = allowUnknownDependencies;
            for (final FileDescriptor dependency : dependencies) {
                this.dependencies.add(dependency);
                this.importPublicDependencies(dependency);
            }
            for (final FileDescriptor dependency2 : this.dependencies) {
                try {
                    this.addPackage(dependency2.getPackage(), dependency2);
                }
                catch (final DescriptorValidationException e) {
                    throw new AssertionError((Object)e);
                }
            }
        }
        
        private void importPublicDependencies(final FileDescriptor file) {
            for (final FileDescriptor dependency : file.getPublicDependencies()) {
                if (this.dependencies.add(dependency)) {
                    this.importPublicDependencies(dependency);
                }
            }
        }
        
        GenericDescriptor findSymbol(final String fullName) {
            return this.findSymbol(fullName, SearchFilter.ALL_SYMBOLS);
        }
        
        GenericDescriptor findSymbol(final String fullName, final SearchFilter filter) {
            GenericDescriptor result = this.descriptorsByName.get(fullName);
            if (result != null && (filter == SearchFilter.ALL_SYMBOLS || (filter == SearchFilter.TYPES_ONLY && this.isType(result)) || (filter == SearchFilter.AGGREGATES_ONLY && this.isAggregate(result)))) {
                return result;
            }
            for (final FileDescriptor dependency : this.dependencies) {
                result = dependency.tables.descriptorsByName.get(fullName);
                if (result != null && (filter == SearchFilter.ALL_SYMBOLS || (filter == SearchFilter.TYPES_ONLY && this.isType(result)) || (filter == SearchFilter.AGGREGATES_ONLY && this.isAggregate(result)))) {
                    return result;
                }
            }
            return null;
        }
        
        boolean isType(final GenericDescriptor descriptor) {
            return descriptor instanceof Descriptor || descriptor instanceof EnumDescriptor;
        }
        
        boolean isAggregate(final GenericDescriptor descriptor) {
            return descriptor instanceof Descriptor || descriptor instanceof EnumDescriptor || descriptor instanceof PackageDescriptor || descriptor instanceof ServiceDescriptor;
        }
        
        GenericDescriptor lookupSymbol(final String name, final GenericDescriptor relativeTo, final SearchFilter filter) throws DescriptorValidationException {
            String fullname;
            GenericDescriptor result;
            if (name.startsWith(".")) {
                fullname = name.substring(1);
                result = this.findSymbol(fullname, filter);
            }
            else {
                final int firstPartLength = name.indexOf(46);
                String firstPart;
                if (firstPartLength == -1) {
                    firstPart = name;
                }
                else {
                    firstPart = name.substring(0, firstPartLength);
                }
                final StringBuilder scopeToTry = new StringBuilder(relativeTo.getFullName());
                while (true) {
                    final int dotpos = scopeToTry.lastIndexOf(".");
                    if (dotpos == -1) {
                        fullname = name;
                        result = this.findSymbol(name, filter);
                        break;
                    }
                    scopeToTry.setLength();
                    scopeToTry.append(firstPart);
                    result = this.findSymbol(scopeToTry.toString(), SearchFilter.AGGREGATES_ONLY);
                    if (result != null) {
                        if (firstPartLength != -1) {
                            scopeToTry.setLength();
                            scopeToTry.append(name);
                            result = this.findSymbol(scopeToTry.toString(), filter);
                        }
                        fullname = scopeToTry.toString();
                        break;
                    }
                    scopeToTry.setLength();
                }
            }
            if (result != null) {
                return result;
            }
            if (this.allowUnknownDependencies && filter == SearchFilter.TYPES_ONLY) {
                Descriptors.logger.warning("The descriptor for message type \"" + name + "\" cannot be found and a placeholder is created for it");
                result = new Descriptor(fullname);
                this.dependencies.add(result.getFile());
                return result;
            }
            throw new DescriptorValidationException(relativeTo, '\"' + name + "\" is not defined.");
        }
        
        void addSymbol(final GenericDescriptor descriptor) throws DescriptorValidationException {
            validateSymbolName(descriptor);
            final String fullName = descriptor.getFullName();
            final GenericDescriptor old = this.descriptorsByName.put(fullName, descriptor);
            if (old == null) {
                return;
            }
            this.descriptorsByName.put(fullName, old);
            if (descriptor.getFile() != old.getFile()) {
                throw new DescriptorValidationException(descriptor, '\"' + fullName + "\" is already defined in file \"" + old.getFile().getName() + "\".");
            }
            final int dotpos = fullName.lastIndexOf(46);
            if (dotpos == -1) {
                throw new DescriptorValidationException(descriptor, '\"' + fullName + "\" is already defined.");
            }
            throw new DescriptorValidationException(descriptor, '\"' + fullName.substring(dotpos + 1) + "\" is already defined in \"" + fullName.substring(0, dotpos) + "\".");
        }
        
        void addPackage(final String fullName, final FileDescriptor file) throws DescriptorValidationException {
            final int dotpos = fullName.lastIndexOf(46);
            String name;
            if (dotpos == -1) {
                name = fullName;
            }
            else {
                this.addPackage(fullName.substring(0, dotpos), file);
                name = fullName.substring(dotpos + 1);
            }
            final GenericDescriptor old = this.descriptorsByName.put(fullName, new PackageDescriptor(name, fullName, file));
            if (old != null) {
                this.descriptorsByName.put(fullName, old);
                if (!(old instanceof PackageDescriptor)) {
                    throw new DescriptorValidationException(file, '\"' + name + "\" is already defined (as something other than a package) in file \"" + old.getFile().getName() + "\".");
                }
            }
        }
        
        static void validateSymbolName(final GenericDescriptor descriptor) throws DescriptorValidationException {
            final String name = descriptor.getName();
            if (name.length() == 0) {
                throw new DescriptorValidationException(descriptor, "Missing name.");
            }
            for (int i = 0; i < name.length(); ++i) {
                final char c = name.charAt(i);
                if (('a' > c || c > 'z') && ('A' > c || c > 'Z') && c != '_' && ('0' > c || c > '9' || i <= 0)) {
                    throw new DescriptorValidationException(descriptor, '\"' + name + "\" is not a valid identifier.");
                }
            }
        }
        
        enum SearchFilter
        {
            TYPES_ONLY, 
            AGGREGATES_ONLY, 
            ALL_SYMBOLS;
        }
        
        private static final class PackageDescriptor extends GenericDescriptor
        {
            private final String name;
            private final String fullName;
            private final FileDescriptor file;
            
            @Override
            public Message toProto() {
                return this.file.toProto();
            }
            
            @Override
            public String getName() {
                return this.name;
            }
            
            @Override
            public String getFullName() {
                return this.fullName;
            }
            
            @Override
            GenericDescriptor getParent() {
                return this.file;
            }
            
            @Override
            public FileDescriptor getFile() {
                return this.file;
            }
            
            PackageDescriptor(final String name, final String fullName, final FileDescriptor file) {
                this.file = file;
                this.fullName = fullName;
                this.name = name;
            }
        }
    }
    
    public static final class OneofDescriptor extends GenericDescriptor
    {
        private final int index;
        private DescriptorProtos.OneofDescriptorProto proto;
        private volatile DescriptorProtos.OneofOptions options;
        private final String fullName;
        private final Descriptor containingType;
        private int fieldCount;
        private FieldDescriptor[] fields;
        
        public int getIndex() {
            return this.index;
        }
        
        @Override
        public String getName() {
            return this.proto.getName();
        }
        
        @Override
        public FileDescriptor getFile() {
            return this.containingType.getFile();
        }
        
        @Override
        GenericDescriptor getParent() {
            return this.containingType;
        }
        
        @Override
        public String getFullName() {
            return this.fullName;
        }
        
        public Descriptor getContainingType() {
            return this.containingType;
        }
        
        public int getFieldCount() {
            return this.fieldCount;
        }
        
        public DescriptorProtos.OneofOptions getOptions() {
            if (this.options == null) {
                DescriptorProtos.OneofOptions strippedOptions = this.proto.getOptions();
                if (strippedOptions.hasFeatures()) {
                    strippedOptions = strippedOptions.toBuilder().clearFeatures().build();
                }
                synchronized (this) {
                    if (this.options == null) {
                        this.options = strippedOptions;
                    }
                }
            }
            return this.options;
        }
        
        public List<FieldDescriptor> getFields() {
            return Collections.unmodifiableList((List<? extends FieldDescriptor>)Arrays.asList((T[])this.fields));
        }
        
        public FieldDescriptor getField(final int index) {
            return this.fields[index];
        }
        
        @Override
        public DescriptorProtos.OneofDescriptorProto toProto() {
            return this.proto;
        }
        
        boolean isSynthetic() {
            return this.fields.length == 1 && this.fields[0].isProto3Optional;
        }
        
        private void resolveAllFeatures() throws DescriptorValidationException {
            this.resolveFeatures(this.proto.getOptions().getFeatures());
        }
        
        private void setProto(final DescriptorProtos.OneofDescriptorProto proto) throws DescriptorValidationException {
            this.proto = proto;
            this.options = null;
            this.resolveFeatures(proto.getOptions().getFeatures());
        }
        
        private OneofDescriptor(final DescriptorProtos.OneofDescriptorProto proto, final Descriptor parent, final int index) {
            this.proto = proto;
            this.fullName = computeFullName(null, parent, proto.getName());
            this.index = index;
            this.containingType = parent;
            this.fieldCount = 0;
        }
    }
}
