1/*
2 * Copyright (C) 2008 Google Inc.
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 static com.google.common.base.Preconditions.checkNotNull;
21
22import java.io.IOException;
23import java.util.AbstractList;
24import java.util.Arrays;
25import java.util.Iterator;
26import java.util.Map;
27import java.util.Map.Entry;
28
29import javax.annotation.Nullable;
30
31/**
32 * An object which joins pieces of text (specified as an array, {@link
33 * Iterable}, varargs or even a {@link Map}) with a separator. It either
34 * appends the results to an {@link Appendable} or returns them as a {@link
35 * String}. Example: <pre>   {@code
36 *
37 *   Joiner joiner = Joiner.on("; ").skipNulls();
38 *    . . .
39 *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
40 *
41 * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input
42 * elements are converted to strings using {@link Object#toString()} before
43 * being appended.
44 *
45 * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is
46 * specified, the joining methods will throw {@link NullPointerException} if any
47 * given element is null.
48 *
49 * @author Kevin Bourrillion
50 * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
51 */
52@GwtCompatible public class Joiner {
53  /**
54   * Returns a joiner which automatically places {@code separator} between
55   * consecutive elements.
56   */
57  public static Joiner on(String separator) {
58    return new Joiner(separator);
59  }
60
61  /**
62   * Returns a joiner which automatically places {@code separator} between
63   * consecutive elements.
64   */
65  public static Joiner on(char separator) {
66    return new Joiner(String.valueOf(separator));
67  }
68
69  private final String separator;
70
71  private Joiner(String separator) {
72    this.separator = checkNotNull(separator);
73  }
74
75  private Joiner(Joiner prototype) {
76    this.separator = prototype.separator;
77  }
78
79  /**
80   * Appends the string representation of each of {@code parts}, using the
81   * previously configured separator between each, to {@code appendable}.
82   */
83  public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
84      throws IOException {
85    checkNotNull(appendable);
86    Iterator<?> iterator = parts.iterator();
87    if (iterator.hasNext()) {
88      appendable.append(toString(iterator.next()));
89      while (iterator.hasNext()) {
90        appendable.append(separator);
91        appendable.append(toString(iterator.next()));
92      }
93    }
94    return appendable;
95  }
96
97  /**
98   * Appends the string representation of each of {@code parts}, using the
99   * previously configured separator between each, to {@code appendable}.
100   */
101  public final <A extends Appendable> A appendTo(
102      A appendable, Object[] parts) throws IOException {
103    return appendTo(appendable, Arrays.asList(parts));
104  }
105
106  /**
107   * Appends to {@code appendable} the string representation of each of the
108   * remaining arguments.
109   */
110  public final <A extends Appendable> A appendTo(A appendable,
111      @Nullable Object first, @Nullable Object second, Object... rest)
112      throws IOException {
113    return appendTo(appendable, iterable(first, second, rest));
114  }
115
116  /**
117   * Appends the string representation of each of {@code parts}, using the
118   * previously configured separator between each, to {@code builder}. Identical
119   * to {@link #appendTo(Appendable, Iterable)}, except that it does not throw
120   * {@link IOException}.
121   */
122  public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts)
123  {
124    try {
125      appendTo((Appendable) builder, parts);
126    } catch (IOException impossible) {
127      throw new AssertionError(impossible);
128    }
129    return builder;
130  }
131
132  /**
133   * Appends the string representation of each of {@code parts}, using the
134   * previously configured separator between each, to {@code builder}. Identical
135   * to {@link #appendTo(Appendable, Iterable)}, except that it does not throw
136   * {@link IOException}.
137   */
138  public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
139    return appendTo(builder, Arrays.asList(parts));
140  }
141
142  /**
143   * Appends to {@code builder} the string representation of each of the
144   * remaining arguments. Identical to {@link #appendTo(Appendable, Object,
145   * Object, Object[])}, except that it does not throw {@link IOException}.
146   */
147  public final StringBuilder appendTo(StringBuilder builder,
148      @Nullable Object first, @Nullable Object second, Object... rest) {
149    return appendTo(builder, iterable(first, second, rest));
150  }
151
152  /**
153   * Returns a string containing the string representation of each of {@code
154   * parts}, using the previously configured separator between each.
155   */
156  public final String join(Iterable<?> parts) {
157    return appendTo(new StringBuilder(), parts).toString();
158  }
159
160  /**
161   * Returns a string containing the string representation of each of {@code
162   * parts}, using the previously configured separator between each.
163   */
164  public final String join(Object[] parts) {
165    return join(Arrays.asList(parts));
166  }
167
168  /**
169   * Returns a string containing the string representation of each argument,
170   * using the previously configured separator between each.
171   */
172  public final String join(
173      @Nullable Object first, @Nullable Object second, Object... rest) {
174    return join(iterable(first, second, rest));
175  }
176
177  /**
178   * Returns a joiner with the same behavior as this one, except automatically
179   * substituting {@code nullText} for any provided null elements.
180   */
181  public Joiner useForNull(final String nullText) {
182    checkNotNull(nullText);
183    return new Joiner(this) {
184      @Override CharSequence toString(Object part) {
185        return (part == null) ? nullText : Joiner.this.toString(part);
186      }
187      @Override public Joiner useForNull(String nullText) {
188        checkNotNull(nullText); // weird, just to satisfy NullPointerTester!
189        // TODO: fix that?
190        throw new UnsupportedOperationException("already specified useForNull");
191      }
192      @Override public Joiner skipNulls() {
193        throw new UnsupportedOperationException("already specified useForNull");
194      }
195    };
196  }
197
198  /**
199   * Returns a joiner with the same behavior as this joiner, except
200   * automatically skipping over any provided null elements.
201   */
202  public Joiner skipNulls() {
203    return new Joiner(this) {
204      @Override public <A extends Appendable> A appendTo(
205          A appendable, Iterable<?> parts) throws IOException {
206        checkNotNull(appendable, "appendable");
207        checkNotNull(parts, "parts");
208        Iterator<?> iterator = parts.iterator();
209        while (iterator.hasNext()) {
210          Object part = iterator.next();
211          if (part != null) {
212            appendable.append(Joiner.this.toString(part));
213            break;
214          }
215        }
216        while (iterator.hasNext()) {
217          Object part = iterator.next();
218          if (part != null) {
219            appendable.append(separator);
220            appendable.append(Joiner.this.toString(part));
221          }
222        }
223        return appendable;
224      }
225      @Override public Joiner useForNull(String nullText) {
226        checkNotNull(nullText); // weird, just to satisfy NullPointerTester!
227        throw new UnsupportedOperationException("already specified skipNulls");
228      }
229      @Override public MapJoiner withKeyValueSeparator(String kvs) {
230        checkNotNull(kvs); // weird, just to satisfy NullPointerTester!
231        throw new UnsupportedOperationException(
232            "can't use .skipNulls() with maps");
233      }
234    };
235  }
236
237  /**
238   * Returns a {@code MapJoiner} using the given key-value separator, and the
239   * same configuration as this {@code Joiner} otherwise.
240   */
241  public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
242    return new MapJoiner(this, checkNotNull(keyValueSeparator));
243  }
244
245  /**
246   * An object that joins map entries in the same manner as {@code Joiner} joins
247   * iterables and arrays.
248   */
249  public static class MapJoiner {
250    private Joiner joiner;
251    private String keyValueSeparator;
252
253    private MapJoiner(Joiner joiner, String keyValueSeparator) {
254      this.joiner = joiner;
255      this.keyValueSeparator = keyValueSeparator;
256    }
257
258    /**
259     * Appends the string representation of each entry of {@code map}, using the
260     * previously configured separator and key-value separator, to {@code
261     * appendable}.
262     */
263    public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map)
264        throws IOException {
265      checkNotNull(appendable);
266      Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator();
267      if (iterator.hasNext()) {
268        Entry<?, ?> entry = iterator.next();
269        appendable.append(joiner.toString(entry.getKey()));
270        appendable.append(keyValueSeparator);
271        appendable.append(joiner.toString(entry.getValue()));
272        while (iterator.hasNext()) {
273          appendable.append(joiner.separator);
274          Entry<?, ?> e = iterator.next();
275          appendable.append(joiner.toString(e.getKey()));
276          appendable.append(keyValueSeparator);
277          appendable.append(joiner.toString(e.getValue()));
278        }
279      }
280      return appendable;
281    }
282
283    /**
284     * Appends the string representation of each entry of {@code map}, using the
285     * previously configured separator and key-value separator, to {@code
286     * builder}. Identical to {@link #appendTo(Appendable, Map)}, except that it
287     * does not throw {@link IOException}.
288     */
289    public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
290      try {
291        appendTo((Appendable) builder, map);
292      } catch (IOException impossible) {
293        throw new AssertionError(impossible);
294      }
295      return builder;
296    }
297
298    /**
299     * Returns a string containing the string representation of each entry of
300     * {@code map}, using the previously configured separator and key-value
301     * separator.
302     */
303    public String join(Map<?, ?> map) {
304      return appendTo(new StringBuilder(), map).toString();
305    }
306
307    /**
308     * Returns a map joiner with the same behavior as this one, except
309     * automatically substituting {@code nullText} for any provided null keys or
310     * values.
311     */
312    public MapJoiner useForNull(String nullText) {
313      return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
314    }
315  }
316
317  CharSequence toString(Object part) {
318    return (part instanceof CharSequence)
319        ? (CharSequence) part
320        : part.toString();
321  }
322
323  private static Iterable<Object> iterable(
324      final Object first, final Object second, final Object[] rest) {
325    checkNotNull(rest);
326    return new AbstractList<Object>() {
327      @Override public int size() {
328        return rest.length + 2;
329      }
330      @Override public Object get(int index) {
331        switch (index) {
332          case 0:
333            return first;
334          case 1:
335            return second;
336          default:
337            return rest[index - 2];
338        }
339      }
340    };
341  }
342}
343