/* * 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 org.elasticsearch.common.inject.internal; import org.elasticsearch.common.util.CollectionUtils; import java.io.IOException; import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.Objects; /** * 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"}. * * @author Kevin Bourrillion */ 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 join(delimiter, tokens.iterator()); } /** * 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 join(delimiter, Arrays.asList(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, @Nullable Object firstToken, Object... otherTokens) { Objects.requireNonNull(otherTokens); return join(delimiter, CollectionUtils.asArrayList(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 join(new StringBuilder(), keyValueSeparator, entryDelimiter, map) .toString(); } /** * 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) { return join(appendable, delimiter, tokens.iterator()); } /** * 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, @Nullable Object firstToken, Object... otherTokens) { Objects.requireNonNull(otherTokens); return join(appendable, delimiter, CollectionUtils.asArrayList(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) { /* This method is the workhorse of the class */ Objects.requireNonNull(appendable); Objects.requireNonNull(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) { Objects.requireNonNull(appendable); Objects.requireNonNull(keyValueSeparator); Objects.requireNonNull(entryDelimiter); Iterator<? extends Map.Entry<?, ?>> entries = map.entrySet().iterator(); if (entries.hasNext()) { try { appendOneEntry(appendable, keyValueSeparator, entries.next()); while (entries.hasNext()) { appendable.append(entryDelimiter); appendOneEntry(appendable, keyValueSeparator, entries.next()); } } catch (IOException e) { throw new JoinException(e); } } return appendable; } private static void appendOneEntry( Appendable appendable, String keyValueSeparator, Map.Entry<?, ?> entry) throws IOException { appendOneToken(appendable, entry.getKey()); appendable.append(keyValueSeparator); appendOneToken(appendable, entry.getValue()); } 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); } } }