blob: e8cc43062722374c61828d4216b3e26a75a06f7c [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;
21
22 /// <summary>
23 /// Delegate created via reflection trickery (once per type) to create a builder
24 /// and read a message from a CodedInputStream with it. Note that unlike in Java,
25 /// there's one static field per constructed type.
26 /// </summary>
27 private static readonly Func<CodedInputStream, ExtensionRegistry, TMessage> messageReader = BuildMessageReader();
28
29 /// <summary>
30 /// Any exception (within reason) thrown within messageReader is caught and rethrown in the constructor.
31 /// This makes life a lot simpler for the caller.
32 /// </summary>
33 private static Exception typeInitializationException;
34
35 /// <summary>
36 /// Creates the delegate later used to read messages. This is only called once per type, but to
37 /// avoid exceptions occurring at confusing times, if this fails it will set typeInitializationException
38 /// to the appropriate error and return null.
39 /// </summary>
40 private static Func<CodedInputStream, ExtensionRegistry, TMessage> BuildMessageReader() {
41 try {
42 Type builderType = FindBuilderType();
43
44 // Yes, it's redundant to find this again, but it's only the once...
45 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", Type.EmptyTypes);
46 Delegate builderBuilder = Delegate.CreateDelegate(
47 typeof(Func<>).MakeGenericType(builderType), null, createBuilderMethod);
48
49 MethodInfo buildMethod = typeof(MessageStreamIterator<TMessage>)
50 .GetMethod("BuildImpl", BindingFlags.Static | BindingFlags.NonPublic)
51 .MakeGenericMethod(typeof(TMessage), builderType);
52
53 return (Func<CodedInputStream, ExtensionRegistry, TMessage>)Delegate.CreateDelegate(
54 typeof(Func<CodedInputStream, ExtensionRegistry, TMessage>), builderBuilder, buildMethod);
55 } catch (ArgumentException e) {
56 typeInitializationException = e;
57 } catch (InvalidOperationException e) {
58 typeInitializationException = e;
59 } catch (InvalidCastException e) {
60 // Can't see why this would happen, but best to know about it.
61 typeInitializationException = e;
62 }
63 return null;
64 }
65
66 /// <summary>
67 /// Works out the builder type for TMessage, or throws an ArgumentException to explain why it can't.
Jon Skeet68036862008-10-22 13:30:34 +010068 /// </summary>
69 private static Type FindBuilderType() {
70 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", Type.EmptyTypes);
71 if (createBuilderMethod == null) {
72 throw new ArgumentException("Message type " + typeof(TMessage).FullName + " has no CreateBuilder method.");
73 }
74 if (createBuilderMethod.ReturnType == typeof(void)) {
75 throw new ArgumentException("CreateBuilder method in " + typeof(TMessage).FullName + " has void return type");
76 }
77 Type builderType = createBuilderMethod.ReturnType;
78 Type messageInterface = typeof(IMessage<,>).MakeGenericType(typeof(TMessage), builderType);
79 Type builderInterface = typeof(IBuilder<,>).MakeGenericType(typeof(TMessage), builderType);
80 if (Array.IndexOf(typeof(TMessage).GetInterfaces(), messageInterface) == -1) {
81 throw new ArgumentException("Message type " + typeof(TMessage) + " doesn't implement " + messageInterface.FullName);
82 }
83 if (Array.IndexOf(builderType.GetInterfaces(), builderInterface) == -1) {
84 throw new ArgumentException("Builder type " + typeof(TMessage) + " doesn't implement " + builderInterface.FullName);
85 }
86 return builderType;
87 }
88
Jon Skeetcb8644d2009-06-17 16:09:22 +010089// This is only ever fetched by reflection, so the compiler may
90// complain that it's unused
Jon Skeet36721732009-06-17 16:23:30 +010091#pragma warning disable 0169
Jon Skeet68036862008-10-22 13:30:34 +010092 /// <summary>
93 /// Method we'll use to build messageReader, with the first parameter fixed to TMessage.CreateBuilder. Note that we
94 /// have to introduce another type parameter (TMessage2) as we can't constrain TMessage for just a single method
95 /// (and we can't do it at the type level because we don't know TBuilder). However, by constraining TMessage2
96 /// to not only implement IMessage appropriately but also to derive from TMessage2, we can avoid doing a cast
97 /// for every message; the implicit reference conversion will be fine. In practice, TMessage2 and TMessage will
98 /// be the same type when we construct the generic method by reflection.
99 /// </summary>
100 private static TMessage BuildImpl<TMessage2, TBuilder>(Func<TBuilder> builderBuilder, CodedInputStream input, ExtensionRegistry registry)
101 where TBuilder : IBuilder<TMessage2, TBuilder>
102 where TMessage2 : TMessage, IMessage<TMessage2, TBuilder> {
103 TBuilder builder = builderBuilder();
104 input.ReadMessage(builder, registry);
105 return builder.Build();
Jon Skeetcb8644d2009-06-17 16:09:22 +0100106 }
107#pragma warning restore 0414
108
Jon Skeet68036862008-10-22 13:30:34 +0100109 private static readonly uint ExpectedTag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
110
111 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry) {
112 if (messageReader == null) {
113 throw typeInitializationException;
114 }
115 this.streamProvider = streamProvider;
116 this.extensionRegistry = extensionRegistry;
117 }
118
119 /// <summary>
120 /// Creates a new instance which uses the same stream provider as this one,
121 /// but the specified extension registry.
122 /// </summary>
123 public MessageStreamIterator<TMessage> WithExtensionRegistry(ExtensionRegistry newRegistry) {
124 return new MessageStreamIterator<TMessage>(streamProvider, newRegistry);
125 }
126
127 public static MessageStreamIterator<TMessage> FromFile(string file) {
128 return new MessageStreamIterator<TMessage>(() => File.OpenRead(file), ExtensionRegistry.Empty);
129 }
130
131 public static MessageStreamIterator<TMessage> FromStreamProvider(StreamProvider streamProvider) {
132 return new MessageStreamIterator<TMessage>(streamProvider, ExtensionRegistry.Empty);
133 }
134
135 public IEnumerator<TMessage> GetEnumerator() {
136 using (Stream stream = streamProvider()) {
137 CodedInputStream input = CodedInputStream.CreateInstance(stream);
138 uint tag;
139 while ((tag = input.ReadTag()) != 0) {
140 if (tag != ExpectedTag) {
141 throw InvalidProtocolBufferException.InvalidMessageStreamTag();
142 }
143 yield return messageReader(input, extensionRegistry);
144 }
145 }
146 }
147
148 IEnumerator IEnumerable.GetEnumerator() {
149 return GetEnumerator();
150 }
151 }
152}