Home | History | Annotate | Download | only in base
      1 /*
      2  * Copyright (C) 2008 Google Inc.
      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 com.google.common.annotations.GwtCompatible;
     20 import static com.google.common.base.Preconditions.checkNotNull;
     21 
     22 import java.io.IOException;
     23 import java.util.AbstractList;
     24 import java.util.Arrays;
     25 import java.util.Iterator;
     26 import java.util.Map;
     27 import java.util.Map.Entry;
     28 
     29 import javax.annotation.Nullable;
     30 
     31 /**
     32  * An object which joins pieces of text (specified as an array, {@link
     33  * Iterable}, varargs or even a {@link Map}) with a separator. It either
     34  * appends the results to an {@link Appendable} or returns them as a {@link
     35  * String}. Example: <pre>   {@code
     36  *
     37  *   Joiner joiner = Joiner.on("; ").skipNulls();
     38  *    . . .
     39  *   return joiner.join("Harry", null, "Ron", "Hermione");}</pre>
     40  *
     41  * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input
     42  * elements are converted to strings using {@link Object#toString()} before
     43  * being appended.
     44  *
     45  * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is
     46  * specified, the joining methods will throw {@link NullPointerException} if any
     47  * given element is null.
     48  *
     49  * @author Kevin Bourrillion
     50  * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
     51  */
     52 @GwtCompatible public class Joiner {
     53   /**
     54    * Returns a joiner which automatically places {@code separator} between
     55    * consecutive elements.
     56    */
     57   public static Joiner on(String separator) {
     58     return new Joiner(separator);
     59   }
     60 
     61   /**
     62    * Returns a joiner which automatically places {@code separator} between
     63    * consecutive elements.
     64    */
     65   public static Joiner on(char separator) {
     66     return new Joiner(String.valueOf(separator));
     67   }
     68 
     69   private final String separator;
     70 
     71   private Joiner(String separator) {
     72     this.separator = checkNotNull(separator);
     73   }
     74 
     75   private Joiner(Joiner prototype) {
     76     this.separator = prototype.separator;
     77   }
     78 
     79   /**
     80    * Appends the string representation of each of {@code parts}, using the
     81    * previously configured separator between each, to {@code appendable}.
     82    */
     83   public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
     84       throws IOException {
     85     checkNotNull(appendable);
     86     Iterator<?> iterator = parts.iterator();
     87     if (iterator.hasNext()) {
     88       appendable.append(toString(iterator.next()));
     89       while (iterator.hasNext()) {
     90         appendable.append(separator);
     91         appendable.append(toString(iterator.next()));
     92       }
     93     }
     94     return appendable;
     95   }
     96 
     97   /**
     98    * Appends the string representation of each of {@code parts}, using the
     99    * previously configured separator between each, to {@code appendable}.
    100    */
    101   public final <A extends Appendable> A appendTo(
    102       A appendable, Object[] parts) throws IOException {
    103     return appendTo(appendable, Arrays.asList(parts));
    104   }
    105 
    106   /**
    107    * Appends to {@code appendable} the string representation of each of the
    108    * remaining arguments.
    109    */
    110   public final <A extends Appendable> A appendTo(A appendable,
    111       @Nullable Object first, @Nullable Object second, Object... rest)
    112       throws IOException {
    113     return appendTo(appendable, iterable(first, second, rest));
    114   }
    115 
    116   /**
    117    * Appends the string representation of each of {@code parts}, using the
    118    * previously configured separator between each, to {@code builder}. Identical
    119    * to {@link #appendTo(Appendable, Iterable)}, except that it does not throw
    120    * {@link IOException}.
    121    */
    122   public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts)
    123   {
    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
    134    * previously configured separator between each, to {@code builder}. Identical
    135    * to {@link #appendTo(Appendable, Iterable)}, except that it does not throw
    136    * {@link IOException}.
    137    */
    138   public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
    139     return appendTo(builder, Arrays.asList(parts));
    140   }
    141 
    142   /**
    143    * Appends to {@code builder} the string representation of each of the
    144    * remaining arguments. Identical to {@link #appendTo(Appendable, Object,
    145    * Object, Object[])}, except that it does not throw {@link IOException}.
    146    */
    147   public final StringBuilder appendTo(StringBuilder builder,
    148       @Nullable Object first, @Nullable Object second, Object... rest) {
    149     return appendTo(builder, iterable(first, second, rest));
    150   }
    151 
    152   /**
    153    * Returns a string containing the string representation of each of {@code
    154    * parts}, using the previously configured separator between each.
    155    */
    156   public final String join(Iterable<?> parts) {
    157     return appendTo(new StringBuilder(), parts).toString();
    158   }
    159 
    160   /**
    161    * Returns a string containing the string representation of each of {@code
    162    * parts}, using the previously configured separator between each.
    163    */
    164   public final String join(Object[] parts) {
    165     return join(Arrays.asList(parts));
    166   }
    167 
    168   /**
    169    * Returns a string containing the string representation of each argument,
    170    * using the previously configured separator between each.
    171    */
    172   public final String join(
    173       @Nullable Object first, @Nullable Object second, Object... rest) {
    174     return join(iterable(first, second, rest));
    175   }
    176 
    177   /**
    178    * Returns a joiner with the same behavior as this one, except automatically
    179    * substituting {@code nullText} for any provided null elements.
    180    */
    181   public Joiner useForNull(final String nullText) {
    182     checkNotNull(nullText);
    183     return new Joiner(this) {
    184       @Override CharSequence toString(Object part) {
    185         return (part == null) ? nullText : Joiner.this.toString(part);
    186       }
    187       @Override public Joiner useForNull(String nullText) {
    188         checkNotNull(nullText); // weird, just to satisfy NullPointerTester!
    189         // TODO: fix that?
    190         throw new UnsupportedOperationException("already specified useForNull");
    191       }
    192       @Override public Joiner skipNulls() {
    193         throw new UnsupportedOperationException("already specified useForNull");
    194       }
    195     };
    196   }
    197 
    198   /**
    199    * Returns a joiner with the same behavior as this joiner, except
    200    * automatically skipping over any provided null elements.
    201    */
    202   public Joiner skipNulls() {
    203     return new Joiner(this) {
    204       @Override public <A extends Appendable> A appendTo(
    205           A appendable, Iterable<?> parts) throws IOException {
    206         checkNotNull(appendable, "appendable");
    207         checkNotNull(parts, "parts");
    208         Iterator<?> iterator = parts.iterator();
    209         while (iterator.hasNext()) {
    210           Object part = iterator.next();
    211           if (part != null) {
    212             appendable.append(Joiner.this.toString(part));
    213             break;
    214           }
    215         }
    216         while (iterator.hasNext()) {
    217           Object part = iterator.next();
    218           if (part != null) {
    219             appendable.append(separator);
    220             appendable.append(Joiner.this.toString(part));
    221           }
    222         }
    223         return appendable;
    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       @Override public MapJoiner withKeyValueSeparator(String kvs) {
    230         checkNotNull(kvs); // weird, just to satisfy NullPointerTester!
    231         throw new UnsupportedOperationException(
    232             "can't use .skipNulls() with maps");
    233       }
    234     };
    235   }
    236 
    237   /**
    238    * Returns a {@code MapJoiner} using the given key-value separator, and the
    239    * same configuration as 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
    247    * iterables and arrays.
    248    */
    249   public static class MapJoiner {
    250     private Joiner joiner;
    251     private 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
    260      * previously configured separator and key-value separator, to {@code
    261      * appendable}.
    262      */
    263     public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map)
    264         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
    285      * previously configured separator and key-value separator, to {@code
    286      * builder}. Identical to {@link #appendTo(Appendable, Map)}, except that it
    287      * does not throw {@link IOException}.
    288      */
    289     public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
    290       try {
    291         appendTo((Appendable) builder, map);
    292       } catch (IOException impossible) {
    293         throw new AssertionError(impossible);
    294       }
    295       return builder;
    296     }
    297 
    298     /**
    299      * Returns a string containing the string representation of each entry of
    300      * {@code map}, using the previously configured separator and key-value
    301      * separator.
    302      */
    303     public String join(Map<?, ?> map) {
    304       return appendTo(new StringBuilder(), map).toString();
    305     }
    306 
    307     /**
    308      * Returns a map joiner with the same behavior as this one, except
    309      * automatically substituting {@code nullText} for any provided null keys or
    310      * values.
    311      */
    312     public MapJoiner useForNull(String nullText) {
    313       return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
    314     }
    315   }
    316 
    317   CharSequence toString(Object part) {
    318     return (part instanceof CharSequence)
    319         ? (CharSequence) part
    320         : part.toString();
    321   }
    322 
    323   private static Iterable<Object> iterable(
    324       final Object first, final Object second, final Object[] rest) {
    325     checkNotNull(rest);
    326     return new AbstractList<Object>() {
    327       @Override public int size() {
    328         return rest.length + 2;
    329       }
    330       @Override public Object get(int index) {
    331         switch (index) {
    332           case 0:
    333             return first;
    334           case 1:
    335             return second;
    336           default:
    337             return rest[index - 2];
    338         }
    339       }
    340     };
    341   }
    342 }
    343