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.GwtCompatible;
022
023import java.io.IOException;
024import java.util.AbstractList;
025import java.util.Arrays;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Map.Entry;
029
030import javax.annotation.Nullable;
031
032/**
033 * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a
034 * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns
035 * them as a {@link String}. Example: <pre>   {@code
036 *
037 *   Joiner joiner = Joiner.on("; ").skipNulls();
038 *    . . .
039 *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
040 *
041 * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are
042 * converted to strings using {@link Object#toString()} before being appended.
043 *
044 * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining
045 * methods will throw {@link NullPointerException} if any given element is null.
046 *
047 * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code
048 * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner
049 * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code
050 * static final} constants. <pre>   {@code
051 *
052 *   // Bad! Do not do this!
053 *   Joiner joiner = Joiner.on(',');
054 *   joiner.skipNulls(); // does nothing!
055 *   return joiner.join("wrong", null, "wrong");}</pre>
056 *
057 * @author Kevin Bourrillion
058 * @since 2 (imported from Google Collections Library)
059 */
060@GwtCompatible
061public class Joiner {
062  /**
063   * Returns a joiner which automatically places {@code separator} between consecutive elements.
064   */
065  public static Joiner on(String separator) {
066    return new Joiner(separator);
067  }
068
069  /**
070   * Returns a joiner which automatically places {@code separator} between consecutive elements.
071   */
072  public static Joiner on(char separator) {
073    return new Joiner(String.valueOf(separator));
074  }
075
076  private final String separator;
077
078  private Joiner(String separator) {
079    this.separator = checkNotNull(separator);
080  }
081
082  private Joiner(Joiner prototype) {
083    this.separator = prototype.separator;
084  }
085
086  /**
087   * Appends the string representation of each of {@code parts}, using the previously configured
088   * separator between each, to {@code appendable}.
089   */
090  public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
091    checkNotNull(appendable);
092    Iterator<?> iterator = parts.iterator();
093    if (iterator.hasNext()) {
094      appendable.append(toString(iterator.next()));
095      while (iterator.hasNext()) {
096        appendable.append(separator);
097        appendable.append(toString(iterator.next()));
098      }
099    }
100    return appendable;
101  }
102
103  /**
104   * Appends the string representation of each of {@code parts}, using the previously configured
105   * separator between each, to {@code appendable}.
106   */
107  public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
108    return appendTo(appendable, Arrays.asList(parts));
109  }
110
111  /**
112   * Appends to {@code appendable} the string representation of each of the remaining arguments.
113   */
114  public final <A extends Appendable> A appendTo(
115      A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
116          throws IOException {
117    return appendTo(appendable, iterable(first, second, rest));
118  }
119
120  /**
121   * Appends the string representation of each of {@code parts}, using the previously configured
122   * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
123   * Iterable)}, except that it does not throw {@link IOException}.
124   */
125  public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
126    try {
127      appendTo((Appendable) builder, parts);
128    } catch (IOException impossible) {
129      throw new AssertionError(impossible);
130    }
131    return builder;
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, Object[] parts) {
140    return appendTo(builder, Arrays.asList(parts));
141  }
142
143  /**
144   * Appends to {@code builder} the string representation of each of the remaining arguments.
145   * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
146   * throw {@link IOException}.
147   */
148  public final StringBuilder appendTo(
149      StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
150    return appendTo(builder, iterable(first, second, rest));
151  }
152
153  /**
154   * Returns a string containing the string representation of each of {@code parts}, using the
155   * previously configured separator between each.
156   */
157  public final String join(Iterable<?> parts) {
158    return appendTo(new StringBuilder(), parts).toString();
159  }
160
161  /**
162   * Returns a string containing the string representation of each of {@code parts}, using the
163   * previously configured separator between each.
164   */
165  public final String join(Object[] parts) {
166    return join(Arrays.asList(parts));
167  }
168
169  /**
170   * Returns a string containing the string representation of each argument, using the previously
171   * configured separator between each.
172   */
173  public final String join(@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 substituting {@code
179   * 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
188      @Override public Joiner useForNull(String nullText) {
189        checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
190        throw new UnsupportedOperationException("already specified useForNull");
191      }
192
193      @Override public Joiner skipNulls() {
194        throw new UnsupportedOperationException("already specified useForNull");
195      }
196    };
197  }
198
199  /**
200   * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
201   * provided null elements.
202   */
203  public Joiner skipNulls() {
204    return new Joiner(this) {
205      @Override public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
206          throws IOException {
207        checkNotNull(appendable, "appendable");
208        checkNotNull(parts, "parts");
209        Iterator<?> iterator = parts.iterator();
210        while (iterator.hasNext()) {
211          Object part = iterator.next();
212          if (part != null) {
213            appendable.append(Joiner.this.toString(part));
214            break;
215          }
216        }
217        while (iterator.hasNext()) {
218          Object part = iterator.next();
219          if (part != null) {
220            appendable.append(separator);
221            appendable.append(Joiner.this.toString(part));
222          }
223        }
224        return appendable;
225      }
226
227      @Override public Joiner useForNull(String nullText) {
228        checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
229        throw new UnsupportedOperationException("already specified skipNulls");
230      }
231
232      @Override public MapJoiner withKeyValueSeparator(String kvs) {
233        checkNotNull(kvs); // weird: just to satisfy NullPointerTester.
234        throw new UnsupportedOperationException("can't use .skipNulls() with maps");
235      }
236    };
237  }
238
239  /**
240   * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
241   * this {@code Joiner} otherwise.
242   */
243  public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
244    return new MapJoiner(this, keyValueSeparator);
245  }
246
247  /**
248   * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
249   * arrays. Like {@code Joiner}, it is thread-safe and immutable.
250   *
251   * @since 2 (imported from Google Collections Library)
252   */
253  public final static class MapJoiner {
254    private final Joiner joiner;
255    private final String keyValueSeparator;
256
257    private MapJoiner(Joiner joiner, String keyValueSeparator) {
258      this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
259      this.keyValueSeparator = checkNotNull(keyValueSeparator);
260    }
261
262    /**
263     * Appends the string representation of each entry of {@code map}, using the previously
264     * configured separator and key-value separator, to {@code appendable}.
265     */
266    public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
267      checkNotNull(appendable);
268      Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator();
269      if (iterator.hasNext()) {
270        Entry<?, ?> entry = iterator.next();
271        appendable.append(joiner.toString(entry.getKey()));
272        appendable.append(keyValueSeparator);
273        appendable.append(joiner.toString(entry.getValue()));
274        while (iterator.hasNext()) {
275          appendable.append(joiner.separator);
276          Entry<?, ?> e = iterator.next();
277          appendable.append(joiner.toString(e.getKey()));
278          appendable.append(keyValueSeparator);
279          appendable.append(joiner.toString(e.getValue()));
280        }
281      }
282      return appendable;
283    }
284
285    /**
286     * Appends the string representation of each entry of {@code map}, using the previously
287     * configured separator and key-value separator, to {@code builder}. Identical to {@link
288     * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
289     */
290    public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
291      try {
292        appendTo((Appendable) builder, map);
293      } catch (IOException impossible) {
294        throw new AssertionError(impossible);
295      }
296      return builder;
297    }
298
299    /**
300     * Returns a string containing the string representation of each entry of {@code map}, using the
301     * previously configured separator and key-value 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 automatically substituting
309     * {@code nullText} for any provided null keys or values.
310     */
311    public MapJoiner useForNull(String nullText) {
312      return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
313    }
314  }
315
316  CharSequence toString(Object part) {
317    return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
318  }
319
320  private static Iterable<Object> iterable(
321      final Object first, final Object second, final Object[] rest) {
322    checkNotNull(rest);
323    return new AbstractList<Object>() {
324      @Override public int size() {
325        return rest.length + 2;
326      }
327
328      @Override public Object get(int index) {
329        switch (index) {
330          case 0:
331            return first;
332          case 1:
333            return second;
334          default:
335            return rest[index - 2];
336        }
337      }
338    };
339  }
340}