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 * "http://code.google.com/p/guava-libraries/wiki/StringsExplained#Joiner">{@code Joiner}</a>. 
061 *
062 * @author Kevin Bourrillion
063 * @since 2.0 (imported from Google Collections Library)
064 */
065@GwtCompatible
066public class Joiner {
067  /**
068   * Returns a joiner which automatically places {@code separator} between consecutive elements.
069   */
070  public static Joiner on(String separator) {
071    return new Joiner(separator);
072  }
073
074  /**
075   * Returns a joiner which automatically places {@code separator} between consecutive elements.
076   */
077  public static Joiner on(char separator) {
078    return new Joiner(String.valueOf(separator));
079  }
080
081  private final String separator;
082
083  private Joiner(String separator) {
084    this.separator = checkNotNull(separator);
085  }
086
087  private Joiner(Joiner prototype) {
088    this.separator = prototype.separator;
089  }
090
091  /**
092   * Appends the string representation of each of {@code parts}, using the previously configured
093   * separator between each, to {@code appendable}.
094   */
095  public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
096    return appendTo(appendable, parts.iterator());
097  }
098
099  /**
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        checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
226        throw new UnsupportedOperationException("already specified useForNull");
227      }
228
229      @Override public Joiner skipNulls() {
230        throw new UnsupportedOperationException("already specified useForNull");
231      }
232    };
233  }
234
235  /**
236   * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
237   * provided null elements.
238   */
239  @CheckReturnValue
240  public Joiner skipNulls() {
241    return new Joiner(this) {
242      @Override public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)
243          throws IOException {
244        checkNotNull(appendable, "appendable");
245        checkNotNull(parts, "parts");
246        while (parts.hasNext()) {
247          Object part = parts.next();
248          if (part != null) {
249            appendable.append(Joiner.this.toString(part));
250            break;
251          }
252        }
253        while (parts.hasNext()) {
254          Object part = parts.next();
255          if (part != null) {
256            appendable.append(separator);
257            appendable.append(Joiner.this.toString(part));
258          }
259        }
260        return appendable;
261      }
262
263      @Override public Joiner useForNull(String nullText) {
264        checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
265        throw new UnsupportedOperationException("already specified skipNulls");
266      }
267
268      @Override public MapJoiner withKeyValueSeparator(String kvs) {
269        checkNotNull(kvs); // weird: just to satisfy NullPointerTester.
270        throw new UnsupportedOperationException("can't use .skipNulls() with maps");
271      }
272    };
273  }
274
275  /**
276   * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
277   * this {@code Joiner} otherwise.
278   */
279  @CheckReturnValue
280  public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
281    return new MapJoiner(this, keyValueSeparator);
282  }
283
284  /**
285   * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
286   * arrays. Like {@code Joiner}, it is thread-safe and immutable.
287   *
288   * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code
289   * Multimap} entries in two distinct modes:
290   *
291   * <ul>
292   * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a
293   *     {@code MapJoiner} method that accepts entries as input, and receive output of the form
294   *     {@code key1=A&key1=B&key2=C}.
295   * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner}
296   *     method that accepts a map as input, and receive output of the form {@code
297   *     key1=[A, B]&key2=C}.
298   * </ul>
299   *
300   * @since 2.0 (imported from Google Collections Library)
301   */
302  public final static class MapJoiner {
303    private final Joiner joiner;
304    private final String keyValueSeparator;
305
306    private MapJoiner(Joiner joiner, String keyValueSeparator) {
307      this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
308      this.keyValueSeparator = checkNotNull(keyValueSeparator);
309    }
310
311    /**
312     * Appends the string representation of each entry of {@code map}, using the previously
313     * configured separator and key-value separator, to {@code appendable}.
314     */
315    public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
316      return appendTo(appendable, map.entrySet());
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 builder}. Identical to {@link
322     * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
323     */
324    public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
325      return appendTo(builder, map.entrySet());
326    }
327
328    /**
329     * Returns a string containing the string representation of each entry of {@code map}, using the
330     * previously configured separator and key-value separator.
331     */
332    public String join(Map<?, ?> map) {
333      return join(map.entrySet());
334    }
335
336    /**
337     * Appends the string representation of each entry in {@code entries}, using the previously
338     * configured separator and key-value separator, to {@code appendable}.
339     *
340     * @since 10.0
341     */
342    @Beta
343    public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
344        throws IOException {
345      return appendTo(appendable, entries.iterator());
346    }
347
348    /**
349     * Appends the string representation of each entry in {@code entries}, using the previously
350     * configured separator and key-value separator, to {@code appendable}.
351     *
352     * @since 11.0
353     */
354    @Beta
355    public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts)
356        throws IOException {
357      checkNotNull(appendable);
358      if (parts.hasNext()) {
359        Entry<?, ?> entry = parts.next();
360        appendable.append(joiner.toString(entry.getKey()));
361        appendable.append(keyValueSeparator);
362        appendable.append(joiner.toString(entry.getValue()));
363        while (parts.hasNext()) {
364          appendable.append(joiner.separator);
365          Entry<?, ?> e = parts.next();
366          appendable.append(joiner.toString(e.getKey()));
367          appendable.append(keyValueSeparator);
368          appendable.append(joiner.toString(e.getValue()));
369        }
370      }
371      return appendable;
372    }
373
374    /**
375     * Appends the string representation of each entry in {@code entries}, using the previously
376     * configured separator and key-value separator, to {@code builder}. Identical to {@link
377     * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
378     *
379     * @since 10.0
380     */
381    @Beta
382    public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
383      return appendTo(builder, entries.iterator());
384    }
385
386    /**
387     * Appends the string representation of each entry in {@code entries}, using the previously
388     * configured separator and key-value separator, to {@code builder}. Identical to {@link
389     * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
390     *
391     * @since 11.0
392     */
393    @Beta
394    public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) {
395      try {
396        appendTo((Appendable) builder, entries);
397      } catch (IOException impossible) {
398        throw new AssertionError(impossible);
399      }
400      return builder;
401    }
402
403    /**
404     * Returns a string containing the string representation of each entry in {@code entries}, using
405     * the previously configured separator and key-value separator.
406     *
407     * @since 10.0
408     */
409    @Beta
410    public String join(Iterable<? extends Entry<?, ?>> entries) {
411      return join(entries.iterator());
412    }
413
414    /**
415     * Returns a string containing the string representation of each entry in {@code entries}, using
416     * the previously configured separator and key-value separator.
417     *
418     * @since 11.0
419     */
420    @Beta
421    public String join(Iterator<? extends Entry<?, ?>> entries) {
422      return appendTo(new StringBuilder(), entries).toString();
423    }
424
425    /**
426     * Returns a map joiner with the same behavior as this one, except automatically substituting
427     * {@code nullText} for any provided null keys or values.
428     */
429    @CheckReturnValue
430    public MapJoiner useForNull(String nullText) {
431      return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
432    }
433  }
434
435  CharSequence toString(Object part) {
436    checkNotNull(part);  // checkNotNull for GWT (do not optimize).
437    return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
438  }
439
440  private static Iterable<Object> iterable(
441      final Object first, final Object second, final Object[] rest) {
442    checkNotNull(rest);
443    return new AbstractList<Object>() {
444      @Override public int size() {
445        return rest.length + 2;
446      }
447
448      @Override public Object get(int index) {
449        switch (index) {
450          case 0:
451            return first;
452          case 1:
453            return second;
454          default:
455            return rest[index - 2];
456        }
457      }
458    };
459  }
460}