0,0 → 1,1887 |
// 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.util.Arrays; |
import java.util.Collections; |
import java.util.HashMap; |
import java.util.List; |
import java.util.Map; |
import java.io.UnsupportedEncodingException; |
|
/** |
* 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 kenton@google.com Kenton Varda |
*/ |
public final class Descriptors { |
/** |
* 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 { |
/** 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<Descriptor> getMessageTypes() { |
return Collections.unmodifiableList(Arrays.asList(messageTypes)); |
} |
|
/** Get a list of top-level enum types declared in this file. */ |
public List<EnumDescriptor> getEnumTypes() { |
return Collections.unmodifiableList(Arrays.asList(enumTypes)); |
} |
|
/** Get a list of top-level services declared in this file. */ |
public List<ServiceDescriptor> getServices() { |
return Collections.unmodifiableList(Arrays.asList(services)); |
} |
|
/** Get a list of top-level extensions declared in this file. */ |
public List<FieldDescriptor> getExtensions() { |
return Collections.unmodifiableList(Arrays.asList(extensions)); |
} |
|
/** Get a list of this file's dependencies (imports). */ |
public List<FileDescriptor> 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; |
} |
} |
|
/** |
* 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); |
} |
} |
|
/** |
* 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); |
} |
|
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 DescriptorPool pool; |
|
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); |
} |
} |
|
/** 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)); |
} |
} |
} |
|
// ================================================================= |
|
/** Describes a message type. */ |
public static final class Descriptor implements GenericDescriptor { |
/** |
* Get the index of this descriptor within its parent. In other words, |
* given a {@link FileDescriptor} {@code file}, the following is true: |
* <pre> |
* for all i in [0, file.getMessageTypeCount()): |
* file.getMessageType(i).getIndex() == i |
* </pre> |
* Similarly, for a {@link Descriptor} {@code messageType}: |
* <pre> |
* for all i in [0, messageType.getNestedTypeCount()): |
* messageType.getNestedType(i).getIndex() == i |
* </pre> |
*/ |
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}: |
* <pre> |
* package foo.bar; |
* option java_package = "com.example.protos" |
* message Baz {} |
* </pre> |
* {@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<FieldDescriptor> getFields() { |
return Collections.unmodifiableList(Arrays.asList(fields)); |
} |
|
/** Get a list of this message type's extensions. */ |
public List<FieldDescriptor> getExtensions() { |
return Collections.unmodifiableList(Arrays.asList(extensions)); |
} |
|
/** Get a list of message types nested within this one. */ |
public List<Descriptor> getNestedTypes() { |
return Collections.unmodifiableList(Arrays.asList(nestedTypes)); |
} |
|
/** Get a list of enum types nested within this one. */ |
public List<EnumDescriptor> 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; |
} |
} |
|
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 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); |
} |
|
/** 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<FieldDescriptor>, |
FieldSet.FieldDescriptorLite<FieldDescriptor> { |
/** |
* 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()]; |
} |
// 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? */ |
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: |
* <pre> |
* message Foo { |
* extensions 1000 to max; |
* } |
* extend Foo { |
* optional int32 baz = 1234; |
* } |
* message Bar { |
* extend Foo { |
* optional int32 qux = 4321; |
* } |
* } |
* </pre> |
* 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(); |
} |
|
private final int index; |
|
private FieldDescriptorProto proto; |
private final String fullName; |
private final FileDescriptor file; |
private final Descriptor extensionScope; |
|
// Possibly initialized during cross-linking. |
private Type type; |
private Descriptor containingType; |
private Descriptor messageType; |
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.valueOf(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 Desrciptors.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; |
} |
|
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); |
} |
|
/** 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); |
} |
} |
|
// ================================================================= |
|
/** Describes an enum type. */ |
public static final class EnumDescriptor |
implements GenericDescriptor, Internal.EnumLiteMap<EnumValueDescriptor> { |
/** |
* 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<EnumValueDescriptor> 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)); |
} |
|
private final int index; |
private EnumDescriptorProto proto; |
private final String fullName; |
private final FileDescriptor file; |
private final Descriptor containingType; |
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); |
} |
|
/** 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 { |
/** |
* 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(); } |
|
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); |
} |
|
/** See {@link FileDescriptor#setProto}. */ |
private void setProto(final EnumValueDescriptorProto proto) { |
this.proto = proto; |
} |
} |
|
// ================================================================= |
|
/** Describes a service type. */ |
public static final class ServiceDescriptor implements 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. */ |
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<MethodDescriptor> 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 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 implements 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. */ |
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 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); |
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; |
} |
} |
|
// ================================================================= |
|
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(); |
} |
|
/** |
* 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 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 { |
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; |
} |
} |
} |
|
private final DescriptorPool[] dependencies; |
|
private final Map<String, GenericDescriptor> descriptorsByName = |
new HashMap<String, GenericDescriptor>(); |
private final Map<DescriptorIntPair, FieldDescriptor> fieldsByNumber = |
new HashMap<DescriptorIntPair, FieldDescriptor>(); |
private final Map<DescriptorIntPair, EnumValueDescriptor> enumValuesByNumber |
= new HashMap<DescriptorIntPair, EnumValueDescriptor>(); |
|
/** 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() + "\"."); |
} |
} |
} |
|
/** |
* 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 { |
public Message toProto() { return file.toProto(); } |
public String getName() { return name; } |
public String getFullName() { return fullName; } |
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 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. |
} |
} |
|
/** |
* 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."); |
} |
} |
} |
} |
} |