blob: 0466262b4b38c25d0a16516fab515f881f841525 [file] [log] [blame]
Jon Skeet0aac0e42009-09-09 18:48:02 +01001#region Copyright notice and license
Jon Skeetad748532009-06-25 16:55:58 +01002// Protocol Buffers - Google's data interchange format
3// Copyright 2008 Google Inc. All rights reserved.
4// http://github.com/jskeet/dotnet-protobufs/
5// Original C++/Java/Python code:
6// http://code.google.com/p/protobuf/
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions are
10// met:
11//
12// * Redistributions of source code must retain the above copyright
13// notice, this list of conditions and the following disclaimer.
14// * Redistributions in binary form must reproduce the above
15// copyright notice, this list of conditions and the following disclaimer
16// in the documentation and/or other materials provided with the
17// distribution.
18// * Neither the name of Google Inc. nor the names of its
19// contributors may be used to endorse or promote products derived from
20// this software without specific prior written permission.
21//
22// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Jon Skeet0aac0e42009-09-09 18:48:02 +010033#endregion
34
Jon Skeet60c059b2008-10-23 21:17:56 +010035using System;
Jon Skeet68036862008-10-22 13:30:34 +010036using System.Collections.Generic;
Jon Skeet68036862008-10-22 13:30:34 +010037using System.Collections;
38using System.IO;
39using System.Reflection;
40
41namespace Google.ProtocolBuffers {
42
43 /// <summary>
44 /// Iterates over data created using a <see cref="MessageStreamWriter{T}" />.
45 /// Unlike MessageStreamWriter, this class is not usually constructed directly with
46 /// a stream; instead it is provided with a way of opening a stream when iteration
47 /// is started. The stream is closed when the iteration is completed or the enumerator
48 /// is disposed. (This occurs naturally when using <c>foreach</c>.)
49 /// </summary>
50 public class MessageStreamIterator<TMessage> : IEnumerable<TMessage>
51 where TMessage : IMessage<TMessage> {
52
53 private readonly StreamProvider streamProvider;
54 private readonly ExtensionRegistry extensionRegistry;
Jon Skeet2178b932009-06-25 07:52:07 +010055 private readonly int sizeLimit;
Jon Skeet68036862008-10-22 13:30:34 +010056
Jon Skeetb49d3c72009-11-03 16:51:01 +000057 // Type.EmptyTypes isn't present on the compact framework
58 private static readonly Type[] EmptyTypes = new Type[0];
59
Jon Skeet68036862008-10-22 13:30:34 +010060 /// <summary>
61 /// Delegate created via reflection trickery (once per type) to create a builder
62 /// and read a message from a CodedInputStream with it. Note that unlike in Java,
63 /// there's one static field per constructed type.
64 /// </summary>
65 private static readonly Func<CodedInputStream, ExtensionRegistry, TMessage> messageReader = BuildMessageReader();
66
67 /// <summary>
68 /// Any exception (within reason) thrown within messageReader is caught and rethrown in the constructor.
69 /// This makes life a lot simpler for the caller.
70 /// </summary>
71 private static Exception typeInitializationException;
72
73 /// <summary>
74 /// Creates the delegate later used to read messages. This is only called once per type, but to
75 /// avoid exceptions occurring at confusing times, if this fails it will set typeInitializationException
76 /// to the appropriate error and return null.
77 /// </summary>
78 private static Func<CodedInputStream, ExtensionRegistry, TMessage> BuildMessageReader() {
79 try {
80 Type builderType = FindBuilderType();
81
82 // Yes, it's redundant to find this again, but it's only the once...
Jon Skeetb49d3c72009-11-03 16:51:01 +000083 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", EmptyTypes);
Jon Skeet68036862008-10-22 13:30:34 +010084 Delegate builderBuilder = Delegate.CreateDelegate(
85 typeof(Func<>).MakeGenericType(builderType), null, createBuilderMethod);
86
87 MethodInfo buildMethod = typeof(MessageStreamIterator<TMessage>)
88 .GetMethod("BuildImpl", BindingFlags.Static | BindingFlags.NonPublic)
89 .MakeGenericMethod(typeof(TMessage), builderType);
90
91 return (Func<CodedInputStream, ExtensionRegistry, TMessage>)Delegate.CreateDelegate(
92 typeof(Func<CodedInputStream, ExtensionRegistry, TMessage>), builderBuilder, buildMethod);
93 } catch (ArgumentException e) {
94 typeInitializationException = e;
95 } catch (InvalidOperationException e) {
96 typeInitializationException = e;
97 } catch (InvalidCastException e) {
98 // Can't see why this would happen, but best to know about it.
99 typeInitializationException = e;
100 }
101 return null;
102 }
103
104 /// <summary>
105 /// Works out the builder type for TMessage, or throws an ArgumentException to explain why it can't.
Jon Skeet68036862008-10-22 13:30:34 +0100106 /// </summary>
107 private static Type FindBuilderType() {
Jon Skeetb49d3c72009-11-03 16:51:01 +0000108 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", EmptyTypes);
Jon Skeet68036862008-10-22 13:30:34 +0100109 if (createBuilderMethod == null) {
110 throw new ArgumentException("Message type " + typeof(TMessage).FullName + " has no CreateBuilder method.");
111 }
112 if (createBuilderMethod.ReturnType == typeof(void)) {
113 throw new ArgumentException("CreateBuilder method in " + typeof(TMessage).FullName + " has void return type");
114 }
115 Type builderType = createBuilderMethod.ReturnType;
116 Type messageInterface = typeof(IMessage<,>).MakeGenericType(typeof(TMessage), builderType);
117 Type builderInterface = typeof(IBuilder<,>).MakeGenericType(typeof(TMessage), builderType);
118 if (Array.IndexOf(typeof(TMessage).GetInterfaces(), messageInterface) == -1) {
119 throw new ArgumentException("Message type " + typeof(TMessage) + " doesn't implement " + messageInterface.FullName);
120 }
121 if (Array.IndexOf(builderType.GetInterfaces(), builderInterface) == -1) {
122 throw new ArgumentException("Builder type " + typeof(TMessage) + " doesn't implement " + builderInterface.FullName);
123 }
124 return builderType;
125 }
126
Jon Skeetcb8644d2009-06-17 16:09:22 +0100127// This is only ever fetched by reflection, so the compiler may
128// complain that it's unused
Jon Skeet36721732009-06-17 16:23:30 +0100129#pragma warning disable 0169
Jon Skeet68036862008-10-22 13:30:34 +0100130 /// <summary>
131 /// Method we'll use to build messageReader, with the first parameter fixed to TMessage.CreateBuilder. Note that we
132 /// have to introduce another type parameter (TMessage2) as we can't constrain TMessage for just a single method
133 /// (and we can't do it at the type level because we don't know TBuilder). However, by constraining TMessage2
134 /// to not only implement IMessage appropriately but also to derive from TMessage2, we can avoid doing a cast
135 /// for every message; the implicit reference conversion will be fine. In practice, TMessage2 and TMessage will
136 /// be the same type when we construct the generic method by reflection.
137 /// </summary>
138 private static TMessage BuildImpl<TMessage2, TBuilder>(Func<TBuilder> builderBuilder, CodedInputStream input, ExtensionRegistry registry)
139 where TBuilder : IBuilder<TMessage2, TBuilder>
140 where TMessage2 : TMessage, IMessage<TMessage2, TBuilder> {
141 TBuilder builder = builderBuilder();
142 input.ReadMessage(builder, registry);
143 return builder.Build();
Jon Skeet2178b932009-06-25 07:52:07 +0100144 }
Jon Skeetcb8644d2009-06-17 16:09:22 +0100145#pragma warning restore 0414
146
Jon Skeet68036862008-10-22 13:30:34 +0100147 private static readonly uint ExpectedTag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
148
Jon Skeet2178b932009-06-25 07:52:07 +0100149 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry, int sizeLimit) {
Jon Skeet68036862008-10-22 13:30:34 +0100150 if (messageReader == null) {
151 throw typeInitializationException;
152 }
153 this.streamProvider = streamProvider;
154 this.extensionRegistry = extensionRegistry;
Jon Skeet2178b932009-06-25 07:52:07 +0100155 this.sizeLimit = sizeLimit;
156 }
157
158 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry)
159 : this (streamProvider, extensionRegistry, CodedInputStream.DefaultSizeLimit) {
Jon Skeet68036862008-10-22 13:30:34 +0100160 }
161
162 /// <summary>
163 /// Creates a new instance which uses the same stream provider as this one,
164 /// but the specified extension registry.
165 /// </summary>
166 public MessageStreamIterator<TMessage> WithExtensionRegistry(ExtensionRegistry newRegistry) {
Jon Skeet2178b932009-06-25 07:52:07 +0100167 return new MessageStreamIterator<TMessage>(streamProvider, newRegistry, sizeLimit);
168 }
169
170 /// <summary>
171 /// Creates a new instance which uses the same stream provider and extension registry as this one,
172 /// but with the specified size limit. Note that this must be big enough for the largest message
173 /// and the tag and size preceding it.
174 /// </summary>
175 public MessageStreamIterator<TMessage> WithSizeLimit(int newSizeLimit) {
176 return new MessageStreamIterator<TMessage>(streamProvider, extensionRegistry, newSizeLimit);
Jon Skeet68036862008-10-22 13:30:34 +0100177 }
178
179 public static MessageStreamIterator<TMessage> FromFile(string file) {
180 return new MessageStreamIterator<TMessage>(() => File.OpenRead(file), ExtensionRegistry.Empty);
181 }
182
183 public static MessageStreamIterator<TMessage> FromStreamProvider(StreamProvider streamProvider) {
184 return new MessageStreamIterator<TMessage>(streamProvider, ExtensionRegistry.Empty);
185 }
186
187 public IEnumerator<TMessage> GetEnumerator() {
188 using (Stream stream = streamProvider()) {
189 CodedInputStream input = CodedInputStream.CreateInstance(stream);
Jon Skeet2178b932009-06-25 07:52:07 +0100190 input.SetSizeLimit(sizeLimit);
Jon Skeet68036862008-10-22 13:30:34 +0100191 uint tag;
192 while ((tag = input.ReadTag()) != 0) {
193 if (tag != ExpectedTag) {
194 throw InvalidProtocolBufferException.InvalidMessageStreamTag();
195 }
196 yield return messageReader(input, extensionRegistry);
Jon Skeet2178b932009-06-25 07:52:07 +0100197 input.ResetSizeCounter();
Jon Skeet68036862008-10-22 13:30:34 +0100198 }
199 }
200 }
201
202 IEnumerator IEnumerable.GetEnumerator() {
203 return GetEnumerator();
204 }
205 }
206}