001 /* 002 * Copyright (C) 2008 Google Inc. 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 015 package com.google.common.base; 016 017 import static com.google.common.base.Preconditions.checkNotNull; 018 019 import com.google.common.annotations.GwtCompatible; 020 021 import java.io.IOException; 022 import java.util.AbstractList; 023 import java.util.Arrays; 024 import java.util.Iterator; 025 import java.util.Map; 026 import java.util.Map.Entry; 027 028 import 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 * 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 * @author Kevin Bourrillion 056 * @since 2 (imported from Google Collections Library) 057 */ 058 @GwtCompatible 059 public class Joiner { 060 /** 061 * Returns a joiner which automatically places {@code separator} between consecutive elements. 062 */ 063 public static Joiner on(String separator) { 064 return new Joiner(separator); 065 } 066 067 /** 068 * Returns a joiner which automatically places {@code separator} between consecutive elements. 069 */ 070 public static Joiner on(char separator) { 071 return new Joiner(String.valueOf(separator)); 072 } 073 074 private final String separator; 075 076 private Joiner(String separator) { 077 this.separator = checkNotNull(separator); 078 } 079 080 private Joiner(Joiner prototype) { 081 this.separator = prototype.separator; 082 } 083 084 /** 085 * Appends the string representation of each of {@code parts}, using the previously configured 086 * separator between each, to {@code appendable}. 087 */ 088 public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException { 089 checkNotNull(appendable); 090 Iterator<?> iterator = parts.iterator(); 091 if (iterator.hasNext()) { 092 appendable.append(toString(iterator.next())); 093 while (iterator.hasNext()) { 094 appendable.append(separator); 095 appendable.append(toString(iterator.next())); 096 } 097 } 098 return appendable; 099 } 100 101 /** 102 * Appends the string representation of each of {@code parts}, using the previously configured 103 * separator between each, to {@code appendable}. 104 */ 105 public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException { 106 return appendTo(appendable, Arrays.asList(parts)); 107 } 108 109 /** 110 * Appends to {@code appendable} the string representation of each of the remaining arguments. 111 */ 112 public final <A extends Appendable> A appendTo( 113 A appendable, @Nullable Object first, @Nullable Object second, Object... rest) 114 throws IOException { 115 return appendTo(appendable, iterable(first, second, rest)); 116 } 117 118 /** 119 * Appends the string representation of each of {@code parts}, using the previously configured 120 * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable, 121 * Iterable)}, except that it does not throw {@link IOException}. 122 */ 123 public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) { 124 try { 125 appendTo((Appendable) builder, parts); 126 } catch (IOException impossible) { 127 throw new AssertionError(impossible); 128 } 129 return builder; 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 public final StringBuilder appendTo(StringBuilder builder, Object[] parts) { 138 return appendTo(builder, Arrays.asList(parts)); 139 } 140 141 /** 142 * Appends to {@code builder} the string representation of each of the remaining arguments. 143 * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not 144 * throw {@link IOException}. 145 */ 146 public final StringBuilder appendTo( 147 StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) { 148 return appendTo(builder, iterable(first, second, rest)); 149 } 150 151 /** 152 * Returns a string containing the string representation of each of {@code parts}, using the 153 * previously configured separator between each. 154 */ 155 public final String join(Iterable<?> parts) { 156 return appendTo(new StringBuilder(), parts).toString(); 157 } 158 159 /** 160 * Returns a string containing the string representation of each of {@code parts}, using the 161 * previously configured separator between each. 162 */ 163 public final String join(Object[] parts) { 164 return join(Arrays.asList(parts)); 165 } 166 167 /** 168 * Returns a string containing the string representation of each argument, using the previously 169 * configured separator between each. 170 */ 171 public final String join(@Nullable Object first, @Nullable Object second, Object... rest) { 172 return join(iterable(first, second, rest)); 173 } 174 175 /** 176 * Returns a joiner with the same behavior as this one, except automatically substituting {@code 177 * nullText} for any provided null elements. 178 */ 179 public Joiner useForNull(final String nullText) { 180 checkNotNull(nullText); 181 return new Joiner(this) { 182 @Override CharSequence toString(Object part) { 183 return (part == null) ? nullText : Joiner.this.toString(part); 184 } 185 186 @Override public Joiner useForNull(String nullText) { 187 checkNotNull(nullText); // weird: just to satisfy NullPointerTester. 188 throw new UnsupportedOperationException("already specified useForNull"); 189 } 190 191 @Override public Joiner skipNulls() { 192 throw new UnsupportedOperationException("already specified useForNull"); 193 } 194 }; 195 } 196 197 /** 198 * Returns a joiner with the same behavior as this joiner, except automatically skipping over any 199 * provided null elements. 200 */ 201 public Joiner skipNulls() { 202 return new Joiner(this) { 203 @Override public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) 204 throws IOException { 205 checkNotNull(appendable, "appendable"); 206 checkNotNull(parts, "parts"); 207 Iterator<?> iterator = parts.iterator(); 208 while (iterator.hasNext()) { 209 Object part = iterator.next(); 210 if (part != null) { 211 appendable.append(Joiner.this.toString(part)); 212 break; 213 } 214 } 215 while (iterator.hasNext()) { 216 Object part = iterator.next(); 217 if (part != null) { 218 appendable.append(separator); 219 appendable.append(Joiner.this.toString(part)); 220 } 221 } 222 return appendable; 223 } 224 225 @Override public Joiner useForNull(String nullText) { 226 checkNotNull(nullText); // weird: just to satisfy NullPointerTester. 227 throw new UnsupportedOperationException("already specified skipNulls"); 228 } 229 230 @Override public MapJoiner withKeyValueSeparator(String kvs) { 231 checkNotNull(kvs); // weird: just to satisfy NullPointerTester. 232 throw new UnsupportedOperationException("can't use .skipNulls() with maps"); 233 } 234 }; 235 } 236 237 /** 238 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as 239 * this {@code Joiner} otherwise. 240 */ 241 public MapJoiner withKeyValueSeparator(String keyValueSeparator) { 242 return new MapJoiner(this, checkNotNull(keyValueSeparator)); 243 } 244 245 /** 246 * An object that joins map entries in the same manner as {@code Joiner} joins iterables and 247 * arrays. Like {@code Joiner}, it is thread-safe and immutable. 248 */ 249 public static class MapJoiner { 250 private final Joiner joiner; 251 private final String keyValueSeparator; 252 253 private MapJoiner(Joiner joiner, String keyValueSeparator) { 254 this.joiner = joiner; 255 this.keyValueSeparator = keyValueSeparator; 256 } 257 258 /** 259 * Appends the string representation of each entry of {@code map}, using the previously 260 * configured separator and key-value separator, to {@code appendable}. 261 */ 262 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException { 263 checkNotNull(appendable); 264 Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator(); 265 if (iterator.hasNext()) { 266 Entry<?, ?> entry = iterator.next(); 267 appendable.append(joiner.toString(entry.getKey())); 268 appendable.append(keyValueSeparator); 269 appendable.append(joiner.toString(entry.getValue())); 270 while (iterator.hasNext()) { 271 appendable.append(joiner.separator); 272 Entry<?, ?> e = iterator.next(); 273 appendable.append(joiner.toString(e.getKey())); 274 appendable.append(keyValueSeparator); 275 appendable.append(joiner.toString(e.getValue())); 276 } 277 } 278 return appendable; 279 } 280 281 /** 282 * Appends the string representation of each entry of {@code map}, using the previously 283 * configured separator and key-value separator, to {@code builder}. Identical to {@link 284 * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}. 285 */ 286 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) { 287 try { 288 appendTo((Appendable) builder, map); 289 } catch (IOException impossible) { 290 throw new AssertionError(impossible); 291 } 292 return builder; 293 } 294 295 /** 296 * Returns a string containing the string representation of each entry of {@code map}, using the 297 * previously configured separator and key-value separator. 298 */ 299 public String join(Map<?, ?> map) { 300 return appendTo(new StringBuilder(), map).toString(); 301 } 302 303 /** 304 * Returns a map joiner with the same behavior as this one, except automatically substituting 305 * {@code nullText} for any provided null keys or values. 306 */ 307 public MapJoiner useForNull(String nullText) { 308 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator); 309 } 310 } 311 312 CharSequence toString(Object part) { 313 return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); 314 } 315 316 private static Iterable<Object> iterable( 317 final Object first, final Object second, final Object[] rest) { 318 checkNotNull(rest); 319 return new AbstractList<Object>() { 320 @Override public int size() { 321 return rest.length + 2; 322 } 323 324 @Override public Object get(int index) { 325 switch (index) { 326 case 0: 327 return first; 328 case 1: 329 return second; 330 default: 331 return rest[index - 2]; 332 } 333 } 334 }; 335 } 336 }