Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 1 | using System; |
| 2 | using System.Collections.Generic; |
| 3 | using System.Text; |
| 4 | using System.Collections; |
| 5 | using System.IO; |
| 6 | using System.Reflection; |
| 7 | |
| 8 | namespace 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 | } |