@@ -38,6 +38,8 @@ private TextFormat() {}
3838
3939 private static final Logger logger = Logger .getLogger (TextFormat .class .getName ());
4040
41+ private static final String DEBUG_STRING_SILENT_MARKER = " \t " ;
42+
4143 private static final String REDACTED_MARKER = "[REDACTED]" ;
4244
4345 /**
@@ -998,6 +1000,15 @@ private static final class Tokenizer {
9981000 private int previousLine = 0 ;
9991001 private int previousColumn = 0 ;
10001002
1003+ /**
1004+ * {@link containsSilentMarkerAfterCurrentToken} indicates if there is a silent marker after the
1005+ * current token. This value is moved to {@link containsSilentMarkerAfterPrevToken} every time
1006+ * the next token is parsed.
1007+ */
1008+ private boolean containsSilentMarkerAfterCurrentToken = false ;
1009+
1010+ private boolean containsSilentMarkerAfterPrevToken = false ;
1011+
10011012 /** Construct a tokenizer that parses tokens from the given text. */
10021013 private Tokenizer (final CharSequence text ) {
10031014 this .text = text ;
@@ -1021,6 +1032,14 @@ int getColumn() {
10211032 return column ;
10221033 }
10231034
1035+ boolean getContainsSilentMarkerAfterCurrentToken () {
1036+ return containsSilentMarkerAfterCurrentToken ;
1037+ }
1038+
1039+ boolean getContainsSilentMarkerAfterPrevToken () {
1040+ return containsSilentMarkerAfterPrevToken ;
1041+ }
1042+
10241043 /** Are we at the end of the input? */
10251044 boolean atEnd () {
10261045 return currentToken .length () == 0 ;
@@ -1709,6 +1728,19 @@ public static <T extends Message> T parse(
17091728 * control the parser behavior.
17101729 */
17111730 public static class Parser {
1731+
1732+ /**
1733+ * A valid silent marker appears between a field name and its value. If there is a ":" in
1734+ * between, the silent marker will only appear after the colon. This is called after a field
1735+ * name is parsed, and before the ":" if it exists. If the current token is ":", then
1736+ * containsSilentMarkerAfterCurrentToken indicates if there is a valid silent marker. Otherwise,
1737+ * the current token is part of the field value, so the silent marker is indicated by
1738+ * containsSilentMarkerAfterPrevToken.
1739+ */
1740+ private void detectSilentMarker (
1741+ Tokenizer tokenizer , Descriptor immediateMessageType , String fieldName ) {
1742+ }
1743+
17121744 /**
17131745 * Determines if repeated values for non-repeated fields and oneofs are permitted. For example,
17141746 * given required/optional field "foo" and a oneof containing "baz" and "moo":
@@ -2081,12 +2113,14 @@ private void mergeField(
20812113
20822114 // Skips unknown fields.
20832115 if (field == null ) {
2116+ detectSilentMarker (tokenizer , type , name );
20842117 guessFieldTypeAndSkip (tokenizer , type , recursionLimit );
20852118 return ;
20862119 }
20872120
20882121 // Handle potential ':'.
20892122 if (field .getJavaType () == FieldDescriptor .JavaType .MESSAGE ) {
2123+ detectSilentMarker (tokenizer , type , field .getFullName ());
20902124 tokenizer .tryConsume (":" ); // optional
20912125 if (parseTreeBuilder != null ) {
20922126 TextFormatParseInfoTree .Builder childParseTreeBuilder =
@@ -2112,6 +2146,7 @@ private void mergeField(
21122146 recursionLimit );
21132147 }
21142148 } else {
2149+ detectSilentMarker (tokenizer , type , field .getFullName ());
21152150 tokenizer .consume (":" ); // required
21162151 consumeFieldValues (
21172152 tokenizer ,
@@ -2135,26 +2170,26 @@ private void mergeField(
21352170 }
21362171 }
21372172
2138- private void consumeFullTypeName (Tokenizer tokenizer ) throws ParseException {
2173+ private String consumeFullTypeName (Tokenizer tokenizer ) throws ParseException {
21392174 // If there is not a leading `[`, this is just a type name.
21402175 if (!tokenizer .tryConsume ("[" )) {
2141- tokenizer .consumeIdentifier ();
2142- return ;
2176+ return tokenizer .consumeIdentifier ();
21432177 }
21442178
21452179 // Otherwise, this is an extension or google.protobuf.Any type URL: we consume proto path
21462180 // elements until we've addressed the type.
2147- tokenizer .consumeIdentifier ();
2181+ String name = tokenizer .consumeIdentifier ();
21482182 while (tokenizer .tryConsume ("." )) {
2149- tokenizer .consumeIdentifier ();
2183+ name += "." + tokenizer .consumeIdentifier ();
21502184 }
21512185 if (tokenizer .tryConsume ("/" )) {
2152- tokenizer .consumeIdentifier ();
2186+ name += "/" + tokenizer .consumeIdentifier ();
21532187 while (tokenizer .tryConsume ("." )) {
2154- tokenizer .consumeIdentifier ();
2188+ name += "." + tokenizer .consumeIdentifier ();
21552189 }
21562190 }
21572191 tokenizer .consume ("]" );
2192+ return name ;
21582193 }
21592194
21602195 /**
@@ -2400,6 +2435,7 @@ private void mergeAnyFieldValue(
24002435 throw tokenizer .parseExceptionPreviousToken ("Expected a valid type URL." );
24012436 }
24022437 }
2438+ detectSilentMarker (tokenizer , anyDescriptor , typeUrlBuilder .toString ());
24032439 tokenizer .tryConsume (":" );
24042440 final String anyEndToken ;
24052441 if (tokenizer .tryConsume ("<" )) {
@@ -2444,7 +2480,8 @@ private void mergeAnyFieldValue(
24442480 /** Skips the next field including the field's name and value. */
24452481 private void skipField (Tokenizer tokenizer , Descriptor type , int recursionLimit )
24462482 throws ParseException {
2447- consumeFullTypeName (tokenizer );
2483+ String name = consumeFullTypeName (tokenizer );
2484+ detectSilentMarker (tokenizer , type , name );
24482485 guessFieldTypeAndSkip (tokenizer , type , recursionLimit );
24492486
24502487 // For historical reasons, fields may optionally be separated by commas or
0 commit comments