blob: 5ddfc62aa3883c7b8b82a83092c28042789b4edb [file] [log] [blame]
Jon Skeet60c059b2008-10-23 21:17:56 +01001using System;
Jon Skeet68036862008-10-22 13:30:34 +01002using System.Collections.Generic;
Jon Skeet68036862008-10-22 13:30:34 +01003using System.Collections;
4using System.IO;
5using System.Reflection;
6
7namespace Google.ProtocolBuffers {
8
9 /// <summary>
10 /// Iterates over data created using a <see cref="MessageStreamWriter{T}" />.
11 /// Unlike MessageStreamWriter, this class is not usually constructed directly with
12 /// a stream; instead it is provided with a way of opening a stream when iteration
13 /// is started. The stream is closed when the iteration is completed or the enumerator
14 /// is disposed. (This occurs naturally when using <c>foreach</c>.)
15 /// </summary>
16 public class MessageStreamIterator<TMessage> : IEnumerable<TMessage>
17 where TMessage : IMessage<TMessage> {
18
19 private readonly StreamProvider streamProvider;
20 private readonly ExtensionRegistry extensionRegistry;
Jon Skeet2178b932009-06-25 07:52:07 +010021 private readonly int sizeLimit;
Jon Skeet68036862008-10-22 13:30:34 +010022
23 /// <summary>
24 /// Delegate created via reflection trickery (once per type) to create a builder
25 /// and read a message from a CodedInputStream with it. Note that unlike in Java,
26 /// there's one static field per constructed type.
27 /// </summary>
28 private static readonly Func<CodedInputStream, ExtensionRegistry, TMessage> messageReader = BuildMessageReader();
29
30 /// <summary>
31 /// Any exception (within reason) thrown within messageReader is caught and rethrown in the constructor.
32 /// This makes life a lot simpler for the caller.
33 /// </summary>
34 private static Exception typeInitializationException;
35
36 /// <summary>
37 /// Creates the delegate later used to read messages. This is only called once per type, but to
38 /// avoid exceptions occurring at confusing times, if this fails it will set typeInitializationException
39 /// to the appropriate error and return null.
40 /// </summary>
41 private static Func<CodedInputStream, ExtensionRegistry, TMessage> BuildMessageReader() {
42 try {
43 Type builderType = FindBuilderType();
44
45 // Yes, it's redundant to find this again, but it's only the once...
46 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", Type.EmptyTypes);
47 Delegate builderBuilder = Delegate.CreateDelegate(
48 typeof(Func<>).MakeGenericType(builderType), null, createBuilderMethod);
49
50 MethodInfo buildMethod = typeof(MessageStreamIterator<TMessage>)
51 .GetMethod("BuildImpl", BindingFlags.Static | BindingFlags.NonPublic)
52 .MakeGenericMethod(typeof(TMessage), builderType);
53
54 return (Func<CodedInputStream, ExtensionRegistry, TMessage>)Delegate.CreateDelegate(
55 typeof(Func<CodedInputStream, ExtensionRegistry, TMessage>), builderBuilder, buildMethod);
56 } catch (ArgumentException e) {
57 typeInitializationException = e;
58 } catch (InvalidOperationException e) {
59 typeInitializationException = e;
60 } catch (InvalidCastException e) {
61 // Can't see why this would happen, but best to know about it.
62 typeInitializationException = e;
63 }
64 return null;
65 }
66
67 /// <summary>
68 /// Works out the builder type for TMessage, or throws an ArgumentException to explain why it can't.
Jon Skeet68036862008-10-22 13:30:34 +010069 /// </summary>
70 private static Type FindBuilderType() {
71 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", Type.EmptyTypes);
72 if (createBuilderMethod == null) {
73 throw new ArgumentException("Message type " + typeof(TMessage).FullName + " has no CreateBuilder method.");
74 }
75 if (createBuilderMethod.ReturnType == typeof(void)) {
76 throw new ArgumentException("CreateBuilder method in " + typeof(TMessage).FullName + " has void return type");
77 }
78 Type builderType = createBuilderMethod.ReturnType;
79 Type messageInterface = typeof(IMessage<,>).MakeGenericType(typeof(TMessage), builderType);
80 Type builderInterface = typeof(IBuilder<,>).MakeGenericType(typeof(TMessage), builderType);
81 if (Array.IndexOf(typeof(TMessage).GetInterfaces(), messageInterface) == -1) {
82 throw new ArgumentException("Message type " + typeof(TMessage) + " doesn't implement " + messageInterface.FullName);
83 }
84 if (Array.IndexOf(builderType.GetInterfaces(), builderInterface) == -1) {
85 throw new ArgumentException("Builder type " + typeof(TMessage) + " doesn't implement " + builderInterface.FullName);
86 }
87 return builderType;
88 }
89
Jon Skeetcb8644d2009-06-17 16:09:22 +010090// This is only ever fetched by reflection, so the compiler may
91// complain that it's unused
Jon Skeet36721732009-06-17 16:23:30 +010092#pragma warning disable 0169
Jon Skeet68036862008-10-22 13:30:34 +010093 /// <summary>
94 /// Method we'll use to build messageReader, with the first parameter fixed to TMessage.CreateBuilder. Note that we
95 /// have to introduce another type parameter (TMessage2) as we can't constrain TMessage for just a single method
96 /// (and we can't do it at the type level because we don't know TBuilder). However, by constraining TMessage2
97 /// to not only implement IMessage appropriately but also to derive from TMessage2, we can avoid doing a cast
98 /// for every message; the implicit reference conversion will be fine. In practice, TMessage2 and TMessage will
99 /// be the same type when we construct the generic method by reflection.
100 /// </summary>
101 private static TMessage BuildImpl<TMessage2, TBuilder>(Func<TBuilder> builderBuilder, CodedInputStream input, ExtensionRegistry registry)
102 where TBuilder : IBuilder<TMessage2, TBuilder>
103 where TMessage2 : TMessage, IMessage<TMessage2, TBuilder> {
104 TBuilder builder = builderBuilder();
105 input.ReadMessage(builder, registry);
106 return builder.Build();
Jon Skeet2178b932009-06-25 07:52:07 +0100107 }
Jon Skeetcb8644d2009-06-17 16:09:22 +0100108#pragma warning restore 0414
109
Jon Skeet68036862008-10-22 13:30:34 +0100110 private static readonly uint ExpectedTag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
111
Jon Skeet2178b932009-06-25 07:52:07 +0100112 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry, int sizeLimit) {
Jon Skeet68036862008-10-22 13:30:34 +0100113 if (messageReader == null) {
114 throw typeInitializationException;
115 }
116 this.streamProvider = streamProvider;
117 this.extensionRegistry = extensionRegistry;
Jon Skeet2178b932009-06-25 07:52:07 +0100118 this.sizeLimit = sizeLimit;
119 }
120
121 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry)
122 : this (streamProvider, extensionRegistry, CodedInputStream.DefaultSizeLimit) {
Jon Skeet68036862008-10-22 13:30:34 +0100123 }
124
125 /// <summary>
126 /// Creates a new instance which uses the same stream provider as this one,
127 /// but the specified extension registry.
128 /// </summary>
129 public MessageStreamIterator<TMessage> WithExtensionRegistry(ExtensionRegistry newRegistry) {
Jon Skeet2178b932009-06-25 07:52:07 +0100130 return new MessageStreamIterator<TMessage>(streamProvider, newRegistry, sizeLimit);
131 }
132
133 /// <summary>
134 /// Creates a new instance which uses the same stream provider and extension registry as this one,
135 /// but with the specified size limit. Note that this must be big enough for the largest message
136 /// and the tag and size preceding it.
137 /// </summary>
138 public MessageStreamIterator<TMessage> WithSizeLimit(int newSizeLimit) {
139 return new MessageStreamIterator<TMessage>(streamProvider, extensionRegistry, newSizeLimit);
Jon Skeet68036862008-10-22 13:30:34 +0100140 }
141
142 public static MessageStreamIterator<TMessage> FromFile(string file) {
143 return new MessageStreamIterator<TMessage>(() => File.OpenRead(file), ExtensionRegistry.Empty);
144 }
145
146 public static MessageStreamIterator<TMessage> FromStreamProvider(StreamProvider streamProvider) {
147 return new MessageStreamIterator<TMessage>(streamProvider, ExtensionRegistry.Empty);
148 }
149
150 public IEnumerator<TMessage> GetEnumerator() {
151 using (Stream stream = streamProvider()) {
152 CodedInputStream input = CodedInputStream.CreateInstance(stream);
Jon Skeet2178b932009-06-25 07:52:07 +0100153 input.SetSizeLimit(sizeLimit);
Jon Skeet68036862008-10-22 13:30:34 +0100154 uint tag;
155 while ((tag = input.ReadTag()) != 0) {
156 if (tag != ExpectedTag) {
157 throw InvalidProtocolBufferException.InvalidMessageStreamTag();
158 }
159 yield return messageReader(input, extensionRegistry);
Jon Skeet2178b932009-06-25 07:52:07 +0100160 input.ResetSizeCounter();
Jon Skeet68036862008-10-22 13:30:34 +0100161 }
162 }
163 }
164
165 IEnumerator IEnumerable.GetEnumerator() {
166 return GetEnumerator();
167 }
168 }
169}