001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023 002package io.cui.tools.string; 003 004import static io.cui.tools.base.Preconditions.checkArgument; 005import static io.cui.tools.string.MoreStrings.isEmpty; 006import static io.cui.tools.string.MoreStrings.requireNotEmpty; 007import static java.util.Objects.requireNonNull; 008import java.util.Collections; 009import java.util.List; 010import java.util.regex.Pattern; 011import io.cui.tools.collect.CollectionBuilder; 012import io.cui.tools.logging.CuiLogger; 013import lombok.NonNull; 014 015/** 016 * Derived from Googles Splitter. 017 * <p> 018 * It uses internally the {@link String#split(String)} implementation 019 * of java and provides a guava like wrapper. This results in an implicit splitting of the whole 020 * String compared to the lazy / deferred splitting of guava. It focuses and RegEx-based splitting 021 * and omits the fixedLength and Map based variants. 022 * </p> 023 * <h2>Migrating from Guava</h2> 024 * <p> 025 * In order to migrate for most case you only need to replace the package name on the import. A 026 * major different is that the split method provided is {@link #splitToList(String)}, the variant 027 * split() is replaced by it completely. 028 * </p> 029 * <h2>Changes to Guavas-Splitter</h2> 030 * <p> 031 * It is quite similar to the guava variant but behaves a little 032 * different in certain details, especially in the context of {@link Splitter#limit(int)} and 033 * {@link Splitter#trimResults()}, {@link Splitter#omitEmptyStrings()}. 034 * While guavas version will filter the limit elements after the 035 * application of omit / trim, this version will do it the other way round, resulting in a different 036 * result compared to the guava version. 037 * </p> 038 * 039 * @author Oliver Wolff 040 */ 041public final class Splitter { 042 private static final CuiLogger log = new CuiLogger(Splitter.class); 043 @NonNull 044 private final SplitterConfig splitterConfig; 045 046 /** 047 * Returns a splitter that uses the given fixed string as a separator. For example, {@code 048 * Splitter.on(", ").split("foo, bar,baz")} returns an iterable containing {@code ["foo", 049 * "bar,baz"]}. 050 * 051 * @param separator the literal, nonempty string to recognize as a separator 052 * 053 * @return a splitter, with default settings, that recognizes that separator 054 */ 055 public static Splitter on(final String separator) { 056 requireNotEmpty(separator); 057 return new Splitter(SplitterConfig.builder().separator(separator).build()); 058 } 059 060 /** 061 * Returns a splitter that uses the given fixed string as a separator. For example, {@code 062 * Splitter.on(", ").split("foo, bar,baz")} returns an iterable containing {@code ["foo", 063 * "bar,baz"]}. 064 * 065 * @param separator the literal, nonempty string to recognize as a separator 066 * 067 * @return a splitter, with default settings, that recognizes that separator 068 */ 069 public static Splitter on(final char separator) { 070 requireNonNull(separator); 071 return new Splitter(SplitterConfig.builder().separator(String.valueOf(separator)).build()); 072 } 073 074 /** 075 * Returns a splitter that behaves equivalently to {@code this} splitter, but automatically 076 * omits empty strings from the results. For example, {@code 077 * Splitter.on(',').omitEmptyStrings().split(",a,,,b,c,,")} returns an iterable containing only 078 * {@code ["a", "b", "c"]}. 079 * 080 * <p> 081 * If either {@code trimResults} option is also specified when creating a splitter, that 082 * splitter always trims results first before checking for emptiness. So, for example, {@code 083 * Splitter.on(':').omitEmptyStrings().trimResults().split(": : : ")} returns an empty iterable. 084 * <p> 085 * 086 * @return a splitter with the desired configuration 087 */ 088 public Splitter omitEmptyStrings() { 089 return new Splitter(splitterConfig.copy().omitEmptyStrings(true).build()); 090 } 091 092 /** 093 * Usually the separator will be pre-processed before being passed to 094 * {@link String#split(String)}. This is needed to mask special characters that will harm 095 * {@link Pattern#compile(String)}. If you want to disable this behavior and take care for your 096 * self you can change this method by calling this method. 097 * 098 * @return a splitter with the desired configuration 099 */ 100 public Splitter doNotModifySeparatorString() { 101 return new Splitter(splitterConfig.copy().doNotModifySeparatorString(true).build()); 102 } 103 104 /** 105 * Returns a splitter that behaves equivalently to {@code this} splitter, but automatically 106 * removes leading and trailing whitespace from each 107 * returned substring. For example, 108 * {@code Splitter.on(',').trimResults().split(" a, b ,c ")} returns an iterable containing 109 * {@code ["a", "b", "c"]}. 110 * 111 * @return a splitter with the desired configuration 112 */ 113 public Splitter trimResults() { 114 return new Splitter(splitterConfig.copy().trimResults(true).build()); 115 } 116 117 /** 118 * Returns a splitter that behaves equivalently to {@code this} splitter but stops splitting 119 * after it reaches the limit. The limit defines the maximum number of items returned by the 120 * iterator, or the maximum size of the list returned by {@link #splitToList}. 121 * 122 * <p> 123 * For example, {@code Splitter.on(',').limit(3).split("a,b,c,d")} returns an iterable 124 * containing {@code ["a", "b", "c,d"]}. When omitting empty strings, the omitted strings do not 125 * count. Hence, {@code Splitter.on(',').limit(3).omitEmptyStrings().split("a,,,b,,,c,d")} 126 * returns an iterable containing {@code ["a", "b", "c,d"}. When trim is requested, all entries 127 * are trimmed, including the last. Hence 128 * {@code Splitter.on(',').limit(3).trimResults().split(" a , b 129 * , c , d ")} results in {@code ["a", "b", "c , d"]}. 130 * 131 * @param maxItems the maximum number of items returned 132 * @return a splitter with the desired configuration 133 */ 134 public Splitter limit(int maxItems) { 135 checkArgument(maxItems > 0, "must be greater than zero: %s"); 136 return new Splitter(splitterConfig.copy().maxItems(maxItems).build()); 137 } 138 139 /** 140 * Splits {@code sequence} into string components and returns them as an immutable list. 141 * 142 * @param sequence the sequence of characters to split 143 * 144 * @return an immutable list of the segments split from the parameter 145 */ 146 public List<String> splitToList(String sequence) { 147 log.trace("Splitting String {} with configuration {}", sequence, splitterConfig); 148 if (isEmpty(sequence)) { 149 return Collections.emptyList(); 150 } 151 var splitted = sequence.split(handleSplitCharacter(splitterConfig.getSeparator()), splitterConfig.getMaxItems()); 152 if (null == splitted || 0 == splitted.length) { 153 log.trace("No content to be returned for input {} and configuration {}", sequence, splitterConfig); 154 return Collections.emptyList(); 155 } 156 var builder = new CollectionBuilder<String>(); 157 for (String element : splitted) { 158 addIfApplicable(builder, element); 159 } 160 return builder.toImmutableList(); 161 } 162 163 private String handleSplitCharacter(String separator) { 164 if (splitterConfig.isDoNotModifySeparatorString()) { 165 return separator; 166 } 167 return Pattern.quote(separator); 168 } 169 170 private void addIfApplicable(CollectionBuilder<String> builder, String element) { 171 if (null == element) { 172 return; 173 } 174 var toDo = element; 175 if (splitterConfig.isTrimResults()) { 176 toDo = toDo.trim(); 177 } 178 if (!splitterConfig.isOmitEmptyStrings()) { 179 builder.add(toDo); 180 return; 181 } 182 if (!toDo.isEmpty()) { 183 // Omit empty strings 184 builder.add(toDo); 185 } 186 } 187 188 @java.lang.SuppressWarnings("all") 189 @lombok.Generated 190 Splitter(@NonNull final SplitterConfig splitterConfig) { 191 if (splitterConfig == null) { 192 throw new java.lang.NullPointerException("splitterConfig is marked non-null but is null"); 193 } 194 this.splitterConfig = splitterConfig; 195 } 196}