Skip to content

Commit 737803e

Browse files
Implement a mechanism to control the output format of Message.toString within a Runnable instance.
PiperOrigin-RevId: 665539414
1 parent 8bb789e commit 737803e

File tree

6 files changed

+182
-9
lines changed

6 files changed

+182
-9
lines changed

java/core/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,7 @@ LITE_TEST_EXCLUSIONS = [
549549
"src/test/java/com/google/protobuf/Proto2ExtensionLookupSchemaTest.java",
550550
"src/test/java/com/google/protobuf/Proto2SchemaTest.java",
551551
"src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java",
552+
"src/test/java/com/google/protobuf/ProtobufToStringOutputTest.java",
552553
"src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java",
553554
"src/test/java/com/google/protobuf/ServiceTest.java",
554555
"src/test/java/com/google/protobuf/SingleFieldBuilderTest.java",

java/core/src/main/java/com/google/protobuf/AbstractMessage.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,11 @@ public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) {
8484

8585
@Override
8686
public final String toString() {
87-
return TextFormat.printer()
88-
.printToString(this, TextFormat.Printer.FieldReporterLevel.ABSTRACT_TO_STRING);
87+
TextFormat.Printer printer =
88+
ProtobufToStringOutput.shouldOutputDebugFormat()
89+
? TextFormat.debugFormatPrinter()
90+
: TextFormat.printer();
91+
return printer.printToString(this, TextFormat.Printer.FieldReporterLevel.ABSTRACT_TO_STRING);
8992
}
9093

9194
@Override
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.google.protobuf;
2+
3+
/**
4+
* ProtobufToStringOutput controls the output format of {@link Message#toString()}. Specifically, for
5+
* the Runnable object passed to `callWithDebugFormat` and `callWithTextFormat`, Message.toString()
6+
* will always output the specified format unless ProtobufToStringOutput is used again to change the
7+
* output format.
8+
*/
9+
public final class ProtobufToStringOutput {
10+
private enum OutputMode {
11+
DEBUG_FORMAT,
12+
TEXT_FORMAT
13+
}
14+
15+
private static final ThreadLocal<OutputMode> outputMode =
16+
new ThreadLocal<OutputMode>() {
17+
@Override
18+
protected OutputMode initialValue() {
19+
return OutputMode.TEXT_FORMAT;
20+
}
21+
};
22+
23+
private ProtobufToStringOutput() {}
24+
25+
@CanIgnoreReturnValue
26+
private static OutputMode setOutputMode(OutputMode newMode) {
27+
OutputMode oldMode = outputMode.get();
28+
outputMode.set(newMode);
29+
return oldMode;
30+
}
31+
32+
private static void callWithSpecificFormat(Runnable impl, OutputMode mode) {
33+
OutputMode oldMode = setOutputMode(mode);
34+
try {
35+
impl.run();
36+
} finally {
37+
OutputMode unused = setOutputMode(oldMode);
38+
}
39+
}
40+
41+
public static void callWithDebugFormat(Runnable impl) {
42+
callWithSpecificFormat(impl, OutputMode.DEBUG_FORMAT);
43+
}
44+
45+
public static void callWithTextFormat(Runnable impl) {
46+
callWithSpecificFormat(impl, OutputMode.TEXT_FORMAT);
47+
}
48+
49+
public static boolean shouldOutputDebugFormat() {
50+
return outputMode.get() == OutputMode.DEBUG_FORMAT;
51+
}
52+
}

java/core/src/main/java/com/google/protobuf/TextFormat.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,21 +111,35 @@ private static void printUnknownFieldValue(
111111

112112
/** Printer instance which escapes non-ASCII characters. */
113113
public static Printer printer() {
114-
return Printer.DEFAULT;
114+
return Printer.DEFAULT_TEXT_FORMAT;
115+
}
116+
117+
/** Printer instance which escapes non-ASCII characters and prints in the debug format. */
118+
public static Printer debugFormatPrinter() {
119+
return Printer.DEFAULT_DEBUG_FORMAT;
115120
}
116121

117122
/** Helper class for converting protobufs to text. */
118123
public static final class Printer {
119124

120-
// Printer instance which escapes non-ASCII characters.
121-
private static final Printer DEFAULT =
125+
// Printer instance which escapes non-ASCII characters and prints in the text format.
126+
private static final Printer DEFAULT_TEXT_FORMAT =
122127
new Printer(
123128
true,
124129
TypeRegistry.getEmptyTypeRegistry(),
125130
ExtensionRegistryLite.getEmptyRegistry(),
126131
false,
127132
false);
128133

134+
// Printer instance which escapes non-ASCII characters and prints in the debug format.
135+
private static final Printer DEFAULT_DEBUG_FORMAT =
136+
new Printer(
137+
true,
138+
TypeRegistry.getEmptyTypeRegistry(),
139+
ExtensionRegistryLite.getEmptyRegistry(),
140+
true,
141+
false);
142+
129143
/**
130144
* A list of the public APIs that output human-readable text from a message. A higher-level API
131145
* must be larger than any lower-level APIs it calls under the hood, e.g

java/core/src/test/java/com/google/protobuf/DebugFormatTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
import org.junit.runners.JUnit4;
1313

1414
@RunWith(JUnit4.class)
15-
public final class DebugFormatTest {
15+
public class DebugFormatTest {
1616

17-
private static final String REDACTED_REGEX = "\\[REDACTED\\]";
18-
private static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true);
19-
private static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false);
17+
static final String REDACTED_REGEX = "\\[REDACTED\\]";
18+
static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true);
19+
static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false);
2020

2121
private static String getUnstablePrefix(boolean singleLine) {
2222
return "";
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.google.protobuf;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
5+
import protobuf_unittest.UnittestProto.RedactedFields;
6+
import protobuf_unittest.UnittestProto.TestNestedMessageRedaction;
7+
import java.util.ArrayList;
8+
import org.junit.Before;
9+
import org.junit.Test;
10+
import org.junit.runner.RunWith;
11+
import org.junit.runners.JUnit4;
12+
13+
@RunWith(JUnit4.class)
14+
public final class ProtobufToStringOutputTest extends DebugFormatTest {
15+
RedactedFields message;
16+
17+
@Before
18+
public void setupTest() {
19+
message =
20+
RedactedFields.newBuilder()
21+
.setOptionalUnredactedString("foo")
22+
.setOptionalRedactedString("bar")
23+
.setOptionalRedactedMessage(
24+
TestNestedMessageRedaction.newBuilder().setOptionalUnredactedNestedString("foobar"))
25+
.build();
26+
}
27+
28+
@Test
29+
public void toStringFormat_defaultFormat() {
30+
assertThat(message.toString())
31+
.matches(
32+
"optional_redacted_string: \"bar\"\n"
33+
+ "optional_unredacted_string: \"foo\"\n"
34+
+ "optional_redacted_message \\{\n"
35+
+ " optional_unredacted_nested_string: \"foobar\"\n"
36+
+ "\\}\n");
37+
}
38+
39+
@Test
40+
public void toStringFormat_testDebugFormat() {
41+
ProtobufToStringOutput.callWithDebugFormat(
42+
() ->
43+
assertThat(message.toString())
44+
.matches(
45+
String.format(
46+
"%soptional_redacted_string: %s\n"
47+
+ "optional_unredacted_string: \"foo\"\n"
48+
+ "optional_redacted_message \\{\n"
49+
+ " %s\n"
50+
+ "\\}\n",
51+
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX, REDACTED_REGEX)));
52+
}
53+
54+
@Test
55+
public void toStringFormat_testTextFormat() {
56+
ProtobufToStringOutput.callWithTextFormat(
57+
() -> {
58+
assertThat(message.toString())
59+
.matches(
60+
"optional_redacted_string: \"bar\"\n"
61+
+ "optional_unredacted_string: \"foo\"\n"
62+
+ "optional_redacted_message \\{\n"
63+
+ " optional_unredacted_nested_string: \"foobar\"\n"
64+
+ "\\}\n");
65+
});
66+
}
67+
68+
@Test
69+
public void toStringFormat_testProtoWrapperWithDebugFormat() {
70+
ProtobufToStringOutput.callWithDebugFormat(
71+
() -> {
72+
ArrayList<RedactedFields> list = new ArrayList<>();
73+
list.add(message);
74+
assertThat(list.toString())
75+
.matches(
76+
String.format(
77+
"\\[%soptional_redacted_string: %s\n"
78+
+ "optional_unredacted_string: \"foo\"\n"
79+
+ "optional_redacted_message \\{\n"
80+
+ " %s\n"
81+
+ "\\}\n"
82+
+ "\\]",
83+
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX, REDACTED_REGEX));
84+
});
85+
}
86+
87+
@Test
88+
public void toStringFormat_testProtoWrapperWithTextFormat() {
89+
ProtobufToStringOutput.callWithTextFormat(
90+
() -> {
91+
ArrayList<RedactedFields> list = new ArrayList<>();
92+
list.add(message);
93+
assertThat(list.toString())
94+
.matches(
95+
"\\[optional_redacted_string: \"bar\"\n"
96+
+ "optional_unredacted_string: \"foo\"\n"
97+
+ "optional_redacted_message \\{\n"
98+
+ " optional_unredacted_nested_string: \"foobar\"\n"
99+
+ "\\}\n"
100+
+ "\\]");
101+
});
102+
}
103+
}

0 commit comments

Comments
 (0)