Jon Skeet | 60c059b | 2008-10-23 21:17:56 +0100 | [diff] [blame] | 1 | using System; |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 2 | using System.Collections.Generic; |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 3 | using System.Collections; |
| 4 | using System.IO; |
| 5 | using System.Reflection; |
| 6 | |
| 7 | namespace 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 Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 21 | private readonly int sizeLimit; |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 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. |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 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 | |
Jon Skeet | cb8644d | 2009-06-17 16:09:22 +0100 | [diff] [blame] | 90 | // This is only ever fetched by reflection, so the compiler may |
| 91 | // complain that it's unused |
Jon Skeet | 3672173 | 2009-06-17 16:23:30 +0100 | [diff] [blame] | 92 | #pragma warning disable 0169 |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 93 | /// <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 Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 107 | } |
Jon Skeet | cb8644d | 2009-06-17 16:09:22 +0100 | [diff] [blame] | 108 | #pragma warning restore 0414 |
| 109 | |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 110 | private static readonly uint ExpectedTag = WireFormat.MakeTag(1, WireFormat.WireType.LengthDelimited); |
| 111 | |
Jon Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 112 | private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry, int sizeLimit) { |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 113 | if (messageReader == null) { |
| 114 | throw typeInitializationException; |
| 115 | } |
| 116 | this.streamProvider = streamProvider; |
| 117 | this.extensionRegistry = extensionRegistry; |
Jon Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 118 | this.sizeLimit = sizeLimit; |
| 119 | } |
| 120 | |
| 121 | private MessageStreamIterator(StreamProvider streamProvider, ExtensionRegistry extensionRegistry) |
| 122 | : this (streamProvider, extensionRegistry, CodedInputStream.DefaultSizeLimit) { |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 123 | } |
| 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 Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 130 | 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 Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 140 | } |
| 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 Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 153 | input.SetSizeLimit(sizeLimit); |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 154 | uint tag; |
| 155 | while ((tag = input.ReadTag()) != 0) { |
| 156 | if (tag != ExpectedTag) { |
| 157 | throw InvalidProtocolBufferException.InvalidMessageStreamTag(); |
| 158 | } |
| 159 | yield return messageReader(input, extensionRegistry); |
Jon Skeet | 2178b93 | 2009-06-25 07:52:07 +0100 | [diff] [blame^] | 160 | input.ResetSizeCounter(); |
Jon Skeet | 6803686 | 2008-10-22 13:30:34 +0100 | [diff] [blame] | 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | IEnumerator IEnumerable.GetEnumerator() { |
| 166 | return GetEnumerator(); |
| 167 | } |
| 168 | } |
| 169 | } |