1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.utils;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.Objects;
26 import java.util.function.Function;
27 import java.util.function.Predicate;
28 import java.util.function.UnaryOperator;
29
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.commons.text.WordUtils;
32
33
34
35
36
37 public final class CasedString {
38
39 private final String[] segments;
40
41 private final StringCase stringCase;
42
43 private static final Function<String[], String> CAMEL_JOINER = strings -> {
44 StringBuilder sb = new StringBuilder();
45 Arrays.stream(strings).map(s -> s == null ? "" : s).forEach(token -> sb.append(WordUtils.capitalize(token.toLowerCase(Locale.ROOT))));
46 return sb.toString();
47 };
48
49 public static final CasedString NULL = new CasedString(StringCase.KEBAB, CasedString.StringCase.NULL_SEGMENT);
50
51
52
53
54
55
56 public CasedString(final StringCase stringCase, final String string) {
57 this.segments = string == null ? CasedString.StringCase.NULL_SEGMENT : stringCase.getSegments(string.trim());
58 this.stringCase = stringCase;
59 }
60
61
62
63
64
65
66 public CasedString(final StringCase stringCase, final String[] segments) {
67 this.segments = segments.length == 0 ? StringCase.NULL_SEGMENT : segments;
68 this.stringCase = stringCase;
69 }
70
71
72
73
74
75
76 public CasedString as(final StringCase stringCase) {
77 return stringCase.name.equals(this.stringCase.name) ? this : new CasedString(stringCase, copySegments());
78 }
79
80 private String[] copySegments() {
81 return this.segments.length == 0 ? StringCase.NULL_SEGMENT : Arrays.copyOf(this.segments, this.segments.length);
82 }
83
84 public boolean isNull() {
85 return segments.length == 0;
86 }
87
88
89
90
91
92 public String[] getSegments() {
93 return this.segments;
94 }
95
96
97
98
99
100
101 public String toCase(final StringCase stringCase) {
102 return this.segments == CasedString.StringCase.NULL_SEGMENT ? null : stringCase.assemble(this.getSegments());
103 }
104
105 @Override
106 public String toString() {
107 return this.toCase(this.stringCase);
108 }
109
110 @Override
111 public boolean equals(final Object o) {
112 if (o == null || getClass() != o.getClass()) {
113 return false;
114 }
115 CasedString that = (CasedString) o;
116 return Objects.equals(stringCase, that.stringCase) && Objects.deepEquals(getSegments(), that.getSegments());
117 }
118
119 @Override
120 public int hashCode() {
121 return Arrays.hashCode(getSegments());
122 }
123
124
125
126
127 public static final class StringCase {
128
129 public static final StringCase CAMEL;
130
131 public static final StringCase PASCAL;
132
133 public static final StringCase SNAKE;
134
135 public static final StringCase KEBAB;
136
137 public static final StringCase PHRASE;
138
139 public static final StringCase DOT;
140
141 public static final StringCase SLASH;
142
143 static final String[] NULL_SEGMENT;
144
145 static final String[] EMPTY_SEGMENT;
146
147 private final String name;
148
149
150
151
152 private final Predicate<Character> splitter;
153
154
155
156
157 private final boolean preserveSplit;
158
159 private final Function<String[], String> joiner;
160
161 private final UnaryOperator<String> postProcess;
162
163
164
165
166
167
168
169
170 public StringCase(final String name, final Predicate<Character> splitter, final boolean preserveSplit, final Function<String[], String> joiner) {
171 this(name, splitter, preserveSplit, joiner, UnaryOperator.identity());
172 }
173
174
175
176
177
178
179 public StringCase(final String name, final char delimiter) {
180 this(name, c -> c == delimiter, false, simpleJoiner(delimiter));
181 }
182
183
184
185
186
187
188
189
190
191 public StringCase(final String name, final Predicate<Character> splitter, final boolean preserveSplit, final Function<String[], String> joiner,
192 final UnaryOperator<String> postProcess) {
193 this.name = name;
194 this.splitter = splitter;
195 this.preserveSplit = preserveSplit;
196 this.joiner = joiner;
197 this.postProcess = postProcess;
198 }
199
200
201
202
203
204
205
206 public static Function<String[], String> simpleJoiner(final char delimiter) {
207 return s -> String.join(String.valueOf(delimiter), Arrays.stream(s).filter(Objects::nonNull).toArray(String[]::new));
208 }
209
210 @Override
211 public String toString() {
212 return this.name;
213 }
214
215
216
217
218
219
220 public String assemble(final String[] segments) {
221 return this.joiner.apply(segments);
222 }
223
224
225
226
227
228
229 public String[] getSegments(final String string) {
230 if (string == null) {
231 return NULL_SEGMENT;
232 } else if (string.isEmpty()) {
233 return EMPTY_SEGMENT;
234 } else {
235 List<String> lst = new ArrayList<>();
236 StringBuilder sb = new StringBuilder();
237
238 for (char c : string.toCharArray()) {
239 if (this.splitter.test(c)) {
240 lst.add(sb.toString());
241 sb.setLength(0);
242 if (this.preserveSplit) {
243 sb.append(c);
244 }
245 } else {
246 sb.append(c);
247 }
248 }
249
250 if (!sb.isEmpty()) {
251 lst.add(sb.toString());
252 }
253
254 return lst.stream().map(this.postProcess).filter(Objects::nonNull).toArray(String[]::new);
255 }
256 }
257 static {
258 CAMEL = new StringCase("CAMEL", Character::isUpperCase, true, CasedString.CAMEL_JOINER,
259 x -> (String) StringUtils.defaultIfEmpty(x, (CharSequence) null));
260 PASCAL = new StringCase("PASCAL", Character::isUpperCase, true, CasedString.CAMEL_JOINER.andThen(WordUtils::uncapitalize),
261 x -> (String) StringUtils.defaultIfEmpty(x, (CharSequence) null));
262 SNAKE = new StringCase("SNAKE", '_');
263 KEBAB = new StringCase("KEBAB", '-');
264 PHRASE = new StringCase("PHRASE", Character::isWhitespace, false, simpleJoiner(' '));
265 DOT = new StringCase("DOT", '.');
266 SLASH = new StringCase("SLASH", '/');
267 NULL_SEGMENT = new String[0];
268 EMPTY_SEGMENT = new String[]{""};
269 }
270 }
271 }