1 /* 2 * Copyright (C) 2008 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.base; 18 19 import static com.google.common.base.ReflectionFreeAssertThrows.assertThrows; 20 21 import com.google.common.annotations.GwtCompatible; 22 import com.google.common.annotations.GwtIncompatible; 23 import com.google.common.annotations.J2ktIncompatible; 24 import com.google.common.base.Joiner.MapJoiner; 25 import com.google.common.collect.ImmutableMap; 26 import com.google.common.collect.ImmutableMultimap; 27 import com.google.common.collect.Lists; 28 import com.google.common.collect.Maps; 29 import com.google.common.testing.NullPointerTester; 30 import java.io.IOException; 31 import java.util.Arrays; 32 import java.util.Map; 33 import java.util.Map.Entry; 34 import java.util.Set; 35 import junit.framework.AssertionFailedError; 36 import junit.framework.TestCase; 37 import org.checkerframework.checker.nullness.qual.Nullable; 38 39 /** 40 * Unit test for {@link Joiner}. 41 * 42 * @author Kevin Bourrillion 43 */ 44 @GwtCompatible(emulated = true) 45 @ElementTypesAreNonnullByDefault 46 public class JoinerTest extends TestCase { 47 private static final Joiner J = Joiner.on("-"); 48 49 // <Integer> needed to prevent warning :( 50 private static final Iterable<Integer> ITERABLE_ = Arrays.<Integer>asList(); 51 private static final Iterable<Integer> ITERABLE_1 = Arrays.asList(1); 52 private static final Iterable<Integer> ITERABLE_12 = Arrays.asList(1, 2); 53 private static final Iterable<Integer> ITERABLE_123 = Arrays.asList(1, 2, 3); 54 private static final Iterable<@Nullable Integer> ITERABLE_NULL = Arrays.asList((Integer) null); 55 private static final Iterable<@Nullable Integer> ITERABLE_NULL_NULL = 56 Arrays.asList((Integer) null, null); 57 private static final Iterable<@Nullable Integer> ITERABLE_NULL_1 = Arrays.asList(null, 1); 58 private static final Iterable<@Nullable Integer> ITERABLE_1_NULL = Arrays.asList(1, null); 59 private static final Iterable<@Nullable Integer> ITERABLE_1_NULL_2 = Arrays.asList(1, null, 2); 60 private static final Iterable<@Nullable Integer> ITERABLE_FOUR_NULLS = 61 Arrays.asList((Integer) null, null, null, null); 62 63 @SuppressWarnings("JoinIterableIterator") // explicitly testing iterator overload, too testNoSpecialNullBehavior()64 public void testNoSpecialNullBehavior() { 65 checkNoOutput(J, ITERABLE_); 66 checkResult(J, ITERABLE_1, "1"); 67 checkResult(J, ITERABLE_12, "1-2"); 68 checkResult(J, ITERABLE_123, "1-2-3"); 69 70 assertThrows(NullPointerException.class, () -> J.join(ITERABLE_NULL)); 71 assertThrows(NullPointerException.class, () -> J.join(ITERABLE_1_NULL_2)); 72 73 assertThrows(NullPointerException.class, () -> J.join(ITERABLE_NULL.iterator())); 74 assertThrows(NullPointerException.class, () -> J.join(ITERABLE_1_NULL_2.iterator())); 75 } 76 testOnCharOverride()77 public void testOnCharOverride() { 78 Joiner onChar = Joiner.on('-'); 79 checkNoOutput(onChar, ITERABLE_); 80 checkResult(onChar, ITERABLE_1, "1"); 81 checkResult(onChar, ITERABLE_12, "1-2"); 82 checkResult(onChar, ITERABLE_123, "1-2-3"); 83 } 84 testSkipNulls()85 public void testSkipNulls() { 86 Joiner skipNulls = J.skipNulls(); 87 checkNoOutput(skipNulls, ITERABLE_); 88 checkNoOutput(skipNulls, ITERABLE_NULL); 89 checkNoOutput(skipNulls, ITERABLE_NULL_NULL); 90 checkNoOutput(skipNulls, ITERABLE_FOUR_NULLS); 91 checkResult(skipNulls, ITERABLE_1, "1"); 92 checkResult(skipNulls, ITERABLE_12, "1-2"); 93 checkResult(skipNulls, ITERABLE_123, "1-2-3"); 94 checkResult(skipNulls, ITERABLE_NULL_1, "1"); 95 checkResult(skipNulls, ITERABLE_1_NULL, "1"); 96 checkResult(skipNulls, ITERABLE_1_NULL_2, "1-2"); 97 } 98 testUseForNull()99 public void testUseForNull() { 100 Joiner zeroForNull = J.useForNull("0"); 101 checkNoOutput(zeroForNull, ITERABLE_); 102 checkResult(zeroForNull, ITERABLE_1, "1"); 103 checkResult(zeroForNull, ITERABLE_12, "1-2"); 104 checkResult(zeroForNull, ITERABLE_123, "1-2-3"); 105 checkResult(zeroForNull, ITERABLE_NULL, "0"); 106 checkResult(zeroForNull, ITERABLE_NULL_NULL, "0-0"); 107 checkResult(zeroForNull, ITERABLE_NULL_1, "0-1"); 108 checkResult(zeroForNull, ITERABLE_1_NULL, "1-0"); 109 checkResult(zeroForNull, ITERABLE_1_NULL_2, "1-0-2"); 110 checkResult(zeroForNull, ITERABLE_FOUR_NULLS, "0-0-0-0"); 111 } 112 checkNoOutput(Joiner joiner, Iterable<Integer> set)113 private static void checkNoOutput(Joiner joiner, Iterable<Integer> set) { 114 assertEquals("", joiner.join(set)); 115 assertEquals("", joiner.join(set.iterator())); 116 117 Object[] array = Lists.newArrayList(set).toArray(new Integer[0]); 118 assertEquals("", joiner.join(array)); 119 120 StringBuilder sb1FromIterable = new StringBuilder(); 121 assertSame(sb1FromIterable, joiner.appendTo(sb1FromIterable, set)); 122 assertEquals(0, sb1FromIterable.length()); 123 124 StringBuilder sb1FromIterator = new StringBuilder(); 125 assertSame(sb1FromIterator, joiner.appendTo(sb1FromIterator, set)); 126 assertEquals(0, sb1FromIterator.length()); 127 128 StringBuilder sb2 = new StringBuilder(); 129 assertSame(sb2, joiner.appendTo(sb2, array)); 130 assertEquals(0, sb2.length()); 131 132 try { 133 joiner.appendTo(NASTY_APPENDABLE, set); 134 } catch (IOException e) { 135 throw new AssertionError(e); 136 } 137 138 try { 139 joiner.appendTo(NASTY_APPENDABLE, set.iterator()); 140 } catch (IOException e) { 141 throw new AssertionError(e); 142 } 143 144 try { 145 joiner.appendTo(NASTY_APPENDABLE, array); 146 } catch (IOException e) { 147 throw new AssertionError(e); 148 } 149 } 150 151 private static final Appendable NASTY_APPENDABLE = 152 new Appendable() { 153 @Override 154 public Appendable append(@Nullable CharSequence csq) throws IOException { 155 throw new IOException(); 156 } 157 158 @Override 159 public Appendable append(@Nullable CharSequence csq, int start, int end) 160 throws IOException { 161 throw new IOException(); 162 } 163 164 @Override 165 public Appendable append(char c) throws IOException { 166 throw new IOException(); 167 } 168 }; 169 checkResult(Joiner joiner, Iterable<Integer> parts, String expected)170 private static void checkResult(Joiner joiner, Iterable<Integer> parts, String expected) { 171 assertEquals(expected, joiner.join(parts)); 172 assertEquals(expected, joiner.join(parts.iterator())); 173 174 StringBuilder sb1FromIterable = new StringBuilder().append('x'); 175 joiner.appendTo(sb1FromIterable, parts); 176 assertEquals("x" + expected, sb1FromIterable.toString()); 177 178 StringBuilder sb1FromIterator = new StringBuilder().append('x'); 179 joiner.appendTo(sb1FromIterator, parts.iterator()); 180 assertEquals("x" + expected, sb1FromIterator.toString()); 181 182 Integer[] partsArray = Lists.newArrayList(parts).toArray(new Integer[0]); 183 assertEquals(expected, joiner.join(partsArray)); 184 185 StringBuilder sb2 = new StringBuilder().append('x'); 186 joiner.appendTo(sb2, partsArray); 187 assertEquals("x" + expected, sb2.toString()); 188 189 int num = partsArray.length - 2; 190 if (num >= 0) { 191 Object[] rest = new Integer[num]; 192 for (int i = 0; i < num; i++) { 193 rest[i] = partsArray[i + 2]; 194 } 195 196 assertEquals(expected, joiner.join(partsArray[0], partsArray[1], rest)); 197 198 StringBuilder sb3 = new StringBuilder().append('x'); 199 joiner.appendTo(sb3, partsArray[0], partsArray[1], rest); 200 assertEquals("x" + expected, sb3.toString()); 201 } 202 } 203 test_useForNull_skipNulls()204 public void test_useForNull_skipNulls() { 205 Joiner j = Joiner.on("x").useForNull("y"); 206 assertThrows(UnsupportedOperationException.class, j::skipNulls); 207 } 208 test_skipNulls_useForNull()209 public void test_skipNulls_useForNull() { 210 Joiner j = Joiner.on("x").skipNulls(); 211 assertThrows(UnsupportedOperationException.class, () -> j.useForNull("y")); 212 } 213 test_useForNull_twice()214 public void test_useForNull_twice() { 215 Joiner j = Joiner.on("x").useForNull("y"); 216 assertThrows(UnsupportedOperationException.class, () -> j.useForNull("y")); 217 } 218 testMap()219 public void testMap() { 220 MapJoiner j = Joiner.on(';').withKeyValueSeparator(':'); 221 assertEquals("", j.join(ImmutableMap.of())); 222 assertEquals(":", j.join(ImmutableMap.of("", ""))); 223 224 Map<@Nullable String, @Nullable String> mapWithNulls = Maps.newLinkedHashMap(); 225 mapWithNulls.put("a", null); 226 mapWithNulls.put(null, "b"); 227 228 assertThrows(NullPointerException.class, () -> j.join(mapWithNulls)); 229 230 assertEquals("a:00;00:b", j.useForNull("00").join(mapWithNulls)); 231 232 StringBuilder sb = new StringBuilder(); 233 j.appendTo(sb, ImmutableMap.of(1, 2, 3, 4, 5, 6)); 234 assertEquals("1:2;3:4;5:6", sb.toString()); 235 } 236 testEntries()237 public void testEntries() { 238 MapJoiner j = Joiner.on(";").withKeyValueSeparator(":"); 239 assertEquals("", j.join(ImmutableMultimap.of().entries())); 240 assertEquals("", j.join(ImmutableMultimap.of().entries().iterator())); 241 assertEquals(":", j.join(ImmutableMultimap.of("", "").entries())); 242 assertEquals(":", j.join(ImmutableMultimap.of("", "").entries().iterator())); 243 assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries())); 244 assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries().iterator())); 245 246 Map<@Nullable String, @Nullable String> mapWithNulls = Maps.newLinkedHashMap(); 247 mapWithNulls.put("a", null); 248 mapWithNulls.put(null, "b"); 249 Set<Entry<String, String>> entriesWithNulls = mapWithNulls.entrySet(); 250 251 assertThrows(NullPointerException.class, () -> j.join(entriesWithNulls)); 252 253 assertThrows(NullPointerException.class, () -> j.join(entriesWithNulls.iterator())); 254 255 assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls)); 256 assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls.iterator())); 257 258 StringBuilder sb1 = new StringBuilder(); 259 j.appendTo(sb1, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries()); 260 assertEquals("1:2;1:3;3:4;5:6;5:10", sb1.toString()); 261 262 StringBuilder sb2 = new StringBuilder(); 263 j.appendTo(sb2, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries().iterator()); 264 assertEquals("1:2;1:3;3:4;5:6;5:10", sb2.toString()); 265 } 266 test_skipNulls_onMap()267 public void test_skipNulls_onMap() { 268 Joiner j = Joiner.on(",").skipNulls(); 269 assertThrows(UnsupportedOperationException.class, () -> j.withKeyValueSeparator("/")); 270 } 271 272 private static class DontStringMeBro implements CharSequence { 273 @Override length()274 public int length() { 275 return 3; 276 } 277 278 @Override charAt(int index)279 public char charAt(int index) { 280 return "foo".charAt(index); 281 } 282 283 @Override subSequence(int start, int end)284 public CharSequence subSequence(int start, int end) { 285 return "foo".subSequence(start, end); 286 } 287 288 @Override toString()289 public String toString() { 290 throw new AssertionFailedError("shouldn't be invoked"); 291 } 292 } 293 294 @GwtIncompatible // StringBuilder.append in GWT invokes Object.toString(), unlike the JRE version. testDontConvertCharSequenceToString()295 public void testDontConvertCharSequenceToString() { 296 assertEquals("foo,foo", Joiner.on(",").join(new DontStringMeBro(), new DontStringMeBro())); 297 assertEquals( 298 "foo,bar,foo", 299 Joiner.on(",").useForNull("bar").join(new DontStringMeBro(), null, new DontStringMeBro())); 300 } 301 302 @J2ktIncompatible 303 @GwtIncompatible // NullPointerTester testNullPointers()304 public void testNullPointers() { 305 NullPointerTester tester = new NullPointerTester(); 306 tester.testAllPublicStaticMethods(Joiner.class); 307 tester.testInstanceMethods(Joiner.on(","), NullPointerTester.Visibility.PACKAGE); 308 tester.testInstanceMethods(Joiner.on(",").skipNulls(), NullPointerTester.Visibility.PACKAGE); 309 tester.testInstanceMethods( 310 Joiner.on(",").useForNull("x"), NullPointerTester.Visibility.PACKAGE); 311 tester.testInstanceMethods( 312 Joiner.on(",").withKeyValueSeparator("="), NullPointerTester.Visibility.PACKAGE); 313 } 314 } 315