1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.text;
18
19 import java.util.HashSet;
20 import java.util.Locale;
21 import java.util.Set;
22 import java.util.function.IntUnaryOperator;
23 import java.util.function.Predicate;
24 import java.util.regex.Matcher;
25 import java.util.regex.Pattern;
26
27 import org.apache.commons.lang3.ArrayUtils;
28 import org.apache.commons.lang3.StringUtils;
29 import org.apache.commons.lang3.Strings;
30 import org.apache.commons.lang3.Validate;
31
32 /**
33 * Operations on Strings that contain words.
34 *
35 * <p>
36 * This class tries to handle {@code null} input gracefully. An exception will not be thrown for a
37 * {@code null} input. Each method documents its behavior in more detail.
38 * </p>
39 *
40 * @since 1.1
41 */
42 public class WordUtils {
43
44 /**
45 * Abbreviates the words nicely.
46 * <p>
47 * This method searches for the first space after the lower limit and abbreviates the String there. It will also append any String passed as a parameter to
48 * the end of the String. The upper limit can be specified to forcibly abbreviate a String.
49 * </p>
50 *
51 * <pre>
52 * WordUtils.abbreviate("Now is the time for all good men", 0, 40, null)); = "Now"
53 * WordUtils.abbreviate("Now is the time for all good men", 10, 40, null)); = "Now is the"
54 * WordUtils.abbreviate("Now is the time for all good men", 20, 40, null)); = "Now is the time for all"
55 * WordUtils.abbreviate("Now is the time for all good men", 0, 40, "")); = "Now"
56 * WordUtils.abbreviate("Now is the time for all good men", 10, 40, "")); = "Now is the"
57 * WordUtils.abbreviate("Now is the time for all good men", 20, 40, "")); = "Now is the time for all"
58 * WordUtils.abbreviate("Now is the time for all good men", 0, 40, " ...")); = "Now ..."
59 * WordUtils.abbreviate("Now is the time for all good men", 10, 40, " ...")); = "Now is the ..."
60 * WordUtils.abbreviate("Now is the time for all good men", 20, 40, " ...")); = "Now is the time for all ..."
61 * WordUtils.abbreviate("Now is the time for all good men", 0, -1, "")); = "Now"
62 * WordUtils.abbreviate("Now is the time for all good men", 10, -1, "")); = "Now is the"
63 * WordUtils.abbreviate("Now is the time for all good men", 20, -1, "")); = "Now is the time for all"
64 * WordUtils.abbreviate("Now is the time for all good men", 50, -1, "")); = "Now is the time for all good men"
65 * WordUtils.abbreviate("Now is the time for all good men", 1000, -1, "")); = "Now is the time for all good men"
66 * WordUtils.abbreviate("Now is the time for all good men", 9, -10, null)); = Throws {@link IllegalArgumentException}
67 * WordUtils.abbreviate("Now is the time for all good men", 10, 5, null)); = Throws {@link IllegalArgumentException}
68 * </pre>
69 *
70 * @param str The string to be abbreviated. If null is passed, null is returned. If the empty String is passed, the empty string is returned.
71 * @param lower The lower limit; negative value is treated as zero.
72 * @param upper The upper limit; specify -1 if no limit is desired. The upper limit cannot be lower than the lower limit.
73 * @param appendToEnd The String to be appended to the end of the abbreviated string. This is appended ONLY if the string was indeed abbreviated. The append
74 * does not count towards the lower or upper limits.
75 * @return The abbreviated String.
76 */
77 public static String abbreviate(final String str, int lower, int upper, final String appendToEnd) {
78 Validate.isTrue(upper >= -1, "upper value cannot be less than -1");
79 Validate.isTrue(upper >= lower || upper == -1, "upper value is less than lower value");
80 if (StringUtils.isEmpty(str)) {
81 return str;
82 }
83 // if the lower value is greater than the length of the string,
84 // set to the length of the string
85 if (lower > str.length()) {
86 lower = str.length();
87 }
88 // if the upper value is -1 (i.e. no limit) or is greater
89 // than the length of the string, set to the length of the string
90 if (upper == -1 || upper > str.length()) {
91 upper = str.length();
92 }
93 final StringBuilder result = new StringBuilder();
94 final int index = Strings.CS.indexOf(str, " ", lower);
95 if (index == -1) {
96 result.append(str, 0, upper);
97 // only if abbreviation has occurred do we append the appendToEnd value
98 if (upper != str.length()) {
99 result.append(StringUtils.defaultString(appendToEnd));
100 }
101 } else {
102 result.append(str, 0, Math.min(index, upper));
103 result.append(StringUtils.defaultString(appendToEnd));
104 }
105 return result.toString();
106 }
107
108 /**
109 * Applies a function to the first character of each word in a String.
110 * <p>
111 * This is used by {@link #capitalize(String, char...)} and {@link #uncapitalize(String, char...)}. The {@code transform} function is applied to the first
112 * code point of each word; all other code points are passed through unchanged.
113 * </p>
114 *
115 * @param str The String to transform, may be null.
116 * @param delimiters The set of characters to determine word boundaries, null means whitespace.
117 * @param transform The casing function to apply to the first code point of each word (e.g., {@code Character::toTitleCase} or
118 * {@code Character::toLowerCase}).
119 * @return The transformed String, or {@code null}/{@code ""} if the input is null/empty.
120 */
121 private static String applyWordCaseTransform(final String str, final char[] delimiters, final IntUnaryOperator transform) {
122 if (StringUtils.isEmpty(str)) {
123 return str;
124 }
125 final Predicate<Integer> isDelimiter = generateIsDelimiterFunction(delimiters);
126 final int strLen = str.length();
127 final int[] newCodePoints = new int[strLen];
128 int outOffset = 0;
129 boolean transformNext = true;
130 for (int index = 0; index < strLen;) {
131 final int codePoint = str.codePointAt(index);
132 if (isDelimiter.test(codePoint)) {
133 transformNext = true;
134 newCodePoints[outOffset++] = codePoint;
135 index += Character.charCount(codePoint);
136 } else if (transformNext) {
137 final int transformed = transform.applyAsInt(codePoint);
138 newCodePoints[outOffset++] = transformed;
139 index += Character.charCount(transformed);
140 transformNext = false;
141 } else {
142 newCodePoints[outOffset++] = codePoint;
143 index += Character.charCount(codePoint);
144 }
145 }
146 return new String(newCodePoints, 0, outOffset);
147 }
148
149 /**
150 * Capitalizes all the whitespace separated words in a String. Only the first character of each word is changed. To convert the rest of each word to
151 * lowercase at the same time, use {@link #capitalizeFully(String)}.
152 * <p>
153 * Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String returns {@code null}. Capitalization uses the Unicode title
154 * case, normally equivalent to upper case.
155 * </p>
156 *
157 * <pre>
158 * WordUtils.capitalize(null) = null
159 * WordUtils.capitalize("") = ""
160 * WordUtils.capitalize("i am FINE") = "I Am FINE"
161 * </pre>
162 *
163 * @param str The String to capitalize, may be null.
164 * @return A new Capitalized String, or {@code null} if null String input.
165 * @see #uncapitalize(String)
166 * @see #capitalizeFully(String)
167 */
168 public static String capitalize(final String str) {
169 return capitalize(str, null);
170 }
171
172 /**
173 * Capitalizes all the delimiter separated words in a String. Only the first character of each word is changed. To convert the rest of each word to
174 * lowercase at the same time, use {@link #capitalizeFully(String, char[])}.
175 * <p>
176 * The delimiters represent a set of characters understood to separate words. The first string character and the first non-delimiter character after a
177 * delimiter will be capitalized.
178 * </p>
179 * <p>
180 * A {@code null} input String returns {@code null}. Capitalization uses the Unicode title case, normally equivalent to upper case.
181 * </p>
182 *
183 * <pre>
184 * WordUtils.capitalize(null, *) = null
185 * WordUtils.capitalize("", *) = ""
186 * WordUtils.capitalize(*, new char[0]) = *
187 * WordUtils.capitalize("i am fine", null) = "I Am Fine"
188 * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
189 * WordUtils.capitalize("i am fine", new char[]{}) = "I am fine"
190 * </pre>
191 *
192 * @param str The String to capitalize, may be null.
193 * @param delimiters The Set of characters to determine capitalization, null means whitespace.
194 * @return A new Capitalized String, or {@code null} if null String input.
195 * @see #uncapitalize(String)
196 * @see #capitalizeFully(String)
197 */
198 public static String capitalize(final String str, final char... delimiters) {
199 return applyWordCaseTransform(str, delimiters, Character::toTitleCase);
200 }
201
202 /**
203 * Converts all the whitespace separated words in a String into capitalized words,
204 * that is each word is made up of a titlecase character and then a series of
205 * lowercase characters.
206 *
207 * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
208 * A {@code null} input String returns {@code null}.
209 * Capitalization uses the Unicode title case, normally equivalent to
210 * upper case.</p>
211 *
212 * <pre>
213 * WordUtils.capitalizeFully(null) = null
214 * WordUtils.capitalizeFully("") = ""
215 * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
216 * </pre>
217 *
218 * @param str The String to capitalize, may be null.
219 * @return A new capitalized String, or {@code null} if null String input.
220 */
221 public static String capitalizeFully(final String str) {
222 return capitalizeFully(str, null);
223 }
224
225 /**
226 * Converts all the delimiter separated words in a String into capitalized words,
227 * that is each word is made up of a titlecase character and then a series of
228 * lowercase characters.
229 *
230 * <p>The delimiters represent a set of characters understood to separate words.
231 * The first string character and the first non-delimiter character after a
232 * delimiter will be capitalized.</p>
233 *
234 * <p>A {@code null} input String returns {@code null}.
235 * Capitalization uses the Unicode title case, normally equivalent to
236 * upper case.</p>
237 *
238 * <pre>
239 * WordUtils.capitalizeFully(null, *) = null
240 * WordUtils.capitalizeFully("", *) = ""
241 * WordUtils.capitalizeFully(*, null) = *
242 * WordUtils.capitalizeFully(*, new char[0]) = *
243 * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
244 * </pre>
245 *
246 * @param str The String to capitalize, may be null.
247 * @param delimiters The Set of characters to determine capitalization, null means whitespace.
248 * @return A new capitalized String, or {@code null} if null String input.
249 */
250 public static String capitalizeFully(final String str, final char... delimiters) {
251 return StringUtils.isEmpty(str) ? str : capitalize(str.toLowerCase(Locale.ROOT), delimiters);
252 }
253
254 /**
255 * Checks if the String contains all words in the given array.
256 *
257 * <p>
258 * A {@code null} String will return {@code false}. A {@code null}, zero
259 * length search array or if one element of array is null will return {@code false}.
260 * </p>
261 *
262 * <pre>
263 * WordUtils.containsAllWords(null, *) = false
264 * WordUtils.containsAllWords("", *) = false
265 * WordUtils.containsAllWords(*, null) = false
266 * WordUtils.containsAllWords(*, []) = false
267 * WordUtils.containsAllWords("abcd", "ab", "cd") = false
268 * WordUtils.containsAllWords("abc def", "def", "abc") = true
269 * </pre>
270 *
271 * @param word The CharSequence to check, may be null.
272 * @param words The array of String words to search for, may be null.
273 * @return {@code true} if all search words are found, {@code false} otherwise.
274 */
275 public static boolean containsAllWords(final CharSequence word, final CharSequence... words) {
276 if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) {
277 return false;
278 }
279 for (final CharSequence w : words) {
280 if (StringUtils.isBlank(w)) {
281 return false;
282 }
283 final Pattern p = Pattern.compile(".*\\b" + Pattern.quote(w.toString()) + "\\b.*", Pattern.DOTALL);
284 if (!p.matcher(word).matches()) {
285 return false;
286 }
287 }
288 return true;
289 }
290
291 /**
292 * Given the array of delimiters supplied; returns a function determining whether a character code point is a delimiter.
293 * The function provides O(1) lookup time.
294 * Whitespace is defined by {@link Character#isWhitespace(char)} and is used as the defaultvalue if delimiters is null.
295 *
296 * @param delimiters The set of characters to determine delimiters, null means whitespace.
297 * @return A Predicate<Integer> taking a code point value as an argument and returning true if a delimiter.
298 */
299 private static Predicate<Integer> generateIsDelimiterFunction(final char[] delimiters) {
300 final Predicate<Integer> isDelimiter;
301 if (delimiters == null || delimiters.length == 0) {
302 isDelimiter = delimiters == null ? Character::isWhitespace : c -> false;
303 } else {
304 final Set<Integer> delimiterSet = new HashSet<>();
305 for (int index = 0; index < delimiters.length; index++) {
306 delimiterSet.add(Character.codePointAt(delimiters, index));
307 }
308 isDelimiter = delimiterSet::contains;
309 }
310 return isDelimiter;
311 }
312
313 /**
314 * Extracts the initial characters from each word in the String.
315 * <p>
316 * All first characters after whitespace are returned as a new string. Their case is not changed.
317 * </p>
318 * <p>
319 * Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String returns {@code null}.
320 * </p>
321 *
322 * <pre>
323 * WordUtils.initials(null) = null
324 * WordUtils.initials("") = ""
325 * WordUtils.initials("Ben John Lee") = "BJL"
326 * WordUtils.initials("Ben J.Lee") = "BJ"
327 * </pre>
328 *
329 * @param str The String to get initials from, may be null.
330 * @return A new String of initial letters, or {@code null} if null String input.
331 * @see #initials(String,char[])
332 */
333 public static String initials(final String str) {
334 return initials(str, null);
335 }
336
337 /**
338 * Extracts the initial characters from each word in the String.
339 * <p>
340 * All first characters after the defined delimiters are returned as a new string. Their case is not changed.
341 * </p>
342 * <p>
343 * If the delimiters array is null, then Whitespace is used. Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String
344 * returns {@code null}. An empty delimiter array returns an empty String.
345 * </p>
346 *
347 * <pre>
348 * WordUtils.initials(null, *) = null
349 * WordUtils.initials("", *) = ""
350 * WordUtils.initials("Ben John Lee", null) = "BJL"
351 * WordUtils.initials("Ben J.Lee", null) = "BJ"
352 * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
353 * WordUtils.initials(*, new char[0]) = ""
354 * </pre>
355 *
356 * @param str The String to get initials from, may be null.
357 * @param delimiters The Set of characters to determine words, null means whitespace.
358 * @return String of initial characters, or {@code null} if null String input.
359 * @see #initials(String)
360 */
361 public static String initials(final String str, final char... delimiters) {
362 if (StringUtils.isEmpty(str)) {
363 return str;
364 }
365 if (delimiters != null && delimiters.length == 0) {
366 return StringUtils.EMPTY;
367 }
368 final Predicate<Integer> isDelimiter = generateIsDelimiterFunction(delimiters);
369 final int strLen = str.length();
370 final int[] newCodePoints = new int[strLen / 2 + 1];
371 int count = 0;
372 boolean lastWasGap = true;
373 for (int i = 0; i < strLen;) {
374 final int codePoint = str.codePointAt(i);
375 if (isDelimiter.test(codePoint)) {
376 lastWasGap = true;
377 } else if (lastWasGap) {
378 newCodePoints[count++] = codePoint;
379 lastWasGap = false;
380 }
381 i += Character.charCount(codePoint);
382 }
383 return new String(newCodePoints, 0, count);
384 }
385
386 /**
387 * Is the character a delimiter.
388 *
389 * @param ch The character to check.
390 * @param delimiters The delimiters.
391 * @return true if it is a delimiter.
392 * @deprecated as of 1.2 and will be removed in 2.0.
393 */
394 @Deprecated
395 public static boolean isDelimiter(final char ch, final char[] delimiters) {
396 if (delimiters == null) {
397 return Character.isWhitespace(ch);
398 }
399 for (final char delimiter : delimiters) {
400 if (ch == delimiter) {
401 return true;
402 }
403 }
404 return false;
405 }
406
407 /**
408 * Is the codePoint a delimiter.
409 *
410 * @param codePoint The codePint to check.
411 * @param delimiters The delimiters.
412 * @return true if it is a delimiter.
413 * @deprecated as of 1.2 and will be removed in 2.0.
414 */
415 @Deprecated
416 public static boolean isDelimiter(final int codePoint, final char[] delimiters) {
417 if (delimiters == null) {
418 return Character.isWhitespace(codePoint);
419 }
420 for (int index = 0; index < delimiters.length; index++) {
421 final int delimiterCodePoint = Character.codePointAt(delimiters, index);
422 if (delimiterCodePoint == codePoint) {
423 return true;
424 }
425 }
426 return false;
427 }
428
429 /**
430 * Swaps the case of a String using a word based algorithm.
431 * <ul>
432 * <li>Upper case character converts to Lower case</li>
433 * <li>Title case character converts to Lower case</li>
434 * <li>Lower case character after Whitespace or at start converts to Title case</li>
435 * <li>Other Lower case character converts to Upper case</li>
436 * </ul>
437 * <p>
438 * Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String returns {@code null}.
439 * </p>
440 *
441 * <pre>
442 * StringUtils.swapCase(null) = null
443 * StringUtils.swapCase("") = ""
444 * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
445 * </pre>
446 *
447 * @param str The String to swap case, may be null.
448 * @return The changed String, or {@code null} if null String input.
449 */
450 public static String swapCase(final String str) {
451 if (StringUtils.isEmpty(str)) {
452 return str;
453 }
454 final int strLen = str.length();
455 final int[] newCodePoints = new int[strLen];
456 int outOffset = 0;
457 boolean whitespace = true;
458 for (int index = 0; index < strLen;) {
459 final int oldCodepoint = str.codePointAt(index);
460 final int newCodePoint;
461 if (Character.isUpperCase(oldCodepoint) || Character.isTitleCase(oldCodepoint)) {
462 newCodePoint = Character.toLowerCase(oldCodepoint);
463 whitespace = false;
464 } else if (Character.isLowerCase(oldCodepoint)) {
465 if (whitespace) {
466 newCodePoint = Character.toTitleCase(oldCodepoint);
467 whitespace = false;
468 } else {
469 newCodePoint = Character.toUpperCase(oldCodepoint);
470 }
471 } else {
472 whitespace = Character.isWhitespace(oldCodepoint);
473 newCodePoint = oldCodepoint;
474 }
475 newCodePoints[outOffset++] = newCodePoint;
476 index += Character.charCount(newCodePoint);
477 }
478 return new String(newCodePoints, 0, outOffset);
479 }
480
481 /**
482 * Uncapitalizes all the whitespace separated words in a String. Only the first character of each word is changed.
483 * <p>
484 * Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String returns {@code null}.
485 * </p>
486 *
487 * <pre>
488 * WordUtils.uncapitalize(null) = null
489 * WordUtils.uncapitalize("") = ""
490 * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
491 * </pre>
492 *
493 * @param str The String to uncapitalize, may be null.
494 * @return A new uncapitalized String, or {@code null} if null String input.
495 * @see #capitalize(String)
496 */
497 public static String uncapitalize(final String str) {
498 return uncapitalize(str, null);
499 }
500
501 /**
502 * Uncapitalizes all the whitespace separated words in a String. Only the first character of each word is changed.
503 * <p>
504 * The delimiters represent a set of characters understood to separate words. The first string character and the first non-delimiter character after a
505 * delimiter will be uncapitalized.
506 * </p>
507 * <p>
508 * Whitespace is defined by {@link Character#isWhitespace(char)}. A {@code null} input String returns {@code null}.
509 * </p>
510 *
511 * <pre>
512 * WordUtils.uncapitalize(null, *) = null
513 * WordUtils.uncapitalize("", *) = ""
514 * WordUtils.uncapitalize(*, null) = *
515 * WordUtils.uncapitalize(*, new char[0]) = *
516 * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
517 * WordUtils.uncapitalize("I am fine", new char[]{}) = "i am fine"
518 * </pre>
519 *
520 * @param str The String to uncapitalize, may be null.
521 * @param delimiters set of characters to determine uncapitalization, null means whitespace.
522 * @return uncapitalized String, or {@code null} if null String input.
523 * @see #capitalize(String)
524 */
525 public static String uncapitalize(final String str, final char... delimiters) {
526 return applyWordCaseTransform(str, delimiters, Character::toLowerCase);
527 }
528
529 /**
530 * Wraps a single line of text, identifying words by {@code ' '}.
531 *
532 * <p>New lines will be separated by the system property line separator.
533 * Very long words, such as URLs will <em>not</em> be wrapped.</p>
534 *
535 * <p>Leading spaces on a new line are stripped.
536 * Trailing spaces are not stripped.</p>
537 *
538 * <table border="1">
539 * <caption>Examples</caption>
540 * <tr>
541 * <th>input</th>
542 * <th>wrapLength</th>
543 * <th>result</th>
544 * </tr>
545 * <tr>
546 * <td>null</td>
547 * <td>*</td>
548 * <td>null</td>
549 * </tr>
550 * <tr>
551 * <td>""</td>
552 * <td>*</td>
553 * <td>""</td>
554 * </tr>
555 * <tr>
556 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
557 * <td>20</td>
558 * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
559 * </tr>
560 * <tr>
561 * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
562 * <td>20</td>
563 * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
564 * </tr>
565 * <tr>
566 * <td>"Click here, https://commons.apache.org, to jump to the commons website"</td>
567 * <td>20</td>
568 * <td>"Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"</td>
569 * </tr>
570 * </table>
571 *
572 * (assuming that '\n' is the systems line separator)
573 *
574 * @param str The String to be word wrapped, may be null.
575 * @param wrapLength The column to wrap the words at, less than 1 is treated as 1.
576 * @return A line with newlines inserted, {@code null} if null input.
577 */
578 public static String wrap(final String str, final int wrapLength) {
579 return wrap(str, wrapLength, null, false);
580 }
581
582 /**
583 * Wraps a single line of text, identifying words by {@code ' '}.
584 *
585 * <p>Leading spaces on a new line are stripped.
586 * Trailing spaces are not stripped.</p>
587 *
588 * <table border="1">
589 * <caption>Examples</caption>
590 * <tr>
591 * <th>input</th>
592 * <th>wrapLength</th>
593 * <th>newLineString</th>
594 * <th>wrapLongWords</th>
595 * <th>result</th>
596 * </tr>
597 * <tr>
598 * <td>null</td>
599 * <td>*</td>
600 * <td>*</td>
601 * <td>true/false</td>
602 * <td>null</td>
603 * </tr>
604 * <tr>
605 * <td>""</td>
606 * <td>*</td>
607 * <td>*</td>
608 * <td>true/false</td>
609 * <td>""</td>
610 * </tr>
611 * <tr>
612 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
613 * <td>20</td>
614 * <td>"\n"</td>
615 * <td>true/false</td>
616 * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
617 * </tr>
618 * <tr>
619 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
620 * <td>20</td>
621 * <td>"<br />"</td>
622 * <td>true/false</td>
623 * <td>"Here is one line of<br />text that is going<
624 * br />to be wrapped after<br />20 columns."</td>
625 * </tr>
626 * <tr>
627 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
628 * <td>20</td>
629 * <td>null</td>
630 * <td>true/false</td>
631 * <td>"Here is one line of" + systemNewLine + "text that is going"
632 * + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
633 * </tr>
634 * <tr>
635 * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
636 * <td>20</td>
637 * <td>"\n"</td>
638 * <td>false</td>
639 * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
640 * </tr>
641 * <tr>
642 * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
643 * <td>20</td>
644 * <td>"\n"</td>
645 * <td>true</td>
646 * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apach\ne.org"</td>
647 * </tr>
648 * </table>
649 *
650 * @param str The String to be word wrapped, may be null.
651 * @param wrapLength The column to wrap the words at, less than 1 is treated as 1.
652 * @param newLineStr The string to insert for a new line, {@code null} uses the system property line separator.
653 * @param wrapLongWords true if long words (such as URLs) should be wrapped.
654 * @return A line with newlines inserted, {@code null} if null input.
655 */
656 public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) {
657 return wrap(str, wrapLength, newLineStr, wrapLongWords, " ");
658 }
659
660 /**
661 * Wraps a single line of text, identifying words by {@code wrapOn}.
662 *
663 * <p>Leading spaces on a new line are stripped.
664 * Trailing spaces are not stripped.</p>
665 *
666 * <table border="1">
667 * <caption>Examples</caption>
668 * <tr>
669 * <th>input</th>
670 * <th>wrapLength</th>
671 * <th>newLineString</th>
672 * <th>wrapLongWords</th>
673 * <th>wrapOn</th>
674 * <th>result</th>
675 * </tr>
676 * <tr>
677 * <td>null</td>
678 * <td>*</td>
679 * <td>*</td>
680 * <td>true/false</td>
681 * <td>*</td>
682 * <td>null</td>
683 * </tr>
684 * <tr>
685 * <td>""</td>
686 * <td>*</td>
687 * <td>*</td>
688 * <td>true/false</td>
689 * <td>*</td>
690 * <td>""</td>
691 * </tr>
692 * <tr>
693 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
694 * <td>20</td>
695 * <td>"\n"</td>
696 * <td>true/false</td>
697 * <td>" "</td>
698 * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
699 * </tr>
700 * <tr>
701 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
702 * <td>20</td>
703 * <td>"<br />"</td>
704 * <td>true/false</td>
705 * <td>" "</td>
706 * <td>"Here is one line of<br />text that is going<br />
707 * to be wrapped after<br />20 columns."</td>
708 * </tr>
709 * <tr>
710 * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
711 * <td>20</td>
712 * <td>null</td>
713 * <td>true/false</td>
714 * <td>" "</td>
715 * <td>"Here is one line of" + systemNewLine + "text that is going"
716 * + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
717 * </tr>
718 * <tr>
719 * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
720 * <td>20</td>
721 * <td>"\n"</td>
722 * <td>false</td>
723 * <td>" "</td>
724 * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
725 * </tr>
726 * <tr>
727 * <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
728 * <td>20</td>
729 * <td>"\n"</td>
730 * <td>true</td>
731 * <td>" "</td>
732 * <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apach\ne.org"</td>
733 * </tr>
734 * <tr>
735 * <td>"flammable/inflammable"</td>
736 * <td>20</td>
737 * <td>"\n"</td>
738 * <td>true</td>
739 * <td>"/"</td>
740 * <td>"flammable\ninflammable"</td>
741 * </tr>
742 * </table>
743 *
744 * @param str The String to be word wrapped, may be null.
745 * @param wrapLength The column to wrap the words at, less than 1 is treated as 1.
746 * @param newLineStr The string to insert for a new line, {@code null} uses the system property line separator.
747 * @param wrapLongWords true if long words (such as URLs) should be wrapped.
748 * @param wrapOn Regex expression to be used as a breakable characters, if blank string is provided a space character will be used.
749 * @return A line with newlines inserted, {@code null} if null input.
750 */
751 public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) {
752 if (str == null) {
753 return null;
754 }
755 if (newLineStr == null) {
756 newLineStr = System.lineSeparator();
757 }
758 if (wrapLength < 1) {
759 wrapLength = 1;
760 }
761 if (StringUtils.isBlank(wrapOn)) {
762 wrapOn = " ";
763 }
764 final Pattern patternToWrapOn = Pattern.compile(wrapOn);
765 final int inputLineLength = str.length();
766 int offset = 0;
767 final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
768 int matcherSize = -1;
769 while (offset < inputLineLength) {
770 int spaceToWrapAt = -1;
771 Matcher matcher = patternToWrapOn
772 .matcher(str.substring(offset, Math.min((int) Math.min(Integer.MAX_VALUE, offset + wrapLength + 1L), inputLineLength)));
773 if (matcher.find()) {
774 if (matcher.start() == 0) {
775 matcherSize = matcher.end();
776 if (matcherSize != 0) {
777 offset += matcher.end();
778 continue;
779 }
780 offset += 1;
781 }
782 spaceToWrapAt = matcher.start() + offset;
783 }
784 // only last line without leading spaces is left
785 if (inputLineLength - offset <= wrapLength) {
786 break;
787 }
788 while (matcher.find()) {
789 spaceToWrapAt = matcher.start() + offset;
790 }
791 if (spaceToWrapAt >= offset) {
792 // normal case
793 wrappedLine.append(str, offset, spaceToWrapAt);
794 wrappedLine.append(newLineStr);
795 offset = spaceToWrapAt + 1;
796 } else // really long word or URL
797 if (wrapLongWords) {
798 if (matcherSize == 0) {
799 offset--;
800 }
801 // wrap really long word one line at a time, but keep a surrogate pair whole
802 int wrapAt = wrapLength + offset;
803 if (Character.isHighSurrogate(str.charAt(wrapAt - 1)) && Character.isLowSurrogate(str.charAt(wrapAt))) {
804 wrapAt++;
805 }
806 wrappedLine.append(str, offset, wrapAt);
807 wrappedLine.append(newLineStr);
808 offset = wrapAt;
809 matcherSize = -1;
810 } else {
811 // do not wrap really long word, just extend beyond limit
812 matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
813 if (matcher.find()) {
814 matcherSize = matcher.end() - matcher.start();
815 spaceToWrapAt = matcher.start() + offset + wrapLength;
816 }
817 if (spaceToWrapAt >= 0) {
818 if (matcherSize == 0 && offset != 0) {
819 offset--;
820 }
821 wrappedLine.append(str, offset, spaceToWrapAt);
822 wrappedLine.append(newLineStr);
823 offset = spaceToWrapAt + 1;
824 } else {
825 if (matcherSize == 0 && offset != 0) {
826 offset--;
827 }
828 wrappedLine.append(str, offset, str.length());
829 offset = inputLineLength;
830 matcherSize = -1;
831 }
832 }
833 }
834 if (matcherSize == 0 && offset < inputLineLength) {
835 offset--;
836 }
837 // Whatever is left in line is short enough to just pass through
838 wrappedLine.append(str, offset, str.length());
839 return wrappedLine.toString();
840 }
841
842 /**
843 * {@code WordUtils} instances should NOT be constructed in standard programming. Instead, the class should be used as
844 * {@code WordUtils.wrap("foo bar", 20);}.
845 * <p>
846 * This constructor is public to permit tools that require a JavaBean instance to operate.
847 * </p>
848 */
849 public WordUtils() {
850 }
851 }