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