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