com.google.protobuf.Descriptors Maven / Gradle / Ivy
Show all versions of nc-common Show documentation
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// http://code.google.com/p/protobuf/
//
// 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 com.google.protobuf.DescriptorProtos.*;
import java.io.UnsupportedEncodingException;
import java.util.*;
/**
* 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()}.
*
* Descriptors are built from DescriptorProtos, as defined in
* {@code google/protobuf/descriptor.proto}.
*
* @author [email protected] Kenton Varda
*/
public final class Descriptors {
private static String computeFullName(final FileDescriptor file,
final Descriptor parent,
final String name) {
if (parent != null) {
return parent.getFullName() + '.' + name;
} else if (file.getPackage().length() > 0) {
return file.getPackage() + '.' + name;
} else {
return name;
}
}
// =================================================================
/**
* All descriptors except {@code FileDescriptor} implement this to make
* {@code DescriptorPool}'s life easier.
*/
private interface GenericDescriptor {
Message toProto();
String getName();
String getFullName();
FileDescriptor getFile();
}
// =================================================================
/**
* Describes a {@code .proto} file, including everything defined within.
*/
public static final class FileDescriptor {
private final Descriptor[] messageTypes;
private final EnumDescriptor[] enumTypes;
private final ServiceDescriptor[] services;
private final FieldDescriptor[] extensions;
private final FileDescriptor[] dependencies;
private final DescriptorPool pool;
private FileDescriptorProto proto;
private FileDescriptor(final FileDescriptorProto proto,
final FileDescriptor[] dependencies,
final DescriptorPool pool)
throws DescriptorValidationException {
this.pool = pool;
this.proto = proto;
this.dependencies = dependencies.clone();
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);
}
}
/**
* 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, in the exact order listed
* in {@code proto}.
* @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 {
// Building decsriptors 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);
final FileDescriptor result =
new FileDescriptor(proto, dependencies, pool);
if (dependencies.length != proto.getDependencyCount()) {
throw new DescriptorValidationException(result,
"Dependencies passed to FileDescriptor.buildFrom() don't match " +
"those listed in the FileDescriptorProto.");
}
for (int i = 0; i < proto.getDependencyCount(); i++) {
if (!dependencies[i].getName().equals(proto.getDependency(i))) {
throw new DescriptorValidationException(result,
"Dependencies passed to FileDescriptor.buildFrom() don't match " +
"those listed in the FileDescriptorProto.");
}
}
result.crossLink();
return result;
}
/**
* 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 void internalBuildGeneratedFileFrom(
final String[] descriptorDataParts,
final FileDescriptor[] dependencies,
final InternalDescriptorAssigner descriptorAssigner) {
// 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.
// descriptorData may contain multiple strings in order to get around the
// Java 64k string literal limit.
StringBuilder descriptorData = new StringBuilder();
for (String part : descriptorDataParts) {
descriptorData.append(part);
}
final byte[] descriptorBytes;
try {
descriptorBytes = descriptorData.toString().getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
"Standard encoding ISO-8859-1 not supported by JVM.", e);
}
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 {
result = buildFrom(proto, dependencies);
} 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);
}
}
/**
* Convert the descriptor to its protocol message representation.
*/
public FileDescriptorProto toProto() {
return proto;
}
/**
* Get the file name.
*/
public String getName() {
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));
}
/**
* 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;
}
if (getPackage().length() > 0) {
name = getPackage() + '.' + name;
}
final GenericDescriptor result = pool.findSymbol(name);
if (result != null && 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;
}
if (getPackage().length() > 0) {
name = getPackage() + '.' + name;
}
final GenericDescriptor result = pool.findSymbol(name);
if (result != null && 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;
}
if (getPackage().length() > 0) {
name = getPackage() + '.' + name;
}
final GenericDescriptor result = pool.findSymbol(name);
if (result != null && 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;
}
if (getPackage().length() > 0) {
name = getPackage() + '.' + name;
}
final GenericDescriptor result = pool.findSymbol(name);
if (result != null && result instanceof FieldDescriptor &&
result.getFile() == this) {
return (FieldDescriptor) result;
} else {
return null;
}
}
/**
* 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 decsriptors 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));
}
}
/**
* 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 variales 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 decsriptor.
*/
public interface InternalDescriptorAssigner {
ExtensionRegistry assignDescriptors(FileDescriptor root);
}
}
// =================================================================
/**
* Describes a message type.
*/
public static final class Descriptor implements GenericDescriptor {
private final int index;
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 DescriptorProto proto;
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;
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);
}
file.pool.addSymbol(this);
}
/**
* 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.
*/
public DescriptorProto toProto() {
return proto;
}
/**
* Get the type's unqualified name.
*/
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".
*/
public String getFullName() {
return fullName;
}
/**
* Get the {@link FileDescriptor} containing this descriptor.
*/
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 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;
}
/**
* Finds a field by name.
*
* @param name The unqualified name of the field (e.g. "foo").
* @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 != null && 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 != null && 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 != null && result instanceof EnumDescriptor) {
return (EnumDescriptor) result;
} else {
return null;
}
}
/**
* 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 < 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
implements GenericDescriptor, Comparable,
FieldSet.FieldDescriptorLite {
// 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();
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 Desrciptors.java " +
"wasn't updated.");
}
}
private final int index;
private final String fullName;
private final FileDescriptor file;
private final Descriptor extensionScope;
private FieldDescriptorProto proto;
// Possibly initialized during cross-linking.
private Type type;
private Descriptor containingType;
private Descriptor messageType;
private EnumDescriptor enumType;
private Object defaultValue;
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.hasType()) {
type = Type.valueOf(proto.getType());
}
if (getNumber() <= 0) {
throw new DescriptorValidationException(this,
"Field numbers must be positive integers.");
}
// 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.");
}
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;
}
} else {
if (proto.hasExtendee()) {
throw new DescriptorValidationException(this,
"FieldDescriptorProto.extendee set for non-extension field.");
}
containingType = parent;
extensionScope = null;
}
file.pool.addSymbol(this);
}
/**
* Get the index of this descriptor within its parent.
*
* @see Descriptor#getIndex()
*/
public int getIndex() {
return index;
}
/**
* Convert the descriptor to its protocol message representation.
*/
public FieldDescriptorProto toProto() {
return proto;
}
/**
* Get the field's unqualified name.
*/
public String getName() {
return proto.getName();
}
/**
* Get the field's number.
*/
public int getNumber() {
return proto.getNumber();
}
/**
* Get the field's fully-qualified name.
*
* @see Descriptor#getFullName()
*/
public String getFullName() {
return fullName;
}
/**
* 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.
*/
public WireFormat.JavaType getLiteJavaType() {
return getLiteType().getJavaType();
}
/**
* Get the {@code FileDescriptor} containing this descriptor.
*/
public FileDescriptor getFile() {
return file;
}
/**
* Get the field's declared type.
*/
public Type getType() {
return type;
}
/**
* For internal use only.
*/
public WireFormat.FieldType getLiteType() {
return table[type.ordinal()];
}
/**
* 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?
*/
public boolean isRepeated() {
return proto.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED;
}
/**
* Does this field have the {@code [packed = true]} option?
*/
public boolean isPacked() {
return 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;
}
/**
* 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(
"This field is not an extension.");
}
return extensionScope;
}
/**
* For embedded message and group fields, gets the field's type.
*/
public Descriptor getMessageType() {
if (getJavaType() != JavaType.MESSAGE) {
throw new UnsupportedOperationException(
"This field is not of message type.");
}
return messageType;
}
/**
* For enum fields, gets the field's type.
*/
public EnumDescriptor getEnumType() {
if (getJavaType() != JavaType.ENUM) {
throw new UnsupportedOperationException(
"This field is not of enum type.");
}
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.
*/
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();
}
/**
* 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);
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);
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.");
}
}
// 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.
*/
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);
}
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 JavaType javaType;
Type(final JavaType javaType) {
this.javaType = javaType;
}
public static Type valueOf(final FieldDescriptorProto.Type type) {
return values()[type.getNumber() - 1];
}
public FieldDescriptorProto.Type toProto() {
return FieldDescriptorProto.Type.valueOf(ordinal() + 1);
}
public JavaType getJavaType() {
return javaType;
}
}
public enum JavaType {
INT(0),
LONG(0L),
FLOAT(0F),
DOUBLE(0D),
BOOLEAN(false),
STRING(""),
BYTE_STRING(ByteString.EMPTY),
ENUM(null),
MESSAGE(null);
/**
* 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;
JavaType(final Object defaultDefault) {
this.defaultDefault = defaultDefault;
}
}
}
// =================================================================
/**
* Describes an enum type.
*/
public static final class EnumDescriptor
implements GenericDescriptor, Internal.EnumLiteMap {
private final int index;
private final String fullName;
private final FileDescriptor file;
private final Descriptor containingType;
private EnumDescriptorProto proto;
private EnumValueDescriptor[] values;
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);
}
/**
* Get the index of this descriptor within its parent.
*
* @see Descriptor#getIndex()
*/
public int getIndex() {
return index;
}
/**
* Convert the descriptor to its protocol message representation.
*/
public EnumDescriptorProto toProto() {
return proto;
}
/**
* Get the type's unqualified name.
*/
public String getName() {
return proto.getName();
}
/**
* Get the type's fully-qualified name.
*
* @see Descriptor#getFullName()
*/
public String getFullName() {
return fullName;
}
/**
* Get the {@link FileDescriptor} containing this descriptor.
*/
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 decsriptor, or {@code null} if not found.
*/
public EnumValueDescriptor findValueByName(final String name) {
final GenericDescriptor result =
file.pool.findSymbol(fullName + '.' + name);
if (result != null && 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 decsriptor, or {@code null} if not found.
*/
public EnumValueDescriptor findValueByNumber(final int number) {
return file.pool.enumValuesByNumber.get(
new DescriptorPool.DescriptorIntPair(this, number));
}
/**
* 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
implements GenericDescriptor, Internal.EnumLite {
private final int index;
private final String fullName;
private final FileDescriptor file;
private final EnumDescriptor type;
private EnumValueDescriptorProto proto;
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);
}
/**
* Get the index of this descriptor within its parent.
*
* @see Descriptor#getIndex()
*/
public int getIndex() {
return index;
}
/**
* Convert the descriptor to its protocol message representation.
*/
public EnumValueDescriptorProto toProto() {
return proto;
}
/**
* Get the value's unqualified name.
*/
public String getName() {
return proto.getName();
}
/**
* Get the value's number.
*/
public int getNumber() {
return proto.getNumber();
}
/**
* Get the value's fully-qualified name.
*
* @see Descriptor#getFullName()
*/
public String getFullName() {
return fullName;
}
/**
* Get the {@link FileDescriptor} containing this descriptor.
*/
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();
}
/**
* See {@link FileDescriptor#setProto}.
*/
private void setProto(final EnumValueDescriptorProto proto) {
this.proto = proto;
}
}
// =================================================================
/**
* Describes a service type.
*/
public static final class ServiceDescriptor implements GenericDescriptor {
private final int index;
private final String fullName;
private final FileDescriptor file;
private ServiceDescriptorProto proto;
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);
}
/**
* 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.
*/
public ServiceDescriptorProto toProto() {
return proto;
}
/**
* Get the type's unqualified name.
*/
public String getName() {
return proto.getName();
}
/**
* Get the type's fully-qualified name.
*
* @see Descriptor#getFullName()
*/
public String getFullName() {
return fullName;
}
/**
* Get the {@link FileDescriptor} containing this descriptor.
*/
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 decsriptor, or {@code null} if not found.
*/
public MethodDescriptor findMethodByName(final String name) {
final GenericDescriptor result =
file.pool.findSymbol(fullName + '.' + name);
if (result != null && result instanceof MethodDescriptor) {
return (MethodDescriptor) result;
} else {
return null;
}
}
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 implements GenericDescriptor {
private final int index;
private final String fullName;
private final FileDescriptor file;
private final ServiceDescriptor service;
private MethodDescriptorProto proto;
// 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);
}
/**
* 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.
*/
public MethodDescriptorProto toProto() {
return proto;
}
/**
* Get the method's unqualified name.
*/
public String getName() {
return proto.getName();
}
/**
* Get the method's fully-qualified name.
*
* @see Descriptor#getFullName()
*/
public String getFullName() {
return fullName;
}
/**
* Get the {@link FileDescriptor} containing this descriptor.
*/
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 the {@code MethodOptions}, defined in {@code descriptor.proto}.
*/
public MethodOptions getOptions() {
return proto.getOptions();
}
private void crossLink() throws DescriptorValidationException {
final GenericDescriptor input =
file.pool.lookupSymbol(proto.getInputType(), this);
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);
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;
}
}
/**
* Thrown when building descriptors fails because the source DescriptorProtos
* are not valid.
*/
public static class DescriptorValidationException extends Exception {
private static final long serialVersionUID = 5750205775490483148L;
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;
}
/**
* Gets the full name of the descriptor where the error occurred.
*/
public String getProblemSymbolName() {
return name;
}
/**
* Gets the 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;
}
}
// =================================================================
/**
* A private helper class which contains lookup tables containing all the
* descriptors defined in a particular file.
*/
private static final class DescriptorPool {
private final DescriptorPool[] dependencies;
private final Map descriptorsByName =
new HashMap();
private final Map fieldsByNumber =
new HashMap();
private final Map enumValuesByNumber
= new HashMap();
DescriptorPool(final FileDescriptor[] dependencies) {
this.dependencies = new DescriptorPool[dependencies.length];
for (int i = 0; i < dependencies.length; i++) {
this.dependencies[i] = dependencies[i].pool;
}
for (final FileDescriptor dependency : 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.
assert false;
}
}
}
/**
* 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.");
} else {
boolean valid = true;
for (int i = 0; i < name.length(); i++) {
final char c = name.charAt(i);
// Non-ASCII characters are not valid in protobuf identifiers, even
// if they are letters or digits.
if (c >= 128) {
valid = false;
}
// First character must be letter or _. Subsequent characters may
// be letters, numbers, or digits.
if (Character.isLetter(c) || c == '_' ||
(Character.isDigit(c) && i > 0)) {
// Valid
} else {
valid = false;
}
}
if (!valid) {
throw new DescriptorValidationException(descriptor,
'\"' + name + "\" is not a valid identifier.");
}
}
}
/**
* Find a generic descriptor by fully-qualified name.
*/
GenericDescriptor findSymbol(final String fullName) {
GenericDescriptor result = descriptorsByName.get(fullName);
if (result != null) {
return result;
}
for (final DescriptorPool dependency : dependencies) {
result = dependency.descriptorsByName.get(fullName);
if (result != null) {
return result;
}
}
return null;
}
/**
* Look up a 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)
throws DescriptorValidationException {
// TODO(kenton): This could be optimized in a number of ways.
GenericDescriptor result;
if (name.startsWith(".")) {
// Fully-qualified name.
result = findSymbol(name.substring(1));
} else {
// If "name" is a compound identifier, we want to search for the
// first component of it, then search within it for the rest.
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) {
result = findSymbol(name);
break;
} else {
scopeToTry.setLength(dotpos + 1);
// Append firstPart and try to find.
scopeToTry.append(firstPart);
result = findSymbol(scopeToTry.toString());
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());
}
break;
}
// Not found. Remove the name so we can try again.
scopeToTry.setLength(dotpos);
}
}
}
if (result == null) {
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 int dotpos = fullName.lastIndexOf('.');
final GenericDescriptor old = descriptorsByName.put(fullName, descriptor);
if (old != null) {
descriptorsByName.put(fullName, old);
if (descriptor.getFile() == old.getFile()) {
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() + "\".");
}
}
}
/**
* 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() + "\".");
}
}
}
/**
* Adds a field to the fieldsByNumber table. Throws an exception if a
* field with hte 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.
}
}
/**
* 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 implements GenericDescriptor {
private final String name;
private final String fullName;
private final FileDescriptor file;
PackageDescriptor(final String name, final String fullName,
final FileDescriptor file) {
this.file = file;
this.fullName = fullName;
this.name = name;
}
public Message toProto() {
return file.toProto();
}
public String getName() {
return name;
}
public String getFullName() {
return fullName;
}
public FileDescriptor getFile() {
return file;
}
}
/**
* 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;
}
}
}
}