aboutsummaryrefslogtreecommitdiff
path: root/src/ProtoGen
diff options
context:
space:
mode:
authorJon Skeet <skeet@pobox.com>2008-10-22 13:30:34 +0100
committerJon Skeet <skeet@pobox.com>2008-10-22 13:30:34 +0100
commit6803686bc06c4d96afd9bd2637f7b37a58596699 (patch)
tree4b21c563f4cd4e399fbc0b253bc2f15e822eae88 /src/ProtoGen
parentf0589506c96600dcd01319b9d1929d87505f3daa (diff)
downloadprotobuf-6803686bc06c4d96afd9bd2637f7b37a58596699.tar.gz
protobuf-6803686bc06c4d96afd9bd2637f7b37a58596699.tar.bz2
protobuf-6803686bc06c4d96afd9bd2637f7b37a58596699.zip
First cut at new layout
Diffstat (limited to 'src/ProtoGen')
-rw-r--r--src/ProtoGen/DependencyResolutionException.cs17
-rw-r--r--src/ProtoGen/DescriptorUtil.cs108
-rw-r--r--src/ProtoGen/EnumFieldGenerator.cs75
-rw-r--r--src/ProtoGen/EnumGenerator.cs19
-rw-r--r--src/ProtoGen/ExtensionGenerator.cs37
-rw-r--r--src/ProtoGen/FieldGeneratorBase.cs130
-rw-r--r--src/ProtoGen/Generator.cs138
-rw-r--r--src/ProtoGen/GeneratorOptions.cs69
-rw-r--r--src/ProtoGen/Helpers.cs78
-rw-r--r--src/ProtoGen/IFieldSourceGenerator.cs11
-rw-r--r--src/ProtoGen/ISourceGenerator.cs5
-rw-r--r--src/ProtoGen/InvalidOptionsException.cs36
-rw-r--r--src/ProtoGen/MessageFieldGenerator.cs92
-rw-r--r--src/ProtoGen/MessageGenerator.cs453
-rw-r--r--src/ProtoGen/PrimitiveFieldGenerator.cs70
-rw-r--r--src/ProtoGen/Program.cs47
-rw-r--r--src/ProtoGen/Properties/AssemblyInfo.cs42
-rw-r--r--src/ProtoGen/Properties/Google.ProtocolBuffers.ProtoGen.snkbin0 -> 596 bytes
-rw-r--r--src/ProtoGen/ProtoGen.csproj85
-rw-r--r--src/ProtoGen/RepeatedEnumFieldGenerator.cs90
-rw-r--r--src/ProtoGen/RepeatedMessageFieldGenerator.cs100
-rw-r--r--src/ProtoGen/RepeatedPrimitiveFieldGenerator.cs84
-rw-r--r--src/ProtoGen/ServiceGenerator.cs138
-rw-r--r--src/ProtoGen/SourceFileGenerator.cs29
-rw-r--r--src/ProtoGen/SourceGeneratorBase.cs50
-rw-r--r--src/ProtoGen/SourceGenerators.cs42
-rw-r--r--src/ProtoGen/UmbrellaClassGenerator.cs96
-rw-r--r--src/ProtoGen/app.config3
28 files changed, 2144 insertions, 0 deletions
diff --git a/src/ProtoGen/DependencyResolutionException.cs b/src/ProtoGen/DependencyResolutionException.cs
new file mode 100644
index 00000000..4e483fc1
--- /dev/null
+++ b/src/ProtoGen/DependencyResolutionException.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Exception thrown when dependencies within a descriptor set can't be resolved.
+ /// </summary>
+ public sealed class DependencyResolutionException : Exception {
+ public DependencyResolutionException(string message) : base(message) {
+ }
+
+ public DependencyResolutionException(string format, params object[] args)
+ : base(string.Format(format, args)) {
+ }
+ }
+}
diff --git a/src/ProtoGen/DescriptorUtil.cs b/src/ProtoGen/DescriptorUtil.cs
new file mode 100644
index 00000000..e70ad468
--- /dev/null
+++ b/src/ProtoGen/DescriptorUtil.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+using Google.ProtocolBuffers.DescriptorProtos;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Utility class for determining namespaces etc.
+ /// </summary>
+ internal static class DescriptorUtil {
+
+ internal static bool NestClasses(IDescriptor descriptor) {
+ // Defaults to false
+ return descriptor.File.Options.GetExtension(CSharpOptions.CSharpNestClasses);
+ }
+
+ internal static string GetNamespace(FileDescriptor descriptor) {
+ if (descriptor.Name == "google/protobuf/descriptor.proto") {
+ return typeof(DescriptorProtoFile).Namespace;
+ }
+ return descriptor.Options.HasExtension(CSharpOptions.CSharpNamespace) ?
+ descriptor.Options.GetExtension(CSharpOptions.CSharpNamespace) : descriptor.Package;
+ }
+
+ // Groups are hacky: The name of the field is just the lower-cased name
+ // of the group type. In C#, though, we would like to retain the original
+ // capitalization of the type name.
+ internal static string GetFieldName(FieldDescriptor descriptor) {
+ if (descriptor.FieldType == FieldType.Group) {
+ return descriptor.MessageType.Name;
+ } else {
+ return descriptor.Name;
+ }
+ }
+
+ internal static string GetClassName(IDescriptor descriptor) {
+ return ToCSharpName(descriptor.FullName, descriptor.File);
+ }
+
+ internal static string GetFullUmbrellaClassName(FileDescriptor descriptor) {
+ string result = GetNamespace(descriptor);
+ if (result != "") result += '.';
+ result += GetUmbrellaClassName(descriptor);
+ return "global::" + result;
+ }
+
+ internal static string GetUmbrellaClassName(FileDescriptor descriptor) {
+ if (descriptor.Name == "google/protobuf/descriptor.proto") {
+ return typeof(DescriptorProtoFile).Name;
+ }
+ FileOptions options = descriptor.Options;
+ if (options.HasExtension(CSharpOptions.CSharpUmbrellaClassname)) {
+ return descriptor.Options.GetExtension(CSharpOptions.CSharpUmbrellaClassname);
+ }
+ int lastSlash = descriptor.Name.LastIndexOf('/');
+ string baseName = descriptor.Name.Substring(lastSlash + 1);
+ return Helpers.UnderscoresToPascalCase(StripProto(baseName));
+ }
+
+ private static string StripProto(string text) {
+ if (!Helpers.StripSuffix(ref text, ".protodevel")) {
+ Helpers.StripSuffix(ref text, ".proto");
+ }
+ return text;
+ }
+
+ private static string ToCSharpName(string name, FileDescriptor file) {
+ string result;
+ if (!NestClasses(file)) {
+ result = GetNamespace(file);
+ } else {
+ result = GetUmbrellaClassName(file);
+ }
+ if (result != "") {
+ result += '.';
+ }
+ string classname;
+ if (file.Package == "") {
+ classname = name;
+ } else {
+ // Strip the proto package from full_name since we've replaced it with
+ // the C# namespace.
+ classname = name.Substring(file.Package.Length + 1);
+ }
+ result += classname.Replace(".", ".Types.");
+ return "global::" + result;
+ }
+
+ internal static string GetMappedTypeName(MappedType type) {
+ switch(type) {
+ case MappedType.Int32: return "int";
+ case MappedType.Int64: return "long";
+ case MappedType.UInt32: return "uint";
+ case MappedType.UInt64: return "ulong";
+ case MappedType.Single: return "float";
+ case MappedType.Double: return "double";
+ case MappedType.Boolean: return "bool";
+ case MappedType.String: return "string";
+ case MappedType.ByteString: return "pb::ByteString";
+ case MappedType.Enum: return null;
+ case MappedType.Message: return null;
+ default:
+ throw new ArgumentOutOfRangeException("Unknown mapped type " + type);
+ }
+ }
+ }
+}
diff --git a/src/ProtoGen/EnumFieldGenerator.cs b/src/ProtoGen/EnumFieldGenerator.cs
new file mode 100644
index 00000000..9f6a56f2
--- /dev/null
+++ b/src/ProtoGen/EnumFieldGenerator.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class EnumFieldGenerator : FieldGeneratorBase, IFieldSourceGenerator {
+ internal EnumFieldGenerator(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void GenerateMembers(TextGenerator writer) {
+ writer.WriteLine("private bool has{0};", CapitalizedName);
+ writer.WriteLine("private {0} {1}_ = {2};", TypeName, Name, DefaultValue);
+ writer.WriteLine("public bool Has{0} {{", CapitalizedName);
+ writer.WriteLine(" get {{ return has{0}; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} {1} {{", TypeName, PropertyName);
+ writer.WriteLine(" get {{ return {0}_; }}", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuilderMembers(TextGenerator writer) {
+ writer.WriteLine("public bool Has{0} {{", CapitalizedName);
+ writer.WriteLine(" get {{ return result.Has{0}; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} {1} {{", TypeName, PropertyName);
+ writer.WriteLine(" get {{ return result.{0}; }}", PropertyName);
+ writer.WriteLine(" set {{ Set{0}(value); }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.has{0} = true;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = value;", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Clear{0}() {{", CapitalizedName);
+ writer.WriteLine(" result.has{0} = false;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = {1};", Name, DefaultValue);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ }
+
+ public void GenerateMergingCode(TextGenerator writer) {
+ writer.WriteLine("if (other.Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" {0} = other.{0};", PropertyName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuildingCode(TextGenerator writer) {
+ // Nothing to do here for enum types
+ }
+
+ public void GenerateParsingCode(TextGenerator writer) {
+ // TODO(jonskeet): Make a more efficient way of doing this
+ writer.WriteLine("int rawValue = input.ReadEnum();");
+ writer.WriteLine("if (!global::System.Enum.IsDefined(typeof({0}), rawValue)) {{", TypeName);
+ writer.WriteLine(" unknownFields.MergeVarintField({0}, (ulong) rawValue);", Number);
+ writer.WriteLine("} else {");
+ writer.WriteLine(" {0} = ({1}) rawValue;", PropertyName, TypeName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializationCode(TextGenerator writer) {
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" output.WriteEnum({0}, (int) {1});", Number, PropertyName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializedSizeCode(TextGenerator writer) {
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" size += pb::CodedOutputStream.ComputeEnumSize({0}, (int) {1});", Number, PropertyName);
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/EnumGenerator.cs b/src/ProtoGen/EnumGenerator.cs
new file mode 100644
index 00000000..0de6b89c
--- /dev/null
+++ b/src/ProtoGen/EnumGenerator.cs
@@ -0,0 +1,19 @@
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class EnumGenerator : SourceGeneratorBase<EnumDescriptor>, ISourceGenerator {
+ internal EnumGenerator(EnumDescriptor descriptor) : base(descriptor) {
+ }
+
+ public void Generate(TextGenerator writer) {
+ writer.WriteLine("{0} enum {1} {{", ClassAccessLevel, Descriptor.Name);
+ writer.Indent();
+ foreach (EnumValueDescriptor value in Descriptor.Values) {
+ writer.WriteLine("{0} = {1},", value.Name, value.Number);
+ }
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/src/ProtoGen/ExtensionGenerator.cs b/src/ProtoGen/ExtensionGenerator.cs
new file mode 100644
index 00000000..9c235456
--- /dev/null
+++ b/src/ProtoGen/ExtensionGenerator.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class ExtensionGenerator : SourceGeneratorBase<FieldDescriptor>, ISourceGenerator {
+ internal ExtensionGenerator(FieldDescriptor descriptor) : base(descriptor) {
+ }
+
+ public void Generate(TextGenerator writer) {
+ string name = Helpers.UnderscoresToPascalCase(DescriptorUtil.GetFieldName(Descriptor));
+
+ string type;
+ switch (Descriptor.MappedType) {
+ case MappedType.Message:
+ type = DescriptorUtil.GetClassName(Descriptor.MessageType);
+ break;
+ case MappedType.Enum:
+ type = DescriptorUtil.GetClassName(Descriptor.EnumType);
+ break;
+ default:
+ type = DescriptorUtil.GetMappedTypeName(Descriptor.MappedType);
+ break;
+ }
+
+ if (Descriptor.IsRepeated) {
+ writer.WriteLine("{0} static readonly", ClassAccessLevel);
+ writer.WriteLine(" pb::GeneratedExtensionBase<scg::IList<{0}>> {1} =", type, name);
+ writer.WriteLine(" pb::GeneratedRepeatExtension<{0}>.CreateInstance(Descriptor.Extensions[{1}]);", type, Descriptor.Index);
+ } else {
+ writer.WriteLine("{0} static readonly pb::GeneratedExtensionBase<{1}> {2} =", ClassAccessLevel, type, name);
+ writer.WriteLine(" pb::GeneratedSingleExtension<{0}>.CreateInstance(Descriptor.Extensions[{1}]);", type, Descriptor.Index);
+ }
+ }
+ }
+}
diff --git a/src/ProtoGen/FieldGeneratorBase.cs b/src/ProtoGen/FieldGeneratorBase.cs
new file mode 100644
index 00000000..d050ac84
--- /dev/null
+++ b/src/ProtoGen/FieldGeneratorBase.cs
@@ -0,0 +1,130 @@
+using System;
+using Google.ProtocolBuffers.Descriptors;
+using System.Globalization;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal abstract class FieldGeneratorBase : SourceGeneratorBase<FieldDescriptor> {
+ protected FieldGeneratorBase(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ private static bool AllPrintableAscii(string text) {
+ foreach (char c in text) {
+ if (c < 0x20 || c > 0x7e) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected string DefaultValue {
+ get {
+ string suffix = "";
+ switch (Descriptor.FieldType) {
+ case FieldType.Float: suffix = "F"; break;
+ case FieldType.Double: suffix = "D"; break;
+ case FieldType.Int64: suffix = "L"; break;
+ case FieldType.UInt64: suffix = "UL"; break;
+ }
+ switch (Descriptor.FieldType) {
+ case FieldType.Float:
+ case FieldType.Double:
+ case FieldType.Int32:
+ case FieldType.Int64:
+ case FieldType.SInt32:
+ case FieldType.SInt64:
+ case FieldType.SFixed32:
+ case FieldType.SFixed64:
+ case FieldType.UInt32:
+ case FieldType.UInt64:
+ case FieldType.Fixed32:
+ case FieldType.Fixed64:
+ // The simple Object.ToString converts using the current culture.
+ // We want to always use the invariant culture so it's predictable.
+ IConvertible value = (IConvertible) Descriptor.DefaultValue;
+ return value.ToString(CultureInfo.InvariantCulture) + suffix;
+ case FieldType.Bool:
+ return (bool) Descriptor.DefaultValue ? "true" : "false";
+
+ case FieldType.Bytes:
+ if (!Descriptor.HasDefaultValue) {
+ return "pb::ByteString.Empty";
+ }
+ return string.Format("(pb::ByteString) {0}.Descriptor.Fields[{1}].DefaultValue", DescriptorUtil.GetClassName(Descriptor.ContainingType), Descriptor.Index);
+ case FieldType.String:
+ if (AllPrintableAscii(Descriptor.Proto.DefaultValue)) {
+ // All chars are ASCII and printable. In this case we only
+ // need to escape quotes and backslashes.
+ return "\"" + Descriptor.Proto.DefaultValue
+ .Replace("\\", "\\\\")
+ .Replace("'", "\\'")
+ .Replace("\"", "\\\"")
+ + "\"";
+ }
+ return string.Format("(string) {0}.Descriptor.Fields[{1}].DefaultValue", DescriptorUtil.GetClassName(Descriptor.ContainingType), Descriptor.Index);
+ case FieldType.Enum:
+ return TypeName + "." + ((EnumValueDescriptor) Descriptor.DefaultValue).Name;
+ case FieldType.Message:
+ case FieldType.Group:
+ return TypeName + ".DefaultInstance";
+ default:
+ throw new InvalidOperationException("Invalid field descriptor type");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Usually the same as CapitalizedName, except when the enclosing type has the same name,
+ /// in which case an underscore is appended.
+ /// </summary>
+ protected string PropertyName {
+ get {
+ string ret = CapitalizedName;
+ if (ret == Descriptor.ContainingType.Name) {
+ ret += "_";
+ }
+ return ret;
+ }
+ }
+
+ protected string CapitalizedName {
+ get { return Helpers.UnderscoresToPascalCase(DescriptorUtil.GetFieldName(Descriptor)); }
+ }
+
+ protected string Name {
+ get { return Helpers.UnderscoresToCamelCase(DescriptorUtil.GetFieldName(Descriptor)); }
+ }
+
+ protected int Number {
+ get { return Descriptor.FieldNumber; }
+ }
+
+ protected string TypeName {
+ get {
+ switch (Descriptor.FieldType) {
+ case FieldType.Enum:
+ return DescriptorUtil.GetClassName(Descriptor.EnumType);
+ case FieldType.Message:
+ case FieldType.Group:
+ return DescriptorUtil.GetClassName(Descriptor.MessageType);
+ default:
+ return DescriptorUtil.GetMappedTypeName(Descriptor.MappedType);
+ }
+ }
+ }
+
+ protected string MessageOrGroup {
+ get { return Descriptor.FieldType == FieldType.Group ? "Group" : "Message"; }
+ }
+
+ /// <summary>
+ /// Returns the type name as used in CodedInputStream method names: SFixed32, UInt32 etc.
+ /// </summary>
+ protected string CapitalizedTypeName {
+ get {
+ // Our enum names match perfectly. How serendipitous.
+ return Descriptor.FieldType.ToString();
+ }
+ }
+ }
+}
diff --git a/src/ProtoGen/Generator.cs b/src/ProtoGen/Generator.cs
new file mode 100644
index 00000000..f656386b
--- /dev/null
+++ b/src/ProtoGen/Generator.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.DescriptorProtos;
+using System.IO;
+using Google.ProtocolBuffers.Descriptors;
+using Google.ProtocolBuffers.Collections;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Code generator for protocol buffers. Only C# is supported at the moment.
+ /// </summary>
+ public sealed class Generator {
+
+ readonly GeneratorOptions options;
+
+ private Generator(GeneratorOptions options) {
+ options.Validate();
+ this.options = options;
+ }
+
+ /// <summary>
+ /// Returns a generator configured with the specified options.
+ /// </summary>
+ public static Generator CreateGenerator(GeneratorOptions options) {
+ return new Generator(options);
+ }
+
+ public void Generate() {
+ foreach (string inputFile in options.InputFiles) {
+ FileDescriptorSet descriptorProtos;
+ ExtensionRegistry extensionRegistry = ExtensionRegistry.CreateInstance();
+ extensionRegistry.Add(CSharpOptions.CSharpUmbrellaClassname);
+ extensionRegistry.Add(CSharpOptions.CSharpMultipleFiles);
+ extensionRegistry.Add(CSharpOptions.CSharpNamespace);
+ extensionRegistry.Add(CSharpOptions.CSharpNestClasses);
+ extensionRegistry.Add(CSharpOptions.CSharpPublicClasses);
+ using (Stream inputStream = File.OpenRead(inputFile)) {
+ descriptorProtos = FileDescriptorSet.ParseFrom(inputStream, extensionRegistry);
+ }
+ IList<FileDescriptor> descriptors = ConvertDescriptors(descriptorProtos);
+
+ foreach (FileDescriptor descriptor in descriptors) {
+ Generate(descriptor);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Generates code for a particular file. All dependencies must
+ /// already have been resolved.
+ /// </summary>
+ private void Generate(FileDescriptor descriptor) {
+ string umbrellaClass = DescriptorUtil.GetUmbrellaClassName(descriptor);
+ string ns = DescriptorUtil.GetNamespace(descriptor);
+ using (TextWriter textWriter = File.CreateText(Path.Combine(options.OutputDirectory, umbrellaClass + ".cs"))) {
+ TextGenerator writer = new TextGenerator(textWriter);
+
+ UmbrellaClassGenerator ucg = new UmbrellaClassGenerator(descriptor);
+ ucg.Generate(writer);
+ /*
+ GenerateSiblings(umbrellaSource, descriptor, descriptor.MessageTypes);
+ GenerateSiblings(umbrellaSource, descriptor, descriptor.EnumTypes);
+ GenerateSiblings(umbrellaSource, descriptor, descriptor.Services);*/
+ }
+ }
+
+ private static void GenerateSiblings<T>(SourceFileGenerator parentSourceGenerator, FileDescriptor file, IEnumerable<T> siblings)
+ where T : IDescriptor {
+ }
+
+ /// <summary>
+ /// Resolves any dependencies and converts FileDescriptorProtos into FileDescriptors.
+ /// The list returned is in the same order as the protos are listed in the descriptor set.
+ /// Note: this method is internal rather than private to allow testing.
+ /// </summary>
+ /// <exception cref="DependencyResolutionException">Not all dependencies could be resolved.</exception>
+ internal static IList<FileDescriptor> ConvertDescriptors(FileDescriptorSet descriptorProtos) {
+ // Simple strategy: Keep going through the list of protos to convert, only doing ones where
+ // we've already converted all the dependencies, until we get to a stalemate
+ IList<FileDescriptorProto> fileList = descriptorProtos.FileList;
+ FileDescriptor[] converted = new FileDescriptor[fileList.Count];
+
+ Dictionary<string, FileDescriptor> convertedMap = new Dictionary<string, FileDescriptor>();
+
+ int totalConverted = 0;
+
+ bool madeProgress = true;
+ while (madeProgress && totalConverted < converted.Length) {
+ madeProgress = false;
+ for (int i = 0; i < converted.Length; i++) {
+ if (converted[i] != null) {
+ // Already done this one
+ continue;
+ }
+ FileDescriptorProto candidate = fileList[i];
+ FileDescriptor[] dependencies = new FileDescriptor[candidate.DependencyList.Count];
+ bool foundAllDependencies = true;
+ for (int j = 0; j < dependencies.Length; j++) {
+ if (!convertedMap.TryGetValue(candidate.DependencyList[j], out dependencies[j])) {
+ foundAllDependencies = false;
+ break;
+ }
+ }
+ if (!foundAllDependencies) {
+ continue;
+ }
+ madeProgress = true;
+ totalConverted++;
+ converted[i] = FileDescriptor.BuildFrom(candidate, dependencies);
+ convertedMap[candidate.Name] = converted[i];
+ }
+ }
+ if (!madeProgress) {
+ StringBuilder remaining = new StringBuilder();
+ for (int i = 0; i < converted.Length; i++) {
+ if (converted[i] == null) {
+ if (remaining.Length != 0) {
+ remaining.Append(", ");
+ }
+ FileDescriptorProto failure = fileList[i];
+ remaining.Append(failure.Name);
+ remaining.Append(":");
+ foreach (string dependency in failure.DependencyList) {
+ if (!convertedMap.ContainsKey(dependency)) {
+ remaining.Append(" ");
+ remaining.Append(dependency);
+ }
+ }
+ remaining.Append(";");
+ }
+ }
+ throw new DependencyResolutionException("Unable to resolve all dependencies: " + remaining);
+ }
+ return Lists.AsReadOnly(converted);
+ }
+ }
+}
diff --git a/src/ProtoGen/GeneratorOptions.cs b/src/ProtoGen/GeneratorOptions.cs
new file mode 100644
index 00000000..c37352ed
--- /dev/null
+++ b/src/ProtoGen/GeneratorOptions.cs
@@ -0,0 +1,69 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+
+ /// <summary>
+ /// All the configuration required for the generator - where to generate
+ /// output files, the location of input files etc. While this isn't immutable
+ /// in practice, the contents shouldn't be changed after being passed to
+ /// the generator.
+ /// </summary>
+ public sealed class GeneratorOptions {
+
+ public string OutputDirectory { get; set; }
+ public IList<string> InputFiles { get; set; }
+
+ /// <summary>
+ /// Attempts to validate the options, but doesn't throw an exception if they're invalid.
+ /// Instead, when this method returns false, the output variable will contain a collection
+ /// of reasons for the validation failure.
+ /// </summary>
+ /// <param name="reasons">Variable to receive a list of reasons in case of validation failure.</param>
+ /// <returns>true if the options are valid; false otherwise</returns>
+ public bool TryValidate(out IList<string> reasons) {
+ List<string> tmpReasons = new List<string>();
+
+ // Output directory validation
+ if (string.IsNullOrEmpty(OutputDirectory)) {
+ tmpReasons.Add("No output directory specified");
+ } else {
+ if (!Directory.Exists(OutputDirectory)) {
+ tmpReasons.Add("Specified output directory (" + OutputDirectory + " doesn't exist.");
+ }
+ }
+
+ // Input file validation (just in terms of presence)
+ if (InputFiles == null || InputFiles.Count == 0) {
+ tmpReasons.Add("No input files specified");
+ } else {
+ foreach (string input in InputFiles) {
+ FileInfo fi = new FileInfo(input);
+ if (!fi.Exists) {
+ tmpReasons.Add("Input file " + input + " doesn't exist.");
+ }
+ }
+ }
+
+ if (tmpReasons.Count != 0) {
+ reasons = tmpReasons;
+ return false;
+ }
+
+ reasons = null;
+ return true;
+ }
+
+ /// <summary>
+ /// Validates that all the options have been set and are valid,
+ /// throwing an exception if they haven't.
+ /// </summary>
+ /// <exception cref="InvalidOptionsException">The options are invalid.</exception>
+ public void Validate() {
+ IList<string> reasons;
+ if (!TryValidate(out reasons)) {
+ throw new InvalidOptionsException(reasons);
+ }
+ }
+ }
+}
diff --git a/src/ProtoGen/Helpers.cs b/src/ProtoGen/Helpers.cs
new file mode 100644
index 00000000..79a2912c
--- /dev/null
+++ b/src/ProtoGen/Helpers.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Text;
+using Google.ProtocolBuffers.DescriptorProtos;
+using Google.ProtocolBuffers.Descriptors;
+namespace Google.ProtocolBuffers.ProtoGen {
+
+ /// <summary>
+ /// Helpers to resolve class names etc.
+ /// </summary>
+ internal static class Helpers {
+ internal static string UnderscoresToPascalCase(string input) {
+ return UnderscoresToPascalOrCamelCase(input, true);
+ }
+
+ internal static string UnderscoresToCamelCase(string input) {
+ return UnderscoresToPascalOrCamelCase(input, false);
+ }
+
+ internal static void WriteNamespaces(TextGenerator writer) {
+ writer.WriteLine("using pb = global::Google.ProtocolBuffers;");
+ writer.WriteLine("using pbc = global::Google.ProtocolBuffers.Collections;");
+ writer.WriteLine("using pbd = global::Google.ProtocolBuffers.Descriptors;");
+ writer.WriteLine("using scg = global::System.Collections.Generic;");
+ }
+
+ /// <summary>
+ /// Converts a string to Pascal or Camel case. The first letter is capitalized or
+ /// lower-cased depending on <paramref name="pascal"/> is true.
+ /// After the first letter, any punctuation is removed but triggers capitalization
+ /// of the next letter. Digits are preserved but trigger capitalization of the next
+ /// letter.
+ /// All capitalisation is done in the invariant culture.
+ /// </summary>
+ private static string UnderscoresToPascalOrCamelCase(string input, bool pascal) {
+ StringBuilder result = new StringBuilder();
+ bool capitaliseNext = pascal;
+ for (int i=0; i < input.Length; i++) {
+ char c = input[i];
+ if ('a' <= c && c <= 'z') {
+ if (capitaliseNext) {
+ result.Append(char.ToUpperInvariant(c));
+ } else {
+ result.Append(c);
+ }
+ capitaliseNext = false;
+ } else if ('A' <= c && c <= 'Z') {
+ if (i == 0 && !pascal) {
+ // Force first letter to lower-case unless explicitly told to
+ // capitalize it.
+ result.Append(char.ToLowerInvariant(c));
+ } else {
+ // Capital letters after the first are left as-is.
+ result.Append(c);
+ }
+ capitaliseNext = false;
+ } else if ('0' <= c && c <= '9') {
+ result.Append(c);
+ capitaliseNext = true;
+ } else {
+ capitaliseNext = true;
+ }
+ }
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Attempts to strip a suffix from a string, returning whether
+ /// or not the suffix was actually present.
+ /// </summary>
+ internal static bool StripSuffix(ref string text, string suffix) {
+ if (text.EndsWith(suffix)) {
+ text = text.Substring(0, text.Length - suffix.Length);
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/ProtoGen/IFieldSourceGenerator.cs b/src/ProtoGen/IFieldSourceGenerator.cs
new file mode 100644
index 00000000..a64043c8
--- /dev/null
+++ b/src/ProtoGen/IFieldSourceGenerator.cs
@@ -0,0 +1,11 @@
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal interface IFieldSourceGenerator {
+ void GenerateMembers(TextGenerator writer);
+ void GenerateBuilderMembers(TextGenerator writer);
+ void GenerateMergingCode(TextGenerator writer);
+ void GenerateBuildingCode(TextGenerator writer);
+ void GenerateParsingCode(TextGenerator writer);
+ void GenerateSerializationCode(TextGenerator writer);
+ void GenerateSerializedSizeCode(TextGenerator writer);
+ }
+}
diff --git a/src/ProtoGen/ISourceGenerator.cs b/src/ProtoGen/ISourceGenerator.cs
new file mode 100644
index 00000000..16e7c29a
--- /dev/null
+++ b/src/ProtoGen/ISourceGenerator.cs
@@ -0,0 +1,5 @@
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal interface ISourceGenerator {
+ void Generate(TextGenerator writer);
+ }
+}
diff --git a/src/ProtoGen/InvalidOptionsException.cs b/src/ProtoGen/InvalidOptionsException.cs
new file mode 100644
index 00000000..aee03228
--- /dev/null
+++ b/src/ProtoGen/InvalidOptionsException.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Collections;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Exception thrown to indicate that the options passed were invalid.
+ /// </summary>
+ public sealed class InvalidOptionsException : Exception {
+
+ private readonly IList<string> reasons;
+
+ /// <summary>
+ /// An immutable list of reasons why the options were invalid.
+ /// </summary>
+ public IList<string> Reasons {
+ get { return reasons; }
+ }
+
+ public InvalidOptionsException(IList<string> reasons)
+ : base(BuildMessage(reasons)) {
+ this.reasons = Lists.AsReadOnly(reasons);
+ }
+
+ private static string BuildMessage(IEnumerable<string> reasons) {
+ StringBuilder builder = new StringBuilder("Invalid options:");
+ builder.AppendLine();
+ foreach (string reason in reasons) {
+ builder.Append(" ");
+ builder.AppendLine(reason);
+ }
+ return builder.ToString();
+ }
+ }
+}
diff --git a/src/ProtoGen/MessageFieldGenerator.cs b/src/ProtoGen/MessageFieldGenerator.cs
new file mode 100644
index 00000000..c73edfc0
--- /dev/null
+++ b/src/ProtoGen/MessageFieldGenerator.cs
@@ -0,0 +1,92 @@
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class MessageFieldGenerator : FieldGeneratorBase, IFieldSourceGenerator {
+
+ internal MessageFieldGenerator(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void GenerateMembers(TextGenerator writer) {
+ writer.WriteLine("private bool has{0};", CapitalizedName);
+ writer.WriteLine("private {0} {1}_ = {2};", TypeName, Name, DefaultValue);
+ writer.WriteLine("public bool Has{0} {{", CapitalizedName);
+ writer.WriteLine(" get {{ return has{0}; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} {1} {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return {0}_; }}", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuilderMembers(TextGenerator writer) {
+ writer.WriteLine("public bool Has{0} {{", CapitalizedName);
+ writer.WriteLine(" get {{ return result.Has{0}; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} {1} {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}; }}", CapitalizedName);
+ writer.WriteLine(" set {{ Set{0}(value); }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.has{0} = true;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = value;", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}({1}.Builder builderForValue) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.has{0} = true;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = builderForValue.Build();", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Merge{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" if (result.Has{0} &&", CapitalizedName);
+ writer.WriteLine(" result.{0}_ != {1}) {{", Name, DefaultValue);
+ writer.WriteLine(" result.{0}_ = {1}.CreateBuilder(result.{0}_).MergeFrom(value).BuildPartial();", Name, TypeName);
+ writer.WriteLine(" } else {");
+ writer.WriteLine(" result.{0}_ = value;", Name);
+ writer.WriteLine(" }");
+ writer.WriteLine(" result.has{0} = true;", CapitalizedName);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Clear{0}() {{", CapitalizedName);
+ writer.WriteLine(" result.has{0} = false;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = {1};", Name, DefaultValue);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ }
+
+ public void GenerateMergingCode(TextGenerator writer) {
+ writer.WriteLine("if (other.Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" Merge{0}(other.{0});", CapitalizedName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuildingCode(TextGenerator writer) {
+ // Nothing to do for singular fields
+ }
+
+ public void GenerateParsingCode(TextGenerator writer) {
+ writer.WriteLine("{0}.Builder subBuilder = {0}.CreateBuilder();", TypeName);
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" subBuilder.MergeFrom({0});", CapitalizedName);
+ writer.WriteLine("}");
+ if (Descriptor.FieldType == FieldType.Group) {
+ writer.WriteLine("input.ReadGroup({0}, subBuilder, extensionRegistry);", Number);
+ } else {
+ writer.WriteLine("input.ReadMessage(subBuilder, extensionRegistry);");
+ }
+ writer.WriteLine("{0} = subBuilder.BuildPartial();", CapitalizedName);
+ }
+
+ public void GenerateSerializationCode(TextGenerator writer) {
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" output.Write{0}({1}, {2});", MessageOrGroup, Number, CapitalizedName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializedSizeCode(TextGenerator writer) {
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" size += pb::CodedOutputStream.Compute{0}Size({1}, {2});",
+ MessageOrGroup, Number, CapitalizedName);
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/MessageGenerator.cs b/src/ProtoGen/MessageGenerator.cs
new file mode 100644
index 00000000..95de1a38
--- /dev/null
+++ b/src/ProtoGen/MessageGenerator.cs
@@ -0,0 +1,453 @@
+using System.Collections;
+using Google.ProtocolBuffers.Descriptors;
+using Google.ProtocolBuffers.DescriptorProtos;
+using System.Collections.Generic;
+
+using ExtensionRange = Google.ProtocolBuffers.DescriptorProtos.DescriptorProto.Types.ExtensionRange;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class MessageGenerator : SourceGeneratorBase<MessageDescriptor>, ISourceGenerator {
+ internal MessageGenerator(MessageDescriptor descriptor) : base(descriptor) {
+ }
+
+ private string ClassName {
+ get { return Descriptor.Name; }
+ }
+
+ private string FullClassName {
+ get { return DescriptorUtil.GetClassName(Descriptor); }
+ }
+
+ /// <summary>
+ /// Get an identifier that uniquely identifies this type within the file.
+ /// This is used to declare static variables related to this type at the
+ /// outermost file scope.
+ /// </summary>
+ static string GetUniqueFileScopeIdentifier(IDescriptor descriptor) {
+ return "static_" + descriptor.FullName.Replace(".", "_");
+ }
+
+ internal void GenerateStaticVariables(TextGenerator writer) {
+ // Because descriptor.proto (Google.ProtocolBuffers.DescriptorProtos) is
+ // used in the construction of descriptors, we have a tricky bootstrapping
+ // problem. To help control static initialization order, we make sure all
+ // descriptors and other static data that depends on them are members of
+ // the proto-descriptor class. This way, they will be initialized in
+ // a deterministic order.
+
+ string identifier = GetUniqueFileScopeIdentifier(Descriptor);
+
+ // The descriptor for this type.
+ string access = Descriptor.File.Options.GetExtension(CSharpOptions.CSharpNestClasses) ? "private" : "internal";
+ writer.WriteLine("{0} static readonly pbd::MessageDescriptor internal__{1}__Descriptor", access, identifier);
+ if (Descriptor.ContainingType == null) {
+ writer.WriteLine(" = Descriptor.MessageTypes[{0}];", Descriptor.Index);
+ } else {
+ writer.WriteLine(" = internal__{0}__Descriptor.NestedTypes[{1}];", GetUniqueFileScopeIdentifier(Descriptor.ContainingType), Descriptor.Index);
+ }
+ writer.WriteLine("{0} static pb::FieldAccess.FieldAccessorTable<{1}, {1}.Builder> internal__{2}__FieldAccessorTable",
+ access, FullClassName, identifier);
+ writer.WriteLine(" = new pb::FieldAccess.FieldAccessorTable<{0}, {0}.Builder>(internal__{1}__Descriptor,",
+ FullClassName, identifier);
+ writer.Print(" new string[] { ");
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ writer.Write("\"{0}\", ", Helpers.UnderscoresToPascalCase(DescriptorUtil.GetFieldName(field)));
+ }
+ writer.WriteLine("});");
+
+ // Generate static members for all nested types.
+ foreach (MessageDescriptor nestedMessage in Descriptor.NestedTypes) {
+ new MessageGenerator(nestedMessage).GenerateStaticVariables(writer);
+ }
+ }
+
+ public void Generate(TextGenerator writer) {
+ writer.WriteLine("{0} sealed partial class {1} : pb::{2}Message<{1}, {1}.Builder> {{",
+ ClassAccessLevel, ClassName, Descriptor.Proto.ExtensionRangeCount > 0 ? "Extendable" : "Generated");
+ writer.Indent();
+ // Must call BuildPartial() to make sure all lists are made read-only
+ writer.WriteLine("private static readonly {0} defaultInstance = new Builder().BuildPartial();", ClassName);
+ writer.WriteLine("public static {0} DefaultInstance {{", ClassName);
+ writer.WriteLine(" get { return defaultInstance; }");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override {0} DefaultInstanceForType {{", ClassName);
+ writer.WriteLine(" get { return defaultInstance; }");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("protected override {0} ThisMessage {{", ClassName);
+ writer.WriteLine(" get { return this; }");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public static pbd::MessageDescriptor Descriptor {");
+ writer.WriteLine(" get {{ return {0}.internal__{1}__Descriptor; }}", DescriptorUtil.GetFullUmbrellaClassName(Descriptor.File),
+ GetUniqueFileScopeIdentifier(Descriptor));
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("protected override pb::FieldAccess.FieldAccessorTable<{0}, {0}.Builder> InternalFieldAccessors {{", ClassName);
+ writer.WriteLine(" get {{ return {0}.internal__{1}__FieldAccessorTable; }}", DescriptorUtil.GetFullUmbrellaClassName(Descriptor.File),
+ GetUniqueFileScopeIdentifier(Descriptor));
+ writer.WriteLine("}");
+ writer.WriteLine();
+
+ // Extensions don't need to go in an extra nested type
+ WriteChildren(writer, null, Descriptor.Extensions);
+
+ if (Descriptor.EnumTypes.Count + Descriptor.NestedTypes.Count > 0) {
+ writer.WriteLine("#region Nested types");
+ writer.WriteLine("public static class Types {");
+ writer.Indent();
+ WriteChildren(writer, null, Descriptor.EnumTypes);
+ WriteChildren(writer, null, Descriptor.NestedTypes);
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine("#endregion");
+ writer.WriteLine();
+ }
+
+ foreach(FieldDescriptor fieldDescriptor in Descriptor.Fields) {
+ // Rats: we lose the debug comment here :(
+ SourceGenerators.CreateFieldGenerator(fieldDescriptor).GenerateMembers(writer);
+ writer.WriteLine();
+ }
+
+ if (Descriptor.File.Options.OptimizeFor == FileOptions.Types.OptimizeMode.SPEED) {
+ GenerateIsInitialized(writer);
+ GenerateMessageSerializationMethods(writer);
+ }
+
+ GenerateParseFromMethods(writer);
+ GenerateBuilder(writer);
+ }
+
+ private void GenerateMessageSerializationMethods(TextGenerator writer) {
+ List<FieldDescriptor> sortedFields = new List<FieldDescriptor>(Descriptor.Fields);
+ sortedFields.Sort((f1, f2) => f1.FieldNumber.CompareTo(f2.FieldNumber));
+
+ List<ExtensionRange> sortedExtensions = new List<ExtensionRange>(Descriptor.Proto.ExtensionRangeList);
+ sortedExtensions.Sort((r1, r2) => (r1.Start.CompareTo(r2.Start)));
+
+ writer.WriteLine("public override void WriteTo(pb::CodedOutputStream output) {");
+ writer.Indent();
+ if (Descriptor.Proto.ExtensionRangeList.Count > 0) {
+ writer.WriteLine("pb::ExtendableMessage<{0}, {0}.Builder>.ExtensionWriter extensionWriter = CreateExtensionWriter(this);",
+ ClassName);
+ }
+
+ // Merge the fields and the extension ranges, both sorted by field number.
+ for (int i = 0, j = 0; i < Descriptor.Fields.Count || j < sortedExtensions.Count; ) {
+ if (i == Descriptor.Fields.Count) {
+ GenerateSerializeOneExtensionRange(writer, sortedExtensions[j++]);
+ } else if (j == sortedExtensions.Count) {
+ GenerateSerializeOneField(writer, sortedFields[i++]);
+ } else if (sortedFields[i].FieldNumber < sortedExtensions[j].Start) {
+ GenerateSerializeOneField(writer, sortedFields[i++]);
+ } else {
+ GenerateSerializeOneExtensionRange(writer, sortedExtensions[j++]);
+ }
+ }
+
+ if (Descriptor.Proto.Options.MessageSetWireFormat) {
+ writer.WriteLine("UnknownFields.WriteAsMessageSetTo(output);");
+ } else {
+ writer.WriteLine("UnknownFields.WriteTo(output);");
+ }
+
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("private int memoizedSerializedSize = -1;");
+ writer.WriteLine("public override int SerializedSize {");
+ writer.Indent();
+ writer.WriteLine("get {");
+ writer.Indent();
+ writer.WriteLine("int size = memoizedSerializedSize;");
+ writer.WriteLine("if (size != -1) return size;");
+ writer.WriteLine();
+ writer.WriteLine("size = 0;");
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ SourceGenerators.CreateFieldGenerator(field).GenerateSerializedSizeCode(writer);
+ }
+ if (Descriptor.Proto.ExtensionRangeCount > 0) {
+ writer.WriteLine("size += ExtensionsSerializedSize;");
+ }
+
+ if (Descriptor.Options.MessageSetWireFormat) {
+ writer.WriteLine("size += UnknownFields.SerializedSizeAsMessageSet;");
+ } else {
+ writer.WriteLine("size += UnknownFields.SerializedSize;");
+ }
+ writer.WriteLine("memoizedSerializedSize = size;");
+ writer.WriteLine("return size;");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+
+ private static void GenerateSerializeOneField(TextGenerator writer, FieldDescriptor fieldDescriptor) {
+ SourceGenerators.CreateFieldGenerator(fieldDescriptor).GenerateSerializationCode(writer);
+ }
+
+ private static void GenerateSerializeOneExtensionRange(TextGenerator writer, ExtensionRange extensionRange) {
+ writer.WriteLine("extensionWriter.WriteUntil({0}, output);", extensionRange.End);
+ }
+
+ private void GenerateParseFromMethods(TextGenerator writer) {
+ // Note: These are separate from GenerateMessageSerializationMethods()
+ // because they need to be generated even for messages that are optimized
+ // for code size.
+
+ writer.WriteLine("public static {0} ParseFrom(pb::ByteString data) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(pb::ByteString data, pb::ExtensionRegistry extensionRegistry) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(byte[] data) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(data)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(byte[] data, pb::ExtensionRegistry extensionRegistry) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(data, extensionRegistry)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(global::System.IO.Stream input) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(global::System.IO.Stream input, pb::ExtensionRegistry extensionRegistry) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(pb::CodedInputStream input) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(input)).BuildParsed();");
+ writer.WriteLine("}");
+ writer.WriteLine("public static {0} ParseFrom(pb::CodedInputStream input, pb::ExtensionRegistry extensionRegistry) {{", ClassName);
+ writer.WriteLine(" return ((Builder) CreateBuilder().MergeFrom(input, extensionRegistry)).BuildParsed();");
+ writer.WriteLine("}");
+ }
+
+ /// <summary>
+ /// Returns whether or not the specified message type has any required fields.
+ /// If it doesn't, calls to check for initialization can be optimised.
+ /// TODO(jonskeet): Move this into MessageDescriptor?
+ /// </summary>
+ private static bool HasRequiredFields(MessageDescriptor descriptor, Dictionary<MessageDescriptor,object> alreadySeen) {
+ if (alreadySeen.ContainsKey(descriptor)) {
+ // The type is already in cache. This means that either:
+ // a. The type has no required fields.
+ // b. We are in the midst of checking if the type has required fields,
+ // somewhere up the stack. In this case, we know that if the type
+ // has any required fields, they'll be found when we return to it,
+ // and the whole call to HasRequiredFields() will return true.
+ // Therefore, we don't have to check if this type has required fields
+ // here.
+ return false;
+ }
+ alreadySeen[descriptor] = descriptor; // Value is irrelevant
+
+ // If the type has extensions, an extension with message type could contain
+ // required fields, so we have to be conservative and assume such an
+ // extension exists.
+ if (descriptor.Extensions.Count > 0) {
+ return true;
+ }
+
+ foreach (FieldDescriptor field in descriptor.Fields) {
+ if (field.IsRequired) {
+ return true;
+ }
+ // Message or group
+ if (field.MappedType == MappedType.Message) {
+ if (HasRequiredFields(field.MessageType, alreadySeen)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void GenerateBuilder(TextGenerator writer) {
+ writer.WriteLine("public static Builder CreateBuilder() { return new Builder(); }");
+ writer.WriteLine("public override Builder CreateBuilderForType() { return new Builder(); }");
+ writer.WriteLine("public static Builder CreateBuilder({0} prototype) {{", ClassName);
+ writer.WriteLine(" return (Builder) new Builder().MergeFrom(prototype);");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("{0} sealed partial class Builder : pb::{2}Builder<{1}, Builder> {{",
+ ClassAccessLevel, ClassName, Descriptor.Proto.ExtensionRangeCount > 0 ? "Extendable" : "Generated");
+ writer.Indent();
+ writer.WriteLine("protected override Builder ThisBuilder {");
+ writer.WriteLine(" get { return this; }");
+ writer.WriteLine("}");
+ GenerateCommonBuilderMethods(writer);
+ if (Descriptor.File.Options.OptimizeFor == FileOptions.Types.OptimizeMode.SPEED) {
+ GenerateBuilderParsingMethods(writer);
+ }
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ writer.WriteLine();
+ // No field comment :(
+ SourceGenerators.CreateFieldGenerator(field).GenerateBuilderMembers(writer);
+ }
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+
+ private void GenerateCommonBuilderMethods(TextGenerator writer) {
+ writer.WriteLine("{0} Builder() {{}}", ClassAccessLevel);
+ writer.WriteLine();
+ writer.WriteLine("{0} result = new {0}();", ClassName);
+ writer.WriteLine();
+ writer.WriteLine("protected override {0} MessageBeingBuilt {{", ClassName);
+ writer.WriteLine(" get { return result; }");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override Builder Clear() {");
+ writer.WriteLine(" result = new {0}();", ClassName);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override Builder Clone() {");
+ writer.WriteLine(" return new Builder().MergeFrom(result);");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override pbd::MessageDescriptor DescriptorForType {");
+ writer.WriteLine(" get {{ return {0}.Descriptor; }}", ClassName);
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override {0} DefaultInstanceForType {{", ClassName);
+ writer.WriteLine(" get {{ return {0}.DefaultInstance; }}", ClassName);
+ writer.WriteLine("}");
+ writer.WriteLine();
+
+ writer.WriteLine("public override {0} BuildPartial() {{", ClassName);
+ writer.Indent();
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ SourceGenerators.CreateFieldGenerator(field).GenerateBuildingCode(writer);
+ }
+ writer.WriteLine("{0} returnMe = result;", ClassName);
+ writer.WriteLine("result = null;");
+ writer.WriteLine("return returnMe;");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+
+ if (Descriptor.File.Options.OptimizeFor == FileOptions.Types.OptimizeMode.SPEED) {
+ writer.WriteLine("public override Builder MergeFrom(pb::IMessage other) {");
+ writer.WriteLine(" if (other is {0}) {{", ClassName);
+ writer.WriteLine(" return MergeFrom(({0}) other);", ClassName);
+ writer.WriteLine(" } else {");
+ writer.WriteLine(" base.MergeFrom(other);");
+ writer.WriteLine(" return this;");
+ writer.WriteLine(" }");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override Builder MergeFrom({0} other) {{", ClassName);
+ // Optimization: If other is the default instance, we know none of its
+ // fields are set so we can skip the merge.
+ writer.Indent();
+ writer.WriteLine("if (other == {0}.DefaultInstance) return this;", ClassName);
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ SourceGenerators.CreateFieldGenerator(field).GenerateMergingCode(writer);
+ }
+ writer.WriteLine("this.MergeUnknownFields(other.UnknownFields);");
+ writer.WriteLine("return this;");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+ }
+
+ private void GenerateBuilderParsingMethods(TextGenerator writer) {
+ List<FieldDescriptor> sortedFields = new List<FieldDescriptor>(Descriptor.Fields);
+ sortedFields.Sort((f1, f2) => f1.FieldNumber.CompareTo(f2.FieldNumber));
+
+ writer.WriteLine("public override Builder MergeFrom(pb::CodedInputStream input) {");
+ writer.WriteLine(" return MergeFrom(input, pb::ExtensionRegistry.Empty);");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("public override Builder MergeFrom(pb::CodedInputStream input, pb::ExtensionRegistry extensionRegistry) {");
+ writer.Indent();
+ writer.WriteLine("pb::UnknownFieldSet.Builder unknownFields = pb::UnknownFieldSet.CreateBuilder(this.UnknownFields);");
+ writer.WriteLine("while (true) {");
+ writer.Indent();
+ writer.WriteLine("uint tag = input.ReadTag();");
+ writer.WriteLine("switch (tag) {");
+ writer.Indent();
+ writer.WriteLine("case 0: {"); // 0 signals EOF / limit reached
+ writer.WriteLine(" this.UnknownFields = unknownFields.Build();");
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("default: {");
+ writer.WriteLine(" if (!ParseUnknownField(input, unknownFields, extensionRegistry, tag)) {");
+ writer.WriteLine(" this.UnknownFields = unknownFields.Build();");
+ writer.WriteLine(" return this;"); // it's an endgroup tag
+ writer.WriteLine(" }");
+ writer.WriteLine(" break;");
+ writer.WriteLine("}");
+ foreach (FieldDescriptor field in sortedFields) {
+ uint tag = WireFormat.MakeTag(field.FieldNumber, WireFormat.GetWireType(field.FieldType));
+ writer.WriteLine("case {0}: {{", tag);
+ writer.Indent();
+ SourceGenerators.CreateFieldGenerator(field).GenerateParsingCode(writer);
+ writer.WriteLine("break;");
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+
+ private void GenerateIsInitialized(TextGenerator writer) {
+ writer.WriteLine("public override bool IsInitialized {");
+ writer.Indent();
+ writer.WriteLine("get {");
+ writer.Indent();
+
+ // Check that all required fields in this message are set.
+ // TODO(kenton): We can optimize this when we switch to putting all the
+ // "has" fields into a single bitfield.
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ if (field.IsRequired) {
+ writer.WriteLine("if (!has{0}) return false;", Helpers.UnderscoresToPascalCase(field.Name));
+ }
+ }
+
+ // Now check that all embedded messages are initialized.
+ foreach (FieldDescriptor field in Descriptor.Fields) {
+ if (field.FieldType != FieldType.Message ||
+ !HasRequiredFields(field.MessageType, new Dictionary<MessageDescriptor, object>())) {
+ continue;
+ }
+ string propertyName = Helpers.UnderscoresToPascalCase(DescriptorUtil.GetFieldName(field));
+ if (field.IsRepeated) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", DescriptorUtil.GetClassName(field.MessageType), propertyName);
+ writer.WriteLine(" if (!element.IsInitialized) return false;");
+ writer.WriteLine("}");
+ } else if (field.IsOptional) {
+ writer.WriteLine("if (Has{0}) {{", propertyName);
+ writer.WriteLine(" if (!{0}.IsInitialized) return false;", propertyName);
+ writer.WriteLine("}");
+ } else {
+ writer.WriteLine("if (!{0}.IsInitialized) return false;", propertyName);
+ }
+ }
+
+ if (Descriptor.Proto.ExtensionRangeCount > 0) {
+ writer.WriteLine("if (!ExtensionsAreInitialized) return false;");
+ }
+ writer.WriteLine("return true;");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/src/ProtoGen/PrimitiveFieldGenerator.cs b/src/ProtoGen/PrimitiveFieldGenerator.cs
new file mode 100644
index 00000000..9107def9
--- /dev/null
+++ b/src/ProtoGen/PrimitiveFieldGenerator.cs
@@ -0,0 +1,70 @@
+using System;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ // TODO(jonskeet): Refactor this. There's loads of common code here.
+ internal class PrimitiveFieldGenerator : FieldGeneratorBase, IFieldSourceGenerator {
+
+ internal PrimitiveFieldGenerator(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void GenerateMembers(TextGenerator writer) {
+ writer.WriteLine("private bool has{0};", CapitalizedName);
+ writer.WriteLine("private {0} {1}_ = {2};", TypeName, Name, DefaultValue);
+ writer.WriteLine("public bool Has{0} {{", CapitalizedName);
+ writer.WriteLine(" get {{ return has{0}; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} {1} {{", TypeName, PropertyName);
+ writer.WriteLine(" get {{ return {0}_; }}", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuilderMembers(TextGenerator writer) {
+ writer.WriteLine("public bool Has{0} {{", CapitalizedName);
+ writer.WriteLine(" get {{ return result.Has{0}; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} {1} {{", TypeName, PropertyName);
+ writer.WriteLine(" get {{ return result.{0}; }}", PropertyName);
+ writer.WriteLine(" set {{ Set{0}(value); }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.has{0} = true;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = value;", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Clear{0}() {{", CapitalizedName);
+ writer.WriteLine(" result.has{0} = false;", CapitalizedName);
+ writer.WriteLine(" result.{0}_ = {1};", Name, DefaultValue);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ }
+
+ public void GenerateMergingCode(TextGenerator writer) {
+ writer.WriteLine("if (other.Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" {0} = other.{0};", PropertyName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuildingCode(TextGenerator writer) {
+ // Nothing to do here for primitive types
+ }
+
+ public void GenerateParsingCode(TextGenerator writer) {
+ writer.WriteLine("{0} = input.Read{1}();", PropertyName, CapitalizedTypeName);
+ }
+
+ public void GenerateSerializationCode(TextGenerator writer) {
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" output.Write{0}({1}, {2});", CapitalizedTypeName, Number, PropertyName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializedSizeCode(TextGenerator writer) {
+ writer.WriteLine("if (Has{0}) {{", CapitalizedName);
+ writer.WriteLine(" size += pb::CodedOutputStream.Compute{0}Size({1}, {2});",
+ CapitalizedTypeName, Number, PropertyName);
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/Program.cs b/src/ProtoGen/Program.cs
new file mode 100644
index 00000000..08cad358
--- /dev/null
+++ b/src/ProtoGen/Program.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.DescriptorProtos;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Entry point for the Protocol Buffers generator.
+ /// </summary>
+ class Program {
+ static int Main(string[] args) {
+ try {
+ // Hack to make sure everything's initialized
+ DescriptorProtoFile.Descriptor.ToString();
+ GeneratorOptions options = ParseCommandLineArguments(args);
+
+ IList<string> validationFailures;
+ if (!options.TryValidate(out validationFailures)) {
+ // We've already got the message-building logic in the exception...
+ InvalidOptionsException exception = new InvalidOptionsException(validationFailures);
+ Console.WriteLine(exception.Message);
+ return 1;
+ }
+
+ Generator generator = Generator.CreateGenerator(options);
+ generator.Generate();
+
+
+ return 0;
+ } catch (Exception e) {
+ Console.Error.WriteLine("Error: {0}", e.Message);
+ Console.Error.WriteLine();
+ Console.Error.WriteLine("Detailed exception information: {0}", e);
+ return 1;
+ }
+ }
+
+ private static GeneratorOptions ParseCommandLineArguments(string[] args) {
+ GeneratorOptions options = new GeneratorOptions();
+ //string baseDir = "c:\\Users\\Jon\\Documents\\Visual Studio 2008\\Projects\\ProtocolBuffers";
+ //options.OutputDirectory = baseDir + "\\tmp";
+ //options.InputFiles = new[] { baseDir + "\\protos\\nwind-solo.protobin" };
+ options.OutputDirectory = ".";
+ options.InputFiles = args;
+ return options;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/ProtoGen/Properties/AssemblyInfo.cs b/src/ProtoGen/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..24f5d781
--- /dev/null
+++ b/src/ProtoGen/Properties/AssemblyInfo.cs
@@ -0,0 +1,42 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ProtoGen")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ProtoGen")]
+[assembly: AssemblyCopyright("Copyright © 2008")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7101763b-7a38-41be-87f5-7ede4c554509")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: InternalsVisibleTo("Google.ProtocolBuffers.ProtoGen.Test,PublicKey=" +
+"0024000004800000940000000602000000240000525341310004000001000100cf43741ffc3e65" +
+"c85707245e144e90f1bb82f20d1b1555846008d4d5d5c9270a980350dcb1ddd40fcdde13c2780c" +
+"75c9057123daa5613cb6551e2b8bd2254e8f84b3893369869e5119b752442aef7156c4defc489b" +
+"96c44ff801fe8d94199e048f8ff414813c9c811a029bcd697040700dc66982539e9b368cb5e725" +
+"feed60f2")]
diff --git a/src/ProtoGen/Properties/Google.ProtocolBuffers.ProtoGen.snk b/src/ProtoGen/Properties/Google.ProtocolBuffers.ProtoGen.snk
new file mode 100644
index 00000000..8e4d98a5
--- /dev/null
+++ b/src/ProtoGen/Properties/Google.ProtocolBuffers.ProtoGen.snk
Binary files differ
diff --git a/src/ProtoGen/ProtoGen.csproj b/src/ProtoGen/ProtoGen.csproj
new file mode 100644
index 00000000..f48df42c
--- /dev/null
+++ b/src/ProtoGen/ProtoGen.csproj
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.30729</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{250ADE34-82FD-4BAE-86D5-985FBE589C4A}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Google.ProtocolBuffers.ProtoGen</RootNamespace>
+ <AssemblyName>ProtoGen</AssemblyName>
+ <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <TargetFrameworkSubset>
+ </TargetFrameworkSubset>
+ <SignAssembly>true</SignAssembly>
+ <AssemblyOriginatorKeyFile>Properties\Google.ProtocolBuffers.ProtoGen.snk</AssemblyOriginatorKeyFile>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="DescriptorUtil.cs" />
+ <Compile Include="EnumFieldGenerator.cs" />
+ <Compile Include="EnumGenerator.cs" />
+ <Compile Include="ExtensionGenerator.cs" />
+ <Compile Include="FieldGeneratorBase.cs" />
+ <Compile Include="IFieldSourceGenerator.cs" />
+ <Compile Include="ISourceGenerator.cs" />
+ <Compile Include="MessageFieldGenerator.cs" />
+ <Compile Include="MessageGenerator.cs" />
+ <Compile Include="PrimitiveFieldGenerator.cs" />
+ <Compile Include="RepeatedEnumFieldGenerator.cs" />
+ <Compile Include="RepeatedMessageFieldGenerator.cs" />
+ <Compile Include="RepeatedPrimitiveFieldGenerator.cs" />
+ <Compile Include="ServiceGenerator.cs" />
+ <Compile Include="SourceFileGenerator.cs" />
+ <Compile Include="DependencyResolutionException.cs" />
+ <Compile Include="Generator.cs" />
+ <Compile Include="GeneratorOptions.cs" />
+ <Compile Include="Helpers.cs" />
+ <Compile Include="InvalidOptionsException.cs" />
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="SourceGeneratorBase.cs" />
+ <Compile Include="SourceGenerators.cs" />
+ <Compile Include="UmbrellaClassGenerator.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="app.config" />
+ <None Include="Properties\Google.ProtocolBuffers.ProtoGen.snk" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\ProtocolBuffers\ProtocolBuffers.csproj">
+ <Project>{6908BDCE-D925-43F3-94AC-A531E6DF2591}</Project>
+ <Name>ProtocolBuffers</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/src/ProtoGen/RepeatedEnumFieldGenerator.cs b/src/ProtoGen/RepeatedEnumFieldGenerator.cs
new file mode 100644
index 00000000..3a988fbc
--- /dev/null
+++ b/src/ProtoGen/RepeatedEnumFieldGenerator.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class RepeatedEnumFieldGenerator : FieldGeneratorBase, IFieldSourceGenerator {
+
+ internal RepeatedEnumFieldGenerator(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void GenerateMembers(TextGenerator writer) {
+ writer.WriteLine("private pbc::PopsicleList<{0}> {1}_ = new pbc::PopsicleList<{0}>();", TypeName, Name);
+ writer.WriteLine("public scg::IList<{0}> {1}List {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return pbc::Lists.AsReadOnly({0}_); }}", Name);
+ writer.WriteLine("}");
+
+ // TODO(jonskeet): Redundant API calls? Possibly - include for portability though. Maybe create an option.
+ writer.WriteLine("public int {0}Count {{", CapitalizedName);
+ writer.WriteLine(" get {{ return {0}_.Count; }}", Name);
+ writer.WriteLine("}");
+
+ writer.WriteLine("public {0} Get{1}(int index) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" return {0}_[index];", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuilderMembers(TextGenerator writer) {
+ // Note: We can return the original list here, because we make it unmodifiable when we build
+ writer.WriteLine("public scg::IList<{0}> {1}List {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}_; }}", Name);
+ writer.WriteLine("}");
+ writer.WriteLine("public int {0}Count {{", CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}Count; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} Get{1}(int index) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" return result.Get{0}(index);", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}(int index, {1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_[index] = value;", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Add{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_.Add(value);", Name, TypeName);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder AddRange{0}(scg::IEnumerable<{1}> values) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" base.AddRange(values, result.{0}_);", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Clear{0}() {{", CapitalizedName);
+ writer.WriteLine(" result.{0}_.Clear();", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ }
+
+ public void GenerateMergingCode(TextGenerator writer) {
+ writer.WriteLine("if (other.{0}_.Count != 0) {{", Name);
+ writer.WriteLine(" base.AddRange(other.{0}_, result.{0}_);", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuildingCode(TextGenerator writer) {
+ writer.WriteLine("result.{0}_.MakeReadOnly();", Name);
+ }
+
+ public void GenerateParsingCode(TextGenerator writer) {
+ // TODO(jonskeet): Make a more efficient way of doing this
+ writer.WriteLine("int rawValue = input.ReadEnum();");
+ writer.WriteLine("if (!global::System.Enum.IsDefined(typeof({0}), rawValue)) {{", TypeName);
+ writer.WriteLine(" unknownFields.MergeVarintField({0}, (ulong) rawValue);", Number);
+ writer.WriteLine("} else {");
+ writer.WriteLine(" Add{0}(({1}) rawValue);", CapitalizedName, TypeName);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializationCode(TextGenerator writer) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" output.WriteEnum({0}, (int) element);", Number);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializedSizeCode(TextGenerator writer) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" size += pb::CodedOutputStream.ComputeEnumSize({0}, (int) element);", Number);
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/RepeatedMessageFieldGenerator.cs b/src/ProtoGen/RepeatedMessageFieldGenerator.cs
new file mode 100644
index 00000000..08d574a9
--- /dev/null
+++ b/src/ProtoGen/RepeatedMessageFieldGenerator.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class RepeatedMessageFieldGenerator : FieldGeneratorBase, IFieldSourceGenerator {
+
+ internal RepeatedMessageFieldGenerator(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void GenerateMembers(TextGenerator writer) {
+ writer.WriteLine("private pbc::PopsicleList<{0}> {1}_ = new pbc::PopsicleList<{0}>();", TypeName, Name);
+ writer.WriteLine("public scg::IList<{0}> {1}List {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return {0}_; }}", Name);
+ writer.WriteLine("}");
+
+ // TODO(jonskeet): Redundant API calls? Possibly - include for portability though. Maybe create an option.
+ writer.WriteLine("public int {0}Count {{", CapitalizedName);
+ writer.WriteLine(" get {{ return {0}_.Count; }}", Name);
+ writer.WriteLine("}");
+
+ writer.WriteLine("public {0} Get{1}(int index) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" return {0}_[index];", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuilderMembers(TextGenerator writer) {
+ // Note: We can return the original list here, because we make it unmodifiable when we build
+ writer.WriteLine("public scg::IList<{0}> {1}List {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}_; }}", Name);
+ writer.WriteLine("}");
+ writer.WriteLine("public int {0}Count {{", CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}Count; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} Get{1}(int index) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" return result.Get{0}(index);", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}(int index, {1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_[index] = value;", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ // Extra overload for builder (just on messages)
+ writer.WriteLine("public Builder Set{0}(int index, {1}.Builder builderForValue) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_[index] = builderForValue.Build();", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Add{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_.Add(value);", Name, TypeName);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ // Extra overload for builder (just on messages)
+ writer.WriteLine("public Builder Add{0}({1}.Builder builderForValue) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_.Add(builderForValue.Build());", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder AddRange{0}(scg::IEnumerable<{1}> values) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" base.AddRange(values, result.{0}_);", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Clear{0}() {{", CapitalizedName);
+ writer.WriteLine(" result.{0}_.Clear();", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ }
+
+ public void GenerateMergingCode(TextGenerator writer) {
+ writer.WriteLine("if (other.{0}_.Count != 0) {{", Name);
+ writer.WriteLine(" base.AddRange(other.{0}_, result.{0}_);", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuildingCode(TextGenerator writer) {
+ writer.WriteLine("result.{0}_.MakeReadOnly();", Name);
+ }
+
+ public void GenerateParsingCode(TextGenerator writer) {
+ writer.WriteLine("{0}.Builder subBuilder = {0}.CreateBuilder();", TypeName);
+ if (Descriptor.FieldType == FieldType.Group) {
+ writer.WriteLine("input.ReadGroup({0}, subBuilder, extensionRegistry);", Number);
+ } else {
+ writer.WriteLine("input.ReadMessage(subBuilder, extensionRegistry);");
+ }
+ writer.WriteLine("Add{0}(subBuilder.BuildPartial());", CapitalizedName);
+ }
+
+ public void GenerateSerializationCode(TextGenerator writer) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" output.Write{0}({1}, element);", MessageOrGroup, Number);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializedSizeCode(TextGenerator writer) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" size += pb::CodedOutputStream.Compute{0}Size({1}, element);", MessageOrGroup, Number);
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/RepeatedPrimitiveFieldGenerator.cs b/src/ProtoGen/RepeatedPrimitiveFieldGenerator.cs
new file mode 100644
index 00000000..cdb7cd6f
--- /dev/null
+++ b/src/ProtoGen/RepeatedPrimitiveFieldGenerator.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class RepeatedPrimitiveFieldGenerator : FieldGeneratorBase, IFieldSourceGenerator {
+
+ internal RepeatedPrimitiveFieldGenerator(FieldDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void GenerateMembers(TextGenerator writer) {
+ writer.WriteLine("private pbc::PopsicleList<{0}> {1}_ = new pbc::PopsicleList<{0}>();", TypeName, Name);
+ writer.WriteLine("public scg::IList<{0}> {1}List {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return pbc::Lists.AsReadOnly({0}_); }}", Name);
+ writer.WriteLine("}");
+
+ // TODO(jonskeet): Redundant API calls? Possibly - include for portability though. Maybe create an option.
+ writer.WriteLine("public int {0}Count {{", CapitalizedName);
+ writer.WriteLine(" get {{ return {0}_.Count; }}", Name);
+ writer.WriteLine("}");
+
+ writer.WriteLine("public {0} Get{1}(int index) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" return {0}_[index];", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuilderMembers(TextGenerator writer) {
+ // Note: We can return the original list here, because we make it unmodifiable when we build
+ writer.WriteLine("public scg::IList<{0}> {1}List {{", TypeName, CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}_; }}", Name);
+ writer.WriteLine("}");
+ writer.WriteLine("public int {0}Count {{", CapitalizedName);
+ writer.WriteLine(" get {{ return result.{0}Count; }}", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public {0} Get{1}(int index) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" return result.Get{0}(index);", CapitalizedName);
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Set{0}(int index, {1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_[index] = value;", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Add{0}({1} value) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" result.{0}_.Add(value);", Name, TypeName);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder AddRange{0}(scg::IEnumerable<{1}> values) {{", CapitalizedName, TypeName);
+ writer.WriteLine(" base.AddRange(values, result.{0}_);", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ writer.WriteLine("public Builder Clear{0}() {{", CapitalizedName);
+ writer.WriteLine(" result.{0}_.Clear();", Name);
+ writer.WriteLine(" return this;");
+ writer.WriteLine("}");
+ }
+
+ public void GenerateMergingCode(TextGenerator writer) {
+ writer.WriteLine("if (other.{0}_.Count != 0) {{", Name);
+ writer.WriteLine(" base.AddRange(other.{0}_, result.{0}_);", Name);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateBuildingCode(TextGenerator writer) {
+ writer.WriteLine("result.{0}_.MakeReadOnly();", Name);
+ }
+
+ public void GenerateParsingCode(TextGenerator writer) {
+ writer.WriteLine("Add{0}(input.Read{1}());", CapitalizedName, CapitalizedTypeName);
+ }
+
+ public void GenerateSerializationCode(TextGenerator writer) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" output.Write{0}({1}, element);", CapitalizedTypeName, Number);
+ writer.WriteLine("}");
+ }
+
+ public void GenerateSerializedSizeCode(TextGenerator writer) {
+ writer.WriteLine("foreach ({0} element in {1}List) {{", TypeName, CapitalizedName);
+ writer.WriteLine(" size += pb::CodedOutputStream.Compute{0}Size({1}, element);", CapitalizedTypeName, Number);
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/ServiceGenerator.cs b/src/ProtoGen/ServiceGenerator.cs
new file mode 100644
index 00000000..089172db
--- /dev/null
+++ b/src/ProtoGen/ServiceGenerator.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal class ServiceGenerator : SourceGeneratorBase<ServiceDescriptor>, ISourceGenerator {
+
+ private enum RequestOrResponse {
+ Request,
+ Response
+ }
+
+ internal ServiceGenerator(ServiceDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void Generate(TextGenerator writer) {
+ writer.WriteLine("{0} abstract class {1} : pb::IService {{", ClassAccessLevel, Descriptor.Name);
+ writer.Indent();
+
+ foreach (MethodDescriptor method in Descriptor.Methods) {
+ writer.WriteLine("{0} abstract void {1}(", ClassAccessLevel, Helpers.UnderscoresToPascalCase(method.Name));
+ writer.WriteLine(" pb::IRpcController controller,");
+ writer.WriteLine(" {0} request,", DescriptorUtil.GetClassName(method.InputType));
+ writer.WriteLine(" global::System.Action<{0}> done);", DescriptorUtil.GetClassName(method.OutputType));
+ }
+
+ // Generate Descriptor and DescriptorForType.
+ writer.WriteLine();
+ writer.WriteLine("{0} static pbd::ServiceDescriptor Descriptor {{", ClassAccessLevel);
+ writer.WriteLine(" get {{ return {0}.Descriptor.Services[{1}]; }}",
+ DescriptorUtil.GetUmbrellaClassName(Descriptor.File), Descriptor.Index);
+ writer.WriteLine("}");
+ writer.WriteLine("{0} pbd::ServiceDescriptor DescriptorForType {{", ClassAccessLevel);
+ writer.WriteLine(" get { return Descriptor; }");
+ writer.WriteLine("}");
+
+ GenerateCallMethod(writer);
+ GenerateGetPrototype(RequestOrResponse.Request, writer);
+ GenerateGetPrototype(RequestOrResponse.Response, writer);
+ GenerateStub(writer);
+
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+
+ private void GenerateCallMethod(TextGenerator writer) {
+ writer.WriteLine();
+ writer.WriteLine("public void CallMethod(", ClassAccessLevel);
+ writer.WriteLine(" pbd::MethodDescriptor method,");
+ writer.WriteLine(" pb::IRpcController controller,");
+ writer.WriteLine(" pb::IMessage request,");
+ writer.WriteLine(" global::System.Action<pb::IMessage> done) {");
+ writer.Indent();
+ writer.WriteLine("if (method.Service != Descriptor) {");
+ writer.WriteLine(" throw new global::System.ArgumentException(");
+ writer.WriteLine(" \"Service.CallMethod() given method descriptor for wrong service type.\");");
+ writer.WriteLine("}");
+ writer.WriteLine("switch(method.Index) {");
+ writer.Indent();
+ foreach (MethodDescriptor method in Descriptor.Methods) {
+ writer.WriteLine("case {0}:", method.Index);
+ writer.WriteLine(" this.{0}(controller, ({1}) request,",
+ Helpers.UnderscoresToPascalCase(method.Name), DescriptorUtil.GetClassName(method.InputType));
+ writer.WriteLine(" pb::RpcUtil.SpecializeCallback<{0}>(", DescriptorUtil.GetClassName(method.OutputType));
+ writer.WriteLine(" done));");
+ writer.WriteLine(" return;");
+ }
+ writer.WriteLine("default:");
+ writer.WriteLine(" throw new global::System.InvalidOperationException(\"Can't get here.\");");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+
+ private void GenerateGetPrototype(RequestOrResponse which, TextGenerator writer) {
+ writer.WriteLine("public pb::IMessage Get{0}Prototype(pbd::MethodDescriptor method) {{", which);
+ writer.Indent();
+ writer.WriteLine("if (method.Service != Descriptor) {");
+ writer.WriteLine(" throw new global::System.ArgumentException(");
+ writer.WriteLine(" \"Service.Get{0}Prototype() given method descriptor for wrong service type.\");", which);
+ writer.WriteLine("}");
+ writer.WriteLine("switch(method.Index) {");
+ writer.Indent();
+
+ foreach (MethodDescriptor method in Descriptor.Methods) {
+ writer.WriteLine("case {0}:", method.Index);
+ writer.WriteLine(" return {0}.DefaultInstance;",
+ DescriptorUtil.GetClassName(which == RequestOrResponse.Request ? method.InputType : method.OutputType));
+ }
+ writer.WriteLine("default:");
+ writer.WriteLine(" throw new global::System.InvalidOperationException(\"Can't get here.\");");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.Outdent();
+ writer.WriteLine("}");
+ writer.WriteLine();
+ }
+
+ private void GenerateStub(TextGenerator writer) {
+ writer.WriteLine("public static Stub CreateStub(pb::IRpcChannel channel) {");
+ writer.WriteLine(" return new Stub(channel);");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("{0} class Stub : {1} {{", ClassAccessLevel, DescriptorUtil.GetClassName(Descriptor));
+ writer.Indent();
+ writer.WriteLine("internal Stub(pb::IRpcChannel channel) {");
+ writer.WriteLine(" this.channel = channel;");
+ writer.WriteLine("}");
+ writer.WriteLine();
+ writer.WriteLine("private readonly pb::IRpcChannel channel;");
+ writer.WriteLine();
+ writer.WriteLine("public pb::IRpcChannel Channel {");
+ writer.WriteLine(" get { return channel; }");
+ writer.WriteLine("}");
+
+ foreach (MethodDescriptor method in Descriptor.Methods) {
+ writer.WriteLine();
+ writer.WriteLine("public override void {0}(", Helpers.UnderscoresToPascalCase(method.Name));
+ writer.WriteLine(" pb::IRpcController controller,");
+ writer.WriteLine(" {0} request,", DescriptorUtil.GetClassName(method.InputType));
+ writer.WriteLine(" global::System.Action<{0}> done) {{", DescriptorUtil.GetClassName(method.OutputType));
+ writer.Indent();
+ writer.WriteLine("channel.CallMethod(Descriptor.Methods[{0}],", method.Index);
+ writer.WriteLine(" controller, request, {0}.DefaultInstance,", DescriptorUtil.GetClassName(method.OutputType));
+ writer.WriteLine(" pb::RpcUtil.GeneralizeCallback<{0}, {0}.Builder>(done, {0}.DefaultInstance));",
+ DescriptorUtil.GetClassName(method.OutputType));
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+ }
+}
diff --git a/src/ProtoGen/SourceFileGenerator.cs b/src/ProtoGen/SourceFileGenerator.cs
new file mode 100644
index 00000000..1984d297
--- /dev/null
+++ b/src/ProtoGen/SourceFileGenerator.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Generator to hold a TextGenerator, generate namespace aliases etc.
+ /// Each source file created uses one of these, and it can be used to create
+ /// multiple classes within the same file.
+ /// </summary>
+ internal class SourceFileGenerator {
+
+ private readonly TextGenerator output;
+
+ private SourceFileGenerator(TextWriter writer) {
+ output = new TextGenerator(writer);
+ }
+
+ /// <summary>
+ /// Creates a ClassFileGenerator for the given writer, which will be closed
+ /// when the instance is disposed. The specified namespace is created, if it's non-null.
+ /// </summary>
+ internal static SourceFileGenerator ForWriter(TextWriter writer) {
+ return new SourceFileGenerator(writer);
+ }
+ }
+}
diff --git a/src/ProtoGen/SourceGeneratorBase.cs b/src/ProtoGen/SourceGeneratorBase.cs
new file mode 100644
index 00000000..563c64a1
--- /dev/null
+++ b/src/ProtoGen/SourceGeneratorBase.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+using Google.ProtocolBuffers.DescriptorProtos;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal abstract class SourceGeneratorBase<T> where T : IDescriptor {
+
+ private readonly T descriptor;
+
+ protected SourceGeneratorBase(T descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ protected T Descriptor {
+ get { return descriptor; }
+ }
+
+ protected string ClassAccessLevel {
+ get {
+ // Default to public
+ return !descriptor.File.Options.HasExtension(CSharpOptions.CSharpPublicClasses)
+ || descriptor.File.Options.GetExtension(CSharpOptions.CSharpPublicClasses) ? "public" : "internal";
+ }
+ }
+
+ public bool MultipleFiles {
+ get { return descriptor.File.Options.GetExtension(CSharpOptions.CSharpMultipleFiles); }
+ }
+
+ protected static void WriteChildren<TChild>(TextGenerator writer, string region, IEnumerable<TChild> children)
+ where TChild : IDescriptor {
+ // Copy the set of children; makes access easier
+ List<TChild> copy = new List<TChild>(children);
+ if (copy.Count == 0) {
+ return;
+ }
+
+ if (region != null) {
+ writer.WriteLine("#region {0}", region);
+ }
+ foreach (TChild child in children) {
+ SourceGenerators.CreateGenerator(child).Generate(writer);
+ }
+ if (region != null) {
+ writer.WriteLine("#endregion");
+ writer.WriteLine();
+ }
+ }
+ }
+}
diff --git a/src/ProtoGen/SourceGenerators.cs b/src/ProtoGen/SourceGenerators.cs
new file mode 100644
index 00000000..c775e419
--- /dev/null
+++ b/src/ProtoGen/SourceGenerators.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ internal static class SourceGenerators {
+
+ private static readonly Dictionary<Type, Func<IDescriptor, ISourceGenerator>> GeneratorFactories = new Dictionary<Type, Func<IDescriptor, ISourceGenerator>> {
+ { typeof(FileDescriptor), descriptor => new UmbrellaClassGenerator((FileDescriptor) descriptor) },
+ { typeof(EnumDescriptor), descriptor => new EnumGenerator((EnumDescriptor) descriptor) },
+ { typeof(ServiceDescriptor), descriptor => new ServiceGenerator((ServiceDescriptor) descriptor) },
+ { typeof(MessageDescriptor), descriptor => new MessageGenerator((MessageDescriptor) descriptor) },
+ // For other fields, we have IFieldSourceGenerators.
+ { typeof(FieldDescriptor), descriptor => new ExtensionGenerator((FieldDescriptor) descriptor) }
+ };
+
+ public static IFieldSourceGenerator CreateFieldGenerator(FieldDescriptor field) {
+ switch (field.MappedType) {
+ case MappedType.Message :
+ return field.IsRepeated
+ ? (IFieldSourceGenerator) new RepeatedMessageFieldGenerator(field)
+ : new MessageFieldGenerator(field);
+ case MappedType.Enum:
+ return field.IsRepeated
+ ? (IFieldSourceGenerator)new RepeatedEnumFieldGenerator(field)
+ : new EnumFieldGenerator(field);
+ default:
+ return field.IsRepeated
+ ? (IFieldSourceGenerator)new RepeatedPrimitiveFieldGenerator(field)
+ : new PrimitiveFieldGenerator(field);
+ }
+ }
+
+ public static ISourceGenerator CreateGenerator<T>(T descriptor) where T : IDescriptor {
+ Func<IDescriptor, ISourceGenerator> factory;
+ if (!GeneratorFactories.TryGetValue(typeof(T), out factory)) {
+ throw new ArgumentException("No generator registered for " + typeof(T).Name);
+ }
+ return factory(descriptor);
+ }
+ }
+}
diff --git a/src/ProtoGen/UmbrellaClassGenerator.cs b/src/ProtoGen/UmbrellaClassGenerator.cs
new file mode 100644
index 00000000..7db5c46c
--- /dev/null
+++ b/src/ProtoGen/UmbrellaClassGenerator.cs
@@ -0,0 +1,96 @@
+using System;
+using Google.ProtocolBuffers.DescriptorProtos;
+using Google.ProtocolBuffers.Descriptors;
+
+namespace Google.ProtocolBuffers.ProtoGen {
+ /// <summary>
+ /// Generator for the class describing the .proto file in general,
+ /// containing things like the message descriptor.
+ /// </summary>
+ internal sealed class UmbrellaClassGenerator : SourceGeneratorBase<FileDescriptor>, ISourceGenerator {
+
+ internal UmbrellaClassGenerator(FileDescriptor descriptor)
+ : base(descriptor) {
+ }
+
+ public void Generate(TextGenerator writer) {
+ WriteIntroduction(writer);
+ WriteDescriptor(writer);
+ WriteChildren(writer, "Extensions", Descriptor.Extensions);
+ writer.WriteLine("#region Static variables");
+ foreach (MessageDescriptor message in Descriptor.MessageTypes) {
+ new MessageGenerator(message).GenerateStaticVariables(writer);
+ }
+ writer.WriteLine("#endregion");
+ // The class declaration either gets closed before or after the children are written.
+ if (!DescriptorUtil.NestClasses(Descriptor)) {
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+ WriteChildren(writer, "Enums", Descriptor.EnumTypes);
+ WriteChildren(writer, "Messages", Descriptor.MessageTypes);
+ WriteChildren(writer, "Services", Descriptor.Services);
+ if (DescriptorUtil.NestClasses(Descriptor)) {
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+ if (DescriptorUtil.GetNamespace(Descriptor) != "") {
+ writer.Outdent();
+ writer.WriteLine("}");
+ }
+ }
+
+ private void WriteIntroduction(TextGenerator writer) {
+ writer.WriteLine("// Generated by the protocol buffer compiler. DO NOT EDIT!");
+ writer.WriteLine();
+ Helpers.WriteNamespaces(writer);
+
+ if (DescriptorUtil.GetNamespace(Descriptor) != "") {
+ writer.WriteLine("namespace {0} {{", DescriptorUtil.GetNamespace(Descriptor));
+ writer.Indent();
+ writer.WriteLine();
+ }
+
+ writer.WriteLine("{0} static partial class {1} {{", ClassAccessLevel, DescriptorUtil.GetUmbrellaClassName(Descriptor));
+ writer.WriteLine();
+ writer.Indent();
+ }
+
+ private void WriteDescriptor(TextGenerator writer) {
+ writer.WriteLine("#region Descriptor");
+
+ writer.WriteLine("public static pbd::FileDescriptor Descriptor {");
+ writer.WriteLine(" get { return descriptor; }");
+ writer.WriteLine("}");
+ writer.WriteLine("private static readonly pbd::FileDescriptor descriptor = pbd::FileDescriptor.InternalBuildGeneratedFileFrom(");
+ writer.WriteLine(" global::System.Convert.FromBase64String(");
+ writer.Indent();
+ writer.Indent();
+
+ // TODO(jonskeet): Consider a C#-escaping format here instead of just Base64.
+ byte[] bytes = Descriptor.Proto.ToByteArray();
+ string base64 = Convert.ToBase64String(bytes);
+
+ while (base64.Length > 60) {
+ writer.WriteLine("\"{0}\" + ", base64.Substring(0, 60));
+ base64 = base64.Substring(60);
+ }
+ writer.WriteLine("\"{0}\"),", base64);
+
+ writer.WriteLine("new pbd::FileDescriptor[] {");
+ foreach (FileDescriptor dependency in Descriptor.Dependencies) {
+ // TODO(jonskeet): The normal code won't work for the bootstrapping descriptor, because we don't get unknown fields :(
+ if (dependency.Package == "google.protobuf" && dependency.Name.EndsWith("descriptor.proto")) {
+ writer.WriteLine(" global::" + typeof(DescriptorProtoFile).FullName + ".Descriptor, ");
+ continue;
+ }
+ writer.WriteLine(" {0}.Descriptor, ", DescriptorUtil.GetFullUmbrellaClassName(dependency));
+ }
+ writer.WriteLine("});");
+ writer.Outdent();
+ writer.Outdent();
+ writer.WriteLine("#endregion");
+ writer.WriteLine();
+ }
+ }
+}
diff --git a/src/ProtoGen/app.config b/src/ProtoGen/app.config
new file mode 100644
index 00000000..df20690a
--- /dev/null
+++ b/src/ProtoGen/app.config
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<configuration>
+<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>