From f5ec2ab85ce0ca94917d02a8619d5018c52b8986 Mon Sep 17 00:00:00 2001 From: Kurt Alfred Kluever Date: Wed, 9 Oct 2024 14:16:31 -0700 Subject: [PATCH] add `ToStringHelper.omitEmptyValues()`. Fixes https://github.com/google/guava/issues/7415 RELNOTES=Add `ToStringHelper.omitEmptyValues()`. PiperOrigin-RevId: 684168908 --- .../common/base/ToStringHelperTest.java | 157 ++++++++++++++++ .../com/google/common/base/MoreObjects.java | 18 ++ .../common/base/ToStringHelperTest.java | 168 ++++++++++++++++++ .../com/google/common/base/MoreObjects.java | 18 ++ 4 files changed, 361 insertions(+) diff --git a/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java b/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java index ae1d5a20a380..c0bf9b25ce76 100644 --- a/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java +++ b/android/guava-tests/test/com/google/common/base/ToStringHelperTest.java @@ -20,8 +20,12 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap; +import java.nio.CharBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import junit.framework.TestCase; @@ -316,6 +320,13 @@ public void testToStringOmitNullValues_oneField() { assertEquals("TestClass{}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_oneField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitEmptyValues().add("field1", "").toString(); + assertEquals("TestClass{}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsFirstNull() { String toTest = @@ -328,6 +339,18 @@ public void testToStringOmitNullValues_manyFieldsFirstNull() { assertEquals("TestClass{field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsFirstEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { String toTest = @@ -340,6 +363,18 @@ public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { assertEquals("TestClass{field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsOmitAfterEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "") + .add("field2", "Googley") + .add("field3", "World") + .omitEmptyValues() + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsLastNull() { String toTest = @@ -352,6 +387,25 @@ public void testToStringOmitNullValues_manyFieldsLastNull() { assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsLastEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); + } + + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_oneValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitEmptyValues().addValue("").toString(); + assertEquals("TestClass{}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitEmptyValues_oneValue() { String toTest = @@ -371,6 +425,18 @@ public void testToStringOmitNullValues_manyValuesFirstNull() { assertEquals("TestClass{Googley, World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyValuesFirstEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .addValue("") + .addValue("Googley") + .addValue("World") + .toString(); + assertEquals("TestClass{Googley, World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyValuesLastNull() { String toTest = @@ -383,6 +449,18 @@ public void testToStringOmitNullValues_manyValuesLastNull() { assertEquals("TestClass{Hello, Googley}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyValuesLastEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .addValue("Hello") + .addValue("Googley") + .addValue("") + .toString(); + assertEquals("TestClass{Hello, Googley}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_differentOrder() { String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; @@ -404,6 +482,27 @@ public void testToStringOmitNullValues_differentOrder() { assertEquals(expected, toTest2); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_differentOrder() { + String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; + String toTest1 = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + String toTest2 = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "Hello") + .add("field2", "Googley") + .omitEmptyValues() + .add("field3", "World") + .toString(); + assertEquals(expected, toTest1); + assertEquals(expected, toTest2); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_canBeCalledManyTimes() { String toTest = @@ -419,6 +518,64 @@ public void testToStringOmitNullValues_canBeCalledManyTimes() { assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_canBeCalledManyTimes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .omitEmptyValues() + .add("field1", "Hello") + .omitEmptyValues() + .add("field2", "Googley") + .omitEmptyValues() + .add("field3", "World") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); + } + + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_allEmptyTypes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + // CharSequences + .add("field1", "") + .add("field2", new StringBuilder()) + // nio CharBuffer (implements CharSequence) is tested separately below + // Collections and Maps + .add("field11", Arrays.asList("Hello")) + .add("field12", new ArrayList<>()) + .add("field13", new HashMap<>()) + // Optionals + .add("field26", Optional.of("World")) + .add("field27", Optional.absent()) + // Arrays + .add("field31", new Object[] {"!!!"}) + .add("field32", new boolean[0]) + .add("field33", new byte[0]) + .add("field34", new char[0]) + .add("field35", new short[0]) + .add("field36", new int[0]) + .add("field37", new long[0]) + .add("field38", new float[0]) + .add("field39", new double[0]) + .add("field40", new Object[0]) + .toString(); + assertEquals("TestClass{field11=[Hello], field26=Optional.of(World), field31=[!!!]}", toTest); + } + + @J2ktIncompatible // J2kt CharBuffer does not implement CharSequence so not recognized as empty + @GwtIncompatible // CharBuffer not available + public void testToStringOmitEmptyValues_charBuffer() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", CharBuffer.allocate(0)) + .toString(); + assertEquals("TestClass{field1=Hello}", toTest); + } + public void testToStringHelperWithArrays() { String[] strings = {"hello", "world"}; int[] ints = {2, 42}; diff --git a/android/guava/src/com/google/common/base/MoreObjects.java b/android/guava/src/com/google/common/base/MoreObjects.java index 6b76006884db..ad36faec5a69 100644 --- a/android/guava/src/com/google/common/base/MoreObjects.java +++ b/android/guava/src/com/google/common/base/MoreObjects.java @@ -169,6 +169,24 @@ public ToStringHelper omitNullValues() { return this; } + /** + * Configures the {@link ToStringHelper} so {@link #toString()} will ignore properties with + * empty values. The order of calling this method, relative to the {@code add()}/{@code + * addValue()} methods, is not significant. + * + *

