1 /* 2 * Copyright (C) 2008 The Guava Authors 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 static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.common.annotations.Beta; 22 import com.google.common.annotations.GwtCompatible; 23 24 import java.io.IOException; 25 import java.util.AbstractList; 26 import java.util.Arrays; 27 import java.util.Iterator; 28 import java.util.Map; 29 import java.util.Map.Entry; 30 31 import javax.annotation.CheckReturnValue; 32 import javax.annotation.Nullable; 33 34 /** 35 * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a 36 * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns 37 * them as a {@link String}. Example: <pre> {@code 38 * 39 * Joiner joiner = Joiner.on("; ").skipNulls(); 40 * . . . 41 * return joiner.join("Harry", null, "Ron", "Hermione");}</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. <pre> {@code 53 * 54 * // Bad! Do not do this! 55 * Joiner joiner = Joiner.on(','); 56 * joiner.skipNulls(); // does nothing! 57 * return joiner.join("wrong", null, "wrong");}</pre> 58 * 59 * <p>See the Guava User Guide article on <a href= 60 * "http://code.google.com/p/guava-libraries/wiki/StringsExplained#Joiner">{@code Joiner}</a>. 61 * 62 * @author Kevin Bourrillion 63 * @since 2.0 (imported from Google Collections Library) 64 */ 65 @GwtCompatible 66 public class Joiner { 67 /** 68 * Returns a joiner which automatically places {@code separator} between consecutive elements. 69 */ on(String separator)70 public static Joiner on(String separator) { 71 return new Joiner(separator); 72 } 73 74 /** 75 * Returns a joiner which automatically places {@code separator} between consecutive elements. 76 */ on(char separator)77 public static Joiner on(char separator) { 78 return new Joiner(String.valueOf(separator)); 79 } 80 81 private final String separator; 82 Joiner(String separator)83 private Joiner(String separator) { 84 this.separator = checkNotNull(separator); 85 } 86 Joiner(Joiner prototype)87 private Joiner(Joiner prototype) { 88 this.separator = prototype.separator; 89 } 90 91 /** 92 * Appends the string representation of each of {@code parts}, using the previously configured 93 * separator between each, to {@code appendable}. 94 */ appendTo(A appendable, Iterable<?> parts)95 public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException { 96 return appendTo(appendable, parts.iterator()); 97 } 98 99 /** 100 * Appends the string representation of each of {@code parts}, using the previously configured 101 * separator between each, to {@code appendable}. 102 * 103 * @since 11.0 104 */ appendTo(A appendable, Iterator<?> parts)105 public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException { 106 checkNotNull(appendable); 107 if (parts.hasNext()) { 108 appendable.append(toString(parts.next())); 109 while (parts.hasNext()) { 110 appendable.append(separator); 111 appendable.append(toString(parts.next())); 112 } 113 } 114 return appendable; 115 } 116 117 /** 118 * Appends the string representation of each of {@code parts}, using the previously configured 119 * separator between each, to {@code appendable}. 120 */ appendTo(A appendable, Object[] parts)121 public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException { 122 return appendTo(appendable, Arrays.asList(parts)); 123 } 124 125 /** 126 * Appends to {@code appendable} the string representation of each of the remaining arguments. 127 */ appendTo( A appendable, @Nullable Object first, @Nullable Object second, Object... rest)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 {@link #appendTo(Appendable, 137 * Iterable)}, except that it does not throw {@link IOException}. 138 */ appendTo(StringBuilder builder, Iterable<?> parts)139 public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) { 140 return appendTo(builder, parts.iterator()); 141 } 142 143 /** 144 * Appends the string representation of each of {@code parts}, using the previously configured 145 * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 146 * Iterable)}, except that it does not throw {@link IOException}. 147 * 148 * @since 11.0 149 */ appendTo(StringBuilder builder, Iterator<?> parts)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 */ appendTo(StringBuilder builder, Object[] parts)164 public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { 165 return appendTo(builder, Arrays.asList(parts)); 166 } 167 168 /** 169 * Appends to {@code builder} the string representation of each of the remaining arguments. 170 * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not 171 * throw {@link IOException}. 172 */ appendTo( StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest)173 public final StringBuilder appendTo( 174 StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) { 175 return appendTo(builder, iterable(first, second, rest)); 176 } 177 178 /** 179 * Returns a string containing the string representation of each of {@code parts}, using the 180 * previously configured separator between each. 181 */ join(Iterable<?> parts)182 public final String join(Iterable<?> parts) { 183 return join(parts.iterator()); 184 } 185 186 /** 187 * Returns a string containing the string representation of each of {@code parts}, using the 188 * previously configured separator between each. 189 * 190 * @since 11.0 191 */ join(Iterator<?> parts)192 public final String join(Iterator<?> parts) { 193 return appendTo(new StringBuilder(), parts).toString(); 194 } 195 196 /** 197 * Returns a string containing the string representation of each of {@code parts}, using the 198 * previously configured separator between each. 199 */ join(Object[] parts)200 public final String join(Object[] parts) { 201 return join(Arrays.asList(parts)); 202 } 203 204 /** 205 * Returns a string containing the string representation of each argument, using the previously 206 * configured separator between each. 207 */ join(@ullable Object first, @Nullable Object second, Object... rest)208 public final String join(@Nullable Object first, @Nullable Object second, Object... rest) { 209 return join(iterable(first, second, rest)); 210 } 211 212 /** 213 * Returns a joiner with the same behavior as this one, except automatically substituting {@code 214 * nullText} for any provided null elements. 215 */ 216 @CheckReturnValue useForNull(final String nullText)217 public Joiner useForNull(final String nullText) { 218 checkNotNull(nullText); 219 return new Joiner(this) { 220 @Override CharSequence toString(@Nullable Object part) { 221 return (part == null) ? nullText : Joiner.this.toString(part); 222 } 223 224 @Override public Joiner useForNull(String nullText) { 225 throw new UnsupportedOperationException("already specified useForNull"); 226 } 227 228 @Override public Joiner skipNulls() { 229 throw new UnsupportedOperationException("already specified useForNull"); 230 } 231 }; 232 } 233 234 /** 235 * Returns a joiner with the same behavior as this joiner, except automatically skipping over any 236 * provided null elements. 237 */ 238 @CheckReturnValue 239 public Joiner skipNulls() { 240 return new Joiner(this) { 241 @Override public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) 242 throws IOException { 243 checkNotNull(appendable, "appendable"); 244 checkNotNull(parts, "parts"); 245 while (parts.hasNext()) { 246 Object part = parts.next(); 247 if (part != null) { 248 appendable.append(Joiner.this.toString(part)); 249 break; 250 } 251 } 252 while (parts.hasNext()) { 253 Object part = parts.next(); 254 if (part != null) { 255 appendable.append(separator); 256 appendable.append(Joiner.this.toString(part)); 257 } 258 } 259 return appendable; 260 } 261 262 @Override public Joiner useForNull(String nullText) { 263 throw new UnsupportedOperationException("already specified skipNulls"); 264 } 265 266 @Override public MapJoiner withKeyValueSeparator(String kvs) { 267 throw new UnsupportedOperationException("can't use .skipNulls() with maps"); 268 } 269 }; 270 } 271 272 /** 273 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 274 * this {@code Joiner} otherwise. 275 */ 276 @CheckReturnValue 277 public MapJoiner withKeyValueSeparator(String keyValueSeparator) { 278 return new MapJoiner(this, keyValueSeparator); 279 } 280 281 /** 282 * An object that joins map entries in the same manner as {@code Joiner} joins iterables and 283 * arrays. Like {@code Joiner}, it is thread-safe and immutable. 284 * 285 * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code 286 * Multimap} entries in two distinct modes: 287 * 288 * <ul> 289 * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a 290 * {@code MapJoiner} method that accepts entries as input, and receive output of the form 291 * {@code key1=A&key1=B&key2=C}. 292 * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner} 293 * method that accepts a map as input, and receive output of the form {@code 294 * key1=[A, B]&key2=C}. 295 * </ul> 296 * 297 * @since 2.0 (imported from Google Collections Library) 298 */ 299 public static final class MapJoiner { 300 private final Joiner joiner; 301 private final String keyValueSeparator; 302 303 private MapJoiner(Joiner joiner, String keyValueSeparator) { 304 this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull 305 this.keyValueSeparator = checkNotNull(keyValueSeparator); 306 } 307 308 /** 309 * Appends the string representation of each entry of {@code map}, using the previously 310 * configured separator and key-value separator, to {@code appendable}. 311 */ 312 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException { 313 return appendTo(appendable, map.entrySet()); 314 } 315 316 /** 317 * Appends the string representation of each entry of {@code map}, using the previously 318 * configured separator and key-value separator, to {@code builder}. Identical to {@link 319 * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. 320 */ 321 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) { 322 return appendTo(builder, map.entrySet()); 323 } 324 325 /** 326 * Returns a string containing the string representation of each entry of {@code map}, using the 327 * previously configured separator and key-value separator. 328 */ 329 public String join(Map<?, ?> map) { 330 return join(map.entrySet()); 331 } 332 333 /** 334 * Appends the string representation of each entry in {@code entries}, using the previously 335 * configured separator and key-value separator, to {@code appendable}. 336 * 337 * @since 10.0 338 */ 339 @Beta 340 public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries) 341 throws IOException { 342 return appendTo(appendable, entries.iterator()); 343 } 344 345 /** 346 * Appends the string representation of each entry in {@code entries}, using the previously 347 * configured separator and key-value separator, to {@code appendable}. 348 * 349 * @since 11.0 350 */ 351 @Beta 352 public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts) 353 throws IOException { 354 checkNotNull(appendable); 355 if (parts.hasNext()) { 356 Entry<?, ?> entry = parts.next(); 357 appendable.append(joiner.toString(entry.getKey())); 358 appendable.append(keyValueSeparator); 359 appendable.append(joiner.toString(entry.getValue())); 360 while (parts.hasNext()) { 361 appendable.append(joiner.separator); 362 Entry<?, ?> e = parts.next(); 363 appendable.append(joiner.toString(e.getKey())); 364 appendable.append(keyValueSeparator); 365 appendable.append(joiner.toString(e.getValue())); 366 } 367 } 368 return appendable; 369 } 370 371 /** 372 * Appends the string representation of each entry in {@code entries}, using the previously 373 * configured separator and key-value separator, to {@code builder}. Identical to {@link 374 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 375 * 376 * @since 10.0 377 */ 378 @Beta 379 public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) { 380 return appendTo(builder, entries.iterator()); 381 } 382 383 /** 384 * Appends the string representation of each entry in {@code entries}, using the previously 385 * configured separator and key-value separator, to {@code builder}. Identical to {@link 386 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 387 * 388 * @since 11.0 389 */ 390 @Beta 391 public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) { 392 try { 393 appendTo((Appendable) builder, entries); 394 } catch (IOException impossible) { 395 throw new AssertionError(impossible); 396 } 397 return builder; 398 } 399 400 /** 401 * Returns a string containing the string representation of each entry in {@code entries}, using 402 * the previously configured separator and key-value separator. 403 * 404 * @since 10.0 405 */ 406 @Beta 407 public String join(Iterable<? extends Entry<?, ?>> entries) { 408 return join(entries.iterator()); 409 } 410 411 /** 412 * Returns a string containing the string representation of each entry in {@code entries}, using 413 * the previously configured separator and key-value separator. 414 * 415 * @since 11.0 416 */ 417 @Beta 418 public String join(Iterator<? extends Entry<?, ?>> entries) { 419 return appendTo(new StringBuilder(), entries).toString(); 420 } 421 422 /** 423 * Returns a map joiner with the same behavior as this one, except automatically substituting 424 * {@code nullText} for any provided null keys or values. 425 */ 426 @CheckReturnValue 427 public MapJoiner useForNull(String nullText) { 428 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); 429 } 430 } 431 432 CharSequence toString(Object part) { 433 checkNotNull(part); // checkNotNull for GWT (do not optimize). 434 return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); 435 } 436 437 private static Iterable<Object> iterable( 438 final Object first, final Object second, final Object[] rest) { 439 checkNotNull(rest); 440 return new AbstractList<Object>() { 441 @Override public int size() { 442 return rest.length + 2; 443 } 444 445 @Override public Object get(int index) { 446 switch (index) { 447 case 0: 448 return first; 449 case 1: 450 return second; 451 default: 452 return rest[index - 2]; 453 } 454 } 455 }; 456 } 457 } 458