001    /*
002     * Copyright (C) 2008 Google Inc.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005     * in compliance with the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License
010     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011     * or implied. See the License for the specific language governing permissions and limitations under
012     * the License.
013     */
014    
015    package com.google.common.base;
016    
017    import static com.google.common.base.Preconditions.checkNotNull;
018    
019    import com.google.common.annotations.GwtCompatible;
020    
021    import java.io.IOException;
022    import java.util.AbstractList;
023    import java.util.Arrays;
024    import java.util.Iterator;
025    import java.util.Map;
026    import java.util.Map.Entry;
027    
028    import javax.annotation.Nullable;
029    
030    /**
031     * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a
032     * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns
033     * them as a {@link String}. Example: <pre>   {@code
034     *
035     *   Joiner joiner = Joiner.on("; ").skipNulls();
036     *    . . .
037     *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
038     *
039     * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are
040     * converted to strings using {@link Object#toString()} before being appended.
041     *
042     * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining
043     * methods will throw {@link NullPointerException} if any given element is null.
044     *
045     * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code
046     * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner
047     * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code
048     * static final} constants. <pre>   {@code
049     *
050     *   // Bad! Do not do this!
051     *   Joiner joiner = Joiner.on(',');
052     *   joiner.skipNulls(); // does nothing!
053     *   return joiner.join("wrong", null, "wrong");}</pre>
054     *
055     * @author Kevin Bourrillion
056     * @since 2 (imported from Google Collections Library)
057     */
058    @GwtCompatible
059    public class Joiner {
060      /**
061       * Returns a joiner which automatically places {@code separator} between consecutive elements.
062       */
063      public static Joiner on(String separator) {
064        return new Joiner(separator);
065      }
066    
067      /**
068       * Returns a joiner which automatically places {@code separator} between consecutive elements.
069       */
070      public static Joiner on(char separator) {
071        return new Joiner(String.valueOf(separator));
072      }
073    
074      private final String separator;
075    
076      private Joiner(String separator) {
077        this.separator = checkNotNull(separator);
078      }
079    
080      private Joiner(Joiner prototype) {
081        this.separator = prototype.separator;
082      }
083    
084      /**
085       * Appends the string representation of each of {@code parts}, using the previously configured
086       * separator between each, to {@code appendable}.
087       */
088      public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
089        checkNotNull(appendable);
090        Iterator<?> iterator = parts.iterator();
091        if (iterator.hasNext()) {
092          appendable.append(toString(iterator.next()));
093          while (iterator.hasNext()) {
094            appendable.append(separator);
095            appendable.append(toString(iterator.next()));
096          }
097        }
098        return appendable;
099      }
100    
101      /**
102       * Appends the string representation of each of {@code parts}, using the previously configured
103       * separator between each, to {@code appendable}.
104       */
105      public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
106        return appendTo(appendable, Arrays.asList(parts));
107      }
108    
109      /**
110       * Appends to {@code appendable} the string representation of each of the remaining arguments.
111       */
112      public final <A extends Appendable> A appendTo(
113          A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
114              throws IOException {
115        return appendTo(appendable, iterable(first, second, rest));
116      }
117    
118      /**
119       * Appends the string representation of each of {@code parts}, using the previously configured
120       * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
121       * Iterable)}, except that it does not throw {@link IOException}.
122       */
123      public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
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 previously configured
134       * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
135       * Iterable)}, except that it does not throw {@link IOException}.
136       */
137      public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
138        return appendTo(builder, Arrays.asList(parts));
139      }
140    
141      /**
142       * Appends to {@code builder} the string representation of each of the remaining arguments.
143       * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
144       * throw {@link IOException}.
145       */
146      public final StringBuilder appendTo(
147          StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
148        return appendTo(builder, iterable(first, second, rest));
149      }
150    
151      /**
152       * Returns a string containing the string representation of each of {@code parts}, using the
153       * previously configured separator between each.
154       */
155      public final String join(Iterable<?> parts) {
156        return appendTo(new StringBuilder(), parts).toString();
157      }
158    
159      /**
160       * Returns a string containing the string representation of each of {@code parts}, using the
161       * previously configured separator between each.
162       */
163      public final String join(Object[] parts) {
164        return join(Arrays.asList(parts));
165      }
166    
167      /**
168       * Returns a string containing the string representation of each argument, using the previously
169       * configured separator between each.
170       */
171      public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
172        return join(iterable(first, second, rest));
173      }
174    
175      /**
176       * Returns a joiner with the same behavior as this one, except automatically substituting {@code
177       * nullText} for any provided null elements.
178       */
179      public Joiner useForNull(final String nullText) {
180        checkNotNull(nullText);
181        return new Joiner(this) {
182          @Override CharSequence toString(Object part) {
183            return (part == null) ? nullText : Joiner.this.toString(part);
184          }
185    
186          @Override public Joiner useForNull(String nullText) {
187            checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
188            throw new UnsupportedOperationException("already specified useForNull");
189          }
190    
191          @Override public Joiner skipNulls() {
192            throw new UnsupportedOperationException("already specified useForNull");
193          }
194        };
195      }
196    
197      /**
198       * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
199       * provided null elements.
200       */
201      public Joiner skipNulls() {
202        return new Joiner(this) {
203          @Override public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
204              throws IOException {
205            checkNotNull(appendable, "appendable");
206            checkNotNull(parts, "parts");
207            Iterator<?> iterator = parts.iterator();
208            while (iterator.hasNext()) {
209              Object part = iterator.next();
210              if (part != null) {
211                appendable.append(Joiner.this.toString(part));
212                break;
213              }
214            }
215            while (iterator.hasNext()) {
216              Object part = iterator.next();
217              if (part != null) {
218                appendable.append(separator);
219                appendable.append(Joiner.this.toString(part));
220              }
221            }
222            return appendable;
223          }
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    
230          @Override public MapJoiner withKeyValueSeparator(String kvs) {
231            checkNotNull(kvs); // weird: just to satisfy NullPointerTester.
232            throw new UnsupportedOperationException("can't use .skipNulls() with maps");
233          }
234        };
235      }
236    
237      /**
238       * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
239       * 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 iterables and
247       * arrays. Like {@code Joiner}, it is thread-safe and immutable.
248       */
249      public static class MapJoiner {
250        private final Joiner joiner;
251        private final 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 previously
260         * configured separator and key-value separator, to {@code appendable}.
261         */
262        public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
263          checkNotNull(appendable);
264          Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator();
265          if (iterator.hasNext()) {
266            Entry<?, ?> entry = iterator.next();
267            appendable.append(joiner.toString(entry.getKey()));
268            appendable.append(keyValueSeparator);
269            appendable.append(joiner.toString(entry.getValue()));
270            while (iterator.hasNext()) {
271              appendable.append(joiner.separator);
272              Entry<?, ?> e = iterator.next();
273              appendable.append(joiner.toString(e.getKey()));
274              appendable.append(keyValueSeparator);
275              appendable.append(joiner.toString(e.getValue()));
276            }
277          }
278          return appendable;
279        }
280    
281        /**
282         * Appends the string representation of each entry of {@code map}, using the previously
283         * configured separator and key-value separator, to {@code builder}. Identical to {@link
284         * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
285         */
286        public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
287          try {
288            appendTo((Appendable) builder, map);
289          } catch (IOException impossible) {
290            throw new AssertionError(impossible);
291          }
292          return builder;
293        }
294    
295        /**
296         * Returns a string containing the string representation of each entry of {@code map}, using the
297         * previously configured separator and key-value separator.
298         */
299        public String join(Map<?, ?> map) {
300          return appendTo(new StringBuilder(), map).toString();
301        }
302    
303        /**
304         * Returns a map joiner with the same behavior as this one, except automatically substituting
305         * {@code nullText} for any provided null keys or values.
306         */
307        public MapJoiner useForNull(String nullText) {
308          return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
309        }
310      }
311    
312      CharSequence toString(Object part) {
313        return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
314      }
315    
316      private static Iterable<Object> iterable(
317          final Object first, final Object second, final Object[] rest) {
318        checkNotNull(rest);
319        return new AbstractList<Object>() {
320          @Override public int size() {
321            return rest.length + 2;
322          }
323    
324          @Override public Object get(int index) {
325            switch (index) {
326              case 0:
327                return first;
328              case 1:
329                return second;
330              default:
331                return rest[index - 2];
332            }
333          }
334        };
335      }
336    }