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, 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 * @since 2 (imported from Google Collections Library)
250 */
251 public final static class MapJoiner {
252 private final Joiner joiner;
253 private final String keyValueSeparator;
254
255 private MapJoiner(Joiner joiner, String keyValueSeparator) {
256 this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
257 this.keyValueSeparator = checkNotNull(keyValueSeparator);
258 }
259
260 /**
261 * Appends the string representation of each entry of {@code map}, using the previously
262 * configured separator and key-value separator, to {@code appendable}.
263 */
264 public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
265 checkNotNull(appendable);
266 Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator();
267 if (iterator.hasNext()) {
268 Entry<?, ?> entry = iterator.next();
269 appendable.append(joiner.toString(entry.getKey()));
270 appendable.append(keyValueSeparator);
271 appendable.append(joiner.toString(entry.getValue()));
272 while (iterator.hasNext()) {
273 appendable.append(joiner.separator);
274 Entry<?, ?> e = iterator.next();
275 appendable.append(joiner.toString(e.getKey()));
276 appendable.append(keyValueSeparator);
277 appendable.append(joiner.toString(e.getValue()));
278 }
279 }
280 return appendable;
281 }
282
283 /**
284 * Appends the string representation of each entry of {@code map}, using the previously
285 * configured separator and key-value separator, to {@code builder}. Identical to {@link
286 * #appendTo(Appendable, Map)}, except that it does not throw {@link IOException}.
287 */
288 public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
289 try {
290 appendTo((Appendable) builder, map);
291 } catch (IOException impossible) {
292 throw new AssertionError(impossible);
293 }
294 return builder;
295 }
296
297 /**
298 * Returns a string containing the string representation of each entry of {@code map}, using the
299 * previously configured separator and key-value separator.
300 */
301 public String join(Map<?, ?> map) {
302 return appendTo(new StringBuilder(), map).toString();
303 }
304
305 /**
306 * Returns a map joiner with the same behavior as this one, except automatically substituting
307 * {@code nullText} for any provided null keys or values.
308 */
309 public MapJoiner useForNull(String nullText) {
310 return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
311 }
312 }
313
314 CharSequence toString(Object part) {
315 return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
316 }
317
318 private static Iterable<Object> iterable(
319 final Object first, final Object second, final Object[] rest) {
320 checkNotNull(rest);
321 return new AbstractList<Object>() {
322 @Override public int size() {
323 return rest.length + 2;
324 }
325
326 @Override public Object get(int index) {
327 switch (index) {
328 case 0:
329 return first;
330 case 1:
331 return second;
332 default:
333 return rest[index - 2];
334 }
335 }
336 };
337 }
338 }