Skip to content

Commit

Permalink
Add support to print repeated fields with primitive values using shor…
Browse files Browse the repository at this point in the history
…t notation

This is inspired by the corrosponding options in the C++ and Python formatters.

PiperOrigin-RevId: 687471623
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Oct 19, 2024
1 parent fe53593 commit b8d3567
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 14 deletions.
97 changes: 83 additions & 14 deletions java/core/src/main/java/com/google/protobuf/TextFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,22 @@ public static final class Printer {
// Printer instance which escapes non-ASCII characters and prints in the text format.
private static final Printer DEFAULT_TEXT_FORMAT =
new Printer(
true,
/* escapeNonAscii= */ true,
/* useShortRepeatedPrimitives= */ false,
TypeRegistry.getEmptyTypeRegistry(),
ExtensionRegistryLite.getEmptyRegistry(),
false,
false);
/* enablingSafeDebugFormat= */ false,
/* singleLine= */ false);

// Printer instance which escapes non-ASCII characters and prints in the debug format.
private static final Printer DEFAULT_DEBUG_FORMAT =
new Printer(
true,
/* escapeNonAscii= */ true,
/* useShortRepeatedPrimitives= */ true,
TypeRegistry.getEmptyTypeRegistry(),
ExtensionRegistryLite.getEmptyRegistry(),
true,
false);
/* enablingSafeDebugFormat= */ true,
/* singleLine= */ false);

/**
* A list of the public APIs that output human-readable text from a message. A higher-level API
Expand Down Expand Up @@ -168,6 +170,9 @@ static enum FieldReporterLevel {
/** Whether to escape non ASCII characters with backslash and octal. */
private final boolean escapeNonAscii;

/** Whether to print repeated primitive fields using short square bracket notation. */
private final boolean useShortRepeatedPrimitives;

private final TypeRegistry typeRegistry;
private final ExtensionRegistryLite extensionRegistry;

Expand All @@ -191,11 +196,13 @@ protected FieldReporterLevel initialValue() {

private Printer(
boolean escapeNonAscii,
boolean useShortRepeatedPrimitives,
TypeRegistry typeRegistry,
ExtensionRegistryLite extensionRegistry,
boolean enablingSafeDebugFormat,
boolean singleLine) {
this.escapeNonAscii = escapeNonAscii;
this.useShortRepeatedPrimitives = useShortRepeatedPrimitives;
this.typeRegistry = typeRegistry;
this.extensionRegistry = extensionRegistry;
this.enablingSafeDebugFormat = enablingSafeDebugFormat;
Expand All @@ -213,7 +220,12 @@ private Printer(
*/
public Printer escapingNonAscii(boolean escapeNonAscii) {
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
escapeNonAscii,
useShortRepeatedPrimitives,
typeRegistry,
extensionRegistry,
enablingSafeDebugFormat,
singleLine);
}

/**
Expand All @@ -227,7 +239,12 @@ public Printer usingTypeRegistry(TypeRegistry typeRegistry) {
throw new IllegalArgumentException("Only one typeRegistry is allowed.");
}
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
escapeNonAscii,
useShortRepeatedPrimitives,
typeRegistry,
extensionRegistry,
enablingSafeDebugFormat,
singleLine);
}

/**
Expand All @@ -241,7 +258,12 @@ public Printer usingExtensionRegistry(ExtensionRegistryLite extensionRegistry) {
throw new IllegalArgumentException("Only one extensionRegistry is allowed.");
}
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
escapeNonAscii,
useShortRepeatedPrimitives,
typeRegistry,
extensionRegistry,
enablingSafeDebugFormat,
singleLine);
}

/**
Expand All @@ -255,7 +277,30 @@ public Printer usingExtensionRegistry(ExtensionRegistryLite extensionRegistry) {
*/
Printer enablingSafeDebugFormat(boolean enablingSafeDebugFormat) {
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
escapeNonAscii,
useShortRepeatedPrimitives,
typeRegistry,
extensionRegistry,
enablingSafeDebugFormat,
singleLine);
}

/**
* Return a new Printer instance that outputs primitive repeated fields in short notation
*
* @param useShortRepeatedPrimitives If true, repeated fields with a primitive type are printed
* using the short hand notation with comma-delimited field values in square brackets.
* @return a new Printer that clones all other configurations from the current {@link Printer},
* with the useShortRepeatedPrimitives mode set to the given parameter.
*/
Printer usingShortRepeatedPrimitives(boolean useShortRepeatedPrimitives) {
return new Printer(
escapeNonAscii,
useShortRepeatedPrimitives,
typeRegistry,
extensionRegistry,
enablingSafeDebugFormat,
singleLine);
}

/**
Expand All @@ -267,7 +312,12 @@ Printer enablingSafeDebugFormat(boolean enablingSafeDebugFormat) {
*/
public Printer emittingSingleLine(boolean singleLine) {
return new Printer(
escapeNonAscii, typeRegistry, extensionRegistry, enablingSafeDebugFormat, singleLine);
escapeNonAscii,
useShortRepeatedPrimitives,
typeRegistry,
extensionRegistry,
enablingSafeDebugFormat,
singleLine);
}

