blob: cb1ead1a212c7ab279b6e0b0ac3ed74ca124507c [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.
68 /// This will check
69 /// </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
90 /// <summary>
91 /// Method we'll use to build messageReader, with the first parameter fixed to TMessage.CreateBuilder. Note that we
92 /// have to introduce another type parameter (TMessage2) as we can't constrain TMessage for just a single method
93 /// (and we can't do it at the type level because we don't know TBuilder). However, by constraining TMessage2
94 /// to not only implement IMessage appropriately but also to derive from TMessage2, we can avoid doing a cast
95 /// for every message; the implicit reference conversion will be fine. In practice, TMessage2 and TMessage will
96 /// be the same type when we construct the generic method by reflection.
97 /// </summary>
98 private static TMessage BuildImpl<TMessage2, TBuilder>(Func<TBuilder> builderBuilder, CodedInputStream input, ExtensionRegistry registry)
99 where TBuilder : IBuilder<TMessage2, TBuilder>
100 where TMessage2 : TMessage, IMessage<TMessage2, TBuilder> {
101 TBuilder builder = builderBuilder();
102 input.ReadMessage(builder, registry);
103 return builder.Build();
104 }
105
106 private static readonly uint ExpectedTag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
107
108 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry) {
109 if (messageReader == null) {
110 throw typeInitializationException;
111 }
112 this.streamProvider = streamProvider;
113 this.extensionRegistry = extensionRegistry;
114 }
115
116 /// <summary>
117 /// Creates a new instance which uses the same stream provider as this one,
118 /// but the specified extension registry.
119 /// </summary>
120 public MessageStreamIterator<TMessage> WithExtensionRegistry(ExtensionRegistry newRegistry) {
121 return new MessageStreamIterator<TMessage>(streamProvider, newRegistry);
122 }
123
124 public static MessageStreamIterator<TMessage> FromFile(string file) {
125 return new MessageStreamIterator<TMessage>(() => File.OpenRead(file), ExtensionRegistry.Empty);
126 }
127
128 public static MessageStreamIterator<TMessage> FromStreamProvider(StreamProvider streamProvider) {
129 return new MessageStreamIterator<TMessage>(streamProvider, ExtensionRegistry.Empty);
130 }
131
132 public IEnumerator<TMessage> GetEnumerator() {
133 using (Stream stream = streamProvider()) {
134 CodedInputStream input = CodedInputStream.CreateInstance(stream);
135 uint tag;
136 while ((tag = input.ReadTag()) != 0) {
137 if (tag != ExpectedTag) {
138 throw InvalidProtocolBufferException.InvalidMessageStreamTag();
139 }
140 yield return messageReader(input, extensionRegistry);
141 }
142 }
143 }
144
145 IEnumerator IEnumerable.GetEnumerator() {
146 return GetEnumerator();
147 }
148 }
149}