| // 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."); |
| } |
| } |
| } |
| } |
| } |