001/* 002 * Copyright (C) 2008 The Guava Authors 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 015package com.google.common.base; 016 017import static com.google.common.base.Preconditions.checkNotNull; 018 019import com.google.common.annotations.Beta; 020import com.google.common.annotations.GwtCompatible; 021import com.google.errorprone.annotations.CanIgnoreReturnValue; 022import java.io.IOException; 023import java.util.AbstractList; 024import java.util.Arrays; 025import java.util.Iterator; 026import java.util.Map; 027import java.util.Map.Entry; 028import org.checkerframework.checker.nullness.compatqual.NullableDecl; 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: 034 * 035 * <pre>{@code 036 * Joiner joiner = Joiner.on("; ").skipNulls(); 037 * . . . 038 * return joiner.join("Harry", null, "Ron", "Hermione"); 039 * }</pre> 040 * 041 * <p>This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are 042 * converted to strings using {@link Object#toString()} before being appended. 043 * 044 * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining 045 * methods will throw {@link NullPointerException} if any given element is null. 046 * 047 * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code 048 * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner 049 * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code 050 * static final} constants. 051 * 052 * <pre>{@code 053 * // Bad! Do not do this! 054 * Joiner joiner = Joiner.on(','); 055 * joiner.skipNulls(); // does nothing! 056 * return joiner.join("wrong", null, "wrong"); 057 * }</pre> 058 * 059 * <p>See the Guava User Guide article on <a 060 * href="https://github.com/google/guava/wiki/StringsExplained#joiner">{@code Joiner}</a>. 061 * 062 * @author Kevin Bourrillion 063 * @since 2.0 064 */ 065@GwtCompatible 066public class Joiner { 067 /** Returns a joiner which automatically places {@code separator} between consecutive elements. */ 068 public static Joiner on(String separator) { 069 return new Joiner(separator); 070 } 071 072 /** Returns a joiner which automatically places {@code separator} between consecutive elements. */ 073 public static Joiner on(char separator) { 074 return new Joiner(String.valueOf(separator)); 075 } 076 077 private final String separator; 078 079 private Joiner(String separator) { 080 this.separator = checkNotNull(separator); 081 } 082 083 private Joiner(Joiner prototype) { 084 this.separator = prototype.separator; 085 } 086 087 /** 088 * Appends the string representation of each of {@code parts}, using the previously configured 089 * separator between each, to {@code appendable}. 090 */ 091 @CanIgnoreReturnValue 092 public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException { 093 return appendTo(appendable, parts.iterator()); 094 } 095 096 /** 097 * Appends the string representation of each of {@code parts}, using the previously configured 098 * separator between each, to {@code appendable}. 099 * 100 * @since 11.0 101 */ 102 @CanIgnoreReturnValue 103 public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException { 104 checkNotNull(appendable); 105 if (parts.hasNext()) { 106 appendable.append(toString(parts.next())); 107 while (parts.hasNext()) { 108 appendable.append(separator); 109 appendable.append(toString(parts.next())); 110 } 111 } 112 return appendable; 113 } 114 115 /** 116 * Appends the string representation of each of {@code parts}, using the previously configured 117 * separator between each, to {@code appendable}. 118 */ 119 @CanIgnoreReturnValue 120 public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException { 121 return appendTo(appendable, Arrays.asList(parts)); 122 } 123 124 /** Appends to {@code appendable} the string representation of each of the remaining arguments. */ 125 @CanIgnoreReturnValue 126 public final <A extends Appendable> A appendTo( 127 A appendable, @NullableDecl Object first, @NullableDecl Object second, Object... rest) 128 throws IOException { 129 return appendTo(appendable, iterable(first, second, rest)); 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 @CanIgnoreReturnValue 138 public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) { 139 return appendTo(builder, parts.iterator()); 140 } 141 142 /** 143 * Appends the string representation of each of {@code parts}, using the previously configured 144 * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 145 * Iterable)}, except that it does not throw {@link IOException}. 146 * 147 * @since 11.0 148 */ 149 @CanIgnoreReturnValue 150 public final StringBuilder appendTo(StringBuilder builder, Iterator<?> parts) { 151 try { 152 appendTo((Appendable) builder, parts); 153 } catch (IOException impossible) { 154 throw new AssertionError(impossible); 155 } 156 return builder; 157 } 158 159 /** 160 * Appends the string representation of each of {@code parts}, using the previously configured 161 * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 162 * Iterable)}, except that it does not throw {@link IOException}. 163 */ 164 @CanIgnoreReturnValue 165 public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { 166 return appendTo(builder, Arrays.asList(parts)); 167 } 168 169 /** 170 * Appends to {@code builder} the string representation of each of the remaining arguments. 171 * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not 172 * throw {@link IOException}. 173 */ 174 @CanIgnoreReturnValue 175 public final StringBuilder appendTo( 176 StringBuilder builder, 177 @NullableDecl Object first, 178 @NullableDecl Object second, 179 Object... rest) { 180 return appendTo(builder, iterable(first, second, rest)); 181 } 182 183 /** 184 * Returns a string containing the string representation of each of {@code parts}, using the 185 * previously configured separator between each. 186 */ 187 public final String join(Iterable<?> parts) { 188 return join(parts.iterator()); 189 } 190 191 /** 192 * Returns a string containing the string representation of each of {@code parts}, using the 193 * previously configured separator between each. 194 * 195 * @since 11.0 196 */ 197 public final String join(Iterator<?> parts) { 198 return appendTo(new StringBuilder(), parts).toString(); 199 } 200 201 /** 202 * Returns a string containing the string representation of each of {@code parts}, using the 203 * previously configured separator between each. 204 */ 205 public final String join(Object[] parts) { 206 return join(Arrays.asList(parts)); 207 } 208 209 /** 210 * Returns a string containing the string representation of each argument, using the previously 211 * configured separator between each. 212 */ 213 public final String join( 214 @NullableDecl Object first, @NullableDecl Object second, Object... rest) { 215 return join(iterable(first, second, rest)); 216 } 217 218 /** 219 * Returns a joiner with the same behavior as this one, except automatically substituting {@code 220 * nullText} for any provided null elements. 221 */ 222 public Joiner useForNull(final String nullText) { 223 checkNotNull(nullText); 224 return new Joiner(this) { 225 @Override 226 CharSequence toString(@NullableDecl Object part) { 227 return (part == null) ? nullText : Joiner.this.toString(part); 228 } 229 230 @Override 231 public Joiner useForNull(String nullText) { 232 throw new UnsupportedOperationException("already specified useForNull"); 233 } 234 235 @Override 236 public Joiner skipNulls() { 237 throw new UnsupportedOperationException("already specified useForNull"); 238 } 239 }; 240 } 241 242 /** 243 * Returns a joiner with the same behavior as this joiner, except automatically skipping over any 244 * provided null elements. 245 */ 246 public Joiner skipNulls() { 247 return new Joiner(this) { 248 @Override 249 public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException { 250 checkNotNull(appendable, "appendable"); 251 checkNotNull(parts, "parts"); 252 while (parts.hasNext()) { 253 Object part = parts.next(); 254 if (part != null) { 255 appendable.append(Joiner.this.toString(part)); 256 break; 257 } 258 } 259 while (parts.hasNext()) { 260 Object part = parts.next(); 261 if (part != null) { 262 appendable.append(separator); 263 appendable.append(Joiner.this.toString(part)); 264 } 265 } 266 return appendable; 267 } 268 269 @Override 270 public Joiner useForNull(String nullText) { 271 throw new UnsupportedOperationException("already specified skipNulls"); 272 } 273 274 @Override 275 public MapJoiner withKeyValueSeparator(String kvs) { 276 throw new UnsupportedOperationException("can't use .skipNulls() with maps"); 277 } 278 }; 279 } 280 281 /** 282 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 283 * this {@code Joiner} otherwise. 284 * 285 * @since 20.0 286 */ 287 public MapJoiner withKeyValueSeparator(char keyValueSeparator) { 288 return withKeyValueSeparator(String.valueOf(keyValueSeparator)); 289 } 290 291 /** 292 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 293 * this {@code Joiner} otherwise. 294 */ 295 public MapJoiner withKeyValueSeparator(String keyValueSeparator) { 296 return new MapJoiner(this, keyValueSeparator); 297 } 298 299 /** 300 * An object that joins map entries in the same manner as {@code Joiner} joins iterables and 301 * arrays. Like {@code Joiner}, it is thread-safe and immutable. 302 * 303 * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code 304 * Multimap} entries in two distinct modes: 305 * 306 * <ul> 307 * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a 308 * {@code MapJoiner} method that accepts entries as input, and receive output of the form 309 * {@code key1=A&key1=B&key2=C}. 310 * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code 311 * MapJoiner} method that accepts a map as input, and receive output of the form {@code 312 * key1=[A, B]&key2=C}. 313 * </ul> 314 * 315 * @since 2.0 316 */ 317 public static final class MapJoiner { 318 private final Joiner joiner; 319 private final String keyValueSeparator; 320 321 private MapJoiner(Joiner joiner, String keyValueSeparator) { 322 this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull 323 this.keyValueSeparator = checkNotNull(keyValueSeparator); 324 } 325 326 /** 327 * Appends the string representation of each entry of {@code map}, using the previously 328 * configured separator and key-value separator, to {@code appendable}. 329 */ 330 @CanIgnoreReturnValue 331 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException { 332 return appendTo(appendable, map.entrySet()); 333 } 334 335 /** 336 * Appends the string representation of each entry of {@code map}, using the previously 337 * configured separator and key-value separator, to {@code builder}. Identical to {@link 338 * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. 339 */ 340 @CanIgnoreReturnValue 341 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) { 342 return appendTo(builder, map.entrySet()); 343 } 344 345 /** 346 * Returns a string containing the string representation of each entry of {@code map}, using the 347 * previously configured separator and key-value separator. 348 */ 349 public String join(Map<?, ?> map) { 350 return join(map.entrySet()); 351 } 352 353 /** 354 * Appends the string representation of each entry in {@code entries}, using the previously 355 * configured separator and key-value separator, to {@code appendable}. 356 * 357 * @since 10.0 358 */ 359 @Beta 360 @CanIgnoreReturnValue 361 public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries) 362 throws IOException { 363 return appendTo(appendable, entries.iterator()); 364 } 365 366 /** 367 * Appends the string representation of each entry in {@code entries}, using the previously 368 * configured separator and key-value separator, to {@code appendable}. 369 * 370 * @since 11.0 371 */ 372 @Beta 373 @CanIgnoreReturnValue 374 public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts) 375 throws IOException { 376 checkNotNull(appendable); 377 if (parts.hasNext()) { 378 Entry<?, ?> entry = parts.next(); 379 appendable.append(joiner.toString(entry.getKey())); 380 appendable.append(keyValueSeparator); 381 appendable.append(joiner.toString(entry.getValue())); 382 while (parts.hasNext()) { 383 appendable.append(joiner.separator); 384 Entry<?, ?> e = parts.next(); 385 appendable.append(joiner.toString(e.getKey())); 386 appendable.append(keyValueSeparator); 387 appendable.append(joiner.toString(e.getValue())); 388 } 389 } 390 return appendable; 391 } 392 393 /** 394 * Appends the string representation of each entry in {@code entries}, using the previously 395 * configured separator and key-value separator, to {@code builder}. Identical to {@link 396 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 397 * 398 * @since 10.0 399 */ 400 @Beta 401 @CanIgnoreReturnValue 402 public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) { 403 return appendTo(builder, entries.iterator()); 404 } 405 406 /** 407 * Appends the string representation of each entry in {@code entries}, using the previously 408 * configured separator and key-value separator, to {@code builder}. Identical to {@link 409 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 410 * 411 * @since 11.0 412 */ 413 @Beta 414 @CanIgnoreReturnValue 415 public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) { 416 try { 417 appendTo((Appendable) builder, entries); 418 } catch (IOException impossible) { 419 throw new AssertionError(impossible); 420 } 421 return builder; 422 } 423 424 /** 425 * Returns a string containing the string representation of each entry in {@code entries}, using 426 * the previously configured separator and key-value separator. 427 * 428 * @since 10.0 429 */ 430 @Beta 431 public String join(Iterable<? extends Entry<?, ?>> entries) { 432 return join(entries.iterator()); 433 } 434 435 /** 436 * Returns a string containing the string representation of each entry in {@code entries}, using 437 * the previously configured separator and key-value separator. 438 * 439 * @since 11.0 440 */ 441 @Beta 442 public String join(Iterator<? extends Entry<?, ?>> entries) { 443 return appendTo(new StringBuilder(), entries).toString(); 444 } 445 446 /** 447 * Returns a map joiner with the same behavior as this one, except automatically substituting 448 * {@code nullText} for any provided null keys or values. 449 */ 450 public MapJoiner useForNull(String nullText) { 451 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); 452 } 453 } 454 455 CharSequence toString(Object part) { 456 checkNotNull(part); // checkNotNull for GWT (do not optimize). 457 return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); 458 } 459 460 private static Iterable<Object> iterable( 461 final Object first, final Object second, final Object[] rest) { 462 checkNotNull(rest); 463 return new AbstractList<Object>() { 464 @Override 465 public int size() { 466 return rest.length + 2; 467 } 468 469 @Override 470 public Object get(int index) { 471 switch (index) { 472 case 0: 473 return first; 474 case 1: 475 return second; 476 default: 477 return rest[index - 2]; 478 } 479 } 480 }; 481 } 482}