// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.
// http://code.google.com/p/protobuf/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Collections.Generic;
using Google.ProtocolBuffers.Descriptors;
using System;
namespace Google.ProtocolBuffers {
///
/// TODO(jonskeet): Copy docs from Java
///
public sealed class ExtensionRegistry {
private static readonly ExtensionRegistry empty = new ExtensionRegistry(
new Dictionary(),
new Dictionary(),
true);
private readonly IDictionary extensionsByName;
private readonly IDictionary extensionsByNumber;
private readonly bool readOnly;
private ExtensionRegistry(IDictionary extensionsByName,
IDictionary extensionsByNumber,
bool readOnly) {
this.extensionsByName = extensionsByName;
this.extensionsByNumber = extensionsByNumber;
this.readOnly = readOnly;
}
///
/// Construct a new, empty instance.
///
public static ExtensionRegistry CreateInstance() {
return new ExtensionRegistry(new Dictionary(),
new Dictionary(), false);
}
///
/// Get the unmodifiable singleton empty instance.
///
public static ExtensionRegistry Empty {
get { return empty; }
}
public ExtensionRegistry AsReadOnly() {
return new ExtensionRegistry(extensionsByName, extensionsByNumber, true);
}
///
/// Finds an extension by fully-qualified field name, in the
/// proto namespace, i.e. result.Descriptor.FullName will match
/// if a match is found. A null
/// reference is returned if the extension can't be found.
///
public ExtensionInfo this[string fullName] {
get {
ExtensionInfo ret;
extensionsByName.TryGetValue(fullName, out ret);
return ret;
}
}
///
/// Finds an extension by containing type and field number.
/// A null reference is returned if the extension can't be found.
///
public ExtensionInfo this[MessageDescriptor containingType, int fieldNumber] {
get {
ExtensionInfo ret;
extensionsByNumber.TryGetValue(new DescriptorIntPair(containingType, fieldNumber), out ret);
return ret;
}
}
///
/// Add an extension from a generated file to the registry.
///
public void Add (GeneratedExtension extension)
where TContainer : IMessage {
if (extension.Descriptor.MappedType == MappedType.Message) {
Add(new ExtensionInfo(extension.Descriptor, extension.MessageDefaultInstance));
} else {
Add(new ExtensionInfo(extension.Descriptor, null));
}
}
///
/// Adds a non-message-type extension to the registry by descriptor.
///
///
public void Add(FieldDescriptor type) {
if (type.MappedType == MappedType.Message) {
throw new ArgumentException("ExtensionRegistry.Add() must be provided a default instance "
+ "when adding an embedded message extension.");
}
Add(new ExtensionInfo(type, null));
}
///
/// Adds a message-type-extension to the registry by descriptor.
///
///
///
public void Add(FieldDescriptor type, IMessage defaultInstance) {
if (type.MappedType != MappedType.Message) {
throw new ArgumentException("ExtensionRegistry.Add() provided a default instance for a "
+ "non-message extension.");
}
Add(new ExtensionInfo(type, defaultInstance));
}
private void Add(ExtensionInfo extension) {
if (readOnly) {
throw new InvalidOperationException("Cannot add entries to a read-only extension registry");
}
if (!extension.Descriptor.IsExtension) {
throw new ArgumentException("ExtensionRegistry.add() was given a FieldDescriptor for a "
+ "regular (non-extension) field.");
}
extensionsByName[extension.Descriptor.FullName] = extension;
extensionsByNumber[new DescriptorIntPair(extension.Descriptor.ContainingType,
extension.Descriptor.FieldNumber)] = extension;
FieldDescriptor field = extension.Descriptor;
if (field.ContainingType.Options.IsMessageSetWireFormat
&& field.FieldType == FieldType.Message
&& field.IsOptional
&& field.ExtensionScope == field.MessageType) {
// 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[field.MessageType.FullName] = extension;
}
}
///
/// Nested type just used to represent a pair of MessageDescriptor and int, as
/// the key into the "by number" map.
///
private struct DescriptorIntPair : IEquatable {
readonly MessageDescriptor descriptor;
readonly int number;
internal DescriptorIntPair(MessageDescriptor descriptor, int number) {
this.descriptor = descriptor;
this.number = number;
}
public override int GetHashCode() {
return descriptor.GetHashCode() * ((1 << 16) - 1) + number;
}
public override bool Equals(object obj) {
if (!(obj is DescriptorIntPair)) {
return false;
}
return Equals((DescriptorIntPair)obj);
}
public bool Equals(DescriptorIntPair other) {
return descriptor == other.descriptor && number == other.number;
}
}
}
}