From 4aaebca4e1f05891ec8e5ff0183cdde9894815f3 Mon Sep 17 00:00:00 2001 From: cpovirk Date: Fri, 12 May 2023 13:53:21 -0700 Subject: [PATCH] Annotate `firstNonNull` as permitting nullable values for its second parameter. It has permitted them at runtime since the beginning, and that's intentional. (This CL also adds a test, which somehow we didn't have for all these years? _And_ somehow the tests that we did have weren't open-sourced, so this CL open-sources them.) And based on testing, I can say: Many callers pass not only values whose static type is nullable but also whose _actual runtime value_ is `null`. I don't have my testing results handy anymore, so I don't have a real-world example to share right now, but you can imagine something like this: ```java for (Key key : union(map1.keySet(), map2.keySet()) { Value value = firstNonNull(map1.get(key), map2.get(key)); } ``` RELNOTES=n/a PiperOrigin-RevId: 531596646 --- .../google/common/base/MoreObjectsTest.java | 497 ++++++++++++++++++ .../com/google/common/base/MoreObjects.java | 19 +- .../google/common/base/MoreObjectsTest.java | 497 ++++++++++++++++++ .../com/google/common/base/MoreObjects.java | 19 +- 4 files changed, 996 insertions(+), 36 deletions(-) create mode 100644 android/guava-tests/test/com/google/common/base/MoreObjectsTest.java create mode 100644 guava-tests/test/com/google/common/base/MoreObjectsTest.java diff --git a/android/guava-tests/test/com/google/common/base/MoreObjectsTest.java b/android/guava-tests/test/com/google/common/base/MoreObjectsTest.java new file mode 100644 index 000000000000..6a7149d174f9 --- /dev/null +++ b/android/guava-tests/test/com/google/common/base/MoreObjectsTest.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +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 com.google.common.testing.NullPointerTester; +import java.util.Arrays; +import java.util.Map; +import junit.framework.TestCase; + +/** Tests for {@link MoreObjects}. */ +@GwtCompatible(emulated = true) +public class MoreObjectsTest extends TestCase { + public void testFirstNonNull_withNonNull() { + String s1 = "foo"; + String s2 = MoreObjects.firstNonNull(s1, "bar"); + assertSame(s1, s2); + + Long n1 = 42L; + Long n2 = MoreObjects.firstNonNull(null, n1); + assertSame(n1, n2); + + Boolean b1 = true; + Boolean b2 = MoreObjects.firstNonNull(b1, null); + assertSame(b1, b2); + } + + public void testFirstNonNull_throwsNullPointerException() { + try { + MoreObjects.firstNonNull(null, null); + fail(); + } catch (NullPointerException expected) { + } + } + + public void testToStringHelperWithArrays() { + String[] strings = {"hello", "world"}; + int[] ints = {2, 42}; + Object[] objects = {"obj"}; + String[] arrayWithNull = {null}; + Object[] empty = {}; + String toTest = + MoreObjects.toStringHelper("TSH") + .add("strings", strings) + .add("ints", ints) + .add("objects", objects) + .add("arrayWithNull", arrayWithNull) + .add("empty", empty) + .toString(); + assertEquals( + "TSH{strings=[hello, world], ints=[2, 42], objects=[obj], arrayWithNull=[null], empty=[]}", + toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_instance() { + String toTest = MoreObjects.toStringHelper(this).toString(); + assertEquals("MoreObjectsTest{}", toTest); + } + + public void testConstructorLenient_instance() { + String toTest = MoreObjects.toStringHelper(this).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_innerClass() { + String toTest = MoreObjects.toStringHelper(new TestClass()).toString(); + assertEquals("TestClass{}", toTest); + } + + public void testConstructorLenient_innerClass() { + String toTest = MoreObjects.toStringHelper(new TestClass()).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_anonymousClass() { + String toTest = MoreObjects.toStringHelper(new Object() {}).toString(); + assertEquals("{}", toTest); + } + + public void testConstructorLenient_anonymousClass() { + String toTest = MoreObjects.toStringHelper(new Object() {}).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_classObject() { + String toTest = MoreObjects.toStringHelper(TestClass.class).toString(); + assertEquals("TestClass{}", toTest); + } + + public void testConstructorLenient_classObject() { + String toTest = MoreObjects.toStringHelper(TestClass.class).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + public void testConstructor_stringObject() { + String toTest = MoreObjects.toStringHelper("FooBar").toString(); + assertEquals("FooBar{}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringHelper_localInnerClass() { + // Local inner classes have names ending like "Outer.$1Inner" + class LocalInnerClass {} + String toTest = MoreObjects.toStringHelper(new LocalInnerClass()).toString(); + assertEquals("LocalInnerClass{}", toTest); + } + + public void testToStringHelperLenient_localInnerClass() { + class LocalInnerClass {} + String toTest = MoreObjects.toStringHelper(new LocalInnerClass()).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringHelper_localInnerNestedClass() { + class LocalInnerClass { + class LocalInnerNestedClass {} + } + String toTest = + MoreObjects.toStringHelper(new LocalInnerClass().new LocalInnerNestedClass()).toString(); + assertEquals("LocalInnerNestedClass{}", toTest); + } + + public void testToStringHelperLenient_localInnerNestedClass() { + class LocalInnerClass { + class LocalInnerNestedClass {} + } + String toTest = + MoreObjects.toStringHelper(new LocalInnerClass().new LocalInnerNestedClass()).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringHelper_moreThanNineAnonymousClasses() { + // The nth anonymous class has a name ending like "Outer.$n" + Object o1 = new Object() {}; + Object o2 = new Object() {}; + Object o3 = new Object() {}; + Object o4 = new Object() {}; + Object o5 = new Object() {}; + Object o6 = new Object() {}; + Object o7 = new Object() {}; + Object o8 = new Object() {}; + Object o9 = new Object() {}; + Object o10 = new Object() {}; + String toTest = MoreObjects.toStringHelper(o10).toString(); + assertEquals("{}", toTest); + } + + public void testToStringHelperLenient_moreThanNineAnonymousClasses() { + // The nth anonymous class has a name ending like "Outer.$n" + Object o1 = new Object() {}; + Object o2 = new Object() {}; + Object o3 = new Object() {}; + Object o4 = new Object() {}; + Object o5 = new Object() {}; + Object o6 = new Object() {}; + Object o7 = new Object() {}; + Object o8 = new Object() {}; + Object o9 = new Object() {}; + Object o10 = new Object() {}; + String toTest = MoreObjects.toStringHelper(o10).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + // all remaining test are on an inner class with various fields + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_oneField() { + String toTest = MoreObjects.toStringHelper(new TestClass()).add("field1", "Hello").toString(); + assertEquals("TestClass{field1=Hello}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_oneIntegerField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", new Integer(42)).toString(); + assertEquals("TestClass{field1=42}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_nullInteger() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", (Integer) null).toString(); + assertEquals("TestClass{field1=null}", toTest); + } + + public void testToStringLenient_oneField() { + String toTest = MoreObjects.toStringHelper(new TestClass()).add("field1", "Hello").toString(); + assertTrue(toTest, toTest.matches(".*\\{field1\\=Hello\\}")); + } + + public void testToStringLenient_oneIntegerField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", new Integer(42)).toString(); + assertTrue(toTest, toTest.matches(".*\\{field1\\=42\\}")); + } + + public void testToStringLenient_nullInteger() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", (Integer) null).toString(); + assertTrue(toTest, toTest.matches(".*\\{field1\\=null\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_complexFields() { + Map map = + ImmutableMap.builder().put("abc", 1).put("def", 2).put("ghi", 3).build(); + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "This is string.") + .add("field2", Arrays.asList("abc", "def", "ghi")) + .add("field3", map) + .toString(); + final String expected = + "TestClass{" + + "field1=This is string., field2=[abc, def, ghi], field3={abc=1, def=2, ghi=3}}"; + + assertEquals(expected, toTest); + } + + public void testToStringLenient_complexFields() { + Map map = + ImmutableMap.builder().put("abc", 1).put("def", 2).put("ghi", 3).build(); + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "This is string.") + .add("field2", Arrays.asList("abc", "def", "ghi")) + .add("field3", map) + .toString(); + final String expectedRegex = + ".*\\{" + + "field1\\=This is string\\., " + + "field2\\=\\[abc, def, ghi\\], " + + "field3=\\{abc\\=1, def\\=2, ghi\\=3\\}\\}"; + + assertTrue(toTest, toTest.matches(expectedRegex)); + } + + public void testToString_addWithNullName() { + MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(new TestClass()); + try { + helper.add(null, "Hello"); + fail("No exception was thrown."); + } catch (NullPointerException expected) { + } + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_addWithNullValue() { + final String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); + + assertEquals("TestClass{Hello=null}", result); + } + + public void testToStringLenient_addWithNullValue() { + final String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); + assertTrue(result, result.matches(".*\\{Hello\\=null\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_ToStringTwice() { + MoreObjects.ToStringHelper helper = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", 1) + .addValue("value1") + .add("field2", "value2"); + final String expected = "TestClass{field1=1, value1, field2=value2}"; + + assertEquals(expected, helper.toString()); + // Call toString again + assertEquals(expected, helper.toString()); + + // Make sure the cached value is reset when we modify the helper at all + final String expected2 = "TestClass{field1=1, value1, field2=value2, 2}"; + helper.addValue(2); + assertEquals(expected2, helper.toString()); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_addValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", 1) + .addValue("value1") + .add("field2", "value2") + .addValue(2) + .toString(); + final String expected = "TestClass{field1=1, value1, field2=value2, 2}"; + + assertEquals(expected, toTest); + } + + public void testToStringLenient_addValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", 1) + .addValue("value1") + .add("field2", "value2") + .addValue(2) + .toString(); + final String expected = ".*\\{field1\\=1, value1, field2\\=value2, 2\\}"; + + assertTrue(toTest, toTest.matches(expected)); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_addValueWithNullValue() { + final String result = + MoreObjects.toStringHelper(new TestClass()) + .addValue(null) + .addValue("Hello") + .addValue(null) + .toString(); + final String expected = "TestClass{null, Hello, null}"; + + assertEquals(expected, result); + } + + public void testToStringLenient_addValueWithNullValue() { + final String result = + MoreObjects.toStringHelper(new TestClass()) + .addValue(null) + .addValue("Hello") + .addValue(null) + .toString(); + final String expected = ".*\\{null, Hello, null\\}"; + + assertTrue(result, result.matches(expected)); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_oneField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitNullValues().add("field1", null).toString(); + assertEquals("TestClass{}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyFieldsFirstNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .add("field1", null) + .add("field2", "Googley") + .add("field3", "World") + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", null) + .add("field2", "Googley") + .add("field3", "World") + .omitNullValues() + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyFieldsLastNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", null) + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_oneValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitNullValues().addValue(null).toString(); + assertEquals("TestClass{}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyValuesFirstNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .addValue(null) + .addValue("Googley") + .addValue("World") + .toString(); + assertEquals("TestClass{Googley, World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyValuesLastNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .addValue("Hello") + .addValue("Googley") + .addValue(null) + .toString(); + assertEquals("TestClass{Hello, Googley}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_differentOrder() { + String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; + String toTest1 = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + String toTest2 = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "Hello") + .add("field2", "Googley") + .omitNullValues() + .add("field3", "World") + .toString(); + assertEquals(expected, toTest1); + assertEquals(expected, toTest2); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_canBeCalledManyTimes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .omitNullValues() + .add("field1", "Hello") + .omitNullValues() + .add("field2", "Googley") + .omitNullValues() + .add("field3", "World") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible("NullPointerTester") + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.ignore(MoreObjects.class.getMethod("firstNonNull", Object.class, Object.class)); + tester.testAllPublicStaticMethods(MoreObjects.class); + tester.testAllPublicInstanceMethods(MoreObjects.toStringHelper(new TestClass())); + } + + /** Test class for testing formatting of inner classes. */ + private static class TestClass {} +} diff --git a/android/guava/src/com/google/common/base/MoreObjects.java b/android/guava/src/com/google/common/base/MoreObjects.java index 36e912c14c2d..033ac3aa9cc4 100644 --- a/android/guava/src/com/google/common/base/MoreObjects.java +++ b/android/guava/src/com/google/common/base/MoreObjects.java @@ -58,24 +58,7 @@ public final class MoreObjects { * @throws NullPointerException if both {@code first} and {@code second} are null * @since 18.0 (since 3.0 as {@code Objects.firstNonNull()}). */ - /* - * We annotate firstNonNull in a way that protects against NullPointerException at the cost of - * forbidding some reasonable calls. - * - * The more permissive signature would be to accept (@CheckForNull T first, @CheckForNull T - * second), since it's OK for `second` to be null as long as `first` is not also null. But we - * expect for that flexibility to be useful relatively rarely: The more common use case is to - * supply a clearly non-null default, like `firstNonNull(someString, "")`. And users who really - * know that `first` is guaranteed non-null when `second` is null can write the logic out - * longhand, including a requireNonNull call, which calls attention to the fact that the static - * analyzer can't prove that the operation is safe. - * - * This matches the signature we currently have for requireNonNullElse in our own checker. (And - * that in turn matches that method's signature under the Checker Framework.) As always, we could - * consider the more flexible signature if we judge it worth the risks. If we do, we would likely - * update both methods so that they continue to match. - */ - public static T firstNonNull(@CheckForNull T first, T second) { + public static T firstNonNull(@CheckForNull T first, @CheckForNull T second) { if (first != null) { return first; } diff --git a/guava-tests/test/com/google/common/base/MoreObjectsTest.java b/guava-tests/test/com/google/common/base/MoreObjectsTest.java new file mode 100644 index 000000000000..6a7149d174f9 --- /dev/null +++ b/guava-tests/test/com/google/common/base/MoreObjectsTest.java @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.base; + +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 com.google.common.testing.NullPointerTester; +import java.util.Arrays; +import java.util.Map; +import junit.framework.TestCase; + +/** Tests for {@link MoreObjects}. */ +@GwtCompatible(emulated = true) +public class MoreObjectsTest extends TestCase { + public void testFirstNonNull_withNonNull() { + String s1 = "foo"; + String s2 = MoreObjects.firstNonNull(s1, "bar"); + assertSame(s1, s2); + + Long n1 = 42L; + Long n2 = MoreObjects.firstNonNull(null, n1); + assertSame(n1, n2); + + Boolean b1 = true; + Boolean b2 = MoreObjects.firstNonNull(b1, null); + assertSame(b1, b2); + } + + public void testFirstNonNull_throwsNullPointerException() { + try { + MoreObjects.firstNonNull(null, null); + fail(); + } catch (NullPointerException expected) { + } + } + + public void testToStringHelperWithArrays() { + String[] strings = {"hello", "world"}; + int[] ints = {2, 42}; + Object[] objects = {"obj"}; + String[] arrayWithNull = {null}; + Object[] empty = {}; + String toTest = + MoreObjects.toStringHelper("TSH") + .add("strings", strings) + .add("ints", ints) + .add("objects", objects) + .add("arrayWithNull", arrayWithNull) + .add("empty", empty) + .toString(); + assertEquals( + "TSH{strings=[hello, world], ints=[2, 42], objects=[obj], arrayWithNull=[null], empty=[]}", + toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_instance() { + String toTest = MoreObjects.toStringHelper(this).toString(); + assertEquals("MoreObjectsTest{}", toTest); + } + + public void testConstructorLenient_instance() { + String toTest = MoreObjects.toStringHelper(this).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_innerClass() { + String toTest = MoreObjects.toStringHelper(new TestClass()).toString(); + assertEquals("TestClass{}", toTest); + } + + public void testConstructorLenient_innerClass() { + String toTest = MoreObjects.toStringHelper(new TestClass()).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_anonymousClass() { + String toTest = MoreObjects.toStringHelper(new Object() {}).toString(); + assertEquals("{}", toTest); + } + + public void testConstructorLenient_anonymousClass() { + String toTest = MoreObjects.toStringHelper(new Object() {}).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testConstructor_classObject() { + String toTest = MoreObjects.toStringHelper(TestClass.class).toString(); + assertEquals("TestClass{}", toTest); + } + + public void testConstructorLenient_classObject() { + String toTest = MoreObjects.toStringHelper(TestClass.class).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + public void testConstructor_stringObject() { + String toTest = MoreObjects.toStringHelper("FooBar").toString(); + assertEquals("FooBar{}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringHelper_localInnerClass() { + // Local inner classes have names ending like "Outer.$1Inner" + class LocalInnerClass {} + String toTest = MoreObjects.toStringHelper(new LocalInnerClass()).toString(); + assertEquals("LocalInnerClass{}", toTest); + } + + public void testToStringHelperLenient_localInnerClass() { + class LocalInnerClass {} + String toTest = MoreObjects.toStringHelper(new LocalInnerClass()).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringHelper_localInnerNestedClass() { + class LocalInnerClass { + class LocalInnerNestedClass {} + } + String toTest = + MoreObjects.toStringHelper(new LocalInnerClass().new LocalInnerNestedClass()).toString(); + assertEquals("LocalInnerNestedClass{}", toTest); + } + + public void testToStringHelperLenient_localInnerNestedClass() { + class LocalInnerClass { + class LocalInnerNestedClass {} + } + String toTest = + MoreObjects.toStringHelper(new LocalInnerClass().new LocalInnerNestedClass()).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringHelper_moreThanNineAnonymousClasses() { + // The nth anonymous class has a name ending like "Outer.$n" + Object o1 = new Object() {}; + Object o2 = new Object() {}; + Object o3 = new Object() {}; + Object o4 = new Object() {}; + Object o5 = new Object() {}; + Object o6 = new Object() {}; + Object o7 = new Object() {}; + Object o8 = new Object() {}; + Object o9 = new Object() {}; + Object o10 = new Object() {}; + String toTest = MoreObjects.toStringHelper(o10).toString(); + assertEquals("{}", toTest); + } + + public void testToStringHelperLenient_moreThanNineAnonymousClasses() { + // The nth anonymous class has a name ending like "Outer.$n" + Object o1 = new Object() {}; + Object o2 = new Object() {}; + Object o3 = new Object() {}; + Object o4 = new Object() {}; + Object o5 = new Object() {}; + Object o6 = new Object() {}; + Object o7 = new Object() {}; + Object o8 = new Object() {}; + Object o9 = new Object() {}; + Object o10 = new Object() {}; + String toTest = MoreObjects.toStringHelper(o10).toString(); + assertTrue(toTest, toTest.matches(".*\\{\\}")); + } + + // all remaining test are on an inner class with various fields + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_oneField() { + String toTest = MoreObjects.toStringHelper(new TestClass()).add("field1", "Hello").toString(); + assertEquals("TestClass{field1=Hello}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_oneIntegerField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", new Integer(42)).toString(); + assertEquals("TestClass{field1=42}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_nullInteger() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", (Integer) null).toString(); + assertEquals("TestClass{field1=null}", toTest); + } + + public void testToStringLenient_oneField() { + String toTest = MoreObjects.toStringHelper(new TestClass()).add("field1", "Hello").toString(); + assertTrue(toTest, toTest.matches(".*\\{field1\\=Hello\\}")); + } + + public void testToStringLenient_oneIntegerField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", new Integer(42)).toString(); + assertTrue(toTest, toTest.matches(".*\\{field1\\=42\\}")); + } + + public void testToStringLenient_nullInteger() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).add("field1", (Integer) null).toString(); + assertTrue(toTest, toTest.matches(".*\\{field1\\=null\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_complexFields() { + Map map = + ImmutableMap.builder().put("abc", 1).put("def", 2).put("ghi", 3).build(); + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "This is string.") + .add("field2", Arrays.asList("abc", "def", "ghi")) + .add("field3", map) + .toString(); + final String expected = + "TestClass{" + + "field1=This is string., field2=[abc, def, ghi], field3={abc=1, def=2, ghi=3}}"; + + assertEquals(expected, toTest); + } + + public void testToStringLenient_complexFields() { + Map map = + ImmutableMap.builder().put("abc", 1).put("def", 2).put("ghi", 3).build(); + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "This is string.") + .add("field2", Arrays.asList("abc", "def", "ghi")) + .add("field3", map) + .toString(); + final String expectedRegex = + ".*\\{" + + "field1\\=This is string\\., " + + "field2\\=\\[abc, def, ghi\\], " + + "field3=\\{abc\\=1, def\\=2, ghi\\=3\\}\\}"; + + assertTrue(toTest, toTest.matches(expectedRegex)); + } + + public void testToString_addWithNullName() { + MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(new TestClass()); + try { + helper.add(null, "Hello"); + fail("No exception was thrown."); + } catch (NullPointerException expected) { + } + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_addWithNullValue() { + final String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); + + assertEquals("TestClass{Hello=null}", result); + } + + public void testToStringLenient_addWithNullValue() { + final String result = MoreObjects.toStringHelper(new TestClass()).add("Hello", null).toString(); + assertTrue(result, result.matches(".*\\{Hello\\=null\\}")); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_ToStringTwice() { + MoreObjects.ToStringHelper helper = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", 1) + .addValue("value1") + .add("field2", "value2"); + final String expected = "TestClass{field1=1, value1, field2=value2}"; + + assertEquals(expected, helper.toString()); + // Call toString again + assertEquals(expected, helper.toString()); + + // Make sure the cached value is reset when we modify the helper at all + final String expected2 = "TestClass{field1=1, value1, field2=value2, 2}"; + helper.addValue(2); + assertEquals(expected2, helper.toString()); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_addValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", 1) + .addValue("value1") + .add("field2", "value2") + .addValue(2) + .toString(); + final String expected = "TestClass{field1=1, value1, field2=value2, 2}"; + + assertEquals(expected, toTest); + } + + public void testToStringLenient_addValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", 1) + .addValue("value1") + .add("field2", "value2") + .addValue(2) + .toString(); + final String expected = ".*\\{field1\\=1, value1, field2\\=value2, 2\\}"; + + assertTrue(toTest, toTest.matches(expected)); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToString_addValueWithNullValue() { + final String result = + MoreObjects.toStringHelper(new TestClass()) + .addValue(null) + .addValue("Hello") + .addValue(null) + .toString(); + final String expected = "TestClass{null, Hello, null}"; + + assertEquals(expected, result); + } + + public void testToStringLenient_addValueWithNullValue() { + final String result = + MoreObjects.toStringHelper(new TestClass()) + .addValue(null) + .addValue("Hello") + .addValue(null) + .toString(); + final String expected = ".*\\{null, Hello, null\\}"; + + assertTrue(result, result.matches(expected)); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_oneField() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitNullValues().add("field1", null).toString(); + assertEquals("TestClass{}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyFieldsFirstNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .add("field1", null) + .add("field2", "Googley") + .add("field3", "World") + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyFieldsOmitAfterNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", null) + .add("field2", "Googley") + .add("field3", "World") + .omitNullValues() + .toString(); + assertEquals("TestClass{field2=Googley, field3=World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyFieldsLastNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", null) + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_oneValue() { + String toTest = + MoreObjects.toStringHelper(new TestClass()).omitNullValues().addValue(null).toString(); + assertEquals("TestClass{}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyValuesFirstNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .addValue(null) + .addValue("Googley") + .addValue("World") + .toString(); + assertEquals("TestClass{Googley, World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_manyValuesLastNull() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .addValue("Hello") + .addValue("Googley") + .addValue(null) + .toString(); + assertEquals("TestClass{Hello, Googley}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_differentOrder() { + String expected = "TestClass{field1=Hello, field2=Googley, field3=World}"; + String toTest1 = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .add("field1", "Hello") + .add("field2", "Googley") + .add("field3", "World") + .toString(); + String toTest2 = + MoreObjects.toStringHelper(new TestClass()) + .add("field1", "Hello") + .add("field2", "Googley") + .omitNullValues() + .add("field3", "World") + .toString(); + assertEquals(expected, toTest1); + assertEquals(expected, toTest2); + } + + @J2ktIncompatible + @GwtIncompatible // Class names are obfuscated in GWT + public void testToStringOmitNullValues_canBeCalledManyTimes() { + String toTest = + MoreObjects.toStringHelper(new TestClass()) + .omitNullValues() + .omitNullValues() + .add("field1", "Hello") + .omitNullValues() + .add("field2", "Googley") + .omitNullValues() + .add("field3", "World") + .toString(); + assertEquals("TestClass{field1=Hello, field2=Googley, field3=World}", toTest); + } + + @J2ktIncompatible + @GwtIncompatible("NullPointerTester") + public void testNulls() throws Exception { + NullPointerTester tester = new NullPointerTester(); + tester.ignore(MoreObjects.class.getMethod("firstNonNull", Object.class, Object.class)); + tester.testAllPublicStaticMethods(MoreObjects.class); + tester.testAllPublicInstanceMethods(MoreObjects.toStringHelper(new TestClass())); + } + + /** Test class for testing formatting of inner classes. */ + private static class TestClass {} +} diff --git a/guava/src/com/google/common/base/MoreObjects.java b/guava/src/com/google/common/base/MoreObjects.java index 989cfe0678b4..f3826d96f6c2 100644 --- a/guava/src/com/google/common/base/MoreObjects.java +++ b/guava/src/com/google/common/base/MoreObjects.java @@ -61,24 +61,7 @@ public final class MoreObjects { * @throws NullPointerException if both {@code first} and {@code second} are null * @since 18.0 (since 3.0 as {@code Objects.firstNonNull()}). */ - /* - * We annotate firstNonNull in a way that protects against NullPointerException at the cost of - * forbidding some reasonable calls. - * - * The more permissive signature would be to accept (@CheckForNull T first, @CheckForNull T - * second), since it's OK for `second` to be null as long as `first` is not also null. But we - * expect for that flexibility to be useful relatively rarely: The more common use case is to - * supply a clearly non-null default, like `firstNonNull(someString, "")`. And users who really - * know that `first` is guaranteed non-null when `second` is null can write the logic out - * longhand, including a requireNonNull call, which calls attention to the fact that the static - * analyzer can't prove that the operation is safe. - * - * This matches the signature we currently have for requireNonNullElse in our own checker. (And - * that in turn matches that method's signature under the Checker Framework.) As always, we could - * consider the more flexible signature if we judge it worth the risks. If we do, we would likely - * update both methods so that they continue to match. - */ - public static T firstNonNull(@CheckForNull T first, T second) { + public static T firstNonNull(@CheckForNull T first, @CheckForNull T second) { if (first != null) { return first; }