View Javadoc
1   /*
2    * Copyright (C) 2008 The Guava Authors
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  
17  package com.google.common.base;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  
21  import com.google.common.annotations.Beta;
22  import com.google.common.annotations.GwtCompatible;
23  
24  import java.io.IOException;
25  import java.util.AbstractList;
26  import java.util.Arrays;
27  import java.util.Iterator;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  
31  import javax.annotation.CheckReturnValue;
32  import javax.annotation.Nullable;
33  
34  /**
35   * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a
36   * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns
37   * them as a {@link String}. Example: <pre>   {@code
38   *
39   *   Joiner joiner = Joiner.on("; ").skipNulls();
40   *    . . .
41   *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
42   *
43   * <p>This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are
44   * converted to strings using {@link Object#toString()} before being appended.
45   *
46   * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining
47   * methods will throw {@link NullPointerException} if any given element is null.
48   *
49   * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code
50   * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner
51   * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code
52   * static final} constants. <pre>   {@code
53   *
54   *   // Bad! Do not do this!
55   *   Joiner joiner = Joiner.on(',');
56   *   joiner.skipNulls(); // does nothing!
57   *   return joiner.join("wrong", null, "wrong");}</pre>
58   *   
59   * <p>See the Guava User Guide article on <a href=
60   * "http://code.google.com/p/guava-libraries/wiki/StringsExplained#Joiner">{@code Joiner}</a>. 
61   *
62   * @author Kevin Bourrillion
63   * @since 2.0 (imported from Google Collections Library)
64   */
65  @GwtCompatible
66  public class Joiner {
67    /**
68     * Returns a joiner which automatically places {@code separator} between consecutive elements.
69     */
70    public static Joiner on(String separator) {
71      return new Joiner(separator);
72    }
73  
74    /**
75     * Returns a joiner which automatically places {@code separator} between consecutive elements.
76     */
77    public static Joiner on(char separator) {
78      return new Joiner(String.valueOf(separator));
79    }
80  
81    private final String separator;
82  
83    private Joiner(String separator) {
84      this.separator = checkNotNull(separator);
85    }
86  
87    private Joiner(Joiner prototype) {
88      this.separator = prototype.separator;
89    }
90  
91    /**
92     * Appends the string representation of each of {@code parts}, using the previously configured
93     * separator between each, to {@code appendable}.
94     */
95    public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
96      return appendTo(appendable, parts.iterator());
97    }
98  
99    /**
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         throw new UnsupportedOperationException("already specified useForNull");
226       }
227 
228       @Override public Joiner skipNulls() {
229         throw new UnsupportedOperationException("already specified useForNull");
230       }
231     };
232   }
233 
234   /**
235    * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
236    * provided null elements.
237    */
238   @CheckReturnValue
239   public Joiner skipNulls() {
240     return new Joiner(this) {
241       @Override public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)
242           throws IOException {
243         checkNotNull(appendable, "appendable");
244         checkNotNull(parts, "parts");
245         while (parts.hasNext()) {
246           Object part = parts.next();
247           if (part != null) {
248             appendable.append(Joiner.this.toString(part));
249             break;
250           }
251         }
252         while (parts.hasNext()) {
253           Object part = parts.next();
254           if (part != null) {
255             appendable.append(separator);
256             appendable.append(Joiner.this.toString(part));
257           }
258         }
259         return appendable;
260       }
261 
262       @Override public Joiner useForNull(String nullText) {
263         throw new UnsupportedOperationException("already specified skipNulls");
264       }
265 
266       @Override public MapJoiner withKeyValueSeparator(String kvs) {
267         throw new UnsupportedOperationException("can't use .skipNulls() with maps");
268       }
269     };
270   }
271 
272   /**
273    * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
274    * this {@code Joiner} otherwise.
275    */
276   @CheckReturnValue
277   public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
278     return new MapJoiner(this, keyValueSeparator);
279   }
280 
281   /**
282    * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
283    * arrays. Like {@code Joiner}, it is thread-safe and immutable.
284    *
285    * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code
286    * Multimap} entries in two distinct modes:
287    *
288    * <ul>
289    * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a
290    *     {@code MapJoiner} method that accepts entries as input, and receive output of the form
291    *     {@code key1=A&key1=B&key2=C}.
292    * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner}
293    *     method that accepts a map as input, and receive output of the form {@code
294    *     key1=[A, B]&key2=C}.
295    * </ul>
296    *
297    * @since 2.0 (imported from Google Collections Library)
298    */
299   public static final class MapJoiner {
300     private final Joiner joiner;
301     private final String keyValueSeparator;
302 
303     private MapJoiner(Joiner joiner, String keyValueSeparator) {
304       this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
305       this.keyValueSeparator = checkNotNull(keyValueSeparator);
306     }
307 
308     /**
309      * Appends the string representation of each entry of {@code map}, using the previously
310      * configured separator and key-value separator, to {@code appendable}.
311      */
312     public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
313       return appendTo(appendable, map.entrySet());
314     }
315 
316     /**
317      * Appends the string representation of each entry of {@code map}, using the previously
318      * configured separator and key-value separator, to {@code builder}. Identical to {@link
319      * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
320      */
321     public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
322       return appendTo(builder, map.entrySet());
323     }
324 
325     /**
326      * Returns a string containing the string representation of each entry of {@code map}, using the
327      * previously configured separator and key-value separator.
328      */
329     public String join(Map<?, ?> map) {
330       return join(map.entrySet());
331     }
332 
333     /**
334      * Appends the string representation of each entry in {@code entries}, using the previously
335      * configured separator and key-value separator, to {@code appendable}.
336      *
337      * @since 10.0
338      */
339     @Beta
340     public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
341         throws IOException {
342       return appendTo(appendable, entries.iterator());
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 11.0
350      */
351     @Beta
352     public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts)
353         throws IOException {
354       checkNotNull(appendable);
355       if (parts.hasNext()) {
356         Entry<?, ?> entry = parts.next();
357         appendable.append(joiner.toString(entry.getKey()));
358         appendable.append(keyValueSeparator);
359         appendable.append(joiner.toString(entry.getValue()));
360         while (parts.hasNext()) {
361           appendable.append(joiner.separator);
362           Entry<?, ?> e = parts.next();
363           appendable.append(joiner.toString(e.getKey()));
364           appendable.append(keyValueSeparator);
365           appendable.append(joiner.toString(e.getValue()));
366         }
367       }
368       return appendable;
369     }
370 
371     /**
372      * Appends the string representation of each entry in {@code entries}, using the previously
373      * configured separator and key-value separator, to {@code builder}. Identical to {@link
374      * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
375      *
376      * @since 10.0
377      */
378     @Beta
379     public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
380       return appendTo(builder, entries.iterator());
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 11.0
389      */
390     @Beta
391     public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) {
392       try {
393         appendTo((Appendable) builder, entries);
394       } catch (IOException impossible) {
395         throw new AssertionError(impossible);
396       }
397       return builder;
398     }
399 
400     /**
401      * Returns a string containing the string representation of each entry in {@code entries}, using
402      * the previously configured separator and key-value separator.
403      *
404      * @since 10.0
405      */
406     @Beta
407     public String join(Iterable<? extends Entry<?, ?>> entries) {
408       return join(entries.iterator());
409     }
410 
411     /**
412      * Returns a string containing the string representation of each entry in {@code entries}, using
413      * the previously configured separator and key-value separator.
414      *
415      * @since 11.0
416      */
417     @Beta
418     public String join(Iterator<? extends Entry<?, ?>> entries) {
419       return appendTo(new StringBuilder(), entries).toString();
420     }
421 
422     /**
423      * Returns a map joiner with the same behavior as this one, except automatically substituting
424      * {@code nullText} for any provided null keys or values.
425      */
426     @CheckReturnValue
427     public MapJoiner useForNull(String nullText) {
428       return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
429     }
430   }
431 
432   CharSequence toString(Object part) {
433     checkNotNull(part);  // checkNotNull for GWT (do not optimize).
434     return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
435   }
436 
437   private static Iterable<Object> iterable(
438       final Object first, final Object second, final Object[] rest) {
439     checkNotNull(rest);
440     return new AbstractList<Object>() {
441       @Override public int size() {
442         return rest.length + 2;
443       }
444 
445       @Override public Object get(int index) {
446         switch (index) {
447           case 0:
448             return first;
449           case 1:
450             return second;
451           default:
452             return rest[index - 2];
453         }
454       }
455     };
456   }
457 }