001/*
002 * Copyright (C) 2008 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.base;
018
019import static com.google.common.base.Preconditions.checkNotNull;
020
021import com.google.common.annotations.Beta;
022import com.google.common.annotations.GwtCompatible;
023
024import java.io.IOException;
025import java.util.AbstractList;
026import java.util.Arrays;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.Map.Entry;
030
031import javax.annotation.CheckReturnValue;
032import javax.annotation.Nullable;
033
034/**
035 * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a
036 * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns
037 * them as a {@link String}. Example: <pre>   {@code
038 *
039 *   Joiner joiner = Joiner.on("; ").skipNulls();
040 *    . . .
041 *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
042 *
043 * <p>This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are
044 * converted to strings using {@link Object#toString()} before being appended.
045 *
046 * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining
047 * methods will throw {@link NullPointerException} if any given element is null.
048 *
049 * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code
050 * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner
051 * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code
052 * static final} constants. <pre>   {@code
053 *
054 *   // Bad! Do not do this!
055 *   Joiner joiner = Joiner.on(',');
056 *   joiner.skipNulls(); // does nothing!
057 *   return joiner.join("wrong", null, "wrong");}</pre>
058 *
059 * <p>See the Guava User Guide article on <a href=
060 * "https://github.com/google/guava/wiki/StringsExplained#joiner">{@code Joiner}</a>.
061 *
062 * @author Kevin Bourrillion
063 * @since 2.0
064 */
065@GwtCompatible
066public class Joiner {
067  /**
068   * Returns a joiner which automatically places {@code separator} between consecutive elements.
069   */
070  @CheckReturnValue
071  public static Joiner on(String separator) {
072    return new Joiner(separator);
073  }
074
075  /**
076   * Returns a joiner which automatically places {@code separator} between consecutive elements.
077   */
078  @CheckReturnValue
079  public static Joiner on(char separator) {
080    return new Joiner(String.valueOf(separator));
081  }
082
083  private final String separator;
084
085  private Joiner(String separator) {
086    this.separator = checkNotNull(separator);
087  }
088
089  private Joiner(Joiner prototype) {
090    this.separator = prototype.separator;
091  }
092
093  /**
094   * Appends the string representation of each of {@code parts}, using the previously configured
095   * separator between each, to {@code appendable}.
096   */
097  public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
098    return appendTo(appendable, parts.iterator());
099  }
100
101  /**
102   * Appends the string representation of each of {@code parts}, using the previously configured
103   * separator between each, to {@code appendable}.
104   *
105   * @since 11.0
106   */
107  public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
108    checkNotNull(appendable);
109    if (parts.hasNext()) {
110      appendable.append(toString(parts.next()));
111      while (parts.hasNext()) {
112        appendable.append(separator);
113        appendable.append(toString(parts.next()));
114      }
115    }
116    return appendable;
117  }
118
119  /**
120   * Appends the string representation of each of {@code parts}, using the previously configured
121   * separator between each, to {@code appendable}.
122   */
123  public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
124    return appendTo(appendable, Arrays.asList(parts));
125  }
126
127  /**
128   * Appends to {@code appendable} the string representation of each of the remaining arguments.
129   */
130  public final <A extends Appendable> A appendTo(
131      A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
132      throws IOException {
133    return appendTo(appendable, iterable(first, second, rest));
134  }
135
136  /**
137   * Appends the string representation of each of {@code parts}, using the previously configured
138   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
139   * Iterable)}, except that it does not throw {@link IOException}.
140   */
141  public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
142    return appendTo(builder, parts.iterator());
143  }
144
145  /**
146   * Appends the string representation of each of {@code parts}, using the previously configured
147   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
148   * Iterable)}, except that it does not throw {@link IOException}.
149   *
150   * @since 11.0
151   */
152  public final StringBuilder appendTo(StringBuilder builder, Iterator<?> parts) {
153    try {
154      appendTo((Appendable) builder, parts);
155    } catch (IOException impossible) {
156      throw new AssertionError(impossible);
157    }
158    return builder;
159  }
160
161  /**
162   * Appends the string representation of each of {@code parts}, using the previously configured
163   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
164   * Iterable)}, except that it does not throw {@link IOException}.
165   */
166  public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
167    return appendTo(builder, Arrays.asList(parts));
168  }
169
170  /**
171   * Appends to {@code builder} the string representation of each of the remaining arguments.
172   * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
173   * throw {@link IOException}.
174   */
175  public final StringBuilder appendTo(
176      StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
177    return appendTo(builder, iterable(first, second, rest));
178  }
179
180  /**
181   * Returns a string containing the string representation of each of {@code parts}, using the
182   * previously configured separator between each.
183   */
184  @CheckReturnValue
185  public final String join(Iterable<?> parts) {
186    return join(parts.iterator());
187  }
188
189  /**
190   * Returns a string containing the string representation of each of {@code parts}, using the
191   * previously configured separator between each.
192   *
193   * @since 11.0
194   */
195  @CheckReturnValue
196  public final String join(Iterator<?> parts) {
197    return appendTo(new StringBuilder(), parts).toString();
198  }
199
200  /**
201   * Returns a string containing the string representation of each of {@code parts}, using the
202   * previously configured separator between each.
203   */
204  @CheckReturnValue
205  public final String join(Object[] parts) {
206    return join(Arrays.asList(parts));
207  }
208
209  /**
210   * Returns a string containing the string representation of each argument, using the previously
211   * configured separator between each.
212   */
213  @CheckReturnValue
214  public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
215    return join(iterable(first, second, rest));
216  }
217
218  /**
219   * Returns a joiner with the same behavior as this one, except automatically substituting {@code
220   * nullText} for any provided null elements.
221   */
222  @CheckReturnValue
223  public Joiner useForNull(final String nullText) {
224    checkNotNull(nullText);
225    return new Joiner(this) {
226      @Override
227      CharSequence toString(@Nullable Object part) {
228        return (part == null) ? nullText : Joiner.this.toString(part);
229      }
230
231      @Override
232      public Joiner useForNull(String nullText) {
233        throw new UnsupportedOperationException("already specified useForNull");
234      }
235
236      @Override
237      public Joiner skipNulls() {
238        throw new UnsupportedOperationException("already specified useForNull");
239      }
240    };
241  }
242
243  /**
244   * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
245   * provided null elements.
246   */
247  @CheckReturnValue
248  public Joiner skipNulls() {
249    return new Joiner(this) {
250      @Override
251      public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
252        checkNotNull(appendable, "appendable");
253        checkNotNull(parts, "parts");
254        while (parts.hasNext()) {
255          Object part = parts.next();
256          if (part != null) {
257            appendable.append(Joiner.this.toString(part));
258            break;
259          }
260        }
261        while (parts.hasNext()) {
262          Object part = parts.next();
263          if (part != null) {
264            appendable.append(separator);
265            appendable.append(Joiner.this.toString(part));
266          }
267        }
268        return appendable;
269      }
270
271      @Override
272      public Joiner useForNull(String nullText) {
273        throw new UnsupportedOperationException("already specified skipNulls");
274      }
275
276      @Override
277      public MapJoiner withKeyValueSeparator(String kvs) {
278        throw new UnsupportedOperationException("can't use .skipNulls() with maps");
279      }
280    };
281  }
282
283  /**
284   * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
285   * this {@code Joiner} otherwise.
286   */
287  @CheckReturnValue
288  public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
289    return new MapJoiner(this, keyValueSeparator);
290  }
291
292  /**
293   * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
294   * arrays. Like {@code Joiner}, it is thread-safe and immutable.
295   *
296   * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code
297   * Multimap} entries in two distinct modes:
298   *
299   * <ul>
300   * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a
301   *     {@code MapJoiner} method that accepts entries as input, and receive output of the form
302   *     {@code key1=A&key1=B&key2=C}.
303   * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner}
304   *     method that accepts a map as input, and receive output of the form {@code
305   *     key1=[A, B]&key2=C}.
306   * </ul>
307   *
308   * @since 2.0
309   */
310  public static final class MapJoiner {
311    private final Joiner joiner;
312    private final String keyValueSeparator;
313
314    private MapJoiner(Joiner joiner, String keyValueSeparator) {
315      this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
316      this.keyValueSeparator = checkNotNull(keyValueSeparator);
317    }
318
319    /**
320     * Appends the string representation of each entry of {@code map}, using the previously
321     * configured separator and key-value separator, to {@code appendable}.
322     */
323    public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
324      return appendTo(appendable, map.entrySet());
325    }
326
327    /**
328     * Appends the string representation of each entry of {@code map}, using the previously
329     * configured separator and key-value separator, to {@code builder}. Identical to {@link
330     * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
331     */
332    public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
333      return appendTo(builder, map.entrySet());
334    }
335
336    /**
337     * Returns a string containing the string representation of each entry of {@code map}, using the
338     * previously configured separator and key-value separator.
339     */
340    @CheckReturnValue
341    public String join(Map<?, ?> map) {
342      return join(map.entrySet());
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 10.0
350     */
351    @Beta
352    public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
353        throws IOException {
354      return appendTo(appendable, entries.iterator());
355    }
356
357    /**
358     * Appends the string representation of each entry in {@code entries}, using the previously
359     * configured separator and key-value separator, to {@code appendable}.
360     *
361     * @since 11.0
362     */
363    @Beta
364    public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts)
365        throws IOException {
366      checkNotNull(appendable);
367      if (parts.hasNext()) {
368        Entry<?, ?> entry = parts.next();
369        appendable.append(joiner.toString(entry.getKey()));
370        appendable.append(keyValueSeparator);
371        appendable.append(joiner.toString(entry.getValue()));
372        while (parts.hasNext()) {
373          appendable.append(joiner.separator);
374          Entry<?, ?> e = parts.next();
375          appendable.append(joiner.toString(e.getKey()));
376          appendable.append(keyValueSeparator);
377          appendable.append(joiner.toString(e.getValue()));
378        }
379      }
380      return appendable;
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 10.0
389     */
390    @Beta
391    public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
392      return appendTo(builder, entries.iterator());
393    }
394
395    /**
396     * Appends the string representation of each entry in {@code entries}, using the previously
397     * configured separator and key-value separator, to {@code builder}. Identical to {@link
398     * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
399     *
400     * @since 11.0
401     */
402    @Beta
403    public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) {
404      try {
405        appendTo((Appendable) builder, entries);
406      } catch (IOException impossible) {
407        throw new AssertionError(impossible);
408      }
409      return builder;
410    }
411
412    /**
413     * Returns a string containing the string representation of each entry in {@code entries}, using
414     * the previously configured separator and key-value separator.
415     *
416     * @since 10.0
417     */
418    @Beta
419    @CheckReturnValue
420    public String join(Iterable<? extends Entry<?, ?>> entries) {
421      return join(entries.iterator());
422    }
423
424    /**
425     * Returns a string containing the string representation of each entry in {@code entries}, using
426     * the previously configured separator and key-value separator.
427     *
428     * @since 11.0
429     */
430    @Beta
431    @CheckReturnValue
432    public String join(Iterator<? extends Entry<?, ?>> entries) {
433      return appendTo(new StringBuilder(), entries).toString();
434    }
435
436    /**
437     * Returns a map joiner with the same behavior as this one, except automatically substituting
438     * {@code nullText} for any provided null keys or values.
439     */
440    @CheckReturnValue
441    public MapJoiner useForNull(String nullText) {
442      return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
443    }
444  }
445
446  CharSequence toString(Object part) {
447    checkNotNull(part); // checkNotNull for GWT (do not optimize).
448    return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
449  }
450
451  private static Iterable<Object> iterable(
452      final Object first, final Object second, final Object[] rest) {
453    checkNotNull(rest);
454    return new AbstractList<Object>() {
455      @Override
456      public int size() {
457        return rest.length + 2;
458      }
459
460      @Override
461      public Object get(int index) {
462        switch (index) {
463          case 0:
464            return first;
465          case 1:
466            return second;
467          default:
468            return rest[index - 2];
469        }
470      }
471    };
472  }
473}