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 static com.google.common.base.Preconditions.checkNotNull;
20
21import com.google.common.annotations.Beta;
22import com.google.common.annotations.GwtCompatible;
23
24import java.io.IOException;
25import java.util.AbstractList;
26import java.util.Arrays;
27import java.util.Iterator;
28import java.util.Map;
29import java.util.Map.Entry;
30
31import javax.annotation.CheckReturnValue;
32import javax.annotation.Nullable;
33
34/**
35 * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a
36 * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns
37 * them as a {@link String}. Example: <pre>   {@code
38 *
39 *   Joiner joiner = Joiner.on("; ").skipNulls();
40 *    . . .
41 *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
42 *
43 * <p>This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are
44 * converted to strings using {@link Object#toString()} before being appended.
45 *
46 * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining
47 * methods will throw {@link NullPointerException} if any given element is null.
48 *
49 * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code
50 * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner
51 * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code
52 * static final} constants. <pre>   {@code
53 *
54 *   // Bad! Do not do this!
55 *   Joiner joiner = Joiner.on(',');
56 *   joiner.skipNulls(); // does nothing!
57 *   return joiner.join("wrong", null, "wrong");}</pre>
58 *
59 * <p>See the Guava User Guide article on <a href=
60 * "http://code.google.com/p/guava-libraries/wiki/StringsExplained#Joiner">{@code Joiner}</a>.
61 *
62 * @author Kevin Bourrillion
63 * @since 2.0 (imported from Google Collections Library)
64 */
65@GwtCompatible
66public class Joiner {
67  /**
68   * Returns a joiner which automatically places {@code separator} between consecutive elements.
69   */
70  public static Joiner on(String separator) {
71    return new Joiner(separator);
72  }
73
74  /**
75   * Returns a joiner which automatically places {@code separator} between consecutive elements.
76   */
77  public static Joiner on(char separator) {
78    return new Joiner(String.valueOf(separator));
79  }
80
81  private final String separator;
82
83  private Joiner(String separator) {
84    this.separator = checkNotNull(separator);
85  }
86
87  private Joiner(Joiner prototype) {
88    this.separator = prototype.separator;
89  }
90
91  /**
92   * Appends the string representation of each of {@code parts}, using the previously configured
93   * separator between each, to {@code appendable}.
94   */
95  public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
96    return appendTo(appendable, parts.iterator());
97  }
98
99  /**
100   * Appends the string representation of each of {@code parts}, using the previously configured
101   * separator between each, to {@code appendable}.
102   *
103   * @since 11.0
104   */
105  public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
106    checkNotNull(appendable);
107    if (parts.hasNext()) {
108      appendable.append(toString(parts.next()));
109      while (parts.hasNext()) {
110        appendable.append(separator);
111        appendable.append(toString(parts.next()));
112      }
113    }
114    return appendable;
115  }
116
117  /**
118   * Appends the string representation of each of {@code parts}, using the previously configured
119   * separator between each, to {@code appendable}.
120   */
121  public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
122    return appendTo(appendable, Arrays.asList(parts));
123  }
124
125  /**
126   * Appends to {@code appendable} the string representation of each of the remaining arguments.
127   */
128  public final <A extends Appendable> A appendTo(
129      A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
130          throws IOException {
131    return appendTo(appendable, iterable(first, second, rest));
132  }
133
134  /**
135   * Appends the string representation of each of {@code parts}, using the previously configured
136   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
137   * Iterable)}, except that it does not throw {@link IOException}.
138   */
139  public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
140    return appendTo(builder, parts.iterator());
141  }
142
143  /**
144   * Appends the string representation of each of {@code parts}, using the previously configured
145   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
146   * Iterable)}, except that it does not throw {@link IOException}.
147   *
148   * @since 11.0
149   */
150  public final StringBuilder appendTo(StringBuilder builder, Iterator<?> parts) {
151    try {
152      appendTo((Appendable) builder, parts);
153    } catch (IOException impossible) {
154      throw new AssertionError(impossible);
155    }
156    return builder;
157  }
158
159  /**
160   * Appends the string representation of each of {@code parts}, using the previously configured
161   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
162   * Iterable)}, except that it does not throw {@link IOException}.
163   */
164  public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
165    return appendTo(builder, Arrays.asList(parts));
166  }
167
168  /**
169   * Appends to {@code builder} the string representation of each of the remaining arguments.
170   * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
171   * throw {@link IOException}.
172   */
173  public final StringBuilder appendTo(
174      StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
175    return appendTo(builder, iterable(first, second, rest));
176  }
177
178  /**
179   * Returns a string containing the string representation of each of {@code parts}, using the
180   * previously configured separator between each.
181   */
182  public final String join(Iterable<?> parts) {
183    return join(parts.iterator());
184  }
185
186  /**
187   * Returns a string containing the string representation of each of {@code parts}, using the
188   * previously configured separator between each.
189   *
190   * @since 11.0
191   */
192  public final String join(Iterator<?> parts) {
193    return appendTo(new StringBuilder(), parts).toString();
194  }
195
196  /**
197   * Returns a string containing the string representation of each of {@code parts}, using the
198   * previously configured separator between each.
199   */
200  public final String join(Object[] parts) {
201    return join(Arrays.asList(parts));
202  }
203
204  /**
205   * Returns a string containing the string representation of each argument, using the previously
206   * configured separator between each.
207   */
208  public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
209    return join(iterable(first, second, rest));
210  }
211
212  /**
213   * Returns a joiner with the same behavior as this one, except automatically substituting {@code
214   * nullText} for any provided null elements.
215   */
216  @CheckReturnValue
217  public Joiner useForNull(final String nullText) {
218    checkNotNull(nullText);
219    return new Joiner(this) {
220      @Override CharSequence toString(@Nullable Object part) {
221        return (part == null) ? nullText : Joiner.this.toString(part);
222      }
223
224      @Override public Joiner useForNull(String nullText) {
225        throw new UnsupportedOperationException("already specified useForNull");
226      }
227
228      @Override public Joiner skipNulls() {
229        throw new UnsupportedOperationException("already specified useForNull");
230      }
231    };
232  }
233
234  /**
235   * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
236   * provided null elements.
237   */
238  @CheckReturnValue
239  public Joiner skipNulls() {
240    return new Joiner(this) {
241      @Override public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)
242          throws IOException {
243        checkNotNull(appendable, "appendable");
244        checkNotNull(parts, "parts");
245        while (parts.hasNext()) {
246          Object part = parts.next();
247          if (part != null) {
248            appendable.append(Joiner.this.toString(part));
249            break;
250          }
251        }
252        while (parts.hasNext()) {
253          Object part = parts.next();
254          if (part != null) {
255            appendable.append(separator);
256            appendable.append(Joiner.this.toString(part));
257          }
258        }
259        return appendable;
260      }
261
262      @Override public Joiner useForNull(String nullText) {
263        throw new UnsupportedOperationException("already specified skipNulls");
264      }
265
266      @Override public MapJoiner withKeyValueSeparator(String kvs) {
267        throw new UnsupportedOperationException("can't use .skipNulls() with maps");
268      }
269    };
270  }
271
272  /**
273   * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
274   * this {@code Joiner} otherwise.
275   */
276  @CheckReturnValue
277  public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
278    return new MapJoiner(this, keyValueSeparator);
279  }
280
281  /**
282   * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
283   * arrays. Like {@code Joiner}, it is thread-safe and immutable.
284   *
285   * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code
286   * Multimap} entries in two distinct modes:
287   *
288   * <ul>
289   * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a
290   *     {@code MapJoiner} method that accepts entries as input, and receive output of the form
291   *     {@code key1=A&key1=B&key2=C}.
292   * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner}
293   *     method that accepts a map as input, and receive output of the form {@code
294   *     key1=[A, B]&key2=C}.
295   * </ul>
296   *
297   * @since 2.0 (imported from Google Collections Library)
298   */
299  public static final class MapJoiner {
300    private final Joiner joiner;
301    private final String keyValueSeparator;
302
303    private MapJoiner(Joiner joiner, String keyValueSeparator) {
304      this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
305      this.keyValueSeparator = checkNotNull(keyValueSeparator);
306    }
307
308    /**
309     * Appends the string representation of each entry of {@code map}, using the previously
310     * configured separator and key-value separator, to {@code appendable}.
311     */
312    public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
313      return appendTo(appendable, map.entrySet());
314    }
315
316    /**
317     * Appends the string representation of each entry of {@code map}, using the previously
318     * configured separator and key-value separator, to {@code builder}. Identical to {@link
319     * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
320     */
321    public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
322      return appendTo(builder, map.entrySet());
323    }
324
325    /**
326     * Returns a string containing the string representation of each entry of {@code map}, using the
327     * previously configured separator and key-value separator.
328     */
329    public String join(Map<?, ?> map) {
330      return join(map.entrySet());
331    }
332
333    /**
334     * Appends the string representation of each entry in {@code entries}, using the previously
335     * configured separator and key-value separator, to {@code appendable}.
336     *
337     * @since 10.0
338     */
339    @Beta
340    public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
341        throws IOException {
342      return appendTo(appendable, entries.iterator());
343    }
344
345    /**
346     * Appends the string representation of each entry in {@code entries}, using the previously
347     * configured separator and key-value separator, to {@code appendable}.
348     *
349     * @since 11.0
350     */
351    @Beta
352    public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts)
353        throws IOException {
354      checkNotNull(appendable);
355      if (parts.hasNext()) {
356        Entry<?, ?> entry = parts.next();
357        appendable.append(joiner.toString(entry.getKey()));
358        appendable.append(keyValueSeparator);
359        appendable.append(joiner.toString(entry.getValue()));
360        while (parts.hasNext()) {
361          appendable.append(joiner.separator);
362          Entry<?, ?> e = parts.next();
363          appendable.append(joiner.toString(e.getKey()));
364          appendable.append(keyValueSeparator);
365          appendable.append(joiner.toString(e.getValue()));
366        }
367      }
368      return appendable;
369    }
370
371    /**
372     * Appends the string representation of each entry in {@code entries}, using the previously
373     * configured separator and key-value separator, to {@code builder}. Identical to {@link
374     * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
375     *
376     * @since 10.0
377     */
378    @Beta
379    public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
380      return appendTo(builder, entries.iterator());
381    }
382
383    /**
384     * Appends the string representation of each entry in {@code entries}, using the previously
385     * configured separator and key-value separator, to {@code builder}. Identical to {@link
386     * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
387     *
388     * @since 11.0
389     */
390    @Beta
391    public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) {
392      try {
393        appendTo((Appendable) builder, entries);
394      } catch (IOException impossible) {
395        throw new AssertionError(impossible);
396      }
397      return builder;
398    }
399
400    /**
401     * Returns a string containing the string representation of each entry in {@code entries}, using
402     * the previously configured separator and key-value separator.
403     *
404     * @since 10.0
405     */
406    @Beta
407    public String join(Iterable<? extends Entry<?, ?>> entries) {
408      return join(entries.iterator());
409    }
410
411    /**
412     * Returns a string containing the string representation of each entry in {@code entries}, using
413     * the previously configured separator and key-value separator.
414     *
415     * @since 11.0
416     */
417    @Beta
418    public String join(Iterator<? extends Entry<?, ?>> entries) {
419      return appendTo(new StringBuilder(), entries).toString();
420    }
421
422    /**
423     * Returns a map joiner with the same behavior as this one, except automatically substituting
424     * {@code nullText} for any provided null keys or values.
425     */
426    @CheckReturnValue
427    public MapJoiner useForNull(String nullText) {
428      return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
429    }
430  }
431
432  CharSequence toString(Object part) {
433    checkNotNull(part);  // checkNotNull for GWT (do not optimize).
434    return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
435  }
436
437  private static Iterable<Object> iterable(
438      final Object first, final Object second, final Object[] rest) {
439    checkNotNull(rest);
440    return new AbstractList<Object>() {
441      @Override public int size() {
442        return rest.length + 2;
443      }
444
445      @Override public Object get(int index) {
446        switch (index) {
447          case 0:
448            return first;
449          case 1:
450            return second;
451          default:
452            return rest[index - 2];
453        }
454      }
455    };
456  }
457}
458