using Google.Protobuf.Reflection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace ProtoBuf.Reflection
{
///
/// A coded generator that writes C#
///
public class CSharpCodeGenerator : CommonCodeGenerator
{
///
/// Reusable code-generator instance
///
public static CSharpCodeGenerator Default { get; } = new CSharpCodeGenerator();
///
/// Create a new CSharpCodeGenerator instance
///
protected CSharpCodeGenerator() { }
///
/// Returns the language name
///
public override string Name => "C#";
///
/// Returns the default file extension
///
protected override string DefaultFileExtension => "cs";
///
/// Escapes language keywords
///
protected override string Escape(string identifier)
{
switch (identifier)
{
case "abstract":
case "event":
case "new":
case "struct":
case "as":
case "explicit":
case "null":
case "switch":
case "base":
case "extern":
case "object":
case "this":
case "bool":
case "false":
case "operator":
case "throw":
case "break":
case "finally":
case "out":
case "true":
case "byte":
case "fixed":
case "override":
case "try":
case "case":
case "float":
case "params":
case "typeof":
case "catch":
case "for":
case "private":
case "uint":
case "char":
case "foreach":
case "protected":
case "ulong":
case "checked":
case "goto":
case "public":
case "unchecked":
case "class":
case "if":
case "readonly":
case "unsafe":
case "const":
case "implicit":
case "ref":
case "ushort":
case "continue":
case "in":
case "return":
case "using":
case "decimal":
case "int":
case "sbyte":
case "virtual":
case "default":
case "interface":
case "sealed":
case "volatile":
case "delegate":
case "internal":
case "short":
case "void":
case "do":
case "is":
case "sizeof":
case "while":
case "double":
case "lock":
case "stackalloc":
case "else":
case "long":
case "static":
case "enum":
case "namespace":
case "string":
return "@" + identifier;
default:
return identifier;
}
}
///
/// Start a file
///
protected override void WriteFileHeader(GeneratorContext ctx, FileDescriptorProto file, ref object state)
{
ctx.WriteLine("// This file was generated by a tool; you should avoid making direct changes.")
.WriteLine("// Consider using 'partial classes' to extend these types")
.WriteLine($"// Input: {Path.GetFileName(ctx.File.Name)}").WriteLine()
.WriteLine("#pragma warning disable CS1591, CS0612, CS3021").WriteLine();
var @namespace = ctx.NameNormalizer.GetName(file);
if (!string.IsNullOrWhiteSpace(@namespace))
{
state = @namespace;
ctx.WriteLine($"namespace {@namespace}");
ctx.WriteLine("{").Indent().WriteLine();
}
}
///
/// End a file
///
protected override void WriteFileFooter(GeneratorContext ctx, FileDescriptorProto file, ref object state)
{
var @namespace = (string)state;
if (!string.IsNullOrWhiteSpace(@namespace))
{
ctx.Outdent().WriteLine("}").WriteLine();
}
ctx.WriteLine("#pragma warning restore CS1591, CS0612, CS3021");
}
///
/// Start an enum
///
protected override void WriteEnumHeader(GeneratorContext ctx, EnumDescriptorProto obj, ref object state)
{
var name = ctx.NameNormalizer.GetName(obj);
var tw = ctx.Write($@"[global::ProtoBuf.ProtoContract(");
if (name != obj.Name) tw.Write($@"Name = @""{obj.Name}""");
tw.WriteLine(")]");
WriteOptions(ctx, obj.Options);
ctx.WriteLine($"{GetAccess(GetAccess(obj))} enum {Escape(name)}").WriteLine("{").Indent();
}
///
/// End an enum
///
protected override void WriteEnumFooter(GeneratorContext ctx, EnumDescriptorProto obj, ref object state)
{
ctx.Outdent().WriteLine("}").WriteLine();
}
///
/// Write an enum value
///
protected override void WriteEnumValue(GeneratorContext ctx, EnumValueDescriptorProto obj, ref object state)
{
var name = ctx.NameNormalizer.GetName(obj);
if (name != obj.Name)
{
var tw = ctx.Write($@"[global::ProtoBuf.ProtoEnum(");
tw.Write($@"Name = @""{obj.Name}""");
tw.WriteLine(")]");
}
WriteOptions(ctx, obj.Options);
ctx.WriteLine($"{Escape(name)} = {obj.Number},");
}
///
/// End a message
///
protected override void WriteMessageFooter(GeneratorContext ctx, DescriptorProto obj, ref object state)
{
ctx.Outdent().WriteLine("}").WriteLine();
}
///
/// Start a message
///
protected override void WriteMessageHeader(GeneratorContext ctx, DescriptorProto obj, ref object state)
{
var name = ctx.NameNormalizer.GetName(obj);
GetTypeName2(obj.FullyQualifiedName);
var tw = ctx.Write($@"[global::ProtoBuf.ProtoContract(");
if (name != obj.Name) tw.Write($@"Name = @""{obj.Name}""");
tw.WriteLine(")]");
WriteOptions(ctx, obj.Options);
tw = ctx.Write($"{GetAccess(GetAccess(obj))} partial class {Escape(name)}");
if (obj.ExtensionRanges.Count != 0) tw.Write(" : global::ProtoBuf.IExtensible");
tw.WriteLine();
ctx.WriteLine("{").Indent();
if (obj.Options?.MessageSetWireFormat == true)
{
ctx.WriteLine("#error message_set_wire_format is not currently implemented").WriteLine();
}
if (obj.ExtensionRanges.Count != 0)
{
ctx.WriteLine($"private global::ProtoBuf.IExtension {FieldPrefix}extensionData;")
.WriteLine($"global::ProtoBuf.IExtension global::ProtoBuf.IExtensible.GetExtensionObject(bool createIfMissing)").Indent()
.WriteLine($"=> global::ProtoBuf.Extensible.GetExtensionObject(ref {FieldPrefix}extensionData, createIfMissing);").Outdent().WriteLine();
}
}
private static void WriteOptions(GeneratorContext ctx, T obj) where T : class, ISchemaOptions
{
if (obj == null) return;
if (obj.Deprecated)
{
ctx.WriteLine($"[global::System.Obsolete]");
}
}
const string FieldPrefix = "__pbn__";
///
/// Get the language specific keyword representing an access level
///
public override string GetAccess(Access access)
{
switch (access)
{
case Access.Internal: return "internal";
case Access.Public: return "public";
case Access.Private: return "private";
default: return base.GetAccess(access);
}
}
static HashSet TypeNames2 = new HashSet();
static string GetTypeName2(string type) {
if (type.StartsWith(".")) { type = type.Substring(1); }
TypeNames2.Add(type);
return type;
}
public static void ClearTypeNames(){
TypeNames2.Clear ();
}
///
/// Write a field
///
protected override void WriteField(GeneratorContext ctx, FieldDescriptorProto obj, ref object state, OneOfStub[] oneOfs)
{
string dataFormat;
var typeName = GetTypeName(ctx, obj, out dataFormat, out var isMap);
if (isMap)
{
return;
}
var name = ctx.NameNormalizer.GetName(obj);
var tw = ctx.Write($@"[global::ProtoBuf.ProtoMember({obj.Number}");
if (!string.IsNullOrWhiteSpace(dataFormat))
{
tw.Write($", (int)global::ProtoBuf.DataFormat.{dataFormat}");
}
if (name != obj.Name)
{
tw.Write($@", Name = @""{obj.Name}""");
}
var options = obj.Options?.GetOptions();
if (options?.AsReference == true)
{
tw.Write($@", AsReference = true");
}
if (options?.DynamicType == true)
{
tw.Write($@", DynamicType = true");
}
bool isOptional = obj.label == FieldDescriptorProto.Label.LabelOptional;
bool isRepeated = obj.label == FieldDescriptorProto.Label.LabelRepeated;
// Only needed by ILRuntime
/*if (isRepeated && obj.type == FieldDescriptorProto.Type.TypeMessage)
{
tw.Write($@", TypeName = ""{GetTypeName2(obj.TypeName)}""");
}*/
OneOfStub oneOf = obj.ShouldSerializeOneofIndex() ? oneOfs?[obj.OneofIndex] : null;
if (oneOf != null && oneOf.CountTotal == 1)
{
oneOf = null; // not really a one-of, then!
}
bool explicitValues = isOptional && oneOf == null && ctx.Syntax == FileDescriptorProto.SyntaxProto2
&& obj.type != FieldDescriptorProto.Type.TypeMessage
&& obj.type != FieldDescriptorProto.Type.TypeGroup;
string defaultValue = null;
bool suppressDefaultAttribute = !isOptional;
if (isOptional || obj.type == FieldDescriptorProto.Type.TypeEnum)
{
//GetTypeName2(obj.TypeName);
defaultValue = obj.DefaultValue;
if (obj.type == FieldDescriptorProto.Type.TypeString)
{
defaultValue = string.IsNullOrEmpty(defaultValue) ? "\"\""
: ("@\"" + (defaultValue ?? "").Replace("\"", "\"\"") + "\"");
}
else if (obj.type == FieldDescriptorProto.Type.TypeDouble)
{
switch (defaultValue)
{
case "inf": defaultValue = "double.PositiveInfinity"; break;
case "-inf": defaultValue = "double.NegativeInfinity"; break;
case "nan": defaultValue = "double.NaN"; break;
}
}
else if (obj.type == FieldDescriptorProto.Type.TypeFloat)
{
switch (defaultValue)
{
case "inf": defaultValue = "float.PositiveInfinity"; break;
case "-inf": defaultValue = "float.NegativeInfinity"; break;
case "nan": defaultValue = "float.NaN"; break;
}
}
else if (obj.type == FieldDescriptorProto.Type.TypeEnum)
{
var enumType = ctx.TryFind(obj.TypeName);
if (enumType != null)
{
EnumValueDescriptorProto found = null;
if (!string.IsNullOrEmpty(defaultValue))
{
found = enumType.Values.FirstOrDefault(x => x.Name == defaultValue);
}
else if (ctx.Syntax == FileDescriptorProto.SyntaxProto2)
{
// find the first one; if that is a zero, we don't need it after all
found = enumType.Values.FirstOrDefault();
if(found != null && found.Number == 0)
{
if(!isOptional) found = null; // we don't need it after all
}
}
// for proto3 the default is 0, so no need to do anything - GetValueOrDefault() will do it all
if (found != null)
{
defaultValue = ctx.NameNormalizer.GetName(found);
}
if (!string.IsNullOrWhiteSpace(defaultValue))
{
//defaultValue = ctx.NameNormalizer.GetName(enumType) + "." + defaultValue;
defaultValue = "global::"+enumType.FullyQualifiedName.Substring(1) + "." + defaultValue;
}
}
}
}
if (obj.IsPacked(ctx.Syntax))
{
tw.Write($", IsPacked = true");
}
if (obj.label == FieldDescriptorProto.Label.LabelRequired)
{
tw.Write($", IsRequired = true");
}
tw.WriteLine(")]");
if (!isRepeated && !string.IsNullOrWhiteSpace(defaultValue) && !suppressDefaultAttribute)
{
ctx.WriteLine($"[global::System.ComponentModel.DefaultValue({defaultValue})]");
}
WriteOptions(ctx, obj.Options);
if (isRepeated)
{
var mapMsgType = isMap ? ctx.TryFind(obj.TypeName) : null;
if (mapMsgType != null)
{
string keyDataFormat;
bool _;
var keyTypeName = GetTypeName(ctx, mapMsgType.Fields.Single(x => x.Number == 1),
out keyDataFormat, out _);
string valueDataFormat;
var valueTypeName = GetTypeName(ctx, mapMsgType.Fields.Single(x => x.Number == 2),
out valueDataFormat, out _);
bool first = true;
tw = ctx.Write($"[global::ProtoBuf.ProtoMap");
if (!string.IsNullOrWhiteSpace(keyDataFormat))
{
tw.Write($"{(first ? "(" : ", ")}KeyFormat = global::ProtoBuf.DataFormat.{keyDataFormat}");
first = false;
}
if (!string.IsNullOrWhiteSpace(valueDataFormat))
{
tw.Write($"{(first ? "(" : ", ")}ValueFormat = global::ProtoBuf.DataFormat.{valueDataFormat}");
first = false;
}
tw.WriteLine(first ? "]" : ")]");
ctx.WriteLine($"{GetAccess(GetAccess(obj))} global::System.Collections.Generic.Dictionary<{keyTypeName}, {valueTypeName}> {Escape(name)} {{ get; }} = new global::System.Collections.Generic.Dictionary<{keyTypeName}, {valueTypeName}>();");
}
else if (UseArray(obj))
{
ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName}[] {Escape(name)} {{ get; set; }}");
}
else
{
ctx.WriteLine($"{GetAccess(GetAccess(obj))} global::System.Collections.Generic.List<{typeName}> {Escape(name)} {{ get; }} = new global::System.Collections.Generic.List<{typeName}>();");
}
}
else if (oneOf != null)
{
var defValue = string.IsNullOrWhiteSpace(defaultValue) ? $"default({typeName})" : defaultValue;
var fieldName = FieldPrefix + oneOf.OneOf.Name;
var storage = oneOf.GetStorage(obj.type, obj.TypeName);
ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)}").WriteLine("{").Indent();
switch (obj.type)
{
case FieldDescriptorProto.Type.TypeMessage:
case FieldDescriptorProto.Type.TypeGroup:
case FieldDescriptorProto.Type.TypeEnum:
case FieldDescriptorProto.Type.TypeBytes:
case FieldDescriptorProto.Type.TypeString:
ctx.WriteLine($"get {{ return {fieldName}.Is({obj.Number}) ? (({typeName}){fieldName}.{storage}) : {defValue}; }}");
break;
default:
ctx.WriteLine($"get {{ return {fieldName}.Is({obj.Number}) ? {fieldName}.{storage} : {defValue}; }}");
break;
}
var unionType = oneOf.GetUnionType();
ctx.WriteLine($"set {{ {fieldName} = new global::ProtoBuf.{unionType}({obj.Number}, value); }}")
.Outdent().WriteLine("}")
.WriteLine($"{GetAccess(GetAccess(obj))} bool ShouldSerialize{name}() => {fieldName}.Is({obj.Number});")
.WriteLine($"{GetAccess(GetAccess(obj))} void Reset{name}() => global::ProtoBuf.{unionType}.Reset(ref {fieldName}, {obj.Number});");
if (oneOf.IsFirst())
{
ctx.WriteLine().WriteLine($"private global::ProtoBuf.{unionType} {fieldName};");
}
}
else if (explicitValues)
{
string fieldName = FieldPrefix + name, fieldType;
bool isRef = false;
switch (obj.type)
{
case FieldDescriptorProto.Type.TypeString:
case FieldDescriptorProto.Type.TypeBytes:
fieldType = typeName;
isRef = true;
break;
default:
fieldType = typeName + "?";
break;
}
ctx.WriteLine($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)}").WriteLine("{").Indent();
tw = ctx.Write($"get {{ return {fieldName}");
if (!string.IsNullOrWhiteSpace(defaultValue))
{
tw.Write(" ?? ");
tw.Write(defaultValue);
}
else if (!isRef)
{
tw.Write(".GetValueOrDefault()");
}
tw.WriteLine("; }");
ctx.WriteLine($"set {{ {fieldName} = value; }}")
.Outdent().WriteLine("}")
.WriteLine($"{GetAccess(GetAccess(obj))} bool ShouldSerialize{name}() => {fieldName} != null;")
.WriteLine($"{GetAccess(GetAccess(obj))} void Reset{name}() => {fieldName} = null;")
.WriteLine($"private {fieldType} {fieldName};");
}
else
{
tw = ctx.Write($"{GetAccess(GetAccess(obj))} {typeName} {Escape(name)} {{ get; set; }}");
if (!string.IsNullOrWhiteSpace(defaultValue)) tw.Write($" = {defaultValue};");
tw.WriteLine();
}
ctx.WriteLine();
}
///
/// Starts an extgensions block
///
protected override void WriteExtensionsHeader(GeneratorContext ctx, FileDescriptorProto obj, ref object state)
{
var name = obj?.Options?.GetOptions()?.ExtensionTypeName;
if (string.IsNullOrWhiteSpace(name)) name = "Extensions";
ctx.WriteLine($"{GetAccess(GetAccess(obj))} static class {Escape(name)}").WriteLine("{").Indent();
}
///
/// Ends an extgensions block
///
protected override void WriteExtensionsFooter(GeneratorContext ctx, FileDescriptorProto obj, ref object state)
{
ctx.Outdent().WriteLine("}");
}
///
/// Starts an extensions block
///
protected override void WriteExtensionsHeader(GeneratorContext ctx, DescriptorProto obj, ref object state)
{
var name = obj?.Options?.GetOptions()?.ExtensionTypeName;
if (string.IsNullOrWhiteSpace(name)) name = "Extensions";
ctx.WriteLine($"{GetAccess(GetAccess(obj))} static class {Escape(name)}").WriteLine("{").Indent();
}
///
/// Ends an extensions block
///
protected override void WriteExtensionsFooter(GeneratorContext ctx, DescriptorProto obj, ref object state)
{
ctx.Outdent().WriteLine("}");
}
///
/// Write an extension
///
protected override void WriteExtension(GeneratorContext ctx, FieldDescriptorProto field)
{
string dataFormat;
bool isMap;
var type = GetTypeName(ctx, field, out dataFormat, out isMap);
if (isMap)
{
ctx.WriteLine("#error map extensions not yet implemented");
}
else if (field.label == FieldDescriptorProto.Label.LabelRepeated)
{
ctx.WriteLine("#error repeated extensions not yet implemented");
}
else
{
var msg = ctx.TryFind(field.Extendee);
var extendee = MakeRelativeName(field, msg, ctx.NameNormalizer);
var @this = field.Parent is FileDescriptorProto ? "this " : "";
string name = ctx.NameNormalizer.GetName(field);
var tw = ctx.WriteLine($"{GetAccess(GetAccess(field))} static {type} Get{name}({@this}{extendee} obj)")
.Write($"=> obj == null ? default({type}) : global::ProtoBuf.Extensible.GetValue<{type}>(obj, {field.Number}");
if (!string.IsNullOrEmpty(dataFormat))
{
tw.Write($", global::ProtoBuf.DataFormat.{dataFormat}");
}
tw.WriteLine(");");
ctx.WriteLine();
// GetValue(IExtensible instance, int tag, DataFormat format)
}
}
private static bool UseArray(FieldDescriptorProto field)
{
switch (field.type)
{
case FieldDescriptorProto.Type.TypeBool:
case FieldDescriptorProto.Type.TypeDouble:
case FieldDescriptorProto.Type.TypeFixed32:
case FieldDescriptorProto.Type.TypeFixed64:
case FieldDescriptorProto.Type.TypeFloat:
case FieldDescriptorProto.Type.TypeInt32:
case FieldDescriptorProto.Type.TypeInt64:
case FieldDescriptorProto.Type.TypeSfixed32:
case FieldDescriptorProto.Type.TypeSfixed64:
case FieldDescriptorProto.Type.TypeSint32:
case FieldDescriptorProto.Type.TypeSint64:
case FieldDescriptorProto.Type.TypeUint32:
case FieldDescriptorProto.Type.TypeUint64:
return true;
default:
return false;
}
}
private string GetTypeName(GeneratorContext ctx, FieldDescriptorProto field, out string dataFormat, out bool isMap)
{
dataFormat = "";
isMap = false;
switch (field.type)
{
case FieldDescriptorProto.Type.TypeDouble:
return "double";
case FieldDescriptorProto.Type.TypeFloat:
return "float";
case FieldDescriptorProto.Type.TypeBool:
return "bool";
case FieldDescriptorProto.Type.TypeString:
return "string";
case FieldDescriptorProto.Type.TypeSint32:
dataFormat = nameof(DataFormat.ZigZag);
return "int";
case FieldDescriptorProto.Type.TypeInt32:
return "int";
case FieldDescriptorProto.Type.TypeSfixed32:
dataFormat = nameof(DataFormat.FixedSize);
return "int";
case FieldDescriptorProto.Type.TypeSint64:
dataFormat = nameof(DataFormat.ZigZag);
return "long";
case FieldDescriptorProto.Type.TypeInt64:
return "long";
case FieldDescriptorProto.Type.TypeSfixed64:
dataFormat = nameof(DataFormat.FixedSize);
return "long";
case FieldDescriptorProto.Type.TypeFixed32:
dataFormat = nameof(DataFormat.FixedSize);
return "uint";
case FieldDescriptorProto.Type.TypeUint32:
return "uint";
case FieldDescriptorProto.Type.TypeFixed64:
dataFormat = nameof(DataFormat.FixedSize);
return "ulong";
case FieldDescriptorProto.Type.TypeUint64:
return "ulong";
case FieldDescriptorProto.Type.TypeBytes:
return "byte[]";
case FieldDescriptorProto.Type.TypeEnum:
switch (field.TypeName)
{
case ".bcl.DateTime.DateTimeKind":
return "global::System.DateTimeKind";
}
var enumType = ctx.TryFind(field.TypeName);
return MakeRelativeName(field, enumType, ctx.NameNormalizer);
case FieldDescriptorProto.Type.TypeGroup:
case FieldDescriptorProto.Type.TypeMessage:
switch (field.TypeName)
{
case WellKnownTypeTimestamp:
dataFormat = "WellKnown";
return "global::System.DateTime?";
case WellKnownTypeDuration:
dataFormat = "WellKnown";
return "global::System.TimeSpan?";
case ".bcl.NetObjectProxy":
return "object";
case ".bcl.DateTime":
return "global::System.DateTime?";
case ".bcl.TimeSpan":
return "global::System.TimeSpan?";
case ".bcl.Decimal":
return "decimal?";
case ".bcl.Guid":
return "global::System.Guid?";
}
var msgType = ctx.TryFind(field.TypeName);
if (field.type == FieldDescriptorProto.Type.TypeGroup)
{
dataFormat = nameof(DataFormat.Group);
}
isMap = msgType?.Options?.MapEntry ?? false;
return MakeRelativeName(field, msgType, ctx.NameNormalizer);
default:
return field.TypeName;
}
}
private string MakeRelativeName(FieldDescriptorProto field, IType target, NameNormalizer normalizer)
{
if (target == null) return Escape(field.TypeName); // the only thing we know
var declaringType = field.Parent;
if (declaringType is IType)
{
var name = FindNameFromCommonAncestor((IType)declaringType, target, normalizer);
if (!string.IsNullOrWhiteSpace(name)) return name;
}
return Escape(field.TypeName); // give up!
}
// k, what we do is; we have two types; each knows the parent, but nothing else, so:
// for each, use a stack to build the ancestry tree - the "top" of the stack will be the
// package, the bottom of the stack will be the type itself. They will often be stacks
// of different heights.
//
// Find how many is in the smallest stack; now take that many items, in turn, until we
// get something that is different (at which point, put that one back on the stack), or
// we run out of items in one of the stacks.
//
// There are now two options:
// - we ran out of things in the "target" stack - in which case, they are common enough to not
// need any resolution - just give back the fixed name
// - we have things left in the "target" stack - in which case we have found a common ancestor,
// or the target is a descendent; either way, just concat what is left (including the package
// if the package itself was different)
private string FindNameFromCommonAncestor(IType declaring, IType target, NameNormalizer normalizer)
{
// trivial case; asking for self, or asking for immediate child
if (ReferenceEquals(declaring, target) || ReferenceEquals(declaring, target.Parent))
{
if (target is DescriptorProto) return Escape(normalizer.GetName((DescriptorProto)target));
if (target is EnumDescriptorProto) return Escape(normalizer.GetName((EnumDescriptorProto)target));
return null;
}
var origTarget = target;
var xStack = new Stack();
while (declaring != null)
{
xStack.Push(declaring);
declaring = declaring.Parent;
}
var yStack = new Stack();
while (target != null)
{
yStack.Push(target);
target = target.Parent;
}
int lim = Math.Min(xStack.Count, yStack.Count);
for (int i = 0; i < lim; i++)
{
declaring = xStack.Peek();
target = yStack.Pop();
if (!ReferenceEquals(target, declaring))
{
// special-case: if both are the package (file), and they have the same namespace: we're OK
if (target is FileDescriptorProto && declaring is FileDescriptorProto &&
normalizer.GetName((FileDescriptorProto)declaring) == normalizer.GetName((FileDescriptorProto)target))
{
// that's fine, keep going
}
else
{
// put it back
yStack.Push(target);
break;
}
}
}
// if we used everything, then the target is an ancestor-or-self
if (yStack.Count == 0)
{
target = origTarget;
if (target is DescriptorProto) return Escape(normalizer.GetName((DescriptorProto)target));
if (target is EnumDescriptorProto) return Escape(normalizer.GetName((EnumDescriptorProto)target));
return null;
}
var sb = new StringBuilder();
while (yStack.Count != 0)
{
target = yStack.Pop();
string nextName;
if (target is FileDescriptorProto) nextName = normalizer.GetName((FileDescriptorProto)target);
else if (target is DescriptorProto) nextName = normalizer.GetName((DescriptorProto)target);
else if (target is EnumDescriptorProto) nextName = normalizer.GetName((EnumDescriptorProto)target);
else return null;
if (!string.IsNullOrWhiteSpace(nextName))
{
if (sb.Length == 0 && target is FileDescriptorProto) sb.Append("global::");
else if (sb.Length != 0) sb.Append('.');
sb.Append(Escape(nextName));
}
}
return sb.ToString();
}
static bool IsAncestorOrSelf(IType parent, IType child)
{
while (parent != null)
{
if (ReferenceEquals(parent, child)) return true;
parent = parent.Parent;
}
return false;
}
const string WellKnownTypeTimestamp = ".google.protobuf.Timestamp",
WellKnownTypeDuration = ".google.protobuf.Duration";
}
}