@ -122,10 +122,14 @@ namespace Google.Protobuf
{
Preconditions . CheckNotNull ( message , "message" ) ;
StringBuilder builder = new StringBuilder ( ) ;
// TODO(jonskeet): Handle well-known types here.
// Our reflection support needs improving so that we can get at the descriptor
// to find out whether *this* message is a well-known type.
WriteMessage ( builder , message ) ;
if ( message . Descriptor . IsWellKnownType )
{
WriteWellKnownTypeValue ( builder , message . Descriptor , message , false ) ;
}
else
{
WriteMessage ( builder , message ) ;
}
return builder . ToString ( ) ;
}
@ -356,7 +360,7 @@ namespace Google.Protobuf
case FieldType . Group : // Never expect to get this, but...
if ( descriptor . MessageType . IsWellKnownType )
{
WriteWellKnownTypeValue ( builder , descriptor , value ) ;
WriteWellKnownTypeValue ( builder , descriptor . MessageType , value , tr ue ) ;
}
else
{
@ -370,20 +374,126 @@ namespace Google.Protobuf
/// <summary>
/// Central interception point for well-known type formatting. Any well-known types which
/// don't need special handling can fall back to WriteMessage.
/// don't need special handling can fall back to WriteMessage. We avoid assuming that the
/// values are using the embedded well-known types, in order to allow for dynamic messages
/// in the future.
/// </summary>
private void WriteWellKnownTypeValue ( StringBuilder builder , FieldDescriptor descriptor , object value )
private void WriteWellKnownTypeValue ( StringBuilder builder , Message Descriptor descriptor , object value , bool inField )
{
// For wrapper types, the value will be the (possibly boxed) "native" value,
// so we can write it as if we were unconditionally writing the Value field for the wrapper type.
if ( descriptor . MessageType . File = = Int32Value . Descriptor . File & & value ! = null )
if ( descriptor . File = = Int32Value . Descriptor . File & & value ! = null )
{
WriteSingleValue ( builder , descriptor . MessageType . FindFieldByNumber ( 1 ) , value ) ;
WriteSingleValue ( builder , descriptor . FindFieldByNumber ( 1 ) , value ) ;
return ;
}
if ( descriptor . FullName = = Timestamp . Descriptor . FullName & & value ! = null )
{
MaybeWrapInString ( builder , value , WriteTimestamp , inField ) ;
return ;
}
if ( descriptor . FullName = = Duration . Descriptor . FullName & & value ! = null )
{
MaybeWrapInString ( builder , value , WriteDuration , inField ) ;
return ;
}
WriteMessage ( builder , ( IMessage ) value ) ;
}
/// <summary>
/// Some well-known types end up as string values... so they need wrapping in quotes, but only
/// when they're being used as fields within another message.
/// </summary>
private void MaybeWrapInString ( StringBuilder builder , object value , Action < StringBuilder , IMessage > action , bool inField )
{
if ( inField )
{
builder . Append ( '"' ) ;
action ( builder , ( IMessage ) value ) ;
builder . Append ( '"' ) ;
}
else
{
action ( builder , ( IMessage ) value ) ;
}
}
private void WriteTimestamp ( StringBuilder builder , IMessage value )
{
// TODO: In the common case where this *is* using the built-in Timestamp type, we could
// avoid all the reflection at this point, by casting to Timestamp. In the interests of
// avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
// it still works in that case.
int nanos = ( int ) value . Descriptor . Fields [ Timestamp . NanosFieldNumber ] . Accessor . GetValue ( value ) ;
long seconds = ( long ) value . Descriptor . Fields [ Timestamp . SecondsFieldNumber ] . Accessor . GetValue ( value ) ;
// Even if the original message isn't using the built-in classes, we can still build one... and then
// rely on it being normalized.
Timestamp normalized = Timestamp . Normalize ( seconds , nanos ) ;
// Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
DateTime dateTime = normalized . ToDateTime ( ) ;
builder . Append ( dateTime . ToString ( "yyyy'-'MM'-'dd'T'HH:mm:ss" , CultureInfo . InvariantCulture ) ) ;
if ( normalized . Nanos ! = 0 )
{
builder . Append ( '.' ) ;
// Output to 3, 6 or 9 digits.
if ( normalized . Nanos % 1 0 0 0 0 0 0 = = 0 )
{
builder . Append ( ( normalized . Nanos / 1 0 0 0 0 0 0 ) . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
}
else if ( normalized . Nanos % 1 0 0 0 = = 0 )
{
builder . Append ( ( normalized . Nanos / 1 0 0 0 ) . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
}
else
{
builder . Append ( ( normalized . Nanos ) . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
}
}
builder . Append ( 'Z' ) ;
}
private void WriteDuration ( StringBuilder builder , IMessage value )
{
// TODO: In the common case where this *is* using the built-in Timestamp type, we could
// avoid all the reflection at this point, by casting to Timestamp. In the interests of
// avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
// it still works in that case.
int nanos = ( int ) value . Descriptor . Fields [ Duration . NanosFieldNumber ] . Accessor . GetValue ( value ) ;
long seconds = ( long ) value . Descriptor . Fields [ Duration . SecondsFieldNumber ] . Accessor . GetValue ( value ) ;
// Even if the original message isn't using the built-in classes, we can still build one... and then
// rely on it being normalized.
Duration normalized = Duration . Normalize ( seconds , nanos ) ;
// The seconds part will normally provide the minus sign if we need it, but not if it's 0...
if ( normalized . Seconds = = 0 & & normalized . Nanos < 0 )
{
builder . Append ( '-' ) ;
}
builder . Append ( normalized . Seconds . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
nanos = Math . Abs ( normalized . Nanos ) ;
if ( nanos ! = 0 )
{
builder . Append ( '.' ) ;
// Output to 3, 6 or 9 digits.
if ( nanos % 1 0 0 0 0 0 0 = = 0 )
{
builder . Append ( ( nanos / 1 0 0 0 0 0 0 ) . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
}
else if ( normalized . Nanos % 1 0 0 0 = = 0 )
{
builder . Append ( ( nanos / 1 0 0 0 ) . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
}
else
{
builder . Append ( nanos . ToString ( "d" , CultureInfo . InvariantCulture ) ) ;
}
}
builder . Append ( 's' ) ;
}
private void WriteList ( StringBuilder builder , IFieldAccessor accessor , IList list )
{
builder . Append ( "[ " ) ;
@ -537,7 +647,7 @@ namespace Google.Protobuf
}
}
builder . Append ( '"' ) ;
}
}
private const string Hex = "0123456789abcdef" ;
private static void HexEncodeUtf16CodeUnit ( StringBuilder builder , char c )