001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023
002package io.cui.tools.string;
003
004import static io.cui.tools.collect.CollectionLiterals.mutableList;
005import static io.cui.tools.collect.MoreCollections.isEmpty;
006import static io.cui.tools.string.MoreStrings.isBlank;
007import static java.util.Objects.requireNonNull;
008import java.util.ArrayList;
009import java.util.Iterator;
010import java.util.stream.Collectors;
011import io.cui.tools.logging.CuiLogger;
012import lombok.NonNull;
013
014/**
015 * Inspired by Googles Joiner.
016 * <p>
017 * It uses internally the {@link String#join(CharSequence, Iterable)} implementation
018 * of java and provides a guava like wrapper. It focuses on the simplified Joining
019 * and omits the Map based variants.
020 * </p>
021 * <h2>Usage</h2>
022 *
023 * <pre>
024 * assertEquals("key=value", Joiner.on('=').join("key", "value"));
025 * assertEquals("key=no value", Joiner.on('=').useForNull("no value").join("key", null));
026 * assertEquals("key", Joiner.on('=').skipNulls().join("key", null));
027 * assertEquals("key", Joiner.on('=').skipEmptyStrings().join("key", ""));
028 * assertEquals("key", Joiner.on('=').skipBlankStrings().join("key", " "));
029 * </pre>
030 *
031 * <h2>Migrating from Guava</h2>
032 * <p>
033 * In order to migrate for most case you only need to replace the package name on the import.
034 * </p>
035 * <h2>Changes to Guavas-Joiner</h2>
036 * <p>
037 * In case of content to be joined containing {@code null}-values and not set to skip nulls,
038 * {@link #skipNulls()} it does not throw an
039 * {@link NullPointerException} but writes "null" for each {@code null} element. You can define a
040 * different String by calling {@link #useForNull(String)}
041 * </p>
042 * <p>
043 * In addition to {@link #skipEmptyStrings()} it provides a variant {@link #skipBlankStrings()}
044 * </p>
045 *
046 * @author Oliver Wolff
047 */
048public final class Joiner {
049    private static final CuiLogger log = new CuiLogger(Joiner.class);
050    @NonNull
051    private final JoinerConfig joinerConfig;
052
053    /**
054     * Returns a Joiner that uses the given fixed string as a separator. For example, {@code
055     * Joiner.on("-").join("foo", "bar")} returns a String "foo-bar"
056     *
057     * @param separator the literal, nonempty string to recognize as a separator
058     *
059     * @return a {@link Joiner}, with default settings, that uses that separator
060     */
061    public static Joiner on(final String separator) {
062        requireNonNull(separator);
063        return new Joiner(JoinerConfig.builder().separator(separator).build());
064    }
065
066    /**
067     * Returns a Joiner that uses the given fixed string as a separator. For example, {@code
068     * Joiner.on('-').join("foo", "bar")} returns a String "foo-bar"
069     *
070     * @param separator the literal, nonempty string to recognize as a separator
071     *
072     * @return a {@link Joiner}, with default settings, that uses that separator
073     */
074    public static Joiner on(final char separator) {
075        requireNonNull(separator);
076        return on(String.valueOf(separator));
077    }
078
079    /**
080     * @param nullText to be used as substitution for {@code null} elements
081     * @return a joiner with the same behavior as this one, except automatically substituting {@code
082     * nullText} for any provided null elements.
083     */
084    public Joiner useForNull(final String nullText) {
085        return new Joiner(joinerConfig.copy().useForNull(nullText).build());
086    }
087
088    /**
089     * @return a joiner with the same behavior as this one, except automatically skipping
090     *         null-values
091     */
092    public Joiner skipNulls() {
093        return new Joiner(joinerConfig.copy().skipNulls(true).build());
094    }
095
096    /**
097     * @return a joiner with the same behavior as this one, except automatically skipping
098     *         String-values that evaluate to an empty String
099     */
100    public Joiner skipEmptyStrings() {
101        return new Joiner(joinerConfig.copy().skipEmpty(true).build());
102    }
103
104    /**
105     * @return a joiner with the same behavior as this one, except automatically skipping
106     *         String-values that evaluate to a blank String as defined within
107     *         {@link MoreStrings#isBlank(CharSequence)}
108     */
109    public Joiner skipBlankStrings() {
110        return new Joiner(joinerConfig.copy().skipBlank(true).build());
111    }
112
113    /**
114     * @param parts to be joined
115     *
116     * @return a string containing the string representation of each of {@code parts}, using the
117     *         previously configured separator between each.
118     */
119    public String join(Iterable<?> parts) {
120        return doJoin(parts);
121    }
122
123    /**
124     * @param parts to be joined
125     *
126     * @return a string containing the string representation of each of {@code parts}, using the
127     *         previously configured separator between each.
128     */
129    public String join(Iterator<?> parts) {
130        return doJoin(mutableList(parts));
131    }
132
133    /**
134     * @param parts to be joined
135     * @return a string containing the string representation of each of {@code parts}, using the
136     *         previously configured separator between each.
137     */
138    public String join(Object... parts) {
139        return doJoin(mutableList(parts));
140    }
141
142    private String doJoin(Iterable<?> parts) {
143        log.trace("Joining elements with configuration {}", joinerConfig);
144        if (isEmpty(parts)) {
145            return "";
146        }
147        var builder = new ArrayList<CharSequence>();
148        for (Object element : parts) {
149            if (null == element) {
150                if (!joinerConfig.isSkipNulls()) {
151                    builder.add(joinerConfig.getUseForNull());
152                }
153            } else if (element instanceof CharSequence) {
154                builder.add((CharSequence) element);
155            } else {
156                builder.add(MoreStrings.lenientToString(element));
157            }
158        }
159        if (joinerConfig.isSkipEmpty()) {
160            builder = builder.stream().filter(element -> !MoreStrings.isEmpty(element)).collect(Collectors.toCollection(ArrayList::new));
161        }
162        if (joinerConfig.isSkipBlank()) {
163            builder = builder.stream().filter(element -> !isBlank(element)).collect(Collectors.toCollection(ArrayList::new));
164        }
165        return String.join(joinerConfig.getSeparator(), builder);
166    }
167
168    @java.lang.SuppressWarnings("all")
169    @lombok.Generated
170    Joiner(@NonNull final JoinerConfig joinerConfig) {
171        if (joinerConfig == null) {
172            throw new java.lang.NullPointerException("joinerConfig is marked non-null but is null");
173        }
174        this.joinerConfig = joinerConfig;
175    }
176}