blob: dec75733211234a0b11455f6f5f023fc591a391c [file] [log] [blame]
Jon Skeet68036862008-10-22 13:30:34 +01001// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc.
3// http://code.google.com/p/protobuf/
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16using System.Collections.Generic;
17using Google.ProtocolBuffers.Descriptors;
18using System;
19
20namespace Google.ProtocolBuffers {
21 /// <summary>
22 /// A table of known extensions, searchable by name or field number. When
23 /// parsing a protocol message that might have extensions, you must provide
24 /// an <see cref="ExtensionRegistry"/> in which you have registered any extensions
25 /// that you want to be able to parse. Otherwise, those extensions will just
26 /// be treated like unknown fields.
27 /// </summary>
28 /// <example>
29 /// For example, if you had the <c>.proto</c> file:
30 /// <code>
31 /// option java_class = "MyProto";
32 ///
33 /// message Foo {
34 /// extensions 1000 to max;
35 /// }
36 ///
37 /// extend Foo {
38 /// optional int32 bar;
39 /// }
40 /// </code>
41 ///
42 /// Then you might write code like:
43 ///
44 /// <code>
45 /// ExtensionRegistry registry = ExtensionRegistry.CreateInstance();
46 /// registry.Add(MyProto.Bar);
47 /// MyProto.Foo message = MyProto.Foo.ParseFrom(input, registry);
48 /// </code>
49 /// </example>
50 ///
51 /// <remarks>
52 /// <para>You might wonder why this is necessary. Two alternatives might come to
53 /// mind. First, you might imagine a system where generated extensions are
54 /// automatically registered when their containing classes are loaded. This
55 /// is a popular technique, but is bad design; among other things, it creates a
56 /// situation where behavior can change depending on what classes happen to be
57 /// loaded. It also introduces a security vulnerability, because an
58 /// unprivileged class could cause its code to be called unexpectedly from a
59 /// privileged class by registering itself as an extension of the right type.
60 /// </para>
61 /// <para>Another option you might consider is lazy parsing: do not parse an
62 /// extension until it is first requested, at which point the caller must
63 /// provide a type to use. This introduces a different set of problems. First,
64 /// it would require a mutex lock any time an extension was accessed, which
65 /// would be slow. Second, corrupt data would not be detected until first
66 /// access, at which point it would be much harder to deal with it. Third, it
67 /// could violate the expectation that message objects are immutable, since the
68 /// type provided could be any arbitrary message class. An unprivileged user
69 /// could take advantage of this to inject a mutable object into a message
70 /// belonging to privileged code and create mischief.</para>
71 /// </remarks>
72 public sealed class ExtensionRegistry {
73
74 private static readonly ExtensionRegistry empty = new ExtensionRegistry(
75 new Dictionary<string, ExtensionInfo>(),
76 new Dictionary<DescriptorIntPair, ExtensionInfo>(),
77 true);
78
79 private readonly IDictionary<string, ExtensionInfo> extensionsByName;
80 private readonly IDictionary<DescriptorIntPair, ExtensionInfo> extensionsByNumber;
81 private readonly bool readOnly;
82
83 private ExtensionRegistry(IDictionary<String, ExtensionInfo> extensionsByName,
84 IDictionary<DescriptorIntPair, ExtensionInfo> extensionsByNumber,
85 bool readOnly) {
86 this.extensionsByName = extensionsByName;
87 this.extensionsByNumber = extensionsByNumber;
88 this.readOnly = readOnly;
89 }
90
91 /// <summary>
92 /// Construct a new, empty instance.
93 /// </summary>
94 public static ExtensionRegistry CreateInstance() {
95 return new ExtensionRegistry(new Dictionary<string, ExtensionInfo>(),
96 new Dictionary<DescriptorIntPair, ExtensionInfo>(), false);
97 }
98
99 /// <summary>
100 /// Get the unmodifiable singleton empty instance.
101 /// </summary>
102 public static ExtensionRegistry Empty {
103 get { return empty; }
104 }
105
106 public ExtensionRegistry AsReadOnly() {
107 return new ExtensionRegistry(extensionsByName, extensionsByNumber, true);
108 }
109
110 /// <summary>
111 /// Finds an extension by fully-qualified field name, in the
112 /// proto namespace, i.e. result.Descriptor.FullName will match
113 /// <paramref name="fullName"/> if a match is found. A null
114 /// reference is returned if the extension can't be found.
115 /// </summary>
116 public ExtensionInfo this[string fullName] {
117 get {
118 ExtensionInfo ret;
119 extensionsByName.TryGetValue(fullName, out ret);
120 return ret;
121 }
122 }
123
124 /// <summary>
125 /// Finds an extension by containing type and field number.
126 /// A null reference is returned if the extension can't be found.
127 /// </summary>
128 public ExtensionInfo this[MessageDescriptor containingType, int fieldNumber] {
129 get {
130 ExtensionInfo ret;
131 extensionsByNumber.TryGetValue(new DescriptorIntPair(containingType, fieldNumber), out ret);
132 return ret;
133 }
134 }
135
136 /// <summary>
137 /// Add an extension from a generated file to the registry.
138 /// </summary>
139 public void Add<TExtension> (GeneratedExtensionBase<TExtension> extension) {
140 if (extension.Descriptor.MappedType == MappedType.Message) {
141 Add(new ExtensionInfo(extension.Descriptor, extension.MessageDefaultInstance));
142 } else {
143 Add(new ExtensionInfo(extension.Descriptor, null));
144 }
145 }
146
147 /// <summary>
148 /// Adds a non-message-type extension to the registry by descriptor.
149 /// </summary>
150 /// <param name="type"></param>
151 public void Add(FieldDescriptor type) {
152 if (type.MappedType == MappedType.Message) {
153 throw new ArgumentException("ExtensionRegistry.Add() must be provided a default instance "
154 + "when adding an embedded message extension.");
155 }
156 Add(new ExtensionInfo(type, null));
157 }
158
159 /// <summary>
160 /// Adds a message-type-extension to the registry by descriptor.
161 /// </summary>
162 /// <param name="type"></param>
163 /// <param name="defaultInstance"></param>
164 public void Add(FieldDescriptor type, IMessage defaultInstance) {
165 if (type.MappedType != MappedType.Message) {
166 throw new ArgumentException("ExtensionRegistry.Add() provided a default instance for a "
167 + "non-message extension.");
168 }
169 Add(new ExtensionInfo(type, defaultInstance));
170 }
171
172 private void Add(ExtensionInfo extension) {
173 if (readOnly) {
174 throw new InvalidOperationException("Cannot add entries to a read-only extension registry");
175 }
176 if (!extension.Descriptor.IsExtension) {
177 throw new ArgumentException("ExtensionRegistry.add() was given a FieldDescriptor for a "
178 + "regular (non-extension) field.");
179 }
180
181 extensionsByName[extension.Descriptor.FullName] = extension;
182 extensionsByNumber[new DescriptorIntPair(extension.Descriptor.ContainingType,
183 extension.Descriptor.FieldNumber)] = extension;
184
185 FieldDescriptor field = extension.Descriptor;
186 if (field.ContainingType.Options.MessageSetWireFormat
187 && field.FieldType == FieldType.Message
188 && field.IsOptional
189 && field.ExtensionScope == field.MessageType) {
190 // This is an extension of a MessageSet type defined within the extension
191 // type's own scope. For backwards-compatibility, allow it to be looked
192 // up by type name.
193 extensionsByName[field.MessageType.FullName] = extension;
194 }
195 }
196
197 /// <summary>
198 /// Nested type just used to represent a pair of MessageDescriptor and int, as
199 /// the key into the "by number" map.
200 /// </summary>
201 private struct DescriptorIntPair : IEquatable<DescriptorIntPair> {
202 readonly MessageDescriptor descriptor;
203 readonly int number;
204
205 internal DescriptorIntPair(MessageDescriptor descriptor, int number) {
206 this.descriptor = descriptor;
207 this.number = number;
208 }
209
210 public override int GetHashCode() {
211 return descriptor.GetHashCode() * ((1 << 16) - 1) + number;
212 }
213
214 public override bool Equals(object obj) {
215 if (!(obj is DescriptorIntPair)) {
216 return false;
217 }
218 return Equals((DescriptorIntPair)obj);
219 }
220
221 public bool Equals(DescriptorIntPair other) {
222 return descriptor == other.descriptor && number == other.number;
223 }
224 }
225 }
226}