Note: in general, code should assume that the string form returned by + * `ToStringHelper` for a given object may change. In particular, the list of types which are + * checked for emptiness is subject to change. We currently check {@code CharSequence}s, {@code + * Collection}s, {@code Map}s, optionals (including Guava's), and arrays. + * + * @since NEXT + */ + @CanIgnoreReturnValue + public ToStringHelper omitEmptyValues() { + omitEmptyValues = true; + return this; + } + /** * Adds a name/value pair to the formatted output in {@code name=value} format. If {@code value} * is {@code null}, the string {@code "null"} is used, unless {@link #omitNullValues()} is diff --git a/guava-tests/test/com/google/common/base/ToStringHelperTest.java b/guava-tests/test/com/google/common/base/ToStringHelperTest.java index ae1d5a20a380..21e53d90814f 100644 --- a/guava-tests/test/com/google/common/base/ToStringHelperTest.java +++ b/guava-tests/test/com/google/common/base/ToStringHelperTest.java @@ -20,9 +20,16 @@ import com.google.common.annotations.GwtCompatible; import com.google.common.annotations.GwtIncompatible; +import com.google.common.annotations.J2ktIncompatible; import com.google.common.collect.ImmutableMap; +import java.nio.CharBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import junit.framework.TestCase; /** @@ -316,6 +323,13 @@ public void testToStringOmitNullValues_oneField() { assertEquals("TestClass{}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_oneField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitEmptyValues().add("field1", "").toString(); + assertEquals("TestClass{}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsFirstNull() { String toTest = @@ -328,6 +342,18 @@ public void testToStringOmitNullValues_manyFieldsFirstNull() { assertEquals("TestClass{field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsFirstEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { String toTest = @@ -340,6 +366,18 @@ public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { assertEquals("TestClass{field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsOmitAfterEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "") + .add("field2", "Googley") + .add("field3", "World") + .omitEmptyValues() + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyFieldsLastNull() { String toTest = @@ -352,6 +390,25 @@ public void testToStringOmitNullValues_manyFieldsLastNull() { assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyFieldsLastEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); + } + + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_oneValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitEmptyValues().addValue("").toString(); + assertEquals("TestClass{}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitEmptyValues_oneValue() { String toTest = @@ -371,6 +428,18 @@ public void testToStringOmitNullValues_manyValuesFirstNull() { assertEquals("TestClass{Googley, World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyValuesFirstEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .addValue("") + .addValue("Googley") + .addValue("World") + .toString(); + assertEquals("TestClass{Googley, World}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_manyValuesLastNull() { String toTest = @@ -383,6 +452,18 @@ public void testToStringOmitNullValues_manyValuesLastNull() { assertEquals("TestClass{Hello, Googley}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_manyValuesLastEmpty() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .addValue("Hello") + .addValue("Googley") + .addValue("") + .toString(); + assertEquals("TestClass{Hello, Googley}", toTest); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_differentOrder() { String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; @@ -404,6 +485,27 @@ public void testToStringOmitNullValues_differentOrder() { assertEquals(expected, toTest2); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_differentOrder() { + String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; + String toTest1 = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + String toTest2 = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "Hello") + .add("field2", "Googley") + .omitEmptyValues() + .add("field3", "World") + .toString(); + assertEquals(expected, toTest1); + assertEquals(expected, toTest2); + } + @GwtIncompatible // Class names are obfuscated in GWT public void testToStringOmitNullValues_canBeCalledManyTimes() { String toTest = @@ -419,6 +521,72 @@ public void testToStringOmitNullValues_canBeCalledManyTimes() { assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); } + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_canBeCalledManyTimes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .omitEmptyValues() + .add("field1", "Hello") + .omitEmptyValues() + .add("field2", "Googley") + .omitEmptyValues() + .add("field3", "World") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); + } + + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitEmptyValues_allEmptyTypes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + // CharSequences + .add("field1", "") + .add("field2", new StringBuilder()) + // nio CharBuffer (implements CharSequence) is tested separately below + // Collections and Maps + .add("field11", Arrays.asList("Hello")) + .add("field12", new ArrayList<>()) + .add("field13", new HashMap<>()) + // Optionals + .add("field21", java.util.Optional.of("Googley")) + .add("field22", java.util.Optional.empty()) + .add("field23", OptionalInt.empty()) + .add("field24", OptionalLong.empty()) + .add("field25", OptionalDouble.empty()) + .add("field26", Optional.of("World")) + .add("field27", Optional.absent()) + // Arrays + .add("field31", new Object[] {"!!!"}) + .add("field32", new boolean[0]) + .add("field33", new byte[0]) + .add("field34", new char[0]) + .add("field35", new short[0]) + .add("field36", new int[0]) + .add("field37", new long[0]) + .add("field38", new float[0]) + .add("field39", new double[0]) + .add("field40", new Object[0]) + .toString(); + assertEquals( + "TestClass{field11=[Hello], field21=Optional[Googley], field26=Optional.of(World)," + + " field31=[!!!]}", + toTest); + } + + @J2ktIncompatible // J2kt CharBuffer does not implement CharSequence so not recognized as empty + @GwtIncompatible // CharBuffer not available + public void testToStringOmitEmptyValues_charBuffer() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitEmptyValues() + .add("field1", "Hello") + .add("field2", CharBuffer.allocate(0)) + .toString(); + assertEquals("TestClass{field1=Hello}", toTest); + } + public void testToStringHelperWithArrays() { String[] strings = {"hello", "world"}; int[] ints = {2, 42}; diff --git a/guava/src/com/google/common/base/MoreObjects.java b/guava/src/com/google/common/base/MoreObjects.java index 1074336f585a..57011aa9838f 100644 --- a/guava/src/com/google/common/base/MoreObjects.java +++ b/guava/src/com/google/common/base/MoreObjects.java @@ -172,6 +172,24 @@ public ToStringHelper omitNullValues() { return this; } + /** + * Configures the {@link ToStringHelper} so {@link #toString()} will ignore properties with + * empty values. The order of calling this method, relative to the {@code add()}/{@code + * addValue()} methods, is not significant. + * + *

Note: in general, code should assume that the string form returned by + * `ToStringHelper` for a given object may change. In particular, the list of types which are + * checked for emptiness is subject to change. We currently check {@code CharSequence}s, {@code + * Collection}s, {@code Map}s, optionals (including Guava's), and arrays. + * + * @since NEXT + */ + @CanIgnoreReturnValue + public ToStringHelper omitEmptyValues() { + omitEmptyValues = true; + return this; + } + /** * Adds a name/value pair to the formatted output in {@code name=value} format. If {@code value} * is {@code null}, the string {@code "null"} is used, unless {@link #omitNullValues()} is