/* * Copyright (C) 2007 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.gdata.util.common.base; import static com.google.gdata.util.common.base.Preconditions.checkNotNull; import com.google.common.annotations.GwtCompatible; import java.io.IOException; import java.util.AbstractList; import java.util.Arrays; import java.util.Iterator; import java.util.Map; /** * Utility for joining pieces of text separated by a delimiter. It can handle * iterators, collections, arrays, and varargs, and can append to any * {@link Appendable} or just return a {@link String}. For example, * {@code join(":", "a", "b", "c")} returns {@code "a:b:c"}. * * <p>All methods of this class throw {@link NullPointerException} when a value * of {@code null} is supplied for any parameter. The elements within the * collection, iterator, array, or varargs parameter list <i>may</i> be null -- * these will be represented in the output by the string {@code "null"}. * * */ @GwtCompatible public final class Join { private Join() {} /** * Returns a string containing the {@code tokens}, converted to strings if * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it * returns an empty string. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param delimiter a string to append between every element, but not at the * beginning or end * @param tokens objects to append * @return a string consisting of the joined elements */ public static String join(String delimiter, Iterable<?> tokens) { return Joiner.on(delimiter).useForNull("null").join(tokens); } /** * Returns a string containing the {@code tokens}, converted to strings if * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it * returns an empty string. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param delimiter a string to append between every element, but not at the * beginning or end * @param tokens objects to append * @return a string consisting of the joined elements */ public static String join(String delimiter, Object[] tokens) { return Joiner.on(delimiter).useForNull("null").join(tokens); } /** * Returns a string containing the {@code tokens}, converted to strings if * necessary, separated by {@code delimiter}. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param delimiter a string to append between every element, but not at the * beginning or end * @param firstToken the first object to append * @param otherTokens subsequent objects to append * @return a string consisting of the joined elements */ public static String join( String delimiter, Object firstToken, Object... otherTokens) { return Joiner.on(delimiter).useForNull("null").join( iterable(firstToken, otherTokens)); } /** * Returns a string containing the {@code tokens}, converted to strings if * necessary, separated by {@code delimiter}. If {@code tokens} is empty, it * returns an empty string. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param delimiter a string to append between every element, but not at the * beginning or end * @param tokens objects to append * @return a string consisting of the joined elements */ public static String join(String delimiter, Iterator<?> tokens) { StringBuilder sb = new StringBuilder(); join(sb, delimiter, tokens); return sb.toString(); } /** * Returns a string containing the contents of {@code map}, with entries * separated by {@code entryDelimiter}, and keys and values separated with * {@code keyValueSeparator}. * * <p>Each key and value will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param keyValueSeparator a string to append between every key and its * associated value * @param entryDelimiter a string to append between every entry, but not at * the beginning or end * @param map the map containing the data to join * @return a string consisting of the joined entries of the map; empty if the * map is empty */ public static String join( String keyValueSeparator, String entryDelimiter, Map<?, ?> map) { return Joiner .on(entryDelimiter) .useForNull("null") .withKeyValueSeparator(keyValueSeparator) .join(map); } /** * Appends each of the {@code tokens} to {@code appendable}, separated by * {@code delimiter}. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param appendable the object to append the results to * @param delimiter a string to append between every element, but not at the * beginning or end * @param tokens objects to append * @return the same {@code Appendable} instance that was passed in * @throws JoinException if an {@link IOException} occurs */ public static <T extends Appendable> T join( T appendable, String delimiter, Iterable<?> tokens) { try { return Joiner.on(delimiter).useForNull("null") .appendTo(appendable, tokens); } catch (IOException e) { throw new JoinException(e); } } /** * Appends each of the {@code tokens} to {@code appendable}, separated by * {@code delimiter}. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param appendable the object to append the results to * @param delimiter a string to append between every element, but not at the * beginning or end * @param tokens objects to append * @return the same {@code Appendable} instance that was passed in * @throws JoinException if an {@link IOException} occurs */ public static <T extends Appendable> T join( T appendable, String delimiter, Object[] tokens) { return join(appendable, delimiter, Arrays.asList(tokens)); } /** * Appends each of the {@code tokens} to {@code appendable}, separated by * {@code delimiter}. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param appendable the object to append the results to * @param delimiter a string to append between every element, but not at the * beginning or end * @param firstToken the first object to append * @param otherTokens subsequent objects to append * @return the same {@code Appendable} instance that was passed in * @throws JoinException if an {@link IOException} occurs */ public static <T extends Appendable> T join(T appendable, String delimiter, Object firstToken, Object... otherTokens) { return join(appendable, delimiter, iterable(firstToken, otherTokens)); } /** * Appends each of the {@code tokens} to {@code appendable}, separated by * {@code delimiter}. * * <p>Each token will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param appendable the object to append the results to * @param delimiter a string to append between every element, but not at the * beginning or end * @param tokens objects to append * @return the same {@code Appendable} instance that was passed in * @throws JoinException if an {@link IOException} occurs */ public static <T extends Appendable> T join( T appendable, String delimiter, Iterator<?> tokens) { checkNotNull(appendable); checkNotNull(delimiter); if (tokens.hasNext()) { try { appendOneToken(appendable, tokens.next()); while (tokens.hasNext()) { appendable.append(delimiter); appendOneToken(appendable, tokens.next()); } } catch (IOException e) { throw new JoinException(e); } } return appendable; } /** * Appends the contents of {@code map} to {@code appendable}, with entries * separated by {@code entryDelimiter}, and keys and values separated with * {@code keyValueSeparator}. * * <p>Each key and value will be converted to a {@link CharSequence} using * {@link String#valueOf(Object)}, if it isn't a {@link CharSequence} already. * Note that this implies that null tokens will be appended as the * four-character string {@code "null"}. * * @param appendable the object to append the results to * @param keyValueSeparator a string to append between every key and its * associated value * @param entryDelimiter a string to append between every entry, but not at * the beginning or end * @param map the map containing the data to join * @return the same {@code Appendable} instance that was passed in */ public static <T extends Appendable> T join(T appendable, String keyValueSeparator, String entryDelimiter, Map<?, ?> map) { try { return Joiner.on(entryDelimiter).useForNull("null") .withKeyValueSeparator(keyValueSeparator).appendTo(appendable, map); } catch (IOException e) { throw new JoinException(e); } } private static void appendOneToken(Appendable appendable, Object token) throws IOException { appendable.append(toCharSequence(token)); } private static CharSequence toCharSequence(Object token) { return (token instanceof CharSequence) ? (CharSequence) token : String.valueOf(token); } /** * Exception thrown in response to an {@link IOException} from the supplied * {@link Appendable}. This is used because most callers won't want to * worry about catching an IOException. */ public static class JoinException extends RuntimeException { private JoinException(IOException cause) { super(cause); } private static final long serialVersionUID = 1L; } /** * Duplicate of * {@link com.google.common.collect.Lists#asList(Object, Object[])}, copied * here to remove dependencies. */ private static Iterable<Object> iterable( final Object first, final Object[] rest) { checkNotNull(rest); return new AbstractList<Object>() { @Override public int size() { return rest.length + 1; } @Override public Object get(int index) { return (index == 0) ? first : rest[index - 1]; } }; } }