Implement a mechanism to control the output format of Message.toString within a Runnable instance.

PiperOrigin-RevId: 665539414
pull/17878/head
Protobuf Team Bot 7 months ago committed by Copybara-Service
parent 8bb789e462
commit 737803eec4
  1. 1
      java/core/BUILD.bazel
  2. 7
      java/core/src/main/java/com/google/protobuf/AbstractMessage.java
  3. 52
      java/core/src/main/java/com/google/protobuf/ProtobufToStringOutput.java
  4. 20
      java/core/src/main/java/com/google/protobuf/TextFormat.java
  5. 8
      java/core/src/test/java/com/google/protobuf/DebugFormatTest.java
  6. 103
      java/core/src/test/java/com/google/protobuf/ProtobufToStringOutputTest.java

@ -549,6 +549,7 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/Proto2ExtensionLookupSchemaTest.java",
"src/test/java/com/google/protobuf/Proto2SchemaTest.java",
"src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java",
"src/test/java/com/google/protobuf/ProtobufToStringOutputTest.java",
"src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java",
"src/test/java/com/google/protobuf/ServiceTest.java",
"src/test/java/com/google/protobuf/SingleFieldBuilderTest.java",

@ -84,8 +84,11 @@ public abstract class AbstractMessage
@Override
public final String toString() {
return TextFormat.printer()
.printToString(this, TextFormat.Printer.FieldReporterLevel.ABSTRACT_TO_STRING);
TextFormat.Printer printer =
ProtobufToStringOutput.shouldOutputDebugFormat()
? TextFormat.debugFormatPrinter()
: TextFormat.printer();
return printer.printToString(this, TextFormat.Printer.FieldReporterLevel.ABSTRACT_TO_STRING);
}
@Override

@ -0,0 +1,52 @@
package com.google.protobuf;
/**
* ProtobufToStringOutput controls the output format of {@link Message#toString()}. Specifically, for
* the Runnable object passed to `callWithDebugFormat` and `callWithTextFormat`, Message.toString()
* will always output the specified format unless ProtobufToStringOutput is used again to change the
* output format.
*/
public final class ProtobufToStringOutput {
private enum OutputMode {
DEBUG_FORMAT,
TEXT_FORMAT
}
private static final ThreadLocal<OutputMode> outputMode =
new ThreadLocal<OutputMode>() {
@Override
protected OutputMode initialValue() {
return OutputMode.TEXT_FORMAT;
}
};
private ProtobufToStringOutput() {}
@CanIgnoreReturnValue
private static OutputMode setOutputMode(OutputMode newMode) {
OutputMode oldMode = outputMode.get();
outputMode.set(newMode);
return oldMode;
}
private static void callWithSpecificFormat(Runnable impl, OutputMode mode) {
OutputMode oldMode = setOutputMode(mode);
try {
impl.run();
} finally {
OutputMode unused = setOutputMode(oldMode);
}
}
public static void callWithDebugFormat(Runnable impl) {
callWithSpecificFormat(impl, OutputMode.DEBUG_FORMAT);
}
public static void callWithTextFormat(Runnable impl) {
callWithSpecificFormat(impl, OutputMode.TEXT_FORMAT);
}
public static boolean shouldOutputDebugFormat() {
return outputMode.get() == OutputMode.DEBUG_FORMAT;
}
}

@ -111,14 +111,19 @@ public final class TextFormat {
/** Printer instance which escapes non-ASCII characters. */
public static Printer printer() {
return Printer.DEFAULT;
return Printer.DEFAULT_TEXT_FORMAT;
}
/** Printer instance which escapes non-ASCII characters and prints in the debug format. */
public static Printer debugFormatPrinter() {
return Printer.DEFAULT_DEBUG_FORMAT;
}
/** Helper class for converting protobufs to text. */
public static final class Printer {
// Printer instance which escapes non-ASCII characters.
private static final Printer DEFAULT =
// Printer instance which escapes non-ASCII characters and prints in the text format.
private static final Printer DEFAULT_TEXT_FORMAT =
new Printer(
true,
TypeRegistry.getEmptyTypeRegistry(),
@ -126,6 +131,15 @@ public final class TextFormat {
false,
false);
// Printer instance which escapes non-ASCII characters and prints in the debug format.
private static final Printer DEFAULT_DEBUG_FORMAT =
new Printer(
true,
TypeRegistry.getEmptyTypeRegistry(),
ExtensionRegistryLite.getEmptyRegistry(),
true,
false);
/**
* A list of the public APIs that output human-readable text from a message. A higher-level API
* must be larger than any lower-level APIs it calls under the hood, e.g

@ -12,11 +12,11 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class DebugFormatTest {
public class DebugFormatTest {
private static final String REDACTED_REGEX = "\\[REDACTED\\]";
private static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true);
private static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false);
static final String REDACTED_REGEX = "\\[REDACTED\\]";
static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true);
static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false);
private static String getUnstablePrefix(boolean singleLine) {
return "";

@ -0,0 +1,103 @@
package com.google.protobuf;
import static com.google.common.truth.Truth.assertThat;
import protobuf_unittest.UnittestProto.RedactedFields;
import protobuf_unittest.UnittestProto.TestNestedMessageRedaction;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class ProtobufToStringOutputTest extends DebugFormatTest {
RedactedFields message;
@Before
public void setupTest() {
message =
RedactedFields.newBuilder()
.setOptionalUnredactedString("foo")
.setOptionalRedactedString("bar")
.setOptionalRedactedMessage(
TestNestedMessageRedaction.newBuilder().setOptionalUnredactedNestedString("foobar"))
.build();
}
@Test
public void toStringFormat_defaultFormat() {
assertThat(message.toString())
.matches(
"optional_redacted_string: \"bar\"\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " optional_unredacted_nested_string: \"foobar\"\n"
+ "\\}\n");
}
@Test
public void toStringFormat_testDebugFormat() {
ProtobufToStringOutput.callWithDebugFormat(
() ->
assertThat(message.toString())
.matches(
String.format(
"%soptional_redacted_string: %s\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " %s\n"
+ "\\}\n",
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX, REDACTED_REGEX)));
}
@Test
public void toStringFormat_testTextFormat() {
ProtobufToStringOutput.callWithTextFormat(
() -> {
assertThat(message.toString())
.matches(
"optional_redacted_string: \"bar\"\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " optional_unredacted_nested_string: \"foobar\"\n"
+ "\\}\n");
});
}
@Test
public void toStringFormat_testProtoWrapperWithDebugFormat() {
ProtobufToStringOutput.callWithDebugFormat(
() -> {
ArrayList<RedactedFields> list = new ArrayList<>();
list.add(message);
assertThat(list.toString())
.matches(
String.format(
"\\[%soptional_redacted_string: %s\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " %s\n"
+ "\\}\n"
+ "\\]",
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX, REDACTED_REGEX));
});
}
@Test
public void toStringFormat_testProtoWrapperWithTextFormat() {
ProtobufToStringOutput.callWithTextFormat(
() -> {
ArrayList<RedactedFields> list = new ArrayList<>();
list.add(message);
assertThat(list.toString())
.matches(
"\\[optional_redacted_string: \"bar\"\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " optional_unredacted_nested_string: \"foobar\"\n"
+ "\\}\n"
+ "\\]");
});
}
}
Loading…
Cancel
Save