Index: third_party/protobuf/java/src/main/java/com/google/protobuf/Descriptors.java |
diff --git a/third_party/protobuf/java/src/main/java/com/google/protobuf/Descriptors.java b/third_party/protobuf/java/src/main/java/com/google/protobuf/Descriptors.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2ee84594742f3b423e0377965778ed1efe68e77c |
--- /dev/null |
+++ b/third_party/protobuf/java/src/main/java/com/google/protobuf/Descriptors.java |
@@ -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."); |
+ } |
+ } |
+ } |
+ } |
+} |