    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    }