All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.protobuf.Descriptors Maven / Gradle / Ivy

// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf;

import static com.google.protobuf.Internal.checkNotNull;

import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumOptions;
import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
import com.google.protobuf.DescriptorProtos.EnumValueOptions;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldOptions;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileOptions;
import com.google.protobuf.DescriptorProtos.MessageOptions;
import com.google.protobuf.DescriptorProtos.MethodDescriptorProto;
import com.google.protobuf.DescriptorProtos.MethodOptions;
import com.google.protobuf.DescriptorProtos.OneofDescriptorProto;
import com.google.protobuf.DescriptorProtos.OneofOptions;
import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto;
import com.google.protobuf.DescriptorProtos.ServiceOptions;
import com.google.protobuf.Descriptors.FileDescriptor.Syntax;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Logger;

/**
 * Contains a collection of classes which describe protocol message types.
 *
 * 

Every message type has a {@link Descriptor}, which lists all its fields and other information * about a type. You can get a message type's descriptor by calling {@code * MessageType.getDescriptor()}, or (given a message object of the type) {@code * message.getDescriptorForType()}. Furthermore, each message is associated with a {@link * FileDescriptor} for a relevant {@code .proto} file. You can obtain it by calling {@code * Descriptor.getFile()}. A {@link FileDescriptor} contains descriptors for all the messages defined * in that file, and file descriptors for all the imported {@code .proto} files. * *

Descriptors are built from DescriptorProtos, as defined in {@code * google/protobuf/descriptor.proto}. * * @author [email protected] Kenton Varda */ public final class Descriptors { private static final Logger logger = Logger.getLogger(Descriptors.class.getName()); /** * Describes a {@code .proto} file, including everything defined within. That includes, in * particular, descriptors for all the messages and file descriptors for all other imported {@code * .proto} files (dependencies). */ public static final class FileDescriptor extends GenericDescriptor { /** Convert the descriptor to its protocol message representation. */ @Override public FileDescriptorProto toProto() { return proto; } /** Get the file name. */ @Override public String getName() { return proto.getName(); } /** Returns this object. */ @Override public FileDescriptor getFile() { return this; } /** Returns the same as getName(). */ @Override public String getFullName() { return proto.getName(); } /** * Get the proto package name. This is the package name given by the {@code package} statement * in the {@code .proto} file, which differs from the Java package. */ public String getPackage() { return proto.getPackage(); } /** Get the {@code FileOptions}, defined in {@code descriptor.proto}. */ public FileOptions getOptions() { return proto.getOptions(); } /** Get a list of top-level message types declared in this file. */ public List getMessageTypes() { return Collections.unmodifiableList(Arrays.asList(messageTypes)); } /** Get a list of top-level enum types declared in this file. */ public List getEnumTypes() { return Collections.unmodifiableList(Arrays.asList(enumTypes)); } /** Get a list of top-level services declared in this file. */ public List getServices() { return Collections.unmodifiableList(Arrays.asList(services)); } /** Get a list of top-level extensions declared in this file. */ public List getExtensions() { return Collections.unmodifiableList(Arrays.asList(extensions)); } /** Get a list of this file's dependencies (imports). */ public List getDependencies() { return Collections.unmodifiableList(Arrays.asList(dependencies)); } /** Get a list of this file's public dependencies (public imports). */ public List getPublicDependencies() { return Collections.unmodifiableList(Arrays.asList(publicDependencies)); } /** The syntax of the .proto file. */ public enum Syntax { UNKNOWN("unknown"), PROTO2("proto2"), PROTO3("proto3"); Syntax(String name) { this.name = name; } private final String name; } /** Get the syntax of the .proto file. */ public Syntax getSyntax() { if (Syntax.PROTO3.name.equals(proto.getSyntax())) { return Syntax.PROTO3; } return Syntax.PROTO2; } /** * Find a message type in the file by name. Does not find nested types. * * @param name The unqualified type name to look for. * @return The message type's descriptor, or {@code null} if not found. */ public Descriptor findMessageTypeByName(String name) { // Don't allow looking up nested types. This will make optimization // easier later. if (name.indexOf('.') != -1) { return null; } final String packageName = getPackage(); if (!packageName.isEmpty()) { name = packageName + '.' + name; } final GenericDescriptor result = pool.findSymbol(name); if (result instanceof Descriptor && result.getFile() == this) { return (Descriptor) result; } else { return null; } } /** * Find an enum type in the file by name. Does not find nested types. * * @param name The unqualified type name to look for. * @return The enum type's descriptor, or {@code null} if not found. */ public EnumDescriptor findEnumTypeByName(String name) { // Don't allow looking up nested types. This will make optimization // easier later. if (name.indexOf('.') != -1) { return null; } final String packageName = getPackage(); if (!packageName.isEmpty()) { name = packageName + '.' + name; } final GenericDescriptor result = pool.findSymbol(name); if (result instanceof EnumDescriptor && result.getFile() == this) { return (EnumDescriptor) result; } else { return null; } } /** * Find a service type in the file by name. * * @param name The unqualified type name to look for. * @return The service type's descriptor, or {@code null} if not found. */ public ServiceDescriptor findServiceByName(String name) { // Don't allow looking up nested types. This will make optimization // easier later. if (name.indexOf('.') != -1) { return null; } final String packageName = getPackage(); if (!packageName.isEmpty()) { name = packageName + '.' + name; } final GenericDescriptor result = pool.findSymbol(name); if (result instanceof ServiceDescriptor && result.getFile() == this) { return (ServiceDescriptor) result; } else { return null; } } /** * Find an extension in the file by name. Does not find extensions nested inside message types. * * @param name The unqualified extension name to look for. * @return The extension's descriptor, or {@code null} if not found. */ public FieldDescriptor findExtensionByName(String name) { if (name.indexOf('.') != -1) { return null; } final String packageName = getPackage(); if (!packageName.isEmpty()) { name = packageName + '.' + name; } final GenericDescriptor result = pool.findSymbol(name); if (result instanceof FieldDescriptor && result.getFile() == this) { return (FieldDescriptor) result; } else { return null; } } /** * Construct a {@code FileDescriptor}. * * @param proto The protocol message form of the FileDescriptor. * @param dependencies {@code FileDescriptor}s corresponding to all of the file's dependencies. * @throws DescriptorValidationException {@code proto} is not a valid descriptor. This can occur * for a number of reasons, e.g. because a field has an undefined type or because two * messages were defined with the same name. */ public static FileDescriptor buildFrom( final FileDescriptorProto proto, final FileDescriptor[] dependencies) throws DescriptorValidationException { return buildFrom(proto, dependencies, false); } /** * Construct a {@code FileDescriptor}. * * @param proto The protocol message form of the FileDescriptor. * @param dependencies {@code FileDescriptor}s corresponding to all of the file's dependencies. * @param allowUnknownDependencies If true, non-exist dependenncies will be ignored and * undefined message types will be replaced with a placeholder type. * @throws DescriptorValidationException {@code proto} is not a valid descriptor. This can occur * for a number of reasons, e.g. because a field has an undefined type or because two * messages were defined with the same name. */ public static FileDescriptor buildFrom( final FileDescriptorProto proto, final FileDescriptor[] dependencies, final boolean allowUnknownDependencies) throws DescriptorValidationException { // Building descriptors involves two steps: translating and linking. // In the translation step (implemented by FileDescriptor's // constructor), we build an object tree mirroring the // FileDescriptorProto's tree and put all of the descriptors into the // DescriptorPool's lookup tables. In the linking step, we look up all // type references in the DescriptorPool, so that, for example, a // FieldDescriptor for an embedded message contains a pointer directly // to the Descriptor for that message's type. We also detect undefined // types in the linking step. final DescriptorPool pool = new DescriptorPool(dependencies, allowUnknownDependencies); final FileDescriptor result = new FileDescriptor(proto, dependencies, pool, allowUnknownDependencies); result.crossLink(); return result; } private static byte[] latin1Cat(final String[] strings) { // Hack: We can't embed a raw byte array inside generated Java code // (at least, not efficiently), but we can embed Strings. So, the // protocol compiler embeds the FileDescriptorProto as a giant // string literal which is passed to this function to construct the // file's FileDescriptor. The string literal contains only 8-bit // characters, each one representing a byte of the FileDescriptorProto's // serialized form. So, if we convert it to bytes in ISO-8859-1, we // should get the original bytes that we want. // Literal strings are limited to 64k, so it may be split into multiple strings. if (strings.length == 1) { return strings[0].getBytes(Internal.ISO_8859_1); } StringBuilder descriptorData = new StringBuilder(); for (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) { List descriptors = new ArrayList<>(); for (int i = 0; i < dependencyClassNames.length; i++) { try { Class clazz = descriptorOuterClass.getClassLoader().loadClass(dependencyClassNames[i]); descriptors.add((FileDescriptor) clazz.getField("descriptor").get(null)); } catch (Exception e) { // We allow unknown dependencies by default. If a dependency cannot // be found we only generate a warning. logger.warning("Descriptors for \"" + dependencyFileNames[i] + "\" can not be found."); } } return descriptors.toArray(new FileDescriptor[0]); } /** * This method is for backward compatibility with generated code which passed an * InternalDescriptorAssigner. */ @Deprecated public static void internalBuildGeneratedFileFrom( final String[] descriptorDataParts, final FileDescriptor[] dependencies, final InternalDescriptorAssigner descriptorAssigner) { final byte[] descriptorBytes = latin1Cat(descriptorDataParts); FileDescriptorProto proto; try { proto = FileDescriptorProto.parseFrom(descriptorBytes); } catch (InvalidProtocolBufferException e) { throw new IllegalArgumentException( "Failed to parse protocol buffer descriptor for generated code.", e); } final FileDescriptor result; try { // When building descriptors for generated code, we allow unknown // dependencies by default. result = buildFrom(proto, dependencies, true); } catch (DescriptorValidationException e) { throw new IllegalArgumentException( "Invalid embedded descriptor for \"" + proto.getName() + "\".", e); } final ExtensionRegistry registry = descriptorAssigner.assignDescriptors(result); if (registry != null) { // We must re-parse the proto using the registry. try { proto = FileDescriptorProto.parseFrom(descriptorBytes, registry); } catch (InvalidProtocolBufferException e) { throw new IllegalArgumentException( "Failed to parse protocol buffer descriptor for generated code.", e); } result.setProto(proto); } } /** * This method is to be called by generated code only. It is equivalent to {@code buildFrom} * except that the {@code FileDescriptorProto} is encoded in protocol buffer wire format. */ public static FileDescriptor internalBuildGeneratedFileFrom( final String[] descriptorDataParts, final FileDescriptor[] dependencies) { final byte[] descriptorBytes = latin1Cat(descriptorDataParts); FileDescriptorProto proto; try { proto = FileDescriptorProto.parseFrom(descriptorBytes); } catch (InvalidProtocolBufferException e) { throw new IllegalArgumentException( "Failed to parse protocol buffer descriptor for generated code.", e); } try { // When building descriptors for generated code, we allow unknown // dependencies by default. return buildFrom(proto, dependencies, true); } catch (DescriptorValidationException e) { throw new IllegalArgumentException( "Invalid embedded descriptor for \"" + proto.getName() + "\".", e); } } /** * This method is for backward compatibility with generated code which passed an * InternalDescriptorAssigner. */ @Deprecated public static void internalBuildGeneratedFileFrom( final String[] descriptorDataParts, final Class descriptorOuterClass, final String[] dependencyClassNames, final String[] dependencyFileNames, final InternalDescriptorAssigner descriptorAssigner) { FileDescriptor[] dependencies = findDescriptors( descriptorOuterClass, dependencyClassNames, dependencyFileNames); internalBuildGeneratedFileFrom( descriptorDataParts, dependencies, descriptorAssigner); } /** * This method is to be called by generated code only. It uses Java reflection to load the * dependencies' descriptors. */ public static FileDescriptor internalBuildGeneratedFileFrom( final String[] descriptorDataParts, final Class descriptorOuterClass, final String[] dependencyClassNames, final String[] dependencyFileNames) { FileDescriptor[] dependencies = findDescriptors( descriptorOuterClass, dependencyClassNames, dependencyFileNames); return internalBuildGeneratedFileFrom(descriptorDataParts, dependencies); } /** * This method is to be called by generated code only. It is used to update the * FileDescriptorProto associated with the descriptor by parsing it again with the given * ExtensionRegistry. This is needed to recognize custom options. */ public static void internalUpdateFileDescriptor( final FileDescriptor descriptor, final ExtensionRegistry registry) { ByteString bytes = descriptor.proto.toByteString(); FileDescriptorProto proto; try { proto = FileDescriptorProto.parseFrom(bytes, registry); } catch (InvalidProtocolBufferException e) { throw new IllegalArgumentException( "Failed to parse protocol buffer descriptor for generated code.", e); } descriptor.setProto(proto); } /** * This class should be used by generated code only. When calling {@link * FileDescriptor#internalBuildGeneratedFileFrom}, the caller provides a callback implementing * this interface. The callback is called after the FileDescriptor has been constructed, in * order to assign all the global variables defined in the generated code which point at parts * of the FileDescriptor. The callback returns an ExtensionRegistry which contains any * extensions which might be used in the descriptor -- that is, extensions of the various * "Options" messages defined in descriptor.proto. The callback may also return null to indicate * that no extensions are used in the descriptor. * * This interface is deprecated. Use the return value of internalBuildGeneratedFrom() instead. */ @Deprecated public interface InternalDescriptorAssigner { ExtensionRegistry assignDescriptors(FileDescriptor root); } private FileDescriptorProto proto; 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 DescriptorPool pool; private FileDescriptor( final FileDescriptorProto proto, final FileDescriptor[] dependencies, final DescriptorPool pool, boolean allowUnknownDependencies) throws DescriptorValidationException { this.pool = pool; this.proto = proto; this.dependencies = dependencies.clone(); HashMap nameToFileMap = new HashMap<>(); for (FileDescriptor file : dependencies) { nameToFileMap.put(file.getName(), file); } List publicDependencies = new ArrayList<>(); for (int i = 0; i < proto.getPublicDependencyCount(); i++) { int index = proto.getPublicDependency(i); if (index < 0 || index >= proto.getDependencyCount()) { throw new DescriptorValidationException(this, "Invalid public dependency index."); } String name = proto.getDependency(index); FileDescriptor file = nameToFileMap.get(name); if (file == null) { if (!allowUnknownDependencies) { throw new DescriptorValidationException(this, "Invalid public dependency: " + name); } // Ignore unknown dependencies. } else { publicDependencies.add(file); } } this.publicDependencies = new FileDescriptor[publicDependencies.size()]; publicDependencies.toArray(this.publicDependencies); pool.addPackage(getPackage(), this); messageTypes = new Descriptor[proto.getMessageTypeCount()]; for (int i = 0; i < proto.getMessageTypeCount(); i++) { messageTypes[i] = new Descriptor(proto.getMessageType(i), this, null, i); } enumTypes = new EnumDescriptor[proto.getEnumTypeCount()]; for (int i = 0; i < proto.getEnumTypeCount(); i++) { enumTypes[i] = new EnumDescriptor(proto.getEnumType(i), this, null, i); } services = new ServiceDescriptor[proto.getServiceCount()]; for (int i = 0; i < proto.getServiceCount(); i++) { services[i] = new ServiceDescriptor(proto.getService(i), this, i); } extensions = new FieldDescriptor[proto.getExtensionCount()]; for (int i = 0; i < proto.getExtensionCount(); i++) { extensions[i] = new FieldDescriptor(proto.getExtension(i), this, null, i, true); } } /** Create a placeholder FileDescriptor for a message Descriptor. */ FileDescriptor(String packageName, Descriptor message) throws DescriptorValidationException { this.pool = new DescriptorPool(new FileDescriptor[0], true); this.proto = FileDescriptorProto.newBuilder() .setName(message.getFullName() + ".placeholder.proto") .setPackage(packageName) .addMessageType(message.toProto()) .build(); this.dependencies = new FileDescriptor[0]; this.publicDependencies = new FileDescriptor[0]; messageTypes = new Descriptor[] {message}; enumTypes = new EnumDescriptor[0]; services = new ServiceDescriptor[0]; extensions = new FieldDescriptor[0]; pool.addPackage(packageName, this); pool.addSymbol(message); } /** Look up and cross-link all field types, etc. */ private void crossLink() throws DescriptorValidationException { for (final Descriptor messageType : messageTypes) { messageType.crossLink(); } for (final ServiceDescriptor service : services) { service.crossLink(); } for (final FieldDescriptor extension : extensions) { extension.crossLink(); } } /** * Replace our {@link FileDescriptorProto} with the given one, which is identical except that it * might contain extensions that weren't present in the original. This method is needed for * bootstrapping when a file defines custom options. The options may be defined in the file * itself, so we can't actually parse them until we've constructed the descriptors, but to * construct the descriptors we have to have parsed the descriptor protos. So, we have to parse * the descriptor protos a second time after constructing the descriptors. */ private void setProto(final FileDescriptorProto proto) { this.proto = proto; for (int i = 0; i < messageTypes.length; i++) { messageTypes[i].setProto(proto.getMessageType(i)); } for (int i = 0; i < enumTypes.length; i++) { enumTypes[i].setProto(proto.getEnumType(i)); } for (int i = 0; i < services.length; i++) { services[i].setProto(proto.getService(i)); } for (int i = 0; i < extensions.length; i++) { extensions[i].setProto(proto.getExtension(i)); } } boolean supportsUnknownEnumValue() { return getSyntax() == Syntax.PROTO3; } } // ================================================================= /** Describes a message type. */ public static final class Descriptor extends GenericDescriptor { /** * Get the index of this descriptor within its parent. In other words, given a {@link * FileDescriptor} {@code file}, the following is true: * *

     *   for all i in [0, file.getMessageTypeCount()):
     *     file.getMessageType(i).getIndex() == i
     * 
* * Similarly, for a {@link Descriptor} {@code messageType}: * *
     *   for all i in [0, messageType.getNestedTypeCount()):
     *     messageType.getNestedType(i).getIndex() == i
     * 
*/ public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ @Override public DescriptorProto toProto() { return proto; } /** Get the type's unqualified name. */ @Override public String getName() { return proto.getName(); } /** * Get the type's fully-qualified name, within the proto language's namespace. This differs from * the Java name. For example, given this {@code .proto}: * *
     *   package foo.bar;
     *   option java_package = "com.example.protos"
     *   message Baz {}
     * 
* * {@code Baz}'s full name is "foo.bar.Baz". */ @Override public String getFullName() { return fullName; } /** Get the {@link FileDescriptor} containing this descriptor. */ @Override public FileDescriptor getFile() { return file; } /** If this is a nested type, get the outer descriptor, otherwise null. */ public Descriptor getContainingType() { return containingType; } /** Get the {@code MessageOptions}, defined in {@code descriptor.proto}. */ public MessageOptions getOptions() { return proto.getOptions(); } /** Get a list of this message type's fields. */ public List getFields() { return Collections.unmodifiableList(Arrays.asList(fields)); } /** Get a list of this message type's oneofs. */ public List getOneofs() { return Collections.unmodifiableList(Arrays.asList(oneofs)); } /** Get a list of this message type's real oneofs. */ public List getRealOneofs() { return Collections.unmodifiableList(Arrays.asList(oneofs).subList(0, realOneofCount)); } /** Get a list of this message type's extensions. */ public List getExtensions() { return Collections.unmodifiableList(Arrays.asList(extensions)); } /** Get a list of message types nested within this one. */ public List getNestedTypes() { return Collections.unmodifiableList(Arrays.asList(nestedTypes)); } /** Get a list of enum types nested within this one. */ public List getEnumTypes() { return Collections.unmodifiableList(Arrays.asList(enumTypes)); } /** Determines if the given field number is an extension. */ public boolean isExtensionNumber(final int number) { for (final DescriptorProto.ExtensionRange range : proto.getExtensionRangeList()) { if (range.getStart() <= number && number < range.getEnd()) { return true; } } return false; } /** Determines if the given field number is reserved. */ public boolean isReservedNumber(final int number) { for (final DescriptorProto.ReservedRange range : proto.getReservedRangeList()) { if (range.getStart() <= number && number < range.getEnd()) { return true; } } return false; } /** Determines if the given field name is reserved. */ public boolean isReservedName(final String name) { checkNotNull(name); for (final String reservedName : proto.getReservedNameList()) { if (reservedName.equals(name)) { return true; } } return false; } /** * Indicates whether the message can be extended. That is, whether it has any "extensions x to * y" ranges declared on it. */ public boolean isExtendable() { return !proto.getExtensionRangeList().isEmpty(); } /** * Finds a field by name. * * @param name The unqualified name of the field (e.g. "foo"). For protocol buffer messages that * follow Google's * guidance on naming this will be a snake case string, such as
song_name
. * @return The field's descriptor, or {@code null} if not found. */ public FieldDescriptor findFieldByName(final String name) { final GenericDescriptor result = file.pool.findSymbol(fullName + '.' + name); if (result instanceof FieldDescriptor) { return (FieldDescriptor) result; } else { return null; } } /** * Finds a field by field number. * * @param number The field number within this message type. * @return The field's descriptor, or {@code null} if not found. */ public FieldDescriptor findFieldByNumber(final int number) { return file.pool.fieldsByNumber.get(new DescriptorPool.DescriptorIntPair(this, number)); } /** * Finds a nested message type by name. * * @param name The unqualified name of the nested type (e.g. "Foo"). * @return The types's descriptor, or {@code null} if not found. */ public Descriptor findNestedTypeByName(final String name) { final GenericDescriptor result = file.pool.findSymbol(fullName + '.' + name); if (result instanceof Descriptor) { return (Descriptor) result; } else { return null; } } /** * Finds a nested enum type by name. * * @param name The unqualified name of the nested type (e.g. "Foo"). * @return The types's descriptor, or {@code null} if not found. */ public EnumDescriptor findEnumTypeByName(final String name) { final GenericDescriptor result = file.pool.findSymbol(fullName + '.' + name); if (result instanceof EnumDescriptor) { return (EnumDescriptor) result; } else { return null; } } private final int index; private DescriptorProto proto; private final String fullName; private final FileDescriptor file; private final Descriptor containingType; private final Descriptor[] nestedTypes; private final EnumDescriptor[] enumTypes; private final FieldDescriptor[] fields; private final FieldDescriptor[] extensions; private final OneofDescriptor[] oneofs; private final int realOneofCount; // Used to create a placeholder when the type cannot be found. Descriptor(final String fullname) throws DescriptorValidationException { String name = fullname; String packageName = ""; int pos = fullname.lastIndexOf('.'); if (pos != -1) { name = fullname.substring(pos + 1); packageName = fullname.substring(0, pos); } this.index = 0; this.proto = DescriptorProto.newBuilder() .setName(name) .addExtensionRange( DescriptorProto.ExtensionRange.newBuilder().setStart(1).setEnd(536870912).build()) .build(); this.fullName = fullname; this.containingType = null; this.nestedTypes = new Descriptor[0]; this.enumTypes = new EnumDescriptor[0]; this.fields = new FieldDescriptor[0]; this.extensions = new FieldDescriptor[0]; this.oneofs = new OneofDescriptor[0]; this.realOneofCount = 0; // Create a placeholder FileDescriptor to hold this message. this.file = new FileDescriptor(packageName, this); } private Descriptor( final DescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index) throws DescriptorValidationException { this.index = index; this.proto = proto; fullName = computeFullName(file, parent, proto.getName()); this.file = file; containingType = parent; oneofs = new OneofDescriptor[proto.getOneofDeclCount()]; for (int i = 0; i < proto.getOneofDeclCount(); i++) { oneofs[i] = new OneofDescriptor(proto.getOneofDecl(i), file, this, i); } nestedTypes = new Descriptor[proto.getNestedTypeCount()]; for (int i = 0; i < proto.getNestedTypeCount(); i++) { nestedTypes[i] = new Descriptor(proto.getNestedType(i), file, this, i); } enumTypes = new EnumDescriptor[proto.getEnumTypeCount()]; for (int i = 0; i < proto.getEnumTypeCount(); i++) { enumTypes[i] = new EnumDescriptor(proto.getEnumType(i), file, this, i); } fields = new FieldDescriptor[proto.getFieldCount()]; for (int i = 0; i < proto.getFieldCount(); i++) { fields[i] = new FieldDescriptor(proto.getField(i), file, this, i, false); } extensions = new FieldDescriptor[proto.getExtensionCount()]; for (int i = 0; i < proto.getExtensionCount(); i++) { extensions[i] = new FieldDescriptor(proto.getExtension(i), file, this, i, true); } for (int i = 0; i < proto.getOneofDeclCount(); i++) { oneofs[i].fields = new FieldDescriptor[oneofs[i].getFieldCount()]; oneofs[i].fieldCount = 0; } for (int i = 0; i < proto.getFieldCount(); i++) { OneofDescriptor oneofDescriptor = fields[i].getContainingOneof(); if (oneofDescriptor != null) { oneofDescriptor.fields[oneofDescriptor.fieldCount++] = fields[i]; } } int syntheticOneofCount = 0; for (OneofDescriptor oneof : this.oneofs) { if (oneof.isSynthetic()) { syntheticOneofCount++; } else { if (syntheticOneofCount > 0) { throw new DescriptorValidationException(this, "Synthetic oneofs must come last."); } } } this.realOneofCount = this.oneofs.length - syntheticOneofCount; file.pool.addSymbol(this); } /** Look up and cross-link all field types, etc. */ private void crossLink() throws DescriptorValidationException { for (final Descriptor nestedType : nestedTypes) { nestedType.crossLink(); } for (final FieldDescriptor field : fields) { field.crossLink(); } for (final FieldDescriptor extension : extensions) { extension.crossLink(); } } /** See {@link FileDescriptor#setProto}. */ private void setProto(final DescriptorProto proto) { this.proto = proto; for (int i = 0; i < nestedTypes.length; i++) { nestedTypes[i].setProto(proto.getNestedType(i)); } for (int i = 0; i < oneofs.length; i++) { oneofs[i].setProto(proto.getOneofDecl(i)); } for (int i = 0; i < enumTypes.length; i++) { enumTypes[i].setProto(proto.getEnumType(i)); } for (int i = 0; i < fields.length; i++) { fields[i].setProto(proto.getField(i)); } for (int i = 0; i < extensions.length; i++) { extensions[i].setProto(proto.getExtension(i)); } } } // ================================================================= /** Describes a field of a message type. */ public static final class FieldDescriptor extends GenericDescriptor implements Comparable, FieldSet.FieldDescriptorLite { /** * Get the index of this descriptor within its parent. * * @see Descriptors.Descriptor#getIndex() */ public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ @Override public FieldDescriptorProto toProto() { return proto; } /** Get the field's unqualified name. */ @Override public String getName() { return proto.getName(); } /** Get the field's number. */ @Override public int getNumber() { return proto.getNumber(); } /** * Get the field's fully-qualified name. * * @see Descriptors.Descriptor#getFullName() */ @Override public String getFullName() { return fullName; } /** Get the JSON name of this field. */ public String getJsonName() { return jsonName; } /** * Get the field's java type. This is just for convenience. Every {@code * FieldDescriptorProto.Type} maps to exactly one Java type. */ public JavaType getJavaType() { return type.getJavaType(); } /** For internal use only. */ @Override public WireFormat.JavaType getLiteJavaType() { return getLiteType().getJavaType(); } /** Get the {@code FileDescriptor} containing this descriptor. */ @Override public FileDescriptor getFile() { return file; } /** Get the field's declared type. */ public Type getType() { return type; } /** For internal use only. */ @Override public WireFormat.FieldType getLiteType() { return table[type.ordinal()]; } /** For internal use only. */ public boolean needsUtf8Check() { if (type != Type.STRING) { return false; } if (getContainingType().getOptions().getMapEntry()) { // Always enforce strict UTF-8 checking for map fields. return true; } if (getFile().getSyntax() == Syntax.PROTO3) { return true; } return getFile().getOptions().getJavaStringCheckUtf8(); } public boolean isMapField() { return getType() == Type.MESSAGE && isRepeated() && getMessageType().getOptions().getMapEntry(); } // I'm pretty sure values() constructs a new array every time, since there // is nothing stopping the caller from mutating the array. Therefore we // make a static copy here. private static final WireFormat.FieldType[] table = WireFormat.FieldType.values(); /** Is this field declared required? */ public boolean isRequired() { return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REQUIRED; } /** Is this field declared optional? */ public boolean isOptional() { return proto.getLabel() == FieldDescriptorProto.Label.LABEL_OPTIONAL; } /** Is this field declared repeated? */ @Override public boolean isRepeated() { return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED; } /** * Does this field have the {@code [packed = true]} option or is this field packable in proto3 * and not explicitly set to unpacked? */ @Override public boolean isPacked() { if (!isPackable()) { return false; } if (getFile().getSyntax() == FileDescriptor.Syntax.PROTO2) { return getOptions().getPacked(); } else { return !getOptions().hasPacked() || getOptions().getPacked(); } } /** Can this field be packed? i.e. is it a repeated primitive field? */ public boolean isPackable() { return isRepeated() && getLiteType().isPackable(); } /** Returns true if the field had an explicitly-defined default value. */ public boolean hasDefaultValue() { return proto.hasDefaultValue(); } /** * Returns the field's default value. Valid for all types except for messages and groups. For * all other types, the object returned is of the same class that would returned by * Message.getField(this). */ public Object getDefaultValue() { if (getJavaType() == JavaType.MESSAGE) { throw new UnsupportedOperationException( "FieldDescriptor.getDefaultValue() called on an embedded message field."); } return defaultValue; } /** Get the {@code FieldOptions}, defined in {@code descriptor.proto}. */ public FieldOptions getOptions() { return proto.getOptions(); } /** Is this field an extension? */ public boolean isExtension() { return proto.hasExtendee(); } /** * Get the field's containing type. For extensions, this is the type being extended, not the * location where the extension was defined. See {@link #getExtensionScope()}. */ public Descriptor getContainingType() { return containingType; } /** Get the field's containing oneof. */ public OneofDescriptor getContainingOneof() { return containingOneof; } /** Get the field's containing oneof, only if non-synthetic. */ public OneofDescriptor getRealContainingOneof() { return containingOneof != null && !containingOneof.isSynthetic() ? containingOneof : null; } /** * Returns true if this field was syntactically written with "optional" in the .proto file. * Excludes singular proto3 fields that do not have a label. */ public boolean hasOptionalKeyword() { return isProto3Optional || (file.getSyntax() == Syntax.PROTO2 && isOptional() && getContainingOneof() == null); } /** * Returns true if this field tracks presence, ie. does the field distinguish between "unset" * and "present with default value." * *

This includes required, optional, and oneof fields. It excludes maps, repeated fields, and * singular proto3 fields without "optional". * *

For fields where hasPresence() == true, the return value of msg.hasField() is semantically * meaningful. */ boolean hasPresence() { if (isRepeated()) { return false; } return getType() == Type.MESSAGE || getType() == Type.GROUP || getContainingOneof() != null || file.getSyntax() == Syntax.PROTO2; } /** * For extensions defined nested within message types, gets the outer type. Not valid for * non-extension fields. For example, consider this {@code .proto} file: * *

     *   message Foo {
     *     extensions 1000 to max;
     *   }
     *   extend Foo {
     *     optional int32 baz = 1234;
     *   }
     *   message Bar {
     *     extend Foo {
     *       optional int32 qux = 4321;
     *     }
     *   }
     * 
* * Both {@code baz}'s and {@code qux}'s containing type is {@code Foo}. However, {@code baz}'s * extension scope is {@code null} while {@code qux}'s extension scope is {@code Bar}. */ public Descriptor getExtensionScope() { if (!isExtension()) { throw new UnsupportedOperationException( String.format("This field is not an extension. (%s)", fullName)); } return extensionScope; } /** For embedded message and group fields, gets the field's type. */ public Descriptor getMessageType() { if (getJavaType() != JavaType.MESSAGE) { throw new UnsupportedOperationException( String.format("This field is not of message type. (%s)", fullName)); } return messageType; } /** For enum fields, gets the field's type. */ @Override public EnumDescriptor getEnumType() { if (getJavaType() != JavaType.ENUM) { throw new UnsupportedOperationException( String.format("This field is not of enum type. (%s)", fullName)); } return enumType; } /** * Compare with another {@code FieldDescriptor}. This orders fields in "canonical" order, which * simply means ascending order by field number. {@code other} must be a field of the same type * -- i.e. {@code getContainingType()} must return the same {@code Descriptor} for both fields. * * @return negative, zero, or positive if {@code this} is less than, equal to, or greater than * {@code other}, respectively. */ @Override public int compareTo(final FieldDescriptor other) { if (other.containingType != containingType) { throw new IllegalArgumentException( "FieldDescriptors can only be compared to other FieldDescriptors " + "for fields of the same message type."); } return getNumber() - other.getNumber(); } @Override public String toString() { return getFullName(); } private final int index; private FieldDescriptorProto proto; private final String fullName; private final String jsonName; private final FileDescriptor file; private final Descriptor extensionScope; private final boolean isProto3Optional; // Possibly initialized during cross-linking. private Type type; private Descriptor containingType; private Descriptor messageType; private OneofDescriptor containingOneof; private EnumDescriptor enumType; private Object defaultValue; 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); Type(final JavaType javaType) { this.javaType = javaType; } private JavaType javaType; public FieldDescriptorProto.Type toProto() { return FieldDescriptorProto.Type.forNumber(ordinal() + 1); } public JavaType getJavaType() { return javaType; } public static Type valueOf(final FieldDescriptorProto.Type type) { return values()[type.getNumber() - 1]; } } static { // Refuse to init if someone added a new declared type. if (Type.values().length != FieldDescriptorProto.Type.values().length) { throw new RuntimeException( "descriptor.proto has a new declared type but Descriptors.java wasn't updated."); } } public enum JavaType { INT(0), LONG(0L), FLOAT(0F), DOUBLE(0D), BOOLEAN(false), STRING(""), BYTE_STRING(ByteString.EMPTY), ENUM(null), MESSAGE(null); JavaType(final Object defaultDefault) { this.defaultDefault = defaultDefault; } /** * The default default value for fields of this type, if it's a primitive type. This is meant * for use inside this file only, hence is private. */ private final Object defaultDefault; } // This method should match exactly with the ToJsonName() function in C++ // descriptor.cc. private static String fieldNameToJsonName(String name) { final int length = name.length(); 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) { // This closely matches the logic for ASCII characters in: // http://google3/google/protobuf/descriptor.cc?l=249-251&rcl=228891689 if ('a' <= ch && ch <= 'z') { ch = (char) (ch - 'a' + 'A'); } result.append(ch); isNextUpperCase = false; } else { result.append(ch); } } return result.toString(); } private FieldDescriptor( final FieldDescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index, final boolean isExtension) throws DescriptorValidationException { this.index = index; this.proto = proto; fullName = computeFullName(file, parent, proto.getName()); this.file = file; if (proto.hasJsonName()) { jsonName = proto.getJsonName(); } else { jsonName = fieldNameToJsonName(proto.getName()); } if (proto.hasType()) { type = Type.valueOf(proto.getType()); } isProto3Optional = proto.getProto3Optional(); if (getNumber() <= 0) { throw new DescriptorValidationException(this, "Field numbers must be positive integers."); } if (isExtension) { if (!proto.hasExtendee()) { throw new DescriptorValidationException( this, "FieldDescriptorProto.extendee not set for extension field."); } containingType = null; // Will be filled in when cross-linking if (parent != null) { extensionScope = parent; } else { extensionScope = null; } if (proto.hasOneofIndex()) { throw new DescriptorValidationException( this, "FieldDescriptorProto.oneof_index set for extension field."); } containingOneof = null; } else { if (proto.hasExtendee()) { throw new DescriptorValidationException( this, "FieldDescriptorProto.extendee set for non-extension field."); } containingType = parent; if (proto.hasOneofIndex()) { if (proto.getOneofIndex() < 0 || proto.getOneofIndex() >= parent.toProto().getOneofDeclCount()) { throw new DescriptorValidationException( this, "FieldDescriptorProto.oneof_index is out of range for type " + parent.getName()); } containingOneof = parent.getOneofs().get(proto.getOneofIndex()); containingOneof.fieldCount++; } else { containingOneof = null; } extensionScope = null; } file.pool.addSymbol(this); } /** Look up and cross-link all field types, etc. */ private void crossLink() throws DescriptorValidationException { if (proto.hasExtendee()) { final GenericDescriptor extendee = file.pool.lookupSymbol( proto.getExtendee(), this, DescriptorPool.SearchFilter.TYPES_ONLY); if (!(extendee instanceof Descriptor)) { throw new DescriptorValidationException( this, '\"' + proto.getExtendee() + "\" is not a message type."); } containingType = (Descriptor) extendee; if (!getContainingType().isExtensionNumber(getNumber())) { throw new DescriptorValidationException( this, '\"' + getContainingType().getFullName() + "\" does not declare " + getNumber() + " as an extension number."); } } if (proto.hasTypeName()) { final GenericDescriptor typeDescriptor = file.pool.lookupSymbol( proto.getTypeName(), this, DescriptorPool.SearchFilter.TYPES_ONLY); if (!proto.hasType()) { // Choose field type based on symbol. if (typeDescriptor instanceof Descriptor) { type = Type.MESSAGE; } else if (typeDescriptor instanceof EnumDescriptor) { type = Type.ENUM; } else { throw new DescriptorValidationException( this, '\"' + proto.getTypeName() + "\" is not a type."); } } if (getJavaType() == JavaType.MESSAGE) { if (!(typeDescriptor instanceof Descriptor)) { throw new DescriptorValidationException( this, '\"' + proto.getTypeName() + "\" is not a message type."); } messageType = (Descriptor) typeDescriptor; if (proto.hasDefaultValue()) { throw new DescriptorValidationException(this, "Messages can't have default values."); } } else if (getJavaType() == JavaType.ENUM) { if (!(typeDescriptor instanceof EnumDescriptor)) { throw new DescriptorValidationException( this, '\"' + proto.getTypeName() + "\" is not an enum type."); } enumType = (EnumDescriptor) typeDescriptor; } else { throw new DescriptorValidationException(this, "Field with primitive type has type_name."); } } else { if (getJavaType() == JavaType.MESSAGE || getJavaType() == JavaType.ENUM) { throw new DescriptorValidationException( this, "Field with message or enum type missing type_name."); } } // Only repeated primitive fields may be packed. if (proto.getOptions().getPacked() && !isPackable()) { throw new DescriptorValidationException( this, "[packed = true] can only be specified for repeated primitive fields."); } // We don't attempt to parse the default value until here because for // enums we need the enum type's descriptor. if (proto.hasDefaultValue()) { if (isRepeated()) { throw new DescriptorValidationException( this, "Repeated fields cannot have default values."); } try { switch (getType()) { case INT32: case SINT32: case SFIXED32: defaultValue = TextFormat.parseInt32(proto.getDefaultValue()); break; case UINT32: case FIXED32: defaultValue = TextFormat.parseUInt32(proto.getDefaultValue()); break; case INT64: case SINT64: case SFIXED64: defaultValue = TextFormat.parseInt64(proto.getDefaultValue()); break; case UINT64: case FIXED64: defaultValue = TextFormat.parseUInt64(proto.getDefaultValue()); break; case FLOAT: if (proto.getDefaultValue().equals("inf")) { defaultValue = Float.POSITIVE_INFINITY; } else if (proto.getDefaultValue().equals("-inf")) { defaultValue = Float.NEGATIVE_INFINITY; } else if (proto.getDefaultValue().equals("nan")) { defaultValue = Float.NaN; } else { defaultValue = Float.valueOf(proto.getDefaultValue()); } break; case DOUBLE: if (proto.getDefaultValue().equals("inf")) { defaultValue = Double.POSITIVE_INFINITY; } else if (proto.getDefaultValue().equals("-inf")) { defaultValue = Double.NEGATIVE_INFINITY; } else if (proto.getDefaultValue().equals("nan")) { defaultValue = Double.NaN; } else { defaultValue = Double.valueOf(proto.getDefaultValue()); } break; case BOOL: defaultValue = Boolean.valueOf(proto.getDefaultValue()); break; case STRING: defaultValue = proto.getDefaultValue(); break; case BYTES: try { defaultValue = TextFormat.unescapeBytes(proto.getDefaultValue()); } catch (TextFormat.InvalidEscapeSequenceException e) { throw new DescriptorValidationException( this, "Couldn't parse default value: " + e.getMessage(), e); } break; case ENUM: defaultValue = enumType.findValueByName(proto.getDefaultValue()); if (defaultValue == null) { throw new DescriptorValidationException( this, "Unknown enum default value: \"" + proto.getDefaultValue() + '\"'); } break; case MESSAGE: case GROUP: throw new DescriptorValidationException(this, "Message type had default value."); } } catch (NumberFormatException e) { throw new DescriptorValidationException( this, "Could not parse default value: \"" + proto.getDefaultValue() + '\"', e); } } else { // Determine the default default for this field. if (isRepeated()) { defaultValue = Collections.emptyList(); } else { switch (getJavaType()) { case ENUM: // We guarantee elsewhere that an enum type always has at least // one possible value. defaultValue = enumType.getValues().get(0); break; case MESSAGE: defaultValue = null; break; default: defaultValue = getJavaType().defaultDefault; break; } } } if (!isExtension()) { file.pool.addFieldByNumber(this); } if (containingType != null && containingType.getOptions().getMessageSetWireFormat()) { if (isExtension()) { if (!isOptional() || getType() != Type.MESSAGE) { throw new DescriptorValidationException( this, "Extensions of MessageSets must be optional messages."); } } else { throw new DescriptorValidationException( this, "MessageSets cannot have fields, only extensions."); } } } /** See {@link FileDescriptor#setProto}. */ private void setProto(final FieldDescriptorProto proto) { this.proto = proto; } /** For internal use only. This is to satisfy the FieldDescriptorLite interface. */ @Override public MessageLite.Builder internalMergeFrom(MessageLite.Builder to, MessageLite from) { // FieldDescriptors are only used with non-lite messages so we can just // down-cast and call mergeFrom directly. return ((Message.Builder) to).mergeFrom((Message) from); } } // ================================================================= /** Describes an enum type. */ public static final class EnumDescriptor extends GenericDescriptor implements Internal.EnumLiteMap { /** * Get the index of this descriptor within its parent. * * @see Descriptors.Descriptor#getIndex() */ public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ @Override public EnumDescriptorProto toProto() { return proto; } /** Get the type's unqualified name. */ @Override public String getName() { return proto.getName(); } /** * Get the type's fully-qualified name. * * @see Descriptors.Descriptor#getFullName() */ @Override public String getFullName() { return fullName; } /** Get the {@link FileDescriptor} containing this descriptor. */ @Override public FileDescriptor getFile() { return file; } /** If this is a nested type, get the outer descriptor, otherwise null. */ public Descriptor getContainingType() { return containingType; } /** Get the {@code EnumOptions}, defined in {@code descriptor.proto}. */ public EnumOptions getOptions() { return proto.getOptions(); } /** Get a list of defined values for this enum. */ public List getValues() { return Collections.unmodifiableList(Arrays.asList(values)); } /** * Find an enum value by name. * * @param name The unqualified name of the value (e.g. "FOO"). * @return the value's descriptor, or {@code null} if not found. */ public EnumValueDescriptor findValueByName(final String name) { final GenericDescriptor result = file.pool.findSymbol(fullName + '.' + name); if (result instanceof EnumValueDescriptor) { return (EnumValueDescriptor) result; } else { return null; } } /** * Find an enum value by number. If multiple enum values have the same number, this returns the * first defined value with that number. * * @param number The value's number. * @return the value's descriptor, or {@code null} if not found. */ @Override public EnumValueDescriptor findValueByNumber(final int number) { return file.pool.enumValuesByNumber.get(new DescriptorPool.DescriptorIntPair(this, number)); } /** * Get the enum value for a number. If no enum value has this number, construct an * EnumValueDescriptor for it. */ public EnumValueDescriptor findValueByNumberCreatingIfUnknown(final int number) { EnumValueDescriptor result = findValueByNumber(number); if (result != null) { return result; } // The number represents an unknown enum value. synchronized (this) { // Descriptors are compared by object identity so for the same number // we need to return the same EnumValueDescriptor object. This means // we have to store created EnumValueDescriptors. However, as there // are potentially 2G unknown enum values, storing all of these // objects persistently will consume lots of memory for long-running // services and it's also unnecessary as not many EnumValueDescriptors // will be used at the same time. // // To solve the problem we take advantage of Java's weak references and // rely on gc to release unused descriptors. // // Here is how it works: // * We store unknown EnumValueDescriptors in a WeakHashMap with the // value being a weak reference to the descriptor. // * The descriptor holds a strong reference to the key so as long // as the EnumValueDescriptor is in use, the key will be there // and the corresponding map entry will be there. Following-up // queries with the same number will return the same descriptor. // * If the user no longer uses an unknown EnumValueDescriptor, // it will be gc-ed since we only hold a weak reference to it in // the map. The key in the corresponding map entry will also be // gc-ed as the only strong reference to it is in the descriptor // which is just gc-ed. With the key being gone WeakHashMap will // then remove the whole entry. This way unknown descriptors will // be freed automatically and we don't need to do anything to // clean-up unused map entries. // Note: We must use "new Integer(number)" here because we don't want // these Integer objects to be cached. Integer key = new Integer(number); WeakReference reference = unknownValues.get(key); if (reference != null) { result = reference.get(); } if (result == null) { result = new EnumValueDescriptor(file, this, key); unknownValues.put(key, new WeakReference(result)); } } return result; } // Used in tests only. int getUnknownEnumValueDescriptorCount() { return unknownValues.size(); } private final int index; private EnumDescriptorProto proto; private final String fullName; private final FileDescriptor file; private final Descriptor containingType; private EnumValueDescriptor[] values; private final WeakHashMap> unknownValues = new WeakHashMap<>(); private EnumDescriptor( final EnumDescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index) throws DescriptorValidationException { this.index = index; this.proto = proto; fullName = computeFullName(file, parent, proto.getName()); this.file = file; containingType = parent; if (proto.getValueCount() == 0) { // We cannot allow enums with no values because this would mean there // would be no valid default value for fields of this type. throw new DescriptorValidationException(this, "Enums must contain at least one value."); } values = new EnumValueDescriptor[proto.getValueCount()]; for (int i = 0; i < proto.getValueCount(); i++) { values[i] = new EnumValueDescriptor(proto.getValue(i), file, this, i); } file.pool.addSymbol(this); } /** See {@link FileDescriptor#setProto}. */ private void setProto(final EnumDescriptorProto proto) { this.proto = proto; for (int i = 0; i < values.length; i++) { values[i].setProto(proto.getValue(i)); } } } // ================================================================= /** * Describes one value within an enum type. Note that multiple defined values may have the same * number. In generated Java code, all values with the same number after the first become aliases * of the first. However, they still have independent EnumValueDescriptors. */ public static final class EnumValueDescriptor extends GenericDescriptor implements Internal.EnumLite { /** * Get the index of this descriptor within its parent. * * @see Descriptors.Descriptor#getIndex() */ public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ @Override public EnumValueDescriptorProto toProto() { return proto; } /** Get the value's unqualified name. */ @Override public String getName() { return proto.getName(); } /** Get the value's number. */ @Override public int getNumber() { return proto.getNumber(); } @Override public String toString() { return proto.getName(); } /** * Get the value's fully-qualified name. * * @see Descriptors.Descriptor#getFullName() */ @Override public String getFullName() { return fullName; } /** Get the {@link FileDescriptor} containing this descriptor. */ @Override public FileDescriptor getFile() { return file; } /** Get the value's enum type. */ public EnumDescriptor getType() { return type; } /** Get the {@code EnumValueOptions}, defined in {@code descriptor.proto}. */ public EnumValueOptions getOptions() { return proto.getOptions(); } private final int index; private EnumValueDescriptorProto proto; private final String fullName; private final FileDescriptor file; private final EnumDescriptor type; private EnumValueDescriptor( final EnumValueDescriptorProto proto, final FileDescriptor file, final EnumDescriptor parent, final int index) throws DescriptorValidationException { this.index = index; this.proto = proto; this.file = file; type = parent; fullName = parent.getFullName() + '.' + proto.getName(); file.pool.addSymbol(this); file.pool.addEnumValueByNumber(this); } // Create an unknown enum value. private EnumValueDescriptor( final FileDescriptor file, final EnumDescriptor parent, final Integer number) { String name = "UNKNOWN_ENUM_VALUE_" + parent.getName() + "_" + number; EnumValueDescriptorProto proto = EnumValueDescriptorProto.newBuilder().setName(name).setNumber(number).build(); this.index = -1; this.proto = proto; this.file = file; this.type = parent; this.fullName = parent.getFullName() + '.' + proto.getName(); // Don't add this descriptor into pool. } /** See {@link FileDescriptor#setProto}. */ private void setProto(final EnumValueDescriptorProto proto) { this.proto = proto; } } // ================================================================= /** Describes a service type. */ public static final class ServiceDescriptor extends GenericDescriptor { /** * Get the index of this descriptor within its parent. * @see Descriptors.Descriptor#getIndex() */ public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ @Override public ServiceDescriptorProto toProto() { return proto; } /** Get the type's unqualified name. */ @Override public String getName() { return proto.getName(); } /** * Get the type's fully-qualified name. * * @see Descriptors.Descriptor#getFullName() */ @Override public String getFullName() { return fullName; } /** Get the {@link FileDescriptor} containing this descriptor. */ @Override public FileDescriptor getFile() { return file; } /** Get the {@code ServiceOptions}, defined in {@code descriptor.proto}. */ public ServiceOptions getOptions() { return proto.getOptions(); } /** Get a list of methods for this service. */ public List getMethods() { return Collections.unmodifiableList(Arrays.asList(methods)); } /** * Find a method by name. * * @param name The unqualified name of the method (e.g. "Foo"). * @return the method's descriptor, or {@code null} if not found. */ public MethodDescriptor findMethodByName(final String name) { final GenericDescriptor result = file.pool.findSymbol(fullName + '.' + name); if (result instanceof MethodDescriptor) { return (MethodDescriptor) result; } else { return null; } } private final int index; private ServiceDescriptorProto proto; private final String fullName; private final FileDescriptor file; private MethodDescriptor[] methods; private ServiceDescriptor( final ServiceDescriptorProto proto, final FileDescriptor file, final int index) throws DescriptorValidationException { this.index = index; this.proto = proto; fullName = computeFullName(file, null, proto.getName()); this.file = file; methods = new MethodDescriptor[proto.getMethodCount()]; for (int i = 0; i < proto.getMethodCount(); i++) { methods[i] = new MethodDescriptor(proto.getMethod(i), file, this, i); } file.pool.addSymbol(this); } private void crossLink() throws DescriptorValidationException { for (final MethodDescriptor method : methods) { method.crossLink(); } } /** See {@link FileDescriptor#setProto}. */ private void setProto(final ServiceDescriptorProto proto) { this.proto = proto; for (int i = 0; i < methods.length; i++) { methods[i].setProto(proto.getMethod(i)); } } } // ================================================================= /** Describes one method within a service type. */ public static final class MethodDescriptor extends GenericDescriptor { /** * Get the index of this descriptor within its parent. * @see Descriptors.Descriptor#getIndex() */ public int getIndex() { return index; } /** Convert the descriptor to its protocol message representation. */ @Override public MethodDescriptorProto toProto() { return proto; } /** Get the method's unqualified name. */ @Override public String getName() { return proto.getName(); } /** * Get the method's fully-qualified name. * * @see Descriptors.Descriptor#getFullName() */ @Override public String getFullName() { return fullName; } /** Get the {@link FileDescriptor} containing this descriptor. */ @Override public FileDescriptor getFile() { return file; } /** Get the method's service type. */ public ServiceDescriptor getService() { return service; } /** Get the method's input type. */ public Descriptor getInputType() { return inputType; } /** Get the method's output type. */ public Descriptor getOutputType() { return outputType; } /** Get whether or not the inputs are streaming. */ public boolean isClientStreaming() { return proto.getClientStreaming(); } /** Get whether or not the outputs are streaming. */ public boolean isServerStreaming() { return proto.getServerStreaming(); } /** Get the {@code MethodOptions}, defined in {@code descriptor.proto}. */ public MethodOptions getOptions() { return proto.getOptions(); } private final int index; private MethodDescriptorProto proto; private final String fullName; private final FileDescriptor file; private final ServiceDescriptor service; // Initialized during cross-linking. private Descriptor inputType; private Descriptor outputType; private MethodDescriptor( final MethodDescriptorProto proto, final FileDescriptor file, final ServiceDescriptor parent, final int index) throws DescriptorValidationException { this.index = index; this.proto = proto; this.file = file; service = parent; fullName = parent.getFullName() + '.' + proto.getName(); file.pool.addSymbol(this); } private void crossLink() throws DescriptorValidationException { final GenericDescriptor input = file.pool.lookupSymbol( proto.getInputType(), this, DescriptorPool.SearchFilter.TYPES_ONLY); if (!(input instanceof Descriptor)) { throw new DescriptorValidationException( this, '\"' + proto.getInputType() + "\" is not a message type."); } inputType = (Descriptor) input; final GenericDescriptor output = file.pool.lookupSymbol( proto.getOutputType(), this, DescriptorPool.SearchFilter.TYPES_ONLY); if (!(output instanceof Descriptor)) { throw new DescriptorValidationException( this, '\"' + proto.getOutputType() + "\" is not a message type."); } outputType = (Descriptor) output; } /** See {@link FileDescriptor#setProto}. */ private void setProto(final MethodDescriptorProto proto) { this.proto = proto; } } // ================================================================= 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; } // ================================================================= /** * All descriptors implement this to make it easier to implement tools like {@code * DescriptorPool}. */ public abstract static class GenericDescriptor { // Private constructor to prevent subclasses outside of com.google.protobuf.Descriptors private GenericDescriptor() {} public abstract Message toProto(); public abstract String getName(); public abstract String getFullName(); public abstract FileDescriptor getFile(); } /** Thrown when building descriptors fails because the source DescriptorProtos are not valid. */ public static class DescriptorValidationException extends Exception { private static final long serialVersionUID = 5750205775490483148L; /** Gets the full name of the descriptor where the error occurred. */ public String getProblemSymbolName() { return name; } /** Gets the protocol message representation of the invalid descriptor. */ public Message getProblemProto() { return proto; } /** Gets a human-readable description of the error. */ public String getDescription() { return description; } private final String name; private final Message proto; private final String description; private DescriptorValidationException( final GenericDescriptor problemDescriptor, final String description) { super(problemDescriptor.getFullName() + ": " + description); // Note that problemDescriptor may be partially uninitialized, so we // don't want to expose it directly to the user. So, we only provide // the name and the original proto. name = problemDescriptor.getFullName(); proto = problemDescriptor.toProto(); this.description = description; } private DescriptorValidationException( final GenericDescriptor problemDescriptor, final String description, final Throwable cause) { this(problemDescriptor, description); initCause(cause); } private DescriptorValidationException( final FileDescriptor problemDescriptor, final String description) { super(problemDescriptor.getName() + ": " + description); // Note that problemDescriptor may be partially uninitialized, so we // don't want to expose it directly to the user. So, we only provide // the name and the original proto. name = problemDescriptor.getName(); proto = problemDescriptor.toProto(); this.description = description; } } // ================================================================= /** * A private helper class which contains lookup tables containing all the descriptors defined in a * particular file. */ private static final class DescriptorPool { /** Defines what subclass of descriptors to search in the descriptor pool. */ enum SearchFilter { TYPES_ONLY, AGGREGATES_ONLY, ALL_SYMBOLS } DescriptorPool(final FileDescriptor[] dependencies, boolean allowUnknownDependencies) { this.dependencies = new HashSet<>(); this.allowUnknownDependencies = allowUnknownDependencies; for (Descriptors.FileDescriptor dependency : dependencies) { this.dependencies.add(dependency); importPublicDependencies(dependency); } for (final FileDescriptor dependency : this.dependencies) { try { addPackage(dependency.getPackage(), dependency); } catch (DescriptorValidationException e) { // Can't happen, because addPackage() only fails when the name // conflicts with a non-package, but we have not yet added any // non-packages at this point. throw new AssertionError(e); } } } /** Find and put public dependencies of the file into dependencies set. */ private void importPublicDependencies(final FileDescriptor file) { for (FileDescriptor dependency : file.getPublicDependencies()) { if (dependencies.add(dependency)) { importPublicDependencies(dependency); } } } private final Set dependencies; private boolean allowUnknownDependencies; private final Map descriptorsByName = new HashMap<>(); private final Map fieldsByNumber = new HashMap<>(); private final Map enumValuesByNumber = new HashMap<>(); /** Find a generic descriptor by fully-qualified name. */ GenericDescriptor findSymbol(final String fullName) { return findSymbol(fullName, SearchFilter.ALL_SYMBOLS); } /** * Find a descriptor by fully-qualified name and given option to only search valid field type * descriptors. */ GenericDescriptor findSymbol(final String fullName, final SearchFilter filter) { GenericDescriptor result = descriptorsByName.get(fullName); if (result != null) { if ((filter == SearchFilter.ALL_SYMBOLS) || ((filter == SearchFilter.TYPES_ONLY) && isType(result)) || ((filter == SearchFilter.AGGREGATES_ONLY) && isAggregate(result))) { return result; } } for (final FileDescriptor dependency : dependencies) { result = dependency.pool.descriptorsByName.get(fullName); if (result != null) { if ((filter == SearchFilter.ALL_SYMBOLS) || ((filter == SearchFilter.TYPES_ONLY) && isType(result)) || ((filter == SearchFilter.AGGREGATES_ONLY) && isAggregate(result))) { return result; } } } return null; } /** Checks if the descriptor is a valid type for a message field. */ boolean isType(GenericDescriptor descriptor) { return (descriptor instanceof Descriptor) || (descriptor instanceof EnumDescriptor); } /** Checks if the descriptor is a valid namespace type. */ boolean isAggregate(GenericDescriptor descriptor) { return (descriptor instanceof Descriptor) || (descriptor instanceof EnumDescriptor) || (descriptor instanceof PackageDescriptor) || (descriptor instanceof ServiceDescriptor); } /** * Look up a type descriptor by name, relative to some other descriptor. The name may be * fully-qualified (with a leading '.'), partially-qualified, or unqualified. C++-like name * lookup semantics are used to search for the matching descriptor. */ GenericDescriptor lookupSymbol( final String name, final GenericDescriptor relativeTo, final DescriptorPool.SearchFilter filter) throws DescriptorValidationException { // TODO(kenton): This could be optimized in a number of ways. GenericDescriptor result; String fullname; if (name.startsWith(".")) { // Fully-qualified name. fullname = name.substring(1); result = findSymbol(fullname, filter); } else { // If "name" is a compound identifier, we want to search for the // first component of it, then search within it for the rest. // If name is something like "Foo.Bar.baz", and symbols named "Foo" are // defined in multiple parent scopes, we only want to find "Bar.baz" in // the innermost one. E.g., the following should produce an error: // message Bar { message Baz {} } // message Foo { // message Bar { // } // optional Bar.Baz baz = 1; // } // So, we look for just "Foo" first, then look for "Bar.baz" within it // if found. final int firstPartLength = name.indexOf('.'); final String firstPart; if (firstPartLength == -1) { firstPart = name; } else { firstPart = name.substring(0, firstPartLength); } // We will search each parent scope of "relativeTo" looking for the // symbol. final StringBuilder scopeToTry = new StringBuilder(relativeTo.getFullName()); while (true) { // Chop off the last component of the scope. final int dotpos = scopeToTry.lastIndexOf("."); if (dotpos == -1) { fullname = name; result = findSymbol(name, filter); break; } else { scopeToTry.setLength(dotpos + 1); // Append firstPart and try to find scopeToTry.append(firstPart); result = findSymbol(scopeToTry.toString(), DescriptorPool.SearchFilter.AGGREGATES_ONLY); if (result != null) { if (firstPartLength != -1) { // We only found the first part of the symbol. Now look for // the whole thing. If this fails, we *don't* want to keep // searching parent scopes. scopeToTry.setLength(dotpos + 1); scopeToTry.append(name); result = findSymbol(scopeToTry.toString(), filter); } fullname = scopeToTry.toString(); break; } // Not found. Remove the name so we can try again. scopeToTry.setLength(dotpos); } } } if (result == null) { if (allowUnknownDependencies && filter == SearchFilter.TYPES_ONLY) { logger.warning( "The descriptor for message type \"" + name + "\" can not be found and a placeholder is created for it"); // We create a dummy message descriptor here regardless of the // expected type. If the type should be message, this dummy // descriptor will work well and if the type should be enum, a // DescriptorValidationException will be thrown latter. In either // case, the code works as expected: we allow unknown message types // but not unknown enum types. result = new Descriptor(fullname); // Add the placeholder file as a dependency so we can find the // placeholder symbol when resolving other references. this.dependencies.add(result.getFile()); return result; } else { throw new DescriptorValidationException(relativeTo, '\"' + name + "\" is not defined."); } } else { return result; } } /** * Adds a symbol to the symbol table. If a symbol with the same name already exists, throws an * error. */ void addSymbol(final GenericDescriptor descriptor) throws DescriptorValidationException { validateSymbolName(descriptor); final String fullName = descriptor.getFullName(); final GenericDescriptor old = descriptorsByName.put(fullName, descriptor); if (old != null) { descriptorsByName.put(fullName, old); if (descriptor.getFile() == old.getFile()) { final int dotpos = fullName.lastIndexOf('.'); if (dotpos == -1) { throw new DescriptorValidationException( descriptor, '\"' + fullName + "\" is already defined."); } else { throw new DescriptorValidationException( descriptor, '\"' + fullName.substring(dotpos + 1) + "\" is already defined in \"" + fullName.substring(0, dotpos) + "\"."); } } else { throw new DescriptorValidationException( descriptor, '\"' + fullName + "\" is already defined in file \"" + old.getFile().getName() + "\"."); } } } /** * Represents a package in the symbol table. We use PackageDescriptors just as placeholders so * that someone cannot define, say, a message type that has the same name as an existing * package. */ private static final class PackageDescriptor extends GenericDescriptor { @Override public Message toProto() { return file.toProto(); } @Override public String getName() { return name; } @Override public String getFullName() { return fullName; } @Override public FileDescriptor getFile() { return file; } PackageDescriptor(final String name, final String fullName, final FileDescriptor file) { this.file = file; this.fullName = fullName; this.name = name; } private final String name; private final String fullName; private final FileDescriptor file; } /** * Adds a package to the symbol tables. If a package by the same name already exists, that is * fine, but if some other kind of symbol exists under the same name, an exception is thrown. If * the package has multiple components, this also adds the parent package(s). */ void addPackage(final String fullName, final FileDescriptor file) throws DescriptorValidationException { final int dotpos = fullName.lastIndexOf('.'); final String name; if (dotpos == -1) { name = fullName; } else { addPackage(fullName.substring(0, dotpos), file); name = fullName.substring(dotpos + 1); } final GenericDescriptor old = descriptorsByName.put(fullName, new PackageDescriptor(name, fullName, file)); if (old != null) { 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() + "\"."); } } } /** A (GenericDescriptor, int) pair, used as a map key. */ private static final class DescriptorIntPair { private final GenericDescriptor descriptor; private final int number; DescriptorIntPair(final GenericDescriptor descriptor, final int number) { this.descriptor = descriptor; this.number = number; } @Override public int hashCode() { return descriptor.hashCode() * ((1 << 16) - 1) + number; } @Override public boolean equals(final Object obj) { if (!(obj instanceof DescriptorIntPair)) { return false; } final DescriptorIntPair other = (DescriptorIntPair) obj; return descriptor == other.descriptor && number == other.number; } } /** * Adds a field to the fieldsByNumber table. Throws an exception if a field with the same * containing type and number already exists. */ void addFieldByNumber(final FieldDescriptor field) throws DescriptorValidationException { final DescriptorIntPair key = new DescriptorIntPair(field.getContainingType(), field.getNumber()); final FieldDescriptor old = fieldsByNumber.put(key, field); if (old != null) { fieldsByNumber.put(key, old); throw new DescriptorValidationException( field, "Field number " + field.getNumber() + " has already been used in \"" + field.getContainingType().getFullName() + "\" by field \"" + old.getName() + "\"."); } } /** * Adds an enum value to the enumValuesByNumber table. If an enum value with the same type and * number already exists, does nothing. (This is allowed; the first value define with the number * takes precedence.) */ void addEnumValueByNumber(final EnumValueDescriptor value) { final DescriptorIntPair key = new DescriptorIntPair(value.getType(), value.getNumber()); final EnumValueDescriptor old = enumValuesByNumber.put(key, value); if (old != null) { enumValuesByNumber.put(key, old); // Not an error: Multiple enum values may have the same number, but // we only want the first one in the map. } } /** * Verifies that the descriptor's name is valid (i.e. it contains only letters, digits, and * underscores, and does not start with a digit). */ static void validateSymbolName(final GenericDescriptor descriptor) throws DescriptorValidationException { final String name = descriptor.getName(); if (name.length() == 0) { throw new DescriptorValidationException(descriptor, "Missing name."); } // Non-ASCII characters are not valid in protobuf identifiers, even // if they are letters or digits. // The first character must be a letter or '_'. // Subsequent characters may be letters, numbers, or digits. 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)) { // Valid continue; } throw new DescriptorValidationException( descriptor, '\"' + name + "\" is not a valid identifier."); } } } /** Describes an oneof of a message type. */ public static final class OneofDescriptor extends GenericDescriptor { /** Get the index of this descriptor within its parent. */ public int getIndex() { return index; } @Override public String getName() { return proto.getName(); } @Override public FileDescriptor getFile() { return file; } @Override public String getFullName() { return fullName; } public Descriptor getContainingType() { return containingType; } public int getFieldCount() { return fieldCount; } public OneofOptions getOptions() { return proto.getOptions(); } public boolean isSynthetic() { return fields.length == 1 && fields[0].isProto3Optional; } /** Get a list of this message type's fields. */ public List getFields() { return Collections.unmodifiableList(Arrays.asList(fields)); } public FieldDescriptor getField(int index) { return fields[index]; } @Override public OneofDescriptorProto toProto() { return proto; } private void setProto(final OneofDescriptorProto proto) { this.proto = proto; } private OneofDescriptor( final OneofDescriptorProto proto, final FileDescriptor file, final Descriptor parent, final int index) throws DescriptorValidationException { this.proto = proto; fullName = computeFullName(file, parent, proto.getName()); this.file = file; this.index = index; containingType = parent; fieldCount = 0; } private final int index; private OneofDescriptorProto proto; private final String fullName; private final FileDescriptor file; private Descriptor containingType; private int fieldCount; private FieldDescriptor[] fields; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy