| // Protocol Buffers - Google's data interchange format |
| // Copyright 2008 Google Inc. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package com.google.protobuf; |
| |
| import com.google.protobuf.Descriptors.Descriptor; |
| import com.google.protobuf.Descriptors.FieldDescriptor; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * A table of known extensions, searchable by name or field number. When |
| * parsing a protocol message that might have extensions, you must provide |
| * an {@code ExtensionRegistry} in which you have registered any extensions |
| * that you want to be able to parse. Otherwise, those extensions will just |
| * be treated like unknown fields. |
| * |
| * <p>For example, if you had the {@code .proto} file: |
| * |
| * <pre> |
| * option java_class = "MyProto"; |
| * |
| * message Foo { |
| * extensions 1000 to max; |
| * } |
| * |
| * extend Foo { |
| * optional int32 bar; |
| * } |
| * </pre> |
| * |
| * Then you might write code like: |
| * |
| * <pre> |
| * ExtensionRegistry registry = ExtensionRegistry.newInstance(); |
| * registry.add(MyProto.bar); |
| * MyProto.Foo message = MyProto.Foo.parseFrom(input, registry); |
| * </pre> |
| * |
| * <p>Background: |
| * |
| * <p>You might wonder why this is necessary. Two alternatives might come to |
| * mind. First, you might imagine a system where generated extensions are |
| * automatically registered when their containing classes are loaded. This |
| * is a popular technique, but is bad design; among other things, it creates a |
| * situation where behavior can change depending on what classes happen to be |
| * loaded. It also introduces a security vulnerability, because an |
| * unprivileged class could cause its code to be called unexpectedly from a |
| * privileged class by registering itself as an extension of the right type. |
| * |
| * <p>Another option you might consider is lazy parsing: do not parse an |
| * extension until it is first requested, at which point the caller must |
| * provide a type to use. This introduces a different set of problems. First, |
| * it would require a mutex lock any time an extension was accessed, which |
| * would be slow. Second, corrupt data would not be detected until first |
| * access, at which point it would be much harder to deal with it. Third, it |
| * could violate the expectation that message objects are immutable, since the |
| * type provided could be any arbitrary message class. An unprivileged user |
| * could take advantage of this to inject a mutable object into a message |
| * belonging to privileged code and create mischief. |
| * |
| * @author kenton@google.com Kenton Varda |
| */ |
| public class ExtensionRegistry extends ExtensionRegistryLite { |
| /** Construct a new, empty instance. */ |
| public static ExtensionRegistry newInstance() { |
| return new ExtensionRegistry(); |
| } |
| |
| /** Get the unmodifiable singleton empty instance. */ |
| public static ExtensionRegistry getEmptyRegistry() { |
| return EMPTY; |
| } |
| |
| |
| /** Returns an unmodifiable view of the registry. */ |
| @Override |
| public ExtensionRegistry getUnmodifiable() { |
| return new ExtensionRegistry(this); |
| } |
| |
| /** A (Descriptor, Message) pair, returned by lookup methods. */ |
| public static final class ExtensionInfo { |
| /** The extension's descriptor. */ |
| public final FieldDescriptor descriptor; |
| |
| /** |
| * A default instance of the extension's type, if it has a message type. |
| * Otherwise, {@code null}. |
| */ |
| public final Message defaultInstance; |
| |
| private ExtensionInfo(final FieldDescriptor descriptor) { |
| this.descriptor = descriptor; |
| defaultInstance = null; |
| } |
| private ExtensionInfo(final FieldDescriptor descriptor, |
| final Message defaultInstance) { |
| this.descriptor = descriptor; |
| this.defaultInstance = defaultInstance; |
| } |
| } |
| |
| /** |
| * Deprecated. Use {@link #findImmutableExtensionByName(String)} instead. |
| */ |
| public ExtensionInfo findExtensionByName(final String fullName) { |
| return findImmutableExtensionByName(fullName); |
| } |
| |
| /** |
| * Find an extension for immutable APIs by fully-qualified field name, |
| * in the proto namespace. i.e. {@code result.descriptor.fullName()} will |
| * match {@code fullName} if a match is found. |
| * |
| * @return Information about the extension if found, or {@code null} |
| * otherwise. |
| */ |
| public ExtensionInfo findImmutableExtensionByName(final String fullName) { |
| return immutableExtensionsByName.get(fullName); |
| } |
| |
| /** |
| * Find an extension for mutable APIs by fully-qualified field name, |
| * in the proto namespace. i.e. {@code result.descriptor.fullName()} will |
| * match {@code fullName} if a match is found. |
| * |
| * @return Information about the extension if found, or {@code null} |
| * otherwise. |
| */ |
| public ExtensionInfo findMutableExtensionByName(final String fullName) { |
| return mutableExtensionsByName.get(fullName); |
| } |
| |
| /** |
| * Deprecated. Use {@link #findImmutableExtensionByNumber( |
| * Descriptors.Descriptor, int)} |
| */ |
| public ExtensionInfo findExtensionByNumber( |
| final Descriptor containingType, final int fieldNumber) { |
| return findImmutableExtensionByNumber(containingType, fieldNumber); |
| } |
| |
| /** |
| * Find an extension by containing type and field number for immutable APIs. |
| * |
| * @return Information about the extension if found, or {@code null} |
| * otherwise. |
| */ |
| public ExtensionInfo findImmutableExtensionByNumber( |
| final Descriptor containingType, final int fieldNumber) { |
| return immutableExtensionsByNumber.get( |
| new DescriptorIntPair(containingType, fieldNumber)); |
| } |
| |
| /** |
| * Find an extension by containing type and field number for mutable APIs. |
| * |
| * @return Information about the extension if found, or {@code null} |
| * otherwise. |
| */ |
| public ExtensionInfo findMutableExtensionByNumber( |
| final Descriptor containingType, final int fieldNumber) { |
| return mutableExtensionsByNumber.get( |
| new DescriptorIntPair(containingType, fieldNumber)); |
| } |
| |
| /** |
| * Find all extensions for mutable APIs by fully-qualified name of |
| * extended class. Note that this method is more computationally expensive |
| * than getting a single extension by name or number. |
| * |
| * @return Information about the extensions found, or {@code null} if there |
| * are none. |
| */ |
| public Set<ExtensionInfo> getAllMutableExtensionsByExtendedType(final String fullName) { |
| HashSet<ExtensionInfo> extensions = new HashSet<ExtensionInfo>(); |
| for (DescriptorIntPair pair : mutableExtensionsByNumber.keySet()) { |
| if (pair.descriptor.getFullName().equals(fullName)) { |
| extensions.add(mutableExtensionsByNumber.get(pair)); |
| } |
| } |
| return extensions; |
| } |
| |
| /** |
| * Find all extensions for immutable APIs by fully-qualified name of |
| * extended class. Note that this method is more computationally expensive |
| * than getting a single extension by name or number. |
| * |
| * @return Information about the extensions found, or {@code null} if there |
| * are none. |
| */ |
| public Set<ExtensionInfo> getAllImmutableExtensionsByExtendedType(final String fullName) { |
| HashSet<ExtensionInfo> extensions = new HashSet<ExtensionInfo>(); |
| for (DescriptorIntPair pair : immutableExtensionsByNumber.keySet()) { |
| if (pair.descriptor.getFullName().equals(fullName)) { |
| extensions.add(immutableExtensionsByNumber.get(pair)); |
| } |
| } |
| return extensions; |
| } |
| |
| /** Add an extension from a generated file to the registry. */ |
| public void add(final Extension<?, ?> extension) { |
| if (extension.getExtensionType() != Extension.ExtensionType.IMMUTABLE && |
| extension.getExtensionType() != Extension.ExtensionType.MUTABLE) { |
| // do not support other extension types. ignore |
| return; |
| } |
| add(newExtensionInfo(extension), extension.getExtensionType()); |
| } |
| |
| static ExtensionInfo newExtensionInfo(final Extension<?, ?> extension) { |
| if (extension.getDescriptor().getJavaType() == |
| FieldDescriptor.JavaType.MESSAGE) { |
| if (extension.getMessageDefaultInstance() == null) { |
| throw new IllegalStateException( |
| "Registered message-type extension had null default instance: " + |
| extension.getDescriptor().getFullName()); |
| } |
| return new ExtensionInfo(extension.getDescriptor(), |
| (Message) extension.getMessageDefaultInstance()); |
| } else { |
| return new ExtensionInfo(extension.getDescriptor(), null); |
| } |
| } |
| |
| /** Add a non-message-type extension to the registry by descriptor. */ |
| public void add(final FieldDescriptor type) { |
| if (type.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { |
| throw new IllegalArgumentException( |
| "ExtensionRegistry.add() must be provided a default instance when " + |
| "adding an embedded message extension."); |
| } |
| ExtensionInfo info = new ExtensionInfo(type, null); |
| add(info, Extension.ExtensionType.IMMUTABLE); |
| add(info, Extension.ExtensionType.MUTABLE); |
| } |
| |
| /** Add a message-type extension to the registry by descriptor. */ |
| public void add(final FieldDescriptor type, final Message defaultInstance) { |
| if (type.getJavaType() != FieldDescriptor.JavaType.MESSAGE) { |
| throw new IllegalArgumentException( |
| "ExtensionRegistry.add() provided a default instance for a " + |
| "non-message extension."); |
| } |
| add(new ExtensionInfo(type, defaultInstance), |
| Extension.ExtensionType.IMMUTABLE); |
| } |
| |
| // ================================================================= |
| // Private stuff. |
| |
| private ExtensionRegistry() { |
| this.immutableExtensionsByName = new HashMap<String, ExtensionInfo>(); |
| this.mutableExtensionsByName = new HashMap<String, ExtensionInfo>(); |
| this.immutableExtensionsByNumber = |
| new HashMap<DescriptorIntPair, ExtensionInfo>(); |
| this.mutableExtensionsByNumber = |
| new HashMap<DescriptorIntPair, ExtensionInfo>(); |
| } |
| |
| private ExtensionRegistry(ExtensionRegistry other) { |
| super(other); |
| this.immutableExtensionsByName = |
| Collections.unmodifiableMap(other.immutableExtensionsByName); |
| this.mutableExtensionsByName = |
| Collections.unmodifiableMap(other.mutableExtensionsByName); |
| this.immutableExtensionsByNumber = |
| Collections.unmodifiableMap(other.immutableExtensionsByNumber); |
| this.mutableExtensionsByNumber = |
| Collections.unmodifiableMap(other.mutableExtensionsByNumber); |
| } |
| |
| private final Map<String, ExtensionInfo> immutableExtensionsByName; |
| private final Map<String, ExtensionInfo> mutableExtensionsByName; |
| private final Map<DescriptorIntPair, ExtensionInfo> immutableExtensionsByNumber; |
| private final Map<DescriptorIntPair, ExtensionInfo> mutableExtensionsByNumber; |
| |
| ExtensionRegistry(boolean empty) { |
| super(ExtensionRegistryLite.getEmptyRegistry()); |
| this.immutableExtensionsByName = |
| Collections.<String, ExtensionInfo>emptyMap(); |
| this.mutableExtensionsByName = |
| Collections.<String, ExtensionInfo>emptyMap(); |
| this.immutableExtensionsByNumber = |
| Collections.<DescriptorIntPair, ExtensionInfo>emptyMap(); |
| this.mutableExtensionsByNumber = |
| Collections.<DescriptorIntPair, ExtensionInfo>emptyMap(); |
| } |
| private static final ExtensionRegistry EMPTY = new ExtensionRegistry(true); |
| |
| private void add( |
| final ExtensionInfo extension, |
| final Extension.ExtensionType extensionType) { |
| if (!extension.descriptor.isExtension()) { |
| throw new IllegalArgumentException( |
| "ExtensionRegistry.add() was given a FieldDescriptor for a regular " + |
| "(non-extension) field."); |
| } |
| |
| Map<String, ExtensionInfo> extensionsByName; |
| Map<DescriptorIntPair, ExtensionInfo> extensionsByNumber; |
| switch (extensionType) { |
| case IMMUTABLE: |
| extensionsByName = immutableExtensionsByName; |
| extensionsByNumber = immutableExtensionsByNumber; |
| break; |
| case MUTABLE: |
| extensionsByName = mutableExtensionsByName; |
| extensionsByNumber = mutableExtensionsByNumber; |
| break; |
| default: |
| // Ignore the unknown supported type. |
| return; |
| } |
| |
| extensionsByName.put(extension.descriptor.getFullName(), extension); |
| extensionsByNumber.put( |
| new DescriptorIntPair(extension.descriptor.getContainingType(), |
| extension.descriptor.getNumber()), |
| extension); |
| |
| final FieldDescriptor field = extension.descriptor; |
| if (field.getContainingType().getOptions().getMessageSetWireFormat() && |
| field.getType() == FieldDescriptor.Type.MESSAGE && |
| field.isOptional() && |
| field.getExtensionScope() == field.getMessageType()) { |
| // This is an extension of a MessageSet type defined within the extension |
| // type's own scope. For backwards-compatibility, allow it to be looked |
| // up by type name. |
| extensionsByName.put(field.getMessageType().getFullName(), extension); |
| } |
| } |
| |
| /** A (GenericDescriptor, int) pair, used as a map key. */ |
| private static final class DescriptorIntPair { |
| private final Descriptor descriptor; |
| private final int number; |
| |
| DescriptorIntPair(final Descriptor 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; |
| } |
| } |
| } |