Coverage Summary for Class: Joiner (com.google.common.base)

Class Method, % Line, %
Joiner 52.2% (12/23) 65% (26/40)
Joiner$1 50% (2/4) 50% (2/4)
Joiner$2 0% (0/4) 0% (0/18)
Joiner$3 0% (0/3) 0% (0/6)
Joiner$MapJoiner 0% (0/12) 0% (0/31)
Total 30.4% (14/46) 28.3% (28/99)


1 /* 2  * Copyright (C) 2008 The Guava Authors 3  * 4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5  * in compliance with the License. You may obtain a copy of the License at 6  * 7  * http://www.apache.org/licenses/LICENSE-2.0 8  * 9  * Unless required by applicable law or agreed to in writing, software distributed under the License 10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11  * or implied. See the License for the specific language governing permissions and limitations under 12  * the License. 13  */ 14  15 package com.google.common.base; 16  17 import static com.google.common.base.Preconditions.checkNotNull; 18 import static java.util.Objects.requireNonNull; 19  20 import com.google.common.annotations.Beta; 21 import com.google.common.annotations.GwtCompatible; 22 import com.google.errorprone.annotations.CanIgnoreReturnValue; 23 import java.io.IOException; 24 import java.util.AbstractList; 25 import java.util.Arrays; 26 import java.util.Iterator; 27 import java.util.Map; 28 import java.util.Map.Entry; 29 import javax.annotation.CheckForNull; 30 import org.checkerframework.checker.nullness.qual.Nullable; 31  32 /** 33  * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a 34  * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns 35  * them as a {@link String}. Example: 36  * 37  * <pre>{@code 38  * Joiner joiner = Joiner.on("; ").skipNulls(); 39  * . . . 40  * return joiner.join("Harry", null, "Ron", "Hermione"); 41  * }</pre> 42  * 43  * <p>This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are 44  * converted to strings using {@link Object#toString()} before being appended. 45  * 46  * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining 47  * methods will throw {@link NullPointerException} if any given element is null. 48  * 49  * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code 50  * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner 51  * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code 52  * static final} constants. 53  * 54  * <pre>{@code 55  * // Bad! Do not do this! 56  * Joiner joiner = Joiner.on(','); 57  * joiner.skipNulls(); // does nothing! 58  * return joiner.join("wrong", null, "wrong"); 59  * }</pre> 60  * 61  * <p>See the Guava User Guide article on <a 62  * href="https://github.com/google/guava/wiki/StringsExplained#joiner">{@code Joiner}</a>. 63  * 64  * @author Kevin Bourrillion 65  * @since 2.0 66  */ 67 @GwtCompatible 68 @ElementTypesAreNonnullByDefault 69 public class Joiner { 70  /** Returns a joiner which automatically places {@code separator} between consecutive elements. */ 71  public static Joiner on(String separator) { 72  return new Joiner(separator); 73  } 74  75  /** Returns a joiner which automatically places {@code separator} between consecutive elements. */ 76  public static Joiner on(char separator) { 77  return new Joiner(String.valueOf(separator)); 78  } 79  80  private final String separator; 81  82  private Joiner(String separator) { 83  this.separator = checkNotNull(separator); 84  } 85  86  private Joiner(Joiner prototype) { 87  this.separator = prototype.separator; 88  } 89  90  /* 91  * In this file, we use <? extends @Nullable Object> instead of <?> to work around a Kotlin bug 92  * (see b/189937072 until we file a bug against Kotlin itself). (The two should be equivalent, so 93  * we normally prefer the shorter one.) 94  */ 95  96  /** 97  * Appends the string representation of each of {@code parts}, using the previously configured 98  * separator between each, to {@code appendable}. 99  */ 100  @CanIgnoreReturnValue 101  public <A extends Appendable> A appendTo(A appendable, Iterable<? extends @Nullable Object> parts) 102  throws IOException { 103  return appendTo(appendable, parts.iterator()); 104  } 105  106  /** 107  * Appends the string representation of each of {@code parts}, using the previously configured 108  * separator between each, to {@code appendable}. 109  * 110  * @since 11.0 111  */ 112  @CanIgnoreReturnValue 113  public <A extends Appendable> A appendTo(A appendable, Iterator<? extends @Nullable Object> parts) 114  throws IOException { 115  checkNotNull(appendable); 116  if (parts.hasNext()) { 117  appendable.append(toString(parts.next())); 118  while (parts.hasNext()) { 119  appendable.append(separator); 120  appendable.append(toString(parts.next())); 121  } 122  } 123  return appendable; 124  } 125  126  /** 127  * Appends the string representation of each of {@code parts}, using the previously configured 128  * separator between each, to {@code appendable}. 129  */ 130  @CanIgnoreReturnValue 131  public final <A extends Appendable> A appendTo(A appendable, @Nullable Object[] parts) 132  throws IOException { 133  return appendTo(appendable, Arrays.asList(parts)); 134  } 135  136  /** Appends to {@code appendable} the string representation of each of the remaining arguments. */ 137  @CanIgnoreReturnValue 138  public final <A extends Appendable> A appendTo( 139  A appendable, 140  @CheckForNull Object first, 141  @CheckForNull Object second, 142  @Nullable Object... rest) 143  throws IOException { 144  return appendTo(appendable, iterable(first, second, rest)); 145  } 146  147  /** 148  * Appends the string representation of each of {@code parts}, using the previously configured 149  * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 150  * Iterable)}, except that it does not throw {@link IOException}. 151  */ 152  @CanIgnoreReturnValue 153  public final StringBuilder appendTo( 154  StringBuilder builder, Iterable<? extends @Nullable Object> parts) { 155  return appendTo(builder, parts.iterator()); 156  } 157  158  /** 159  * Appends the string representation of each of {@code parts}, using the previously configured 160  * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 161  * Iterable)}, except that it does not throw {@link IOException}. 162  * 163  * @since 11.0 164  */ 165  @CanIgnoreReturnValue 166  public final StringBuilder appendTo( 167  StringBuilder builder, Iterator<? extends @Nullable Object> parts) { 168  try { 169  appendTo((Appendable) builder, parts); 170  } catch (IOException impossible) { 171  throw new AssertionError(impossible); 172  } 173  return builder; 174  } 175  176  /** 177  * Appends the string representation of each of {@code parts}, using the previously configured 178  * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 179  * Iterable)}, except that it does not throw {@link IOException}. 180  */ 181  @CanIgnoreReturnValue 182  public final StringBuilder appendTo(StringBuilder builder, @Nullable Object[] parts) { 183  return appendTo(builder, Arrays.asList(parts)); 184  } 185  186  /** 187  * Appends to {@code builder} the string representation of each of the remaining arguments. 188  * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not 189  * throw {@link IOException}. 190  */ 191  @CanIgnoreReturnValue 192  public final StringBuilder appendTo( 193  StringBuilder builder, 194  @CheckForNull Object first, 195  @CheckForNull Object second, 196  @Nullable Object... rest) { 197  return appendTo(builder, iterable(first, second, rest)); 198  } 199  200  /** 201  * Returns a string containing the string representation of each of {@code parts}, using the 202  * previously configured separator between each. 203  */ 204  public final String join(Iterable<? extends @Nullable Object> parts) { 205  return join(parts.iterator()); 206  } 207  208  /** 209  * Returns a string containing the string representation of each of {@code parts}, using the 210  * previously configured separator between each. 211  * 212  * @since 11.0 213  */ 214  public final String join(Iterator<? extends @Nullable Object> parts) { 215  return appendTo(new StringBuilder(), parts).toString(); 216  } 217  218  /** 219  * Returns a string containing the string representation of each of {@code parts}, using the 220  * previously configured separator between each. 221  */ 222  public final String join(@Nullable Object[] parts) { 223  return join(Arrays.asList(parts)); 224  } 225  226  /** 227  * Returns a string containing the string representation of each argument, using the previously 228  * configured separator between each. 229  */ 230  public final String join( 231  @CheckForNull Object first, @CheckForNull Object second, @Nullable Object... rest) { 232  return join(iterable(first, second, rest)); 233  } 234  235  /** 236  * Returns a joiner with the same behavior as this one, except automatically substituting {@code 237  * nullText} for any provided null elements. 238  */ 239  public Joiner useForNull(final String nullText) { 240  checkNotNull(nullText); 241  return new Joiner(this) { 242  @Override 243  CharSequence toString(@CheckForNull Object part) { 244  return (part == null) ? nullText : Joiner.this.toString(part); 245  } 246  247  @Override 248  public Joiner useForNull(String nullText) { 249  throw new UnsupportedOperationException("already specified useForNull"); 250  } 251  252  @Override 253  public Joiner skipNulls() { 254  throw new UnsupportedOperationException("already specified useForNull"); 255  } 256  }; 257  } 258  259  /** 260  * Returns a joiner with the same behavior as this joiner, except automatically skipping over any 261  * provided null elements. 262  */ 263  public Joiner skipNulls() { 264  return new Joiner(this) { 265  @Override 266  public <A extends Appendable> A appendTo( 267  A appendable, Iterator<? extends @Nullable Object> parts) throws IOException { 268  checkNotNull(appendable, "appendable"); 269  checkNotNull(parts, "parts"); 270  while (parts.hasNext()) { 271  Object part = parts.next(); 272  if (part != null) { 273  appendable.append(Joiner.this.toString(part)); 274  break; 275  } 276  } 277  while (parts.hasNext()) { 278  Object part = parts.next(); 279  if (part != null) { 280  appendable.append(separator); 281  appendable.append(Joiner.this.toString(part)); 282  } 283  } 284  return appendable; 285  } 286  287  @Override 288  public Joiner useForNull(String nullText) { 289  throw new UnsupportedOperationException("already specified skipNulls"); 290  } 291  292  @Override 293  public MapJoiner withKeyValueSeparator(String kvs) { 294  throw new UnsupportedOperationException("can't use .skipNulls() with maps"); 295  } 296  }; 297  } 298  299  /** 300  * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 301  * this {@code Joiner} otherwise. 302  * 303  * @since 20.0 304  */ 305  public MapJoiner withKeyValueSeparator(char keyValueSeparator) { 306  return withKeyValueSeparator(String.valueOf(keyValueSeparator)); 307  } 308  309  /** 310  * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 311  * this {@code Joiner} otherwise. 312  */ 313  public MapJoiner withKeyValueSeparator(String keyValueSeparator) { 314  return new MapJoiner(this, keyValueSeparator); 315  } 316  317  /** 318  * An object that joins map entries in the same manner as {@code Joiner} joins iterables and 319  * arrays. Like {@code Joiner}, it is thread-safe and immutable. 320  * 321  * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code 322  * Multimap} entries in two distinct modes: 323  * 324  * <ul> 325  * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a 326  * {@code MapJoiner} method that accepts entries as input, and receive output of the form 327  * {@code key1=A&key1=B&key2=C}. 328  * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code 329  * MapJoiner} method that accepts a map as input, and receive output of the form {@code 330  * key1=[A, B]&key2=C}. 331  * </ul> 332  * 333  * @since 2.0 334  */ 335  public static final class MapJoiner { 336  private final Joiner joiner; 337  private final String keyValueSeparator; 338  339  private MapJoiner(Joiner joiner, String keyValueSeparator) { 340  this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull 341  this.keyValueSeparator = checkNotNull(keyValueSeparator); 342  } 343  344  /** 345  * Appends the string representation of each entry of {@code map}, using the previously 346  * configured separator and key-value separator, to {@code appendable}. 347  */ 348  @CanIgnoreReturnValue 349  public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException { 350  return appendTo(appendable, map.entrySet()); 351  } 352  353  /** 354  * Appends the string representation of each entry of {@code map}, using the previously 355  * configured separator and key-value separator, to {@code builder}. Identical to {@link 356  * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. 357  */ 358  @CanIgnoreReturnValue 359  public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) { 360  return appendTo(builder, map.entrySet()); 361  } 362  363  /** 364  * Appends the string representation of each entry in {@code entries}, using the previously 365  * configured separator and key-value separator, to {@code appendable}. 366  * 367  * @since 10.0 368  */ 369  @Beta 370  @CanIgnoreReturnValue 371  public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries) 372  throws IOException { 373  return appendTo(appendable, entries.iterator()); 374  } 375  376  /** 377  * Appends the string representation of each entry in {@code entries}, using the previously 378  * configured separator and key-value separator, to {@code appendable}. 379  * 380  * @since 11.0 381  */ 382  @Beta 383  @CanIgnoreReturnValue 384  public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts) 385  throws IOException { 386  checkNotNull(appendable); 387  if (parts.hasNext()) { 388  Entry<?, ?> entry = parts.next(); 389  appendable.append(joiner.toString(entry.getKey())); 390  appendable.append(keyValueSeparator); 391  appendable.append(joiner.toString(entry.getValue())); 392  while (parts.hasNext()) { 393  appendable.append(joiner.separator); 394  Entry<?, ?> e = parts.next(); 395  appendable.append(joiner.toString(e.getKey())); 396  appendable.append(keyValueSeparator); 397  appendable.append(joiner.toString(e.getValue())); 398  } 399  } 400  return appendable; 401  } 402  403  /** 404  * Appends the string representation of each entry in {@code entries}, using the previously 405  * configured separator and key-value separator, to {@code builder}. Identical to {@link 406  * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 407  * 408  * @since 10.0 409  */ 410  @Beta 411  @CanIgnoreReturnValue 412  public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) { 413  return appendTo(builder, entries.iterator()); 414  } 415  416  /** 417  * Appends the string representation of each entry in {@code entries}, using the previously 418  * configured separator and key-value separator, to {@code builder}. Identical to {@link 419  * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 420  * 421  * @since 11.0 422  */ 423  @Beta 424  @CanIgnoreReturnValue 425  public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) { 426  try { 427  appendTo((Appendable) builder, entries); 428  } catch (IOException impossible) { 429  throw new AssertionError(impossible); 430  } 431  return builder; 432  } 433  434  /** 435  * Returns a string containing the string representation of each entry of {@code map}, using the 436  * previously configured separator and key-value separator. 437  */ 438  public String join(Map<?, ?> map) { 439  return join(map.entrySet()); 440  } 441  442  /** 443  * Returns a string containing the string representation of each entry in {@code entries}, using 444  * the previously configured separator and key-value separator. 445  * 446  * @since 10.0 447  */ 448  @Beta 449  public String join(Iterable<? extends Entry<?, ?>> entries) { 450  return join(entries.iterator()); 451  } 452  453  /** 454  * Returns a string containing the string representation of each entry in {@code entries}, using 455  * the previously configured separator and key-value separator. 456  * 457  * @since 11.0 458  */ 459  @Beta 460  public String join(Iterator<? extends Entry<?, ?>> entries) { 461  return appendTo(new StringBuilder(), entries).toString(); 462  } 463  464  /** 465  * Returns a map joiner with the same behavior as this one, except automatically substituting 466  * {@code nullText} for any provided null keys or values. 467  */ 468  public MapJoiner useForNull(String nullText) { 469  return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); 470  } 471  } 472  473  CharSequence toString(@CheckForNull Object part) { 474  /* 475  * requireNonNull is not safe: Joiner.on(...).join(somethingThatContainsNull) will indeed throw. 476  * However, Joiner.on(...).useForNull(...).join(somethingThatContainsNull) *is* safe -- because 477  * it returns a subclass of Joiner that overrides this method to tolerate null inputs. 478  * 479  * Unfortunately, we don't distinguish between these two cases in our public API: Joiner.on(...) 480  * and Joiner.on(...).useForNull(...) both declare the same return type: plain Joiner. To ensure 481  * that users *can* pass null arguments to Joiner, we annotate it as if it always tolerates null 482  * inputs, rather than as if it never tolerates them. 483  * 484  * We rely on checkers to implement special cases to catch dangerous calls to join(), etc. based 485  * on what they know about the particular Joiner instances the calls are performed on. 486  * 487  * (In addition to useForNull, we also offer skipNulls. It, too, tolerates null inputs, but its 488  * tolerance is implemented differently: Its implementation avoids calling this toString(Object) 489  * method in the first place.) 490  */ 491  requireNonNull(part); 492  return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); 493  } 494  495  private static Iterable<@Nullable Object> iterable( 496  @CheckForNull final Object first, 497  @CheckForNull final Object second, 498  final @Nullable Object[] rest) { 499  checkNotNull(rest); 500  return new AbstractList<@Nullable Object>() { 501  @Override 502  public int size() { 503  return rest.length + 2; 504  } 505  506  @Override 507  @CheckForNull 508  public Object get(int index) { 509  switch (index) { 510  case 0: 511  return first; 512  case 1: 513  return second; 514  default: 515  return rest[index - 2]; 516  } 517  } 518  }; 519  } 520 }