001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.lang3;
019
020import java.io.IOException;
021import java.util.Iterator;
022import java.util.StringJoiner;
023import java.util.function.Supplier;
024
025import org.apache.commons.lang3.exception.UncheckedException;
026import org.apache.commons.lang3.function.FailableBiConsumer;
027
028/**
029 * Joins an array or {@link Iterable} into an existing {@link Appendable} like a {@link StringBuilder}; with the goal for call sites to avoid creating
030 * intermediary Strings. This is like {@link String#join(CharSequence, CharSequence...)}, {@link String#join(CharSequence, Iterable)}, and {@link StringJoiner}.
031 * <p>
032 * Keep an instance in a (static) variable for efficient joining into an {@link Appendable} or {@link StringBuilder} without creating temporary Strings.
033 * </p>
034 * <p>
035 * Use the builder and instance methods to reuse the same kind of joining prefix, suffix, delimiter, and string conversion.
036 * </p>
037 * <p>
038 * For example:
039 * </p>
040 *
041 * <pre>{@code
042 * // A reuseable instance
043 * private static final AppendableJoiner<Object> JOINER = AppendableJoiner.builder()
044 *     .setPrefix("[")
045 *     .setSuffix("]")
046 *     .setDelimiter(", ")
047 *     .get();
048 * }
049 * ...
050 * // Builds straight into a StringBuilder:
051 * StringBuilder sbuilder = new StringBuilder("1");
052 * JOINER.join(sbuilder, "A", "B");
053 * sbuilder.append("2");
054 * JOINER.join(sbuilder, "C", "D");
055 * sbuilder.append("3");
056 * // Returns "1[A, B]2[C, D]3"
057 * return sbuilder.toString();
058 * }</pre>
059 * <p>
060 * To provide a custom Object element to {@link CharSequence} converter, call {@link Builder#setElementAppender(FailableBiConsumer)}, for example:
061 * </p>
062 *
063 * <pre>{@code
064 * private static final AppendableJoiner<Item> JOINER = AppendableJoiner.builder()
065 *     .setElementAppender(e -> (a, e) -> a.append(e.getFoo())
066 *                                        a.append(e.getBar())
067 *                                        a.append('!'))
068 *     ...
069 *     .get();
070 * }
071 * }</pre>
072 * <p>
073 * This class is immutable and thread-safe.
074 * </p>
075 *
076 * @param <T> the type of elements to join.
077 * @see Appendable
078 * @see StringBuilder
079 * @see String#join(CharSequence, CharSequence...)
080 * @see String#join(CharSequence, Iterable)
081 * @see StringJoiner
082 * @since 3.15.0
083 */
084public final class AppendableJoiner<T> {
085
086    /**
087     * Builds instances of {@link AppendableJoiner}.
088     *
089     * @param <T> the type of elements to join.
090     */
091    public static final class Builder<T> implements Supplier<AppendableJoiner<T>> {
092
093        /** The sequence of characters to be used at the beginning. */
094        private CharSequence prefix;
095
096        /** The sequence of characters to be used at the end. */
097        private CharSequence suffix;
098
099        /** The delimiter that separates each element. */
100        private CharSequence delimiter;
101
102        /** The consumer used to render each element of type {@code T} onto an {@link Appendable}. */
103        private FailableBiConsumer<Appendable, T, IOException> appender;
104
105        /**
106         * Constructs a new instance.
107         */
108        Builder() {
109            // empty
110        }
111
112        /**
113         * Gets a new instance of {@link AppendableJoiner}.
114         */
115        @Override
116        public AppendableJoiner<T> get() {
117            return new AppendableJoiner<>(prefix, suffix, delimiter, appender);
118        }
119
120        /**
121         * Sets the delimiter that separates each element.
122         *
123         * @param delimiter The delimiter that separates each element.
124         * @return this instance.
125         */
126        public Builder<T> setDelimiter(final CharSequence delimiter) {
127            this.delimiter = delimiter;
128            return this;
129        }
130
131        /**
132         * Sets the consumer used to render each element of type {@code T} onto an {@link Appendable}.
133         *
134         * @param appender The consumer used to render each element of type {@code T} onto an {@link Appendable}.
135         * @return this instance.
136         */
137        public Builder<T> setElementAppender(final FailableBiConsumer<Appendable, T, IOException> appender) {
138            this.appender = appender;
139            return this;
140        }
141
142        /**
143         * Sets the sequence of characters to be used at the beginning.
144         *
145         * @param prefix The sequence of characters to be used at the beginning.
146         * @return this instance.
147         */
148        public Builder<T> setPrefix(final CharSequence prefix) {
149            this.prefix = prefix;
150            return this;
151        }
152
153        /**
154         * Sets the sequence of characters to be used at the end.
155         *
156         * @param suffix The sequence of characters to be used at the end.
157         * @return this instance.
158         */
159        public Builder<T> setSuffix(final CharSequence suffix) {
160            this.suffix = suffix;
161            return this;
162        }
163
164    }
165
166    /**
167     * Creates a new builder.
168     *
169     * @param <T> The type of elements.
170     * @return a new builder.
171     */
172    public static <T> Builder<T> builder() {
173        return new Builder<>();
174    }
175
176    /** Could be public in the future, in some form. */
177    @SafeVarargs
178    static <A extends Appendable, T> A joinA(final A appendable, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter,
179            final FailableBiConsumer<Appendable, T, IOException> appender, final T... elements) throws IOException {
180        return joinArray(appendable, prefix, suffix, delimiter, appender, elements);
181    }
182
183    private static <A extends Appendable, T> A joinArray(final A appendable, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter,
184            final FailableBiConsumer<Appendable, T, IOException> appender, final T[] elements) throws IOException {
185        appendable.append(prefix);
186        if (elements != null) {
187            if (elements.length > 0) {
188                appender.accept(appendable, elements[0]);
189            }
190            for (int i = 1; i < elements.length; i++) {
191                appendable.append(delimiter);
192                appender.accept(appendable, elements[i]);
193            }
194        }
195        appendable.append(suffix);
196        return appendable;
197    }
198
199    /** Could be public in the future, in some form. */
200    static <T> StringBuilder joinI(final StringBuilder stringBuilder, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter,
201            final FailableBiConsumer<Appendable, T, IOException> appender, final Iterable<T> elements) {
202        try {
203            return joinIterable(stringBuilder, prefix, suffix, delimiter, appender, elements);
204        } catch (final IOException e) {
205            // Cannot happen with a StringBuilder.
206            throw new UncheckedException(e);
207        }
208    }
209
210    private static <A extends Appendable, T> A joinIterable(final A appendable, final CharSequence prefix, final CharSequence suffix,
211            final CharSequence delimiter, final FailableBiConsumer<Appendable, T, IOException> appender, final Iterable<T> elements) throws IOException {
212        appendable.append(prefix);
213        if (elements != null) {
214            final Iterator<T> iterator = elements.iterator();
215            if (iterator.hasNext()) {
216                appender.accept(appendable, iterator.next());
217            }
218            while (iterator.hasNext()) {
219                appendable.append(delimiter);
220                appender.accept(appendable, iterator.next());
221            }
222        }
223        appendable.append(suffix);
224        return appendable;
225    }
226
227    /** Could be public in the future, in some form. */
228    @SafeVarargs
229    static <T> StringBuilder joinSB(final StringBuilder stringBuilder, final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter,
230            final FailableBiConsumer<Appendable, T, IOException> appender, final T... elements) {
231        try {
232            return joinArray(stringBuilder, prefix, suffix, delimiter, appender, elements);
233        } catch (final IOException e) {
234            // Cannot happen with a StringBuilder.
235            throw new UncheckedException(e);
236        }
237    }
238
239    private static CharSequence nonNull(final CharSequence value) {
240        return value != null ? value : StringUtils.EMPTY;
241    }
242
243    /** The sequence of characters to be used at the beginning. */
244    private final CharSequence prefix;
245
246    /** The sequence of characters to be used at the end. */
247    private final CharSequence suffix;
248
249    /** The delimiter that separates each element. */
250    private final CharSequence delimiter;
251
252    private final FailableBiConsumer<Appendable, T, IOException> appender;
253
254    /**
255     * Constructs a new instance.
256     */
257    private AppendableJoiner(final CharSequence prefix, final CharSequence suffix, final CharSequence delimiter,
258            final FailableBiConsumer<Appendable, T, IOException> appender) {
259        this.prefix = nonNull(prefix);
260        this.suffix = nonNull(suffix);
261        this.delimiter = nonNull(delimiter);
262        this.appender = appender != null ? appender : (a, e) -> a.append(String.valueOf(e));
263    }
264
265    /**
266     * Joins stringified objects from the given Iterable into a StringBuilder.
267     *
268     * @param stringBuilder The target.
269     * @param elements      The source.
270     * @return The given StringBuilder.
271     */
272    public StringBuilder join(final StringBuilder stringBuilder, final Iterable<T> elements) {
273        return joinI(stringBuilder, prefix, suffix, delimiter, appender, elements);
274    }
275
276    /**
277     * Joins stringified objects from the given array into a StringBuilder.
278     *
279     * @param stringBuilder The target.
280     * @param elements      The source.
281     * @return the given target StringBuilder.
282     */
283    public StringBuilder join(final StringBuilder stringBuilder, @SuppressWarnings("unchecked") final T... elements) {
284        return joinSB(stringBuilder, prefix, suffix, delimiter, appender, elements);
285    }
286
287    /**
288     * Joins stringified objects from the given Iterable into an Appendable.
289     *
290     * @param <A>        the Appendable type.
291     * @param appendable The target.
292     * @param elements   The source.
293     * @return The given StringBuilder.
294     * @throws IOException If an I/O error occurs
295     */
296    public <A extends Appendable> A joinA(final A appendable, final Iterable<T> elements) throws IOException {
297        return joinIterable(appendable, prefix, suffix, delimiter, appender, elements);
298    }
299
300    /**
301     * Joins stringified objects from the given array into an Appendable.
302     *
303     * @param <A>        the Appendable type.
304     * @param appendable The target.
305     * @param elements   The source.
306     * @return The given StringBuilder.
307     * @throws IOException If an I/O error occurs
308     */
309    public <A extends Appendable> A joinA(final A appendable, @SuppressWarnings("unchecked") final T... elements) throws IOException {
310        return joinA(appendable, prefix, suffix, delimiter, appender, elements);
311    }
312
313}