blob: 7239e9abf82070b09a0bbcf0ae8c0811e6f4cdee [file] [log] [blame]
Jon Skeet60c059b2008-10-23 21:17:56 +01001using System;
Jon Skeet68036862008-10-22 13:30:34 +01002using System.Collections.Generic;
3using System.Text;
4using System.Collections;
5using System.IO;
6using System.Reflection;
7
8namespace Google.ProtocolBuffers {
9
10 /// <summary>
11 /// Iterates over data created using a <see cref="MessageStreamWriter{T}" />.
12 /// Unlike MessageStreamWriter, this class is not usually constructed directly with
13 /// a stream; instead it is provided with a way of opening a stream when iteration
14 /// is started. The stream is closed when the iteration is completed or the enumerator
15 /// is disposed. (This occurs naturally when using <c>foreach</c>.)
16 /// </summary>
17 public class MessageStreamIterator<TMessage> : IEnumerable<TMessage>
18 where TMessage : IMessage<TMessage> {
19
20 private readonly StreamProvider streamProvider;
21 private readonly ExtensionRegistry extensionRegistry;
22
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.
69 /// This will check
70 /// </summary>
71 private static Type FindBuilderType() {
72 MethodInfo createBuilderMethod = typeof(TMessage).GetMethod("CreateBuilder", Type.EmptyTypes);
73 if (createBuilderMethod == null) {
74 throw new ArgumentException("Message type " + typeof(TMessage).FullName + " has no CreateBuilder method.");
75 }
76 if (createBuilderMethod.ReturnType == typeof(void)) {
77 throw new ArgumentException("CreateBuilder method in " + typeof(TMessage).FullName + " has void return type");
78 }
79 Type builderType = createBuilderMethod.ReturnType;
80 Type messageInterface = typeof(IMessage<,>).MakeGenericType(typeof(TMessage), builderType);
81 Type builderInterface = typeof(IBuilder<,>).MakeGenericType(typeof(TMessage), builderType);
82 if (Array.IndexOf(typeof(TMessage).GetInterfaces(), messageInterface) == -1) {
83 throw new ArgumentException("Message type " + typeof(TMessage) + " doesn't implement " + messageInterface.FullName);
84 }
85 if (Array.IndexOf(builderType.GetInterfaces(), builderInterface) == -1) {
86 throw new ArgumentException("Builder type " + typeof(TMessage) + " doesn't implement " + builderInterface.FullName);
87 }
88 return builderType;
89 }
90
91 /// <summary>
92 /// Method we'll use to build messageReader, with the first parameter fixed to TMessage.CreateBuilder. Note that we
93 /// have to introduce another type parameter (TMessage2) as we can't constrain TMessage for just a single method
94 /// (and we can't do it at the type level because we don't know TBuilder). However, by constraining TMessage2
95 /// to not only implement IMessage appropriately but also to derive from TMessage2, we can avoid doing a cast
96 /// for every message; the implicit reference conversion will be fine. In practice, TMessage2 and TMessage will
97 /// be the same type when we construct the generic method by reflection.
98 /// </summary>
99 private static TMessage BuildImpl<TMessage2, TBuilder>(Func<TBuilder> builderBuilder, CodedInputStream input, ExtensionRegistry registry)
100 where TBuilder : IBuilder<TMessage2, TBuilder>
101 where TMessage2 : TMessage, IMessage<TMessage2, TBuilder> {
102 TBuilder builder = builderBuilder();
103 input.ReadMessage(builder, registry);
104 return builder.Build();
105 }
106
107 private static readonly uint ExpectedTag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited);
108
109 private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry) {
110 if (messageReader == null) {
111 throw typeInitializationException;
112 }
113 this.streamProvider = streamProvider;
114 this.extensionRegistry = extensionRegistry;
115 }
116
117 /// <summary>
118 /// Creates a new instance which uses the same stream provider as this one,
119 /// but the specified extension registry.
120 /// </summary>
121 public MessageStreamIterator<TMessage> WithExtensionRegistry(ExtensionRegistry newRegistry) {
122 return new MessageStreamIterator<TMessage>(streamProvider, newRegistry);
123 }
124
125 public static MessageStreamIterator<TMessage> FromFile(string file) {
126 return new MessageStreamIterator<TMessage>(() => File.OpenRead(file), ExtensionRegistry.Empty);
127 }
128
129 public static MessageStreamIterator<TMessage> FromStreamProvider(StreamProvider streamProvider) {
130 return new MessageStreamIterator<TMessage>(streamProvider, ExtensionRegistry.Empty);
131 }
132
133 public IEnumerator<TMessage> GetEnumerator() {
134 using (Stream stream = streamProvider()) {
135 CodedInputStream input = CodedInputStream.CreateInstance(stream);
136 uint tag;
137 while ((tag = input.ReadTag()) != 0) {
138 if (tag != ExpectedTag) {
139 throw InvalidProtocolBufferException.InvalidMessageStreamTag();
140 }
141 yield return messageReader(input, extensionRegistry);
142 }
143 }
144 }
145
146 IEnumerator IEnumerable.GetEnumerator() {
147 return GetEnumerator();
148 }
149 }
150}