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