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    
017    package com.google.common.base;
018    
019    import static com.google.common.base.Preconditions.checkNotNull;
020    
021    import com.google.common.annotations.Beta;
022    import com.google.common.annotations.GwtCompatible;
023    
024    import java.io.IOException;
025    import java.util.AbstractList;
026    import java.util.Arrays;
027    import java.util.Iterator;
028    import java.util.Map;
029    import java.util.Map.Entry;
030    
031    import javax.annotation.CheckReturnValue;
032    import 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     * 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     * @author Kevin Bourrillion
060     * @since 2.0 (imported from Google Collections Library)
061     */
062    @GwtCompatible
063    public class Joiner {
064      /**
065       * Returns a joiner which automatically places {@code separator} between consecutive elements.
066       */
067      public static Joiner on(String separator) {
068        return new Joiner(separator);
069      }
070    
071      /**
072       * Returns a joiner which automatically places {@code separator} between consecutive elements.
073       */
074      public static Joiner on(char separator) {
075        return new Joiner(String.valueOf(separator));
076      }
077    
078      private final String separator;
079    
080      private Joiner(String separator) {
081        this.separator = checkNotNull(separator);
082      }
083    
084      private Joiner(Joiner prototype) {
085        this.separator = prototype.separator;
086      }
087    
088      /**
089       * Appends the string representation of each of {@code parts}, using the previously configured
090       * separator between each, to {@code appendable}.
091       */
092      public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
093        checkNotNull(appendable);
094        Iterator<?> iterator = parts.iterator();
095        if (iterator.hasNext()) {
096          appendable.append(toString(iterator.next()));
097          while (iterator.hasNext()) {
098            appendable.append(separator);
099            appendable.append(toString(iterator.next()));
100          }
101        }
102        return appendable;
103      }
104    
105      /**
106       * Appends the string representation of each of {@code parts}, using the previously configured
107       * separator between each, to {@code appendable}.
108       */
109      public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
110        return appendTo(appendable, Arrays.asList(parts));
111      }
112    
113      /**
114       * Appends to {@code appendable} the string representation of each of the remaining arguments.
115       */
116      public final <A extends Appendable> A appendTo(
117          A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
118              throws IOException {
119        return appendTo(appendable, iterable(first, second, rest));
120      }
121    
122      /**
123       * Appends the string representation of each of {@code parts}, using the previously configured
124       * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
125       * Iterable)}, except that it does not throw {@link IOException}.
126       */
127      public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
128        try {
129          appendTo((Appendable) builder, parts);
130        } catch (IOException impossible) {
131          throw new AssertionError(impossible);
132        }
133        return builder;
134      }
135    
136      /**
137       * Appends the string representation of each of {@code parts}, using the previously configured
138       * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
139       * Iterable)}, except that it does not throw {@link IOException}.
140       */
141      public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
142        return appendTo(builder, Arrays.asList(parts));
143      }
144    
145      /**
146       * Appends to {@code builder} the string representation of each of the remaining arguments.
147       * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
148       * throw {@link IOException}.
149       */
150      public final StringBuilder appendTo(
151          StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
152        return appendTo(builder, iterable(first, second, rest));
153      }
154    
155      /**
156       * Returns a string containing the string representation of each of {@code parts}, using the
157       * previously configured separator between each.
158       */
159      public final String join(Iterable<?> parts) {
160        return appendTo(new StringBuilder(), parts).toString();
161      }
162    
163      /**
164       * Returns a string containing the string representation of each of {@code parts}, using the
165       * previously configured separator between each.
166       */
167      public final String join(Object[] parts) {
168        return join(Arrays.asList(parts));
169      }
170    
171      /**
172       * Returns a string containing the string representation of each argument, using the previously
173       * configured separator between each.
174       */
175      public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
176        return join(iterable(first, second, rest));
177      }
178    
179      /**
180       * Returns a joiner with the same behavior as this one, except automatically substituting {@code
181       * nullText} for any provided null elements.
182       */
183      @CheckReturnValue
184      public Joiner useForNull(final String nullText) {
185        checkNotNull(nullText);
186        return new Joiner(this) {
187          @Override CharSequence toString(Object part) {
188            return (part == null) ? nullText : Joiner.this.toString(part);
189          }
190    
191          @Override public Joiner useForNull(String nullText) {
192            checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
193            throw new UnsupportedOperationException("already specified useForNull");
194          }
195    
196          @Override public Joiner skipNulls() {
197            throw new UnsupportedOperationException("already specified useForNull");
198          }
199        };
200      }
201    
202      /**
203       * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
204       * provided null elements.
205       */
206      @CheckReturnValue
207      public Joiner skipNulls() {
208        return new Joiner(this) {
209          @Override public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
210              throws IOException {
211            checkNotNull(appendable, "appendable");
212            checkNotNull(parts, "parts");
213            Iterator<?> iterator = parts.iterator();
214            while (iterator.hasNext()) {
215              Object part = iterator.next();
216              if (part != null) {
217                appendable.append(Joiner.this.toString(part));
218                break;
219              }
220            }
221            while (iterator.hasNext()) {
222              Object part = iterator.next();
223              if (part != null) {
224                appendable.append(separator);
225                appendable.append(Joiner.this.toString(part));
226              }
227            }
228            return appendable;
229          }
230    
231          @Override public Joiner useForNull(String nullText) {
232            checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
233            throw new UnsupportedOperationException("already specified skipNulls");
234          }
235    
236          @Override public MapJoiner withKeyValueSeparator(String kvs) {
237            checkNotNull(kvs); // weird: just to satisfy NullPointerTester.
238            throw new UnsupportedOperationException("can't use .skipNulls() with maps");
239          }
240        };
241      }
242    
243      /**
244       * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
245       * this {@code Joiner} otherwise.
246       */
247      @CheckReturnValue
248      public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
249        return new MapJoiner(this, keyValueSeparator);
250      }
251    
252      /**
253       * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
254       * arrays. Like {@code Joiner}, it is thread-safe and immutable.
255       *
256       * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code
257       * Multimap} entries in two distinct modes:
258       *
259       * <ul>
260       * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a
261       *     {@code MapJoiner} method that accepts entries as input, and receive output of the form
262       *     {@code key1=A&key1=B&key2=C}.
263       * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner}
264       *     method that accepts a map as input, and receive output of the form {@code
265       *     key1=[A, B]&key2=C}.
266       * </ul>
267       *
268       * @since 2.0 (imported from Google Collections Library)
269       */
270      public final static class MapJoiner {
271        private final Joiner joiner;
272        private final String keyValueSeparator;
273    
274        private MapJoiner(Joiner joiner, String keyValueSeparator) {
275          this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
276          this.keyValueSeparator = checkNotNull(keyValueSeparator);
277        }
278    
279        /**
280         * Appends the string representation of each entry of {@code map}, using the previously
281         * configured separator and key-value separator, to {@code appendable}.
282         */
283        public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
284          return appendTo(appendable, map.entrySet());
285        }
286    
287        /**
288         * Appends the string representation of each entry of {@code map}, using the previously
289         * configured separator and key-value separator, to {@code builder}. Identical to {@link
290         * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
291         */
292        public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
293          return appendTo(builder, map.entrySet());
294        }
295    
296        /**
297         * Returns a string containing the string representation of each entry of {@code map}, using the
298         * previously configured separator and key-value separator.
299         */
300        public String join(Map<?, ?> map) {
301          return join(map.entrySet());
302        }
303    
304        /**
305         * Appends the string representation of each entry in {@code entries}, using the previously
306         * configured separator and key-value separator, to {@code appendable}.
307         *
308         * @since 10.0
309         */
310        @Beta
311        public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
312            throws IOException {
313          checkNotNull(appendable);
314          Iterator<? extends Map.Entry<?, ?>> iterator = entries.iterator();
315          if (iterator.hasNext()) {
316            Entry<?, ?> entry = iterator.next();
317            appendable.append(joiner.toString(entry.getKey()));
318            appendable.append(keyValueSeparator);
319            appendable.append(joiner.toString(entry.getValue()));
320            while (iterator.hasNext()) {
321              appendable.append(joiner.separator);
322              Entry<?, ?> e = iterator.next();
323              appendable.append(joiner.toString(e.getKey()));
324              appendable.append(keyValueSeparator);
325              appendable.append(joiner.toString(e.getValue()));
326            }
327          }
328          return appendable;
329        }
330    
331        /**
332         * Appends the string representation of each entry in {@code entries}, using the previously
333         * configured separator and key-value separator, to {@code builder}. Identical to {@link
334         * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
335         *
336         * @since 10.0
337         */
338        @Beta
339        public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
340          try {
341            appendTo((Appendable) builder, entries);
342          } catch (IOException impossible) {
343            throw new AssertionError(impossible);
344          }
345          return builder;
346        }
347    
348        /**
349         * Returns a string containing the string representation of each entry in {@code entries}, using
350         * the previously configured separator and key-value separator.
351         *
352         * @since 10.0
353         */
354        @Beta
355        public String join(Iterable<? extends Entry<?, ?>> entries) {
356          return appendTo(new StringBuilder(), entries).toString();
357        }
358    
359        /**
360         * Returns a map joiner with the same behavior as this one, except automatically substituting
361         * {@code nullText} for any provided null keys or values.
362         */
363        @CheckReturnValue
364        public MapJoiner useForNull(String nullText) {
365          return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
366        }
367      }
368    
369      CharSequence toString(Object part) {
370        checkNotNull(part);  // checkNotNull for GWT (do not optimize).
371        return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
372      }
373    
374      private static Iterable<Object> iterable(
375          final Object first, final Object second, final Object[] rest) {
376        checkNotNull(rest);
377        return new AbstractList<Object>() {
378          @Override public int size() {
379            return rest.length + 2;
380          }
381    
382          @Override public Object get(int index) {
383            switch (index) {
384              case 0:
385                return first;
386              case 1:
387                return second;
388              default:
389                return rest[index - 2];
390            }
391          }
392        };
393      }
394    }