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, 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       * @since 2 (imported from Google Collections Library)
250       */
251      public final static class MapJoiner {
252        private final Joiner joiner;
253        private final String keyValueSeparator;
254    
255        private MapJoiner(Joiner joiner, String keyValueSeparator) {
256          this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
257          this.keyValueSeparator = checkNotNull(keyValueSeparator);
258        }
259    
260        /**
261         * Appends the string representation of each entry of {@code map}, using the previously
262         * configured separator and key-value separator, to {@code appendable}.
263         */
264        public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
265          checkNotNull(appendable);
266          Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator();
267          if (iterator.hasNext()) {
268            Entry<?, ?> entry = iterator.next();
269            appendable.append(joiner.toString(entry.getKey()));
270            appendable.append(keyValueSeparator);
271            appendable.append(joiner.toString(entry.getValue()));
272            while (iterator.hasNext()) {
273              appendable.append(joiner.separator);
274              Entry<?, ?> e = iterator.next();
275              appendable.append(joiner.toString(e.getKey()));
276              appendable.append(keyValueSeparator);
277              appendable.append(joiner.toString(e.getValue()));
278            }
279          }
280          return appendable;
281        }
282    
283        /**
284         * Appends the string representation of each entry of {@code map}, using the previously
285         * configured separator and key-value separator, to {@code builder}. Identical to {@link
286         * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
287         */
288        public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
289          try {
290            appendTo((Appendable) builder, map);
291          } catch (IOException impossible) {
292            throw new AssertionError(impossible);
293          }
294          return builder;
295        }
296    
297        /**
298         * Returns a string containing the string representation of each entry of {@code map}, using the
299         * previously configured separator and key-value separator.
300         */
301        public String join(Map<?, ?> map) {
302          return appendTo(new StringBuilder(), map).toString();
303        }
304    
305        /**
306         * Returns a map joiner with the same behavior as this one, except automatically substituting
307         * {@code nullText} for any provided null keys or values.
308         */
309        public MapJoiner useForNull(String nullText) {
310          return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
311        }
312      }
313    
314      CharSequence toString(Object part) {
315        return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
316      }
317    
318      private static Iterable<Object> iterable(
319          final Object first, final Object second, final Object[] rest) {
320        checkNotNull(rest);
321        return new AbstractList<Object>() {
322          @Override public int size() {
323            return rest.length + 2;
324          }
325    
326          @Override public Object get(int index) {
327            switch (index) {
328              case 0:
329                return first;
330              case 1:
331                return second;
332              default:
333                return rest[index - 2];
334            }
335          }
336        };
337      }
338    }