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 com.google.common.annotations.GwtCompatible; 20 import com.google.common.annotations.GwtIncompatible; 21 import com.google.common.base.Joiner.MapJoiner; 22 import com.google.common.collect.ImmutableMap; 23 import com.google.common.collect.ImmutableMultimap; 24 import com.google.common.collect.ImmutableSet; 25 import com.google.common.collect.Iterators; 26 import com.google.common.collect.Lists; 27 import com.google.common.collect.Maps; 28 import com.google.common.testing.NullPointerTester; 29 30 import junit.framework.TestCase; 31 32 import java.io.IOException; 33 import java.util.Arrays; 34 import java.util.Iterator; 35 import java.util.Map; 36 import java.util.Set; 37 38 /** 39 * Unit test for {@link Joiner}. 40 * 41 * @author Kevin Bourrillion 42 */ 43 @GwtCompatible(emulated = true) 44 public class JoinerTest extends TestCase { 45 private static final Joiner J = Joiner.on("-"); 46 47 // <Integer> needed to prevent warning :( 48 private static final Iterable<Integer> ITERABLE_ = Arrays.<Integer>asList(); 49 private static final Iterable<Integer> ITERABLE_1 = Arrays.asList(1); 50 private static final Iterable<Integer> ITERABLE_12 = Arrays.asList(1, 2); 51 private static final Iterable<Integer> ITERABLE_123 = Arrays.asList(1, 2, 3); 52 private static final Iterable<Integer> ITERABLE_NULL = Arrays.asList((Integer) null); 53 private static final Iterable<Integer> ITERABLE_NULL_NULL 54 = Arrays.asList((Integer) null, null); 55 private static final Iterable<Integer> ITERABLE_NULL_1 = Arrays.asList(null, 1); 56 private static final Iterable<Integer> ITERABLE_1_NULL = Arrays.asList(1, null); 57 private static final Iterable<Integer> ITERABLE_1_NULL_2 = Arrays.asList(1, null, 2); 58 private static final Iterable<Integer> ITERABLE_FOUR_NULLS 59 = Arrays.asList((Integer) null, null, null, null); 60 61 public void testNoSpecialNullBehavior() { 62 checkNoOutput(J, ITERABLE_); 63 checkResult(J, ITERABLE_1, "1"); 64 checkResult(J, ITERABLE_12, "1-2"); 65 checkResult(J, ITERABLE_123, "1-2-3"); 66 67 try { 68 J.join(ITERABLE_NULL); 69 fail(); 70 } catch (NullPointerException expected) { 71 } 72 try { 73 J.join(ITERABLE_1_NULL_2); 74 fail(); 75 } catch (NullPointerException expected) { 76 } 77 78 try { 79 J.join(ITERABLE_NULL.iterator()); 80 fail(); 81 } catch (NullPointerException expected) { 82 } 83 try { 84 J.join(ITERABLE_1_NULL_2.iterator()); 85 fail(); 86 } catch (NullPointerException expected) { 87 } 88 } 89 90 public void testOnCharOverride() { 91 Joiner onChar = Joiner.on('-'); 92 checkNoOutput(onChar, ITERABLE_); 93 checkResult(onChar, ITERABLE_1, "1"); 94 checkResult(onChar, ITERABLE_12, "1-2"); 95 checkResult(onChar, ITERABLE_123, "1-2-3"); 96 } 97 98 public void testSkipNulls() { 99 Joiner skipNulls = J.skipNulls(); 100 checkNoOutput(skipNulls, ITERABLE_); 101 checkNoOutput(skipNulls, ITERABLE_NULL); 102 checkNoOutput(skipNulls, ITERABLE_NULL_NULL); 103 checkNoOutput(skipNulls, ITERABLE_FOUR_NULLS); 104 checkResult(skipNulls, ITERABLE_1, "1"); 105 checkResult(skipNulls, ITERABLE_12, "1-2"); 106 checkResult(skipNulls, ITERABLE_123, "1-2-3"); 107 checkResult(skipNulls, ITERABLE_NULL_1, "1"); 108 checkResult(skipNulls, ITERABLE_1_NULL, "1"); 109 checkResult(skipNulls, ITERABLE_1_NULL_2, "1-2"); 110 } 111 112 public void testUseForNull() { 113 Joiner zeroForNull = J.useForNull("0"); 114 checkNoOutput(zeroForNull, ITERABLE_); 115 checkResult(zeroForNull, ITERABLE_1, "1"); 116 checkResult(zeroForNull, ITERABLE_12, "1-2"); 117 checkResult(zeroForNull, ITERABLE_123, "1-2-3"); 118 checkResult(zeroForNull, ITERABLE_NULL, "0"); 119 checkResult(zeroForNull, ITERABLE_NULL_NULL, "0-0"); 120 checkResult(zeroForNull, ITERABLE_NULL_1, "0-1"); 121 checkResult(zeroForNull, ITERABLE_1_NULL, "1-0"); 122 checkResult(zeroForNull, ITERABLE_1_NULL_2, "1-0-2"); 123 checkResult(zeroForNull, ITERABLE_FOUR_NULLS, "0-0-0-0"); 124 } 125 126 private static void checkNoOutput(Joiner joiner, Iterable<Integer> set) { 127 assertEquals("", joiner.join(set)); 128 assertEquals("", joiner.join(set.iterator())); 129 130 Object[] array = Lists.newArrayList(set).toArray(new Integer[0]); 131 assertEquals("", joiner.join(array)); 132 133 StringBuilder sb1FromIterable = new StringBuilder(); 134 assertSame(sb1FromIterable, joiner.appendTo(sb1FromIterable, set)); 135 assertEquals(0, sb1FromIterable.length()); 136 137 StringBuilder sb1FromIterator = new StringBuilder(); 138 assertSame(sb1FromIterator, joiner.appendTo(sb1FromIterator, set)); 139 assertEquals(0, sb1FromIterator.length()); 140 141 StringBuilder sb2 = new StringBuilder(); 142 assertSame(sb2, joiner.appendTo(sb2, array)); 143 assertEquals(0, sb2.length()); 144 145 try { 146 joiner.appendTo(NASTY_APPENDABLE, set); 147 } catch (IOException e) { 148 throw new AssertionError(e); 149 } 150 151 try { 152 joiner.appendTo(NASTY_APPENDABLE, set.iterator()); 153 } catch (IOException e) { 154 throw new AssertionError(e); 155 } 156 157 try { 158 joiner.appendTo(NASTY_APPENDABLE, array); 159 } catch (IOException e) { 160 throw new AssertionError(e); 161 } 162 } 163 164 private static final Appendable NASTY_APPENDABLE = new Appendable() { 165 @Override 166 public Appendable append(CharSequence csq) throws IOException { 167 throw new IOException(); 168 } 169 @Override 170 public Appendable append(CharSequence csq, int start, int end) throws IOException { 171 throw new IOException(); 172 } 173 @Override 174 public Appendable append(char c) throws IOException { 175 throw new IOException(); 176 } 177 }; 178 179 private static void checkResult(Joiner joiner, Iterable<Integer> parts, String expected) { 180 assertEquals(expected, joiner.join(parts)); 181 assertEquals(expected, joiner.join(parts.iterator())); 182 183 StringBuilder sb1FromIterable = new StringBuilder().append('x'); 184 joiner.appendTo(sb1FromIterable, parts); 185 assertEquals("x" + expected, sb1FromIterable.toString()); 186 187 StringBuilder sb1FromIterator = new StringBuilder().append('x'); 188 joiner.appendTo(sb1FromIterator, parts.iterator()); 189 assertEquals("x" + expected, sb1FromIterator.toString()); 190 191 Integer[] partsArray = Lists.newArrayList(parts).toArray(new Integer[0]); 192 assertEquals(expected, joiner.join(partsArray)); 193 194 StringBuilder sb2 = new StringBuilder().append('x'); 195 joiner.appendTo(sb2, partsArray); 196 assertEquals("x" + expected, sb2.toString()); 197 198 int num = partsArray.length - 2; 199 if (num >= 0) { 200 Object[] rest = new Integer[num]; 201 for (int i = 0; i < num; i++) { 202 rest[i] = partsArray[i + 2]; 203 } 204 205 assertEquals(expected, joiner.join(partsArray[0], partsArray[1], rest)); 206 207 StringBuilder sb3 = new StringBuilder().append('x'); 208 joiner.appendTo(sb3, partsArray[0], partsArray[1], rest); 209 assertEquals("x" + expected, sb3.toString()); 210 } 211 } 212 213 public void testIterableIterator() { 214 Joiner onChar = Joiner.on('-'); 215 checkIterableIterator(onChar, "1-2-3-4"); 216 217 Joiner skipNulls = J.skipNulls(); 218 checkIterableIterator(skipNulls, "1-2-3-4"); 219 220 Joiner zeroForNull = J.useForNull("0"); 221 checkIterableIterator(zeroForNull, "1-2-3-4"); 222 } 223 224 private static void checkIterableIterator(Joiner joiner, String expected) { 225 assertEquals(expected, joiner.join(new IterableIterator())); 226 227 StringBuilder sb1 = new StringBuilder().append('x'); 228 joiner.appendTo(sb1, new IterableIterator()); 229 assertEquals("x" + expected, sb1.toString()); 230 231 Integer[] partsArray = 232 Lists.newArrayList(new IterableIterator().iterator()).toArray(new Integer[0]); 233 assertEquals(expected, joiner.join(partsArray)); 234 235 StringBuilder sb2 = new StringBuilder().append('x'); 236 joiner.appendTo(sb2, partsArray); 237 assertEquals("x" + expected, sb2.toString()); 238 239 int num = partsArray.length - 2; 240 if (num >= 0) { 241 Object[] rest = new Integer[num]; 242 for (int i = 0; i < num; i++) { 243 rest[i] = partsArray[i + 2]; 244 } 245 246 assertEquals(expected, joiner.join(partsArray[0], partsArray[1], rest)); 247 248 StringBuilder sb3 = new StringBuilder().append('x'); 249 joiner.appendTo(sb3, partsArray[0], partsArray[1], rest); 250 assertEquals("x" + expected, sb3.toString()); 251 } 252 } 253 254 public void test_useForNull_skipNulls() { 255 Joiner j = Joiner.on("x").useForNull("y"); 256 try { 257 j.skipNulls(); 258 fail(); 259 } catch (UnsupportedOperationException expected) { 260 } 261 } 262 263 public void test_skipNulls_useForNull() { 264 Joiner j = Joiner.on("x").skipNulls(); 265 try { 266 j.useForNull("y"); 267 fail(); 268 } catch (UnsupportedOperationException expected) { 269 } 270 } 271 272 public void test_useForNull_twice() { 273 Joiner j = Joiner.on("x").useForNull("y"); 274 try { 275 j.useForNull("y"); 276 fail(); 277 } catch (UnsupportedOperationException expected) { 278 } 279 } 280 281 public void testMap() { 282 MapJoiner j = Joiner.on(";").withKeyValueSeparator(":"); 283 assertEquals("", j.join(ImmutableMap.of())); 284 assertEquals(":", j.join(ImmutableMap.of("", ""))); 285 286 Map<String, String> mapWithNulls = Maps.newLinkedHashMap(); 287 mapWithNulls.put("a", null); 288 mapWithNulls.put(null, "b"); 289 290 try { 291 j.join(mapWithNulls); 292 fail(); 293 } catch (NullPointerException expected) { 294 } 295 296 assertEquals("a:00;00:b", j.useForNull("00").join(mapWithNulls)); 297 298 StringBuilder sb = new StringBuilder(); 299 j.appendTo(sb, ImmutableMap.of(1, 2, 3, 4, 5, 6)); 300 assertEquals("1:2;3:4;5:6", sb.toString()); 301 } 302 303 public void testEntries() { 304 MapJoiner j = Joiner.on(";").withKeyValueSeparator(":"); 305 assertEquals("", j.join(ImmutableMultimap.of().entries())); 306 assertEquals("", j.join(ImmutableMultimap.of().entries().iterator())); 307 assertEquals(":", j.join(ImmutableMultimap.of("", "").entries())); 308 assertEquals(":", j.join(ImmutableMultimap.of("", "").entries().iterator())); 309 assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries())); 310 assertEquals("1:a;1:b", j.join(ImmutableMultimap.of("1", "a", "1", "b").entries().iterator())); 311 312 Map<String, String> mapWithNulls = Maps.newLinkedHashMap(); 313 mapWithNulls.put("a", null); 314 mapWithNulls.put(null, "b"); 315 Set<Map.Entry<String, String>> entriesWithNulls = mapWithNulls.entrySet(); 316 317 try { 318 j.join(entriesWithNulls); 319 fail(); 320 } catch (NullPointerException expected) { 321 } 322 323 try { 324 j.join(entriesWithNulls.iterator()); 325 fail(); 326 } catch (NullPointerException expected) { 327 } 328 329 assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls)); 330 assertEquals("a:00;00:b", j.useForNull("00").join(entriesWithNulls.iterator())); 331 332 StringBuilder sb1 = new StringBuilder(); 333 j.appendTo(sb1, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries()); 334 assertEquals("1:2;1:3;3:4;5:6;5:10", sb1.toString()); 335 336 StringBuilder sb2 = new StringBuilder(); 337 j.appendTo(sb2, ImmutableMultimap.of(1, 2, 3, 4, 5, 6, 1, 3, 5, 10).entries().iterator()); 338 assertEquals("1:2;1:3;3:4;5:6;5:10", sb2.toString()); 339 } 340 341 public void test_skipNulls_onMap() { 342 Joiner j = Joiner.on(",").skipNulls(); 343 try { 344 j.withKeyValueSeparator("/"); 345 fail(); 346 } catch (UnsupportedOperationException expected) { 347 } 348 } 349 350 private static class DontStringMeBro implements CharSequence { 351 @Override 352 public int length() { 353 return 3; 354 } 355 @Override 356 public char charAt(int index) { 357 return "foo".charAt(index); 358 } 359 @Override 360 public CharSequence subSequence(int start, int end) { 361 return "foo".subSequence(start, end); 362 } 363 @Override public String toString() { 364 fail("shouldn't be invoked"); 365 return null; 366 } 367 } 368 369 // Don't do this. 370 private static class IterableIterator implements Iterable<Integer>, Iterator<Integer> { 371 private static final ImmutableSet<Integer> INTEGERS = ImmutableSet.of(1, 2, 3, 4); 372 private final Iterator<Integer> iterator; 373 public IterableIterator() { 374 this.iterator = iterator(); 375 } 376 @Override public Iterator<Integer> iterator() { 377 return INTEGERS.iterator(); 378 } 379 @Override public boolean hasNext() { 380 return iterator.hasNext(); 381 } 382 @Override public Integer next() { 383 return iterator.next(); 384 } 385 @Override public void remove() { 386 iterator.remove(); 387 } 388 } 389 390 @GwtIncompatible("StringBuilder.append in GWT invokes Object.toString(), unlike the JRE version.") 391 public void testDontConvertCharSequenceToString() { 392 assertEquals("foo,foo", Joiner.on(",").join( 393 new DontStringMeBro(), new DontStringMeBro())); 394 assertEquals("foo,bar,foo", Joiner.on(",").useForNull("bar").join( 395 new DontStringMeBro(), null, new DontStringMeBro())); 396 } 397 398 @GwtIncompatible("NullPointerTester") 399 public void testNullPointers() throws Exception { 400 NullPointerTester tester = new NullPointerTester(); 401 tester.setDefault(StringBuilder.class, new StringBuilder()); 402 // This is necessary because of the generics hackery we have to temporarily support parameters 403 // which implement both Iterator and Iterable. 404 tester.setDefault(Object.class, Iterators.emptyIterator()); 405 tester.testAllPublicStaticMethods(Joiner.class); 406 tester.testAllPublicInstanceMethods(Joiner.on(",")); 407 tester.testAllPublicInstanceMethods(Joiner.on(",").skipNulls()); 408 tester.testAllPublicInstanceMethods(Joiner.on(",").useForNull("x")); 409 tester.testAllPublicInstanceMethods( 410 Joiner.on(",").withKeyValueSeparator("=")); 411 } 412 } 413