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.Beta;
022 import com.google.common.annotations.GwtCompatible;
023
024 import java.io.IOException;
025 import java.util.AbstractList;
026 import java.util.Arrays;
027 import java.util.Iterator;
028 import java.util.Map;
029 import java.util.Map.Entry;
030
031 import javax.annotation.CheckReturnValue;
032 import 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 * 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 * @author Kevin Bourrillion
060 * @since 2.0 (imported from Google Collections Library)
061 */
062 @GwtCompatible
063 public class Joiner {
064 /**
065 * Returns a joiner which automatically places {@code separator} between consecutive elements.
066 */
067 public static Joiner on(String separator) {
068 return new Joiner(separator);
069 }
070
071 /**
072 * Returns a joiner which automatically places {@code separator} between consecutive elements.
073 */
074 public static Joiner on(char separator) {
075 return new Joiner(String.valueOf(separator));
076 }
077
078 private final String separator;
079
080 private Joiner(String separator) {
081 this.separator = checkNotNull(separator);
082 }
083
084 private Joiner(Joiner prototype) {
085 this.separator = prototype.separator;
086 }
087
088 /**
089 * Appends the string representation of each of {@code parts}, using the previously configured
090 * separator between each, to {@code appendable}.
091 */
092 public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
093 checkNotNull(appendable);
094 Iterator<?> iterator = parts.iterator();
095 if (iterator.hasNext()) {
096 appendable.append(toString(iterator.next()));
097 while (iterator.hasNext()) {
098 appendable.append(separator);
099 appendable.append(toString(iterator.next()));
100 }
101 }
102 return appendable;
103 }
104
105 /**
106 * Appends the string representation of each of {@code parts}, using the previously configured
107 * separator between each, to {@code appendable}.
108 */
109 public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
110 return appendTo(appendable, Arrays.asList(parts));
111 }
112
113 /**
114 * Appends to {@code appendable} the string representation of each of the remaining arguments.
115 */
116 public final <A extends Appendable> A appendTo(
117 A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
118 throws IOException {
119 return appendTo(appendable, iterable(first, second, rest));
120 }
121
122 /**
123 * Appends the string representation of each of {@code parts}, using the previously configured
124 * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
125 * Iterable)}, except that it does not throw {@link IOException}.
126 */
127 public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
128 try {
129 appendTo((Appendable) builder, parts);
130 } catch (IOException impossible) {
131 throw new AssertionError(impossible);
132 }
133 return builder;
134 }
135
136 /**
137 * Appends the string representation of each of {@code parts}, using the previously configured
138 * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
139 * Iterable)}, except that it does not throw {@link IOException}.
140 */
141 public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
142 return appendTo(builder, Arrays.asList(parts));
143 }
144
145 /**
146 * Appends to {@code builder} the string representation of each of the remaining arguments.
147 * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
148 * throw {@link IOException}.
149 */
150 public final StringBuilder appendTo(
151 StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
152 return appendTo(builder, iterable(first, second, rest));
153 }
154
155 /**
156 * Returns a string containing the string representation of each of {@code parts}, using the
157 * previously configured separator between each.
158 */
159 public final String join(Iterable<?> parts) {
160 return appendTo(new StringBuilder(), parts).toString();
161 }
162
163 /**
164 * Returns a string containing the string representation of each of {@code parts}, using the
165 * previously configured separator between each.
166 */
167 public final String join(Object[] parts) {
168 return join(Arrays.asList(parts));
169 }
170
171 /**
172 * Returns a string containing the string representation of each argument, using the previously
173 * configured separator between each.
174 */
175 public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
176 return join(iterable(first, second, rest));
177 }
178
179 /**
180 * Returns a joiner with the same behavior as this one, except automatically substituting {@code
181 * nullText} for any provided null elements.
182 */
183 @CheckReturnValue
184 public Joiner useForNull(final String nullText) {
185 checkNotNull(nullText);
186 return new Joiner(this) {
187 @Override CharSequence toString(Object part) {
188 return (part == null) ? nullText : Joiner.this.toString(part);
189 }
190
191 @Override public Joiner useForNull(String nullText) {
192 checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
193 throw new UnsupportedOperationException("already specified useForNull");
194 }
195
196 @Override public Joiner skipNulls() {
197 throw new UnsupportedOperationException("already specified useForNull");
198 }
199 };
200 }
201
202 /**
203 * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
204 * provided null elements.
205 */
206 @CheckReturnValue
207 public Joiner skipNulls() {
208 return new Joiner(this) {
209 @Override public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)
210 throws IOException {
211 checkNotNull(appendable, "appendable");
212 checkNotNull(parts, "parts");
213 Iterator<?> iterator = parts.iterator();
214 while (iterator.hasNext()) {
215 Object part = iterator.next();
216 if (part != null) {
217 appendable.append(Joiner.this.toString(part));
218 break;
219 }
220 }
221 while (iterator.hasNext()) {
222 Object part = iterator.next();
223 if (part != null) {
224 appendable.append(separator);
225 appendable.append(Joiner.this.toString(part));
226 }
227 }
228 return appendable;
229 }
230
231 @Override public Joiner useForNull(String nullText) {
232 checkNotNull(nullText); // weird: just to satisfy NullPointerTester.
233 throw new UnsupportedOperationException("already specified skipNulls");
234 }
235
236 @Override public MapJoiner withKeyValueSeparator(String kvs) {
237 checkNotNull(kvs); // weird: just to satisfy NullPointerTester.
238 throw new UnsupportedOperationException("can't use .skipNulls() with maps");
239 }
240 };
241 }
242
243 /**
244 * Returns a {@code MapJoiner} using the given key-value separator, and the same configuration as
245 * this {@code Joiner} otherwise.
246 */
247 @CheckReturnValue
248 public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
249 return new MapJoiner(this, keyValueSeparator);
250 }
251
252 /**
253 * An object that joins map entries in the same manner as {@code Joiner} joins iterables and
254 * arrays. Like {@code Joiner}, it is thread-safe and immutable.
255 *
256 * <p>In addition to operating on {@code Map} instances, {@code MapJoiner} can operate on {@code
257 * Multimap} entries in two distinct modes:
258 *
259 * <ul>
260 * <li>To output a separate entry for each key-value pair, pass {@code multimap.entries()} to a
261 * {@code MapJoiner} method that accepts entries as input, and receive output of the form
262 * {@code key1=A&key1=B&key2=C}.
263 * <li>To output a single entry for each key, pass {@code multimap.asMap()} to a {@code MapJoiner}
264 * method that accepts a map as input, and receive output of the form {@code
265 * key1=[A, B]&key2=C}.
266 * </ul>
267 *
268 * @since 2.0 (imported from Google Collections Library)
269 */
270 public final static class MapJoiner {
271 private final Joiner joiner;
272 private final String keyValueSeparator;
273
274 private MapJoiner(Joiner joiner, String keyValueSeparator) {
275 this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
276 this.keyValueSeparator = checkNotNull(keyValueSeparator);
277 }
278
279 /**
280 * Appends the string representation of each entry of {@code map}, using the previously
281 * configured separator and key-value separator, to {@code appendable}.
282 */
283 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
284 return appendTo(appendable, map.entrySet());
285 }
286
287 /**
288 * Appends the string representation of each entry of {@code map}, using the previously
289 * configured separator and key-value separator, to {@code builder}. Identical to {@link
290 * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
291 */
292 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
293 return appendTo(builder, map.entrySet());
294 }
295
296 /**
297 * Returns a string containing the string representation of each entry of {@code map}, using the
298 * previously configured separator and key-value separator.
299 */
300 public String join(Map<?, ?> map) {
301 return join(map.entrySet());
302 }
303
304 /**
305 * Appends the string representation of each entry in {@code entries}, using the previously
306 * configured separator and key-value separator, to {@code appendable}.
307 *
308 * @since 10.0
309 */
310 @Beta
311 public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
312 throws IOException {
313 checkNotNull(appendable);
314 Iterator<? extends Map.Entry<?, ?>> iterator = entries.iterator();
315 if (iterator.hasNext()) {
316 Entry<?, ?> entry = iterator.next();
317 appendable.append(joiner.toString(entry.getKey()));
318 appendable.append(keyValueSeparator);
319 appendable.append(joiner.toString(entry.getValue()));
320 while (iterator.hasNext()) {
321 appendable.append(joiner.separator);
322 Entry<?, ?> e = iterator.next();
323 appendable.append(joiner.toString(e.getKey()));
324 appendable.append(keyValueSeparator);
325 appendable.append(joiner.toString(e.getValue()));
326 }
327 }
328 return appendable;
329 }
330
331 /**
332 * Appends the string representation of each entry in {@code entries}, using the previously
333 * configured separator and key-value separator, to {@code builder}. Identical to {@link
334 * #appendTo(Appendable, Iterable)}, except that it does not throw {@link IOException}.
335 *
336 * @since 10.0
337 */
338 @Beta
339 public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
340 try {
341 appendTo((Appendable) builder, entries);
342 } catch (IOException impossible) {
343 throw new AssertionError(impossible);
344 }
345 return builder;
346 }
347
348 /**
349 * Returns a string containing the string representation of each entry in {@code entries}, using
350 * the previously configured separator and key-value separator.
351 *
352 * @since 10.0
353 */
354 @Beta
355 public String join(Iterable<? extends Entry<?, ?>> entries) {
356 return appendTo(new StringBuilder(), entries).toString();
357 }
358
359 /**
360 * Returns a map joiner with the same behavior as this one, except automatically substituting
361 * {@code nullText} for any provided null keys or values.
362 */
363 @CheckReturnValue
364 public MapJoiner useForNull(String nullText) {
365 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
366 }
367 }
368
369 CharSequence toString(Object part) {
370 checkNotNull(part); // checkNotNull for GWT (do not optimize).
371 return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
372 }
373
374 private static Iterable<Object> iterable(
375 final Object first, final Object second, final Object[] rest) {
376 checkNotNull(rest);
377 return new AbstractList<Object>() {
378 @Override public int size() {
379 return rest.length + 2;
380 }
381
382 @Override public Object get(int index) {
383 switch (index) {
384 case 0:
385 return first;
386 case 1:
387 return second;
388 default:
389 return rest[index - 2];
390 }
391 }
392 };
393 }
394 }