001/* 002 * Copyright (C) 2008 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.base; 018 019import static com.google.common.base.Preconditions.checkNotNull; 020 021import com.google.common.annotations.Beta; 022import com.google.common.annotations.GwtCompatible; 023 024import java.io.IOException; 025import java.util.AbstractList; 026import java.util.Arrays; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.Map.Entry; 030 031import javax.annotation.CheckReturnValue; 032import javax.annotation.Nullable; 033 034/** 035 * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a 036 * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns 037 * them as a {@link String}. Example: <pre> {@code 038 * 039 * Joiner joiner = Joiner.on("; ").skipNulls(); 040 * . . . 041 * return joiner.join("Harry", null, "Ron", "Hermione");}</pre> 042 * 043 * <p>This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are 044 * converted to strings using {@link Object#toString()} before being appended. 045 * 046 * <p>If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining 047 * methods will throw {@link NullPointerException} if any given element is null. 048 * 049 * <p><b>Warning: joiner instances are always immutable</b>; a configuration method such as {@code 050 * useForNull} has no effect on the instance it is invoked on! You must store and use the new joiner 051 * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code 052 * static final} constants. <pre> {@code 053 * 054 * // Bad! Do not do this! 055 * Joiner joiner = Joiner.on(','); 056 * joiner.skipNulls(); // does nothing! 057 * return joiner.join("wrong", null, "wrong");}</pre> 058 * 059 * <p>See the Guava User Guide article on <a href= 060 * "http://code.google.com/p/guava-libraries/wiki/StringsExplained#Joiner">{@code Joiner}</a>. 061 * 062 * @author Kevin Bourrillion 063 * @since 2.0 (imported from Google Collections Library) 064 */ 065@GwtCompatible 066public class Joiner { 067 /** 068 * Returns a joiner which automatically places {@code separator} between consecutive elements. 069 */ 070 public static Joiner on(String separator) { 071 return new Joiner(separator); 072 } 073 074 /** 075 * Returns a joiner which automatically places {@code separator} between consecutive elements. 076 */ 077 public static Joiner on(char separator) { 078 return new Joiner(String.valueOf(separator)); 079 } 080 081 private final String separator; 082 083 private Joiner(String separator) { 084 this.separator = checkNotNull(separator); 085 } 086 087 private Joiner(Joiner prototype) { 088 this.separator = prototype.separator; 089 } 090 091 /** 092 * Appends the string representation of each of {@code parts}, using the previously configured 093 * separator between each, to {@code appendable}. 094 */ 095 public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException { 096 return appendTo(appendable, parts.iterator()); 097 } 098 099 /** 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 checkNotNull(nullText); // weird: just to satisfy NullPointerTester. 226 throw new UnsupportedOperationException("already specified useForNull"); 227 } 228 229 @Override public Joiner skipNulls() { 230 throw new UnsupportedOperationException("already specified useForNull"); 231 } 232 }; 233 } 234 235 /** 236 * Returns a joiner with the same behavior as this joiner, except automatically skipping over any 237 * provided null elements. 238 */ 239 @CheckReturnValue 240 public Joiner skipNulls() { 241 return new Joiner(this) { 242 @Override public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) 243 throws IOException { 244 checkNotNull(appendable, "appendable"); 245 checkNotNull(parts, "parts"); 246 while (parts.hasNext()) { 247 Object part = parts.next(); 248 if (part != null) { 249 appendable.append(Joiner.this.toString(part)); 250 break; 251 } 252 } 253 while (parts.hasNext()) { 254 Object part = parts.next(); 255 if (part != null) { 256 appendable.append(separator); 257 appendable.append(Joiner.this.toString(part)); 258 } 259 } 260 return appendable; 261 } 262 263 @Override public Joiner useForNull(String nullText) { 264 checkNotNull(nullText); // weird: just to satisfy NullPointerTester. 265 throw new UnsupportedOperationException("already specified skipNulls"); 266 } 267 268 @Override public MapJoiner withKeyValueSeparator(String kvs) { 269 checkNotNull(kvs); // weird: just to satisfy NullPointerTester. 270 throw new UnsupportedOperationException("can't use .skipNulls() with maps"); 271 } 272 }; 273 } 274 275 /** 276 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 277 * this {@code Joiner} otherwise. 278 */ 279 @CheckReturnValue 280 public MapJoiner withKeyValueSeparator(String keyValueSeparator) { 281 return new MapJoiner(this, keyValueSeparator); 282 } 283 284 /** 285 * An object that joins map entries in the same manner as {@code Joiner} joins iterables and 286 * arrays. Like {@code Joiner}, it is thread-safe and immutable. 287 * 288 * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code 289 * Multimap} entries in two distinct modes: 290 * 291 * <ul> 292 * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a 293 * {@code MapJoiner} method that accepts entries as input, and receive output of the form 294 * {@code key1=A&key1=B&key2=C}. 295 * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner} 296 * method that accepts a map as input, and receive output of the form {@code 297 * key1=[A, B]&key2=C}. 298 * </ul> 299 * 300 * @since 2.0 (imported from Google Collections Library) 301 */ 302 public final static class MapJoiner { 303 private final Joiner joiner; 304 private final String keyValueSeparator; 305 306 private MapJoiner(Joiner joiner, String keyValueSeparator) { 307 this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull 308 this.keyValueSeparator = checkNotNull(keyValueSeparator); 309 } 310 311 /** 312 * Appends the string representation of each entry of {@code map}, using the previously 313 * configured separator and key-value separator, to {@code appendable}. 314 */ 315 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException { 316 return appendTo(appendable, map.entrySet()); 317 } 318 319 /** 320 * Appends the string representation of each entry of {@code map}, using the previously 321 * configured separator and key-value separator, to {@code builder}. Identical to {@link 322 * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. 323 */ 324 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) { 325 return appendTo(builder, map.entrySet()); 326 } 327 328 /** 329 * Returns a string containing the string representation of each entry of {@code map}, using the 330 * previously configured separator and key-value separator. 331 */ 332 public String join(Map<?, ?> map) { 333 return join(map.entrySet()); 334 } 335 336 /** 337 * Appends the string representation of each entry in {@code entries}, using the previously 338 * configured separator and key-value separator, to {@code appendable}. 339 * 340 * @since 10.0 341 */ 342 @Beta 343 public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries) 344 throws IOException { 345 return appendTo(appendable, entries.iterator()); 346 } 347 348 /** 349 * Appends the string representation of each entry in {@code entries}, using the previously 350 * configured separator and key-value separator, to {@code appendable}. 351 * 352 * @since 11.0 353 */ 354 @Beta 355 public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts) 356 throws IOException { 357 checkNotNull(appendable); 358 if (parts.hasNext()) { 359 Entry<?, ?> entry = parts.next(); 360 appendable.append(joiner.toString(entry.getKey())); 361 appendable.append(keyValueSeparator); 362 appendable.append(joiner.toString(entry.getValue())); 363 while (parts.hasNext()) { 364 appendable.append(joiner.separator); 365 Entry<?, ?> e = parts.next(); 366 appendable.append(joiner.toString(e.getKey())); 367 appendable.append(keyValueSeparator); 368 appendable.append(joiner.toString(e.getValue())); 369 } 370 } 371 return appendable; 372 } 373 374 /** 375 * Appends the string representation of each entry in {@code entries}, using the previously 376 * configured separator and key-value separator, to {@code builder}. Identical to {@link 377 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 378 * 379 * @since 10.0 380 */ 381 @Beta 382 public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) { 383 return appendTo(builder, entries.iterator()); 384 } 385 386 /** 387 * Appends the string representation of each entry in {@code entries}, using the previously 388 * configured separator and key-value separator, to {@code builder}. Identical to {@link 389 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}. 390 * 391 * @since 11.0 392 */ 393 @Beta 394 public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) { 395 try { 396 appendTo((Appendable) builder, entries); 397 } catch (IOException impossible) { 398 throw new AssertionError(impossible); 399 } 400 return builder; 401 } 402 403 /** 404 * Returns a string containing the string representation of each entry in {@code entries}, using 405 * the previously configured separator and key-value separator. 406 * 407 * @since 10.0 408 */ 409 @Beta 410 public String join(Iterable<? extends Entry<?, ?>> entries) { 411 return join(entries.iterator()); 412 } 413 414 /** 415 * Returns a string containing the string representation of each entry in {@code entries}, using 416 * the previously configured separator and key-value separator. 417 * 418 * @since 11.0 419 */ 420 @Beta 421 public String join(Iterator<? extends Entry<?, ?>> entries) { 422 return appendTo(new StringBuilder(), entries).toString(); 423 } 424 425 /** 426 * Returns a map joiner with the same behavior as this one, except automatically substituting 427 * {@code nullText} for any provided null keys or values. 428 */ 429 @CheckReturnValue 430 public MapJoiner useForNull(String nullText) { 431 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); 432 } 433 } 434 435 CharSequence toString(Object part) { 436 checkNotNull(part); // checkNotNull for GWT (do not optimize). 437 return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); 438 } 439 440 private static Iterable<Object> iterable( 441 final Object first, final Object second, final Object[] rest) { 442 checkNotNull(rest); 443 return new AbstractList<Object>() { 444 @Override public int size() { 445 return rest.length + 2; 446 } 447 448 @Override public Object get(int index) { 449 switch (index) { 450 case 0: 451 return first; 452 case 1: 453 return second; 454 default: 455 return rest[index - 2]; 456 } 457 } 458 }; 459 } 460}