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 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 * <p>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 * <p>See the Guava User Guide article on 056 * <a href="https://github.com/google/guava/wiki/StringsExplained#joiner">{@code Joiner}</a>. 057 * 058 * @author Kevin Bourrillion 059 * @since 2.0 060 */ 061@GwtCompatible 062public class Joiner { 063 /** 064 * Returns a joiner which automatically places {@code separator} between consecutive elements. 065 */ 066 public static Joiner on(String separator) { 067 return new Joiner(separator); 068 } 069 070 /** 071 * Returns a joiner which automatically places {@code separator} between consecutive elements. 072 */ 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 /** 125 * Appends to {@code appendable} the string representation of each of the remaining arguments. 126 */ 127 @CanIgnoreReturnValue 128 public final <A extends Appendable> A appendTo( 129 A appendable, @Nullable Object first, @Nullable Object second, Object... rest) 130 throws IOException { 131 return appendTo(appendable, iterable(first, second, rest)); 132 } 133 134 /** 135 * Appends the string representation of each of {@code parts}, using the previously configured 136 * separator between each, to {@code builder}. Identical to 137 * {@link #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 138 */ 139 @CanIgnoreReturnValue 140 public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) { 141 return appendTo(builder, parts.iterator()); 142 } 143 144 /** 145 * Appends the string representation of each of {@code parts}, using the previously configured 146 * separator between each, to {@code builder}. Identical to 147 * {@link #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 148 * 149 * @since 11.0 150 */ 151 @CanIgnoreReturnValue 152 public final StringBuilder appendTo(StringBuilder builder, Iterator<?> parts) { 153 try { 154 appendTo((Appendable) builder, parts); 155 } catch (IOException impossible) { 156 throw new AssertionError(impossible); 157 } 158 return builder; 159 } 160 161 /** 162 * Appends the string representation of each of {@code parts}, using the previously configured 163 * separator between each, to {@code builder}. Identical to 164 * {@link #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 165 */ 166 @CanIgnoreReturnValue 167 public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { 168 return appendTo(builder, Arrays.asList(parts)); 169 } 170 171 /** 172 * Appends to {@code builder} the string representation of each of the remaining arguments. 173 * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not 174 * throw {@link IOException}. 175 */ 176 @CanIgnoreReturnValue 177 public final StringBuilder appendTo( 178 StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) { 179 return appendTo(builder, iterable(first, second, rest)); 180 } 181 182 /** 183 * Returns a string containing the string representation of each of {@code parts}, using the 184 * previously configured separator between each. 185 */ 186 public final String join(Iterable<?> parts) { 187 return join(parts.iterator()); 188 } 189 190 /** 191 * Returns a string containing the string representation of each of {@code parts}, using the 192 * previously configured separator between each. 193 * 194 * @since 11.0 195 */ 196 public final String join(Iterator<?> parts) { 197 return appendTo(new StringBuilder(), parts).toString(); 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(Object[] parts) { 205 return join(Arrays.asList(parts)); 206 } 207 208 /** 209 * Returns a string containing the string representation of each argument, using the previously 210 * configured separator between each. 211 */ 212 public final String join(@Nullable Object first, @Nullable Object second, Object... rest) { 213 return join(iterable(first, second, rest)); 214 } 215 216 /** 217 * Returns a joiner with the same behavior as this one, except automatically substituting {@code 218 * nullText} for any provided null elements. 219 */ 220 public Joiner useForNull(final String nullText) { 221 checkNotNull(nullText); 222 return new Joiner(this) { 223 @Override 224 CharSequence toString(@Nullable Object part) { 225 return (part == null) ? nullText : Joiner.this.toString(part); 226 } 227 228 @Override 229 public Joiner useForNull(String nullText) { 230 throw new UnsupportedOperationException("already specified useForNull"); 231 } 232 233 @Override 234 public Joiner skipNulls() { 235 throw new UnsupportedOperationException("already specified useForNull"); 236 } 237 }; 238 } 239 240 /** 241 * Returns a joiner with the same behavior as this joiner, except automatically skipping over any 242 * provided null elements. 243 */ 244 public Joiner skipNulls() { 245 return new Joiner(this) { 246 @Override 247 public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException { 248 checkNotNull(appendable, "appendable"); 249 checkNotNull(parts, "parts"); 250 while (parts.hasNext()) { 251 Object part = parts.next(); 252 if (part != null) { 253 appendable.append(Joiner.this.toString(part)); 254 break; 255 } 256 } 257 while (parts.hasNext()) { 258 Object part = parts.next(); 259 if (part != null) { 260 appendable.append(separator); 261 appendable.append(Joiner.this.toString(part)); 262 } 263 } 264 return appendable; 265 } 266 267 @Override 268 public Joiner useForNull(String nullText) { 269 throw new UnsupportedOperationException("already specified skipNulls"); 270 } 271 272 @Override 273 public MapJoiner withKeyValueSeparator(String kvs) { 274 throw new UnsupportedOperationException("can't use .skipNulls() with maps"); 275 } 276 }; 277 } 278 279 /** 280 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 281 * this {@code Joiner} otherwise. 282 * 283 * @since 20.0 284 */ 285 public MapJoiner withKeyValueSeparator(char keyValueSeparator) { 286 return withKeyValueSeparator(String.valueOf(keyValueSeparator)); 287 } 288 289 /** 290 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 291 * this {@code Joiner} otherwise. 292 */ 293 public MapJoiner withKeyValueSeparator(String keyValueSeparator) { 294 return new MapJoiner(this, keyValueSeparator); 295 } 296 297 /** 298 * An object that joins map entries in the same manner as {@code Joiner} joins iterables and 299 * arrays. Like {@code Joiner}, it is thread-safe and immutable. 300 * 301 * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code 302 * Multimap} entries in two distinct modes: 303 * 304 * <ul> 305 * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a 306 * {@code MapJoiner} method that accepts entries as input, and receive output of the form 307 * {@code key1=A&key1=B&key2=C}. 308 * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner} 309 * method that accepts a map as input, and receive output of the form {@code 310 * key1=[A, B]&key2=C}. 311 * </ul> 312 * 313 * @since 2.0 314 */ 315 public static final class MapJoiner { 316 private final Joiner joiner; 317 private final String keyValueSeparator; 318 319 private MapJoiner(Joiner joiner, String keyValueSeparator) { 320 this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull 321 this.keyValueSeparator = checkNotNull(keyValueSeparator); 322 } 323 324 /** 325 * Appends the string representation of each entry of {@code map}, using the previously 326 * configured separator and key-value separator, to {@code appendable}. 327 */ 328 @CanIgnoreReturnValue 329 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException { 330 return appendTo(appendable, map.entrySet()); 331 } 332 333 /** 334 * Appends the string representation of each entry of {@code map}, using the previously 335 * configured separator and key-value separator, to {@code builder}. Identical to 336 * {@link #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. 337 */ 338 @CanIgnoreReturnValue 339 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) { 340 return appendTo(builder, map.entrySet()); 341 } 342 343 /** 344 * Returns a string containing the string representation of each entry of {@code map}, using the 345 * previously configured separator and key-value separator. 346 */ 347 public String join(Map<?, ?> map) { 348 return join(map.entrySet()); 349 } 350 351 /** 352 * Appends the string representation of each entry in {@code entries}, using the previously 353 * configured separator and key-value separator, to {@code appendable}. 354 * 355 * @since 10.0 356 */ 357 @Beta 358 @CanIgnoreReturnValue 359 public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries) 360 throws IOException { 361 return appendTo(appendable, entries.iterator()); 362 } 363 364 /** 365 * Appends the string representation of each entry in {@code entries}, using the previously 366 * configured separator and key-value separator, to {@code appendable}. 367 * 368 * @since 11.0 369 */ 370 @Beta 371 @CanIgnoreReturnValue 372 public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts) 373 throws IOException { 374 checkNotNull(appendable); 375 if (parts.hasNext()) { 376 Entry<?, ?> entry = parts.next(); 377 appendable.append(joiner.toString(entry.getKey())); 378 appendable.append(keyValueSeparator); 379 appendable.append(joiner.toString(entry.getValue())); 380 while (parts.hasNext()) { 381 appendable.append(joiner.separator); 382 Entry<?, ?> e = parts.next(); 383 appendable.append(joiner.toString(e.getKey())); 384 appendable.append(keyValueSeparator); 385 appendable.append(joiner.toString(e.getValue())); 386 } 387 } 388 return appendable; 389 } 390 391 /** 392 * Appends the string representation of each entry in {@code entries}, using the previously 393 * configured separator and key-value separator, to {@code builder}. Identical to 394 * {@link #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 395 * 396 * @since 10.0 397 */ 398 @Beta 399 @CanIgnoreReturnValue 400 public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) { 401 return appendTo(builder, entries.iterator()); 402 } 403 404 /** 405 * Appends the string representation of each entry in {@code entries}, using the previously 406 * configured separator and key-value separator, to {@code builder}. Identical to 407 * {@link #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 408 * 409 * @since 11.0 410 */ 411 @Beta 412 @CanIgnoreReturnValue 413 public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) { 414 try { 415 appendTo((Appendable) builder, entries); 416 } catch (IOException impossible) { 417 throw new AssertionError(impossible); 418 } 419 return builder; 420 } 421 422 /** 423 * Returns a string containing the string representation of each entry in {@code entries}, using 424 * the previously configured separator and key-value separator. 425 * 426 * @since 10.0 427 */ 428 @Beta 429 public String join(Iterable<? extends Entry<?, ?>> entries) { 430 return join(entries.iterator()); 431 } 432 433 /** 434 * Returns a string containing the string representation of each entry in {@code entries}, using 435 * the previously configured separator and key-value separator. 436 * 437 * @since 11.0 438 */ 439 @Beta 440 public String join(Iterator<? extends Entry<?, ?>> entries) { 441 return appendTo(new StringBuilder(), entries).toString(); 442 } 443 444 /** 445 * Returns a map joiner with the same behavior as this one, except automatically substituting 446 * {@code nullText} for any provided null keys or values. 447 */ 448 public MapJoiner useForNull(String nullText) { 449 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); 450 } 451 } 452 453 CharSequence toString(Object part) { 454 checkNotNull(part); // checkNotNull for GWT (do not optimize). 455 return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); 456 } 457 458 private static Iterable<Object> iterable( 459 final Object first, final Object second, final Object[] rest) { 460 checkNotNull(rest); 461 return new AbstractList<Object>() { 462 @Override 463 public int size() { 464 return rest.length + 2; 465 } 466 467 @Override 468 public Object get(int index) { 469 switch (index) { 470 case 0: 471 return first; 472 case 1: 473 return second; 474 default: 475 return rest[index - 2]; 476 } 477 } 478 }; 479 } 480}