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 }