void setSensitiveFieldReportingLevel(FieldReporterLevel level) {
Expand Down Expand Up @@ -397,9 +447,12 @@ private void printField(
printSingleField(field, adapter.getEntry(), generator);
}
} else if (field.isRepeated()) {
// Repeated field. Print each element.
for (Object element : (List<?>) value) {
printSingleField(field, element, generator);
if (useShortRepeatedPrimitives && field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) {
printShortRepeatedField(field, value, generator);
} else {
for (Object element : (List<?>) value) {
printSingleField(field, element, generator);
}
}
} else {
printSingleField(field, value, generator);
Expand Down Expand Up @@ -714,6 +767,22 @@ private void printMessage(final MessageOrBuilder message, final TextGenerator ge
printUnknownFields(message.getUnknownFields(), generator, this.enablingSafeDebugFormat);
}

private void printShortRepeatedField(
final FieldDescriptor field, final Object value, final TextGenerator generator)
throws IOException {
generator.print(field.getName());
generator.print(": ");
generator.print("[");
String separator = "";
for (Object element : (List<?>) value) {
generator.print(separator);
printFieldValue(field, element, generator);
separator = ", ";
}
generator.print("]");
generator.eol();
}

private void printSingleField(
final FieldDescriptor field, final Object value, final TextGenerator generator)
throws IOException {
Expand Down
34 changes: 34 additions & 0 deletions java/core/src/test/java/com/google/protobuf/TextFormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import static protobuf_unittest.UnittestProto.optionalInt32Extension;
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableList;
import com.google.protobuf.DescriptorProtos.DescriptorProto;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto;
import com.google.protobuf.DescriptorProtos.FileDescriptorProto;
Expand Down Expand Up @@ -240,6 +241,39 @@ public void testPrintField() throws Exception {
.isEqualTo("optional_nested_message {\n bb: 42\n}\n");
}

@Test
public void testPrintRepeatedFieldUsingShortRepeatedPrimitives_usesRegularNotationForMessageType()
throws Exception {
final FieldDescriptor repeatedMessageField =
TestAllTypes.getDescriptor().findFieldByName("repeated_nested_message");
assertThat(
TextFormat.printer()
.usingShortRepeatedPrimitives(true)
.printFieldToString(
repeatedMessageField,
ImmutableList.of(
TestAllTypes.NestedMessage.getDefaultInstance(),
TestAllTypes.NestedMessage.getDefaultInstance())))
.isEqualTo("repeated_nested_message {\n}\nrepeated_nested_message {\n}\n");
}

@Test
public void testPrintRepeatedFieldUsingShortRepeatedPrimitives_usesShortNotationForPrimitiveType()
throws Exception {
final FieldDescriptor repeatedInt32Field =
TestAllTypes.getDescriptor().findFieldByName("repeated_int32");
assertThat(
TextFormat.printer()
.usingShortRepeatedPrimitives(true)
.printFieldToString(repeatedInt32Field, ImmutableList.of(0)))
.isEqualTo("repeated_int32: [0]\n");
assertThat(
TextFormat.printer()
.usingShortRepeatedPrimitives(true)
.printFieldToString(repeatedInt32Field, ImmutableList.of(0, 1, 2, 3)))
.isEqualTo("repeated_int32: [0, 1, 2, 3]\n");
}

/**
* Helper to construct a ByteString from a String containing only 8-bit characters. The characters
* are converted directly to bytes, *not* encoded using UTF-8.
Expand Down

0 comments on commit b8d3567

Please sign in to comment.