View Javadoc
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.lang3.text;
18  
19  import java.util.regex.Matcher;
20  import java.util.regex.Pattern;
21  
22  import org.apache.commons.lang3.ArrayUtils;
23  import org.apache.commons.lang3.StringUtils;
24  
25  /**
26   * Operations on Strings that contain words.
27   *
28   * <p>This class tries to handle {@code null} input gracefully.
29   * An exception will not be thrown for a {@code null} input.
30   * Each method documents its behavior in more detail.</p>
31   *
32   * @since 2.0
33   * @deprecated As of <a href="https://commons.apache.org/proper/commons-lang/changes-report.html#a3.6">3.6</a>, use Apache Commons Text
34   * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/WordUtils.html">
35   * WordUtils</a>.
36   */
37  @Deprecated
38  public class WordUtils {
39  
40      /**
41       * Capitalizes all the whitespace separated words in a String.
42       * Only the first character of each word is changed. To convert the
43       * rest of each word to lowercase at the same time,
44       * use {@link #capitalizeFully(String)}.
45       *
46       * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
47       * A {@code null} input String returns {@code null}.
48       * Capitalization uses the Unicode title case, normally equivalent to
49       * upper case.</p>
50       *
51       * <pre>
52       * WordUtils.capitalize(null)        = null
53       * WordUtils.capitalize("")          = ""
54       * WordUtils.capitalize("i am FINE") = "I Am FINE"
55       * </pre>
56       *
57       * @param str  the String to capitalize, may be null
58       * @return capitalized String, {@code null} if null String input
59       * @see #uncapitalize(String)
60       * @see #capitalizeFully(String)
61       */
62      public static String capitalize(final String str) {
63          return capitalize(str, null);
64      }
65  
66      /**
67       * Capitalizes all the delimiter separated words in a String.
68       * Only the first character of each word is changed. To convert the
69       * rest of each word to lowercase at the same time,
70       * use {@link #capitalizeFully(String, char[])}.
71       *
72       * <p>The delimiters represent a set of characters understood to separate words.
73       * The first string character and the first non-delimiter character after a
74       * delimiter will be capitalized.</p>
75       *
76       * <p>A {@code null} input String returns {@code null}.
77       * Capitalization uses the Unicode title case, normally equivalent to
78       * upper case.</p>
79       *
80       * <pre>
81       * WordUtils.capitalize(null, *)            = null
82       * WordUtils.capitalize("", *)              = ""
83       * WordUtils.capitalize(*, new char[0])     = *
84       * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
85       * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
86       * </pre>
87       *
88       * @param str  the String to capitalize, may be null
89       * @param delimiters  set of characters to determine capitalization, null means whitespace
90       * @return capitalized String, {@code null} if null String input
91       * @see #uncapitalize(String)
92       * @see #capitalizeFully(String)
93       * @since 2.1
94       */
95      public static String capitalize(final String str, final char... delimiters) {
96          final int delimLen = delimiters == null ? -1 : delimiters.length;
97          if (StringUtils.isEmpty(str) || delimLen == 0) {
98              return str;
99          }
100         final char[] buffer = str.toCharArray();
101         boolean capitalizeNext = true;
102         for (int i = 0; i < buffer.length; i++) {
103             final char ch = buffer[i];
104             if (isDelimiter(ch, delimiters)) {
105                 capitalizeNext = true;
106             } else if (capitalizeNext) {
107                 buffer[i] = Character.toTitleCase(ch);
108                 capitalizeNext = false;
109             }
110         }
111         return new String(buffer);
112     }
113 
114     /**
115      * Converts all the whitespace separated words in a String into capitalized words,
116      * that is each word is made up of a titlecase character and then a series of
117      * lowercase characters.
118      *
119      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
120      * A {@code null} input String returns {@code null}.
121      * Capitalization uses the Unicode title case, normally equivalent to
122      * upper case.</p>
123      *
124      * <pre>
125      * WordUtils.capitalizeFully(null)        = null
126      * WordUtils.capitalizeFully("")          = ""
127      * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
128      * </pre>
129      *
130      * @param str  the String to capitalize, may be null
131      * @return capitalized String, {@code null} if null String input
132      */
133     public static String capitalizeFully(final String str) {
134         return capitalizeFully(str, null);
135     }
136 
137     /**
138      * Converts all the delimiter separated words in a String into capitalized words,
139      * that is each word is made up of a titlecase character and then a series of
140      * lowercase characters.
141      *
142      * <p>The delimiters represent a set of characters understood to separate words.
143      * The first string character and the first non-delimiter character after a
144      * delimiter will be capitalized.</p>
145      *
146      * <p>A {@code null} input String returns {@code null}.
147      * Capitalization uses the Unicode title case, normally equivalent to
148      * upper case.</p>
149      *
150      * <pre>
151      * WordUtils.capitalizeFully(null, *)            = null
152      * WordUtils.capitalizeFully("", *)              = ""
153      * WordUtils.capitalizeFully(*, null)            = *
154      * WordUtils.capitalizeFully(*, new char[0])     = *
155      * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
156      * </pre>
157      *
158      * @param str  the String to capitalize, may be null
159      * @param delimiters  set of characters to determine capitalization, null means whitespace
160      * @return capitalized String, {@code null} if null String input
161      * @since 2.1
162      */
163     public static String capitalizeFully(final String str, final char... delimiters) {
164         final int delimLen = delimiters == null ? -1 : delimiters.length;
165         if (StringUtils.isEmpty(str) || delimLen == 0) {
166             return str;
167         }
168         return capitalize(str.toLowerCase(), delimiters);
169     }
170 
171     /**
172      * Checks if the String contains all words in the given array.
173      *
174      * <p>
175      * A {@code null} String will return {@code false}. A {@code null}, zero
176      * length search array or if one element of array is null will return {@code false}.
177      * </p>
178      *
179      * <pre>
180      * WordUtils.containsAllWords(null, *)            = false
181      * WordUtils.containsAllWords("", *)              = false
182      * WordUtils.containsAllWords(*, null)            = false
183      * WordUtils.containsAllWords(*, [])              = false
184      * WordUtils.containsAllWords("abcd", "ab", "cd") = false
185      * WordUtils.containsAllWords("abc def", "def", "abc") = true
186      * </pre>
187      *
188      * @param word The CharSequence to check, may be null
189      * @param words The array of String words to search for, may be null
190      * @return {@code true} if all search words are found, {@code false} otherwise
191      * @since 3.5
192      */
193     public static boolean containsAllWords(final CharSequence word, final CharSequence... words) {
194         if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) {
195             return false;
196         }
197         for (final CharSequence w : words) {
198             if (StringUtils.isBlank(w)) {
199                 return false;
200             }
201             final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*");
202             if (!p.matcher(word).matches()) {
203                 return false;
204             }
205         }
206         return true;
207     }
208 
209     /**
210      * Extracts the initial characters from each word in the String.
211      *
212      * <p>All first characters after whitespace are returned as a new string.
213      * Their case is not changed.</p>
214      *
215      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
216      * A {@code null} input String returns {@code null}.</p>
217      *
218      * <pre>
219      * WordUtils.initials(null)             = null
220      * WordUtils.initials("")               = ""
221      * WordUtils.initials("Ben John Lee")   = "BJL"
222      * WordUtils.initials("Ben J.Lee")      = "BJ"
223      * </pre>
224      *
225      * @param str  the String to get initials from, may be null
226      * @return String of initial letters, {@code null} if null String input
227      * @see #initials(String,char[])
228      * @since 2.2
229      */
230     public static String initials(final String str) {
231         return initials(str, null);
232     }
233 
234     /**
235      * Extracts the initial characters from each word in the String.
236      *
237      * <p>All first characters after the defined delimiters are returned as a new string.
238      * Their case is not changed.</p>
239      *
240      * <p>If the delimiters array is null, then Whitespace is used.
241      * Whitespace is defined by {@link Character#isWhitespace(char)}.
242      * A {@code null} input String returns {@code null}.
243      * An empty delimiter array returns an empty String.</p>
244      *
245      * <pre>
246      * WordUtils.initials(null, *)                = null
247      * WordUtils.initials("", *)                  = ""
248      * WordUtils.initials("Ben John Lee", null)   = "BJL"
249      * WordUtils.initials("Ben J.Lee", null)      = "BJ"
250      * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
251      * WordUtils.initials(*, new char[0])         = ""
252      * </pre>
253      *
254      * @param str  the String to get initials from, may be null
255      * @param delimiters  set of characters to determine words, null means whitespace
256      * @return String of initial characters, {@code null} if null String input
257      * @see #initials(String)
258      * @since 2.2
259      */
260     public static String initials(final String str, final char... delimiters) {
261         if (StringUtils.isEmpty(str)) {
262             return str;
263         }
264         if (delimiters != null && delimiters.length == 0) {
265             return StringUtils.EMPTY;
266         }
267         final int strLen = str.length();
268         final char[] buf = new char[strLen / 2 + 1];
269         int count = 0;
270         boolean lastWasGap = true;
271         for (int i = 0; i < strLen; i++) {
272             final char ch = str.charAt(i);
273             if (isDelimiter(ch, delimiters)) {
274                 lastWasGap = true;
275                 continue;  // ignore ch
276             }
277             if (lastWasGap) {
278                 buf[count++] = ch;
279                 lastWasGap = false;
280             }
281         }
282         return new String(buf, 0, count);
283     }
284 
285     /**
286      * Tests if the character is a delimiter.
287      *
288      * @param ch  the character to check
289      * @param delimiters  the delimiters
290      * @return true if it is a delimiter
291      */
292     private static boolean isDelimiter(final char ch, final char[] delimiters) {
293         return delimiters == null ? Character.isWhitespace(ch) : ArrayUtils.contains(delimiters, ch);
294     }
295 
296     /**
297      * Swaps the case of a String using a word based algorithm.
298      *
299      * <ul>
300      *  <li>Upper case character converts to Lower case</li>
301      *  <li>Title case character converts to Lower case</li>
302      *  <li>Lower case character after Whitespace or at start converts to Title case</li>
303      *  <li>Other Lower case character converts to Upper case</li>
304      * </ul>
305      *
306      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
307      * A {@code null} input String returns {@code null}.</p>
308      *
309      * <pre>
310      * StringUtils.swapCase(null)                 = null
311      * StringUtils.swapCase("")                   = ""
312      * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
313      * </pre>
314      *
315      * @param str  the String to swap case, may be null
316      * @return the changed String, {@code null} if null String input
317      */
318     public static String swapCase(final String str) {
319         if (StringUtils.isEmpty(str)) {
320             return str;
321         }
322         final char[] buffer = str.toCharArray();
323 
324         boolean whitespace = true;
325 
326         for (int i = 0; i < buffer.length; i++) {
327             final char ch = buffer[i];
328             if (Character.isUpperCase(ch) || Character.isTitleCase(ch)) {
329                 buffer[i] = Character.toLowerCase(ch);
330                 whitespace = false;
331             } else if (Character.isLowerCase(ch)) {
332                 if (whitespace) {
333                     buffer[i] = Character.toTitleCase(ch);
334                     whitespace = false;
335                 } else {
336                     buffer[i] = Character.toUpperCase(ch);
337                 }
338             } else {
339                 whitespace = Character.isWhitespace(ch);
340             }
341         }
342         return new String(buffer);
343     }
344 
345     /**
346      * Uncapitalizes all the whitespace separated words in a String.
347      * Only the first character of each word is changed.
348      *
349      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
350      * A {@code null} input String returns {@code null}.</p>
351      *
352      * <pre>
353      * WordUtils.uncapitalize(null)        = null
354      * WordUtils.uncapitalize("")          = ""
355      * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
356      * </pre>
357      *
358      * @param str  the String to uncapitalize, may be null
359      * @return uncapitalized String, {@code null} if null String input
360      * @see #capitalize(String)
361      */
362     public static String uncapitalize(final String str) {
363         return uncapitalize(str, null);
364     }
365 
366     /**
367      * Uncapitalizes all the whitespace separated words in a String.
368      * Only the first character of each word is changed.
369      *
370      * <p>The delimiters represent a set of characters understood to separate words.
371      * The first string character and the first non-delimiter character after a
372      * delimiter will be uncapitalized.</p>
373      *
374      * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
375      * A {@code null} input String returns {@code null}.</p>
376      *
377      * <pre>
378      * WordUtils.uncapitalize(null, *)            = null
379      * WordUtils.uncapitalize("", *)              = ""
380      * WordUtils.uncapitalize(*, null)            = *
381      * WordUtils.uncapitalize(*, new char[0])     = *
382      * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
383      * </pre>
384      *
385      * @param str  the String to uncapitalize, may be null
386      * @param delimiters  set of characters to determine uncapitalization, null means whitespace
387      * @return uncapitalized String, {@code null} if null String input
388      * @see #capitalize(String)
389      * @since 2.1
390      */
391     public static String uncapitalize(final String str, final char... delimiters) {
392         final int delimLen = delimiters == null ? -1 : delimiters.length;
393         if (StringUtils.isEmpty(str) || delimLen == 0) {
394             return str;
395         }
396         final char[] buffer = str.toCharArray();
397         boolean uncapitalizeNext = true;
398         for (int i = 0; i < buffer.length; i++) {
399             final char ch = buffer[i];
400             if (isDelimiter(ch, delimiters)) {
401                 uncapitalizeNext = true;
402             } else if (uncapitalizeNext) {
403                 buffer[i] = Character.toLowerCase(ch);
404                 uncapitalizeNext = false;
405             }
406         }
407         return new String(buffer);
408     }
409 
410     /**
411      * Wraps a single line of text, identifying words by {@code ' '}.
412      *
413      * <p>New lines will be separated by the system property line separator.
414      * Very long words, such as URLs will <em>not</em> be wrapped.</p>
415      *
416      * <p>Leading spaces on a new line are stripped.
417      * Trailing spaces are not stripped.</p>
418      *
419      * <table border="1">
420      *  <caption>Examples</caption>
421      *  <tr>
422      *   <th>input</th>
423      *   <th>wrapLength</th>
424      *   <th>result</th>
425      *  </tr>
426      *  <tr>
427      *   <td>null</td>
428      *   <td>*</td>
429      *   <td>null</td>
430      *  </tr>
431      *  <tr>
432      *   <td>""</td>
433      *   <td>*</td>
434      *   <td>""</td>
435      *  </tr>
436      *  <tr>
437      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
438      *   <td>20</td>
439      *   <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
440      *  </tr>
441      *  <tr>
442      *   <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
443      *   <td>20</td>
444      *   <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
445      *  </tr>
446      *  <tr>
447      *   <td>"Click here, https://commons.apache.org, to jump to the commons website"</td>
448      *   <td>20</td>
449      *   <td>"Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"</td>
450      *  </tr>
451      * </table>
452      *
453      * (assuming that '\n' is the systems line separator)
454      *
455      * @param str  the String to be word wrapped, may be null
456      * @param wrapLength  the column to wrap the words at, less than 1 is treated as 1
457      * @return a line with newlines inserted, {@code null} if null input
458      */
459     public static String wrap(final String str, final int wrapLength) {
460         return wrap(str, wrapLength, null, false);
461     }
462 
463     /**
464      * Wraps a single line of text, identifying words by {@code ' '}.
465      *
466      * <p>Leading spaces on a new line are stripped.
467      * Trailing spaces are not stripped.</p>
468      *
469      * <table border="1">
470      *  <caption>Examples</caption>
471      *  <tr>
472      *   <th>input</th>
473      *   <th>wrapLength</th>
474      *   <th>newLineString</th>
475      *   <th>wrapLongWords</th>
476      *   <th>result</th>
477      *  </tr>
478      *  <tr>
479      *   <td>null</td>
480      *   <td>*</td>
481      *   <td>*</td>
482      *   <td>true/false</td>
483      *   <td>null</td>
484      *  </tr>
485      *  <tr>
486      *   <td>""</td>
487      *   <td>*</td>
488      *   <td>*</td>
489      *   <td>true/false</td>
490      *   <td>""</td>
491      *  </tr>
492      *  <tr>
493      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
494      *   <td>20</td>
495      *   <td>"\n"</td>
496      *   <td>true/false</td>
497      *   <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
498      *  </tr>
499      *  <tr>
500      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
501      *   <td>20</td>
502      *   <td>"&lt;br /&gt;"</td>
503      *   <td>true/false</td>
504      *   <td>"Here is one line of&lt;br /&gt;text that is going&lt;br /&gt;to be wrapped after&lt;br /&gt;20 columns."</td>
505      *  </tr>
506      *  <tr>
507      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
508      *   <td>20</td>
509      *   <td>null</td>
510      *   <td>true/false</td>
511      *   <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
512      *  </tr>
513      *  <tr>
514      *   <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
515      *   <td>20</td>
516      *   <td>"\n"</td>
517      *   <td>false</td>
518      *   <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
519      *  </tr>
520      *  <tr>
521      *   <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
522      *   <td>20</td>
523      *   <td>"\n"</td>
524      *   <td>true</td>
525      *   <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apach\ne.org"</td>
526      *  </tr>
527      * </table>
528      *
529      * @param str  the String to be word wrapped, may be null
530      * @param wrapLength  the column to wrap the words at, less than 1 is treated as 1
531      * @param newLineStr  the string to insert for a new line,
532      *  {@code null} uses the system property line separator
533      * @param wrapLongWords  true if long words (such as URLs) should be wrapped
534      * @return a line with newlines inserted, {@code null} if null input
535      */
536     public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) {
537         return wrap(str, wrapLength, newLineStr, wrapLongWords, " ");
538     }
539 
540     /**
541      * Wraps a single line of text, identifying words by {@code wrapOn}.
542      *
543      * <p>Leading spaces on a new line are stripped.
544      * Trailing spaces are not stripped.</p>
545      *
546      * <table border="1">
547      *  <caption>Examples</caption>
548      *  <tr>
549      *   <th>input</th>
550      *   <th>wrapLength</th>
551      *   <th>newLineString</th>
552      *   <th>wrapLongWords</th>
553      *   <th>wrapOn</th>
554      *   <th>result</th>
555      *  </tr>
556      *  <tr>
557      *   <td>null</td>
558      *   <td>*</td>
559      *   <td>*</td>
560      *   <td>true/false</td>
561      *   <td>*</td>
562      *   <td>null</td>
563      *  </tr>
564      *  <tr>
565      *   <td>""</td>
566      *   <td>*</td>
567      *   <td>*</td>
568      *   <td>true/false</td>
569      *   <td>*</td>
570      *   <td>""</td>
571      *  </tr>
572      *  <tr>
573      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
574      *   <td>20</td>
575      *   <td>"\n"</td>
576      *   <td>true/false</td>
577      *   <td>" "</td>
578      *   <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
579      *  </tr>
580      *  <tr>
581      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
582      *   <td>20</td>
583      *   <td>"&lt;br /&gt;"</td>
584      *   <td>true/false</td>
585      *   <td>" "</td>
586      *   <td>"Here is one line of&lt;br /&gt;text that is going&lt;br /&gt;to be wrapped after&lt;br /&gt;20 columns."</td>
587      *  </tr>
588      *  <tr>
589      *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
590      *   <td>20</td>
591      *   <td>null</td>
592      *   <td>true/false</td>
593      *   <td>" "</td>
594      *   <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
595      *  </tr>
596      *  <tr>
597      *   <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
598      *   <td>20</td>
599      *   <td>"\n"</td>
600      *   <td>false</td>
601      *   <td>" "</td>
602      *   <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"</td>
603      *  </tr>
604      *  <tr>
605      *   <td>"Click here to jump to the commons website - https://commons.apache.org"</td>
606      *   <td>20</td>
607      *   <td>"\n"</td>
608      *   <td>true</td>
609      *   <td>" "</td>
610      *   <td>"Click here to jump\nto the commons\nwebsite -\nhttps://commons.apach\ne.org"</td>
611      *  </tr>
612      *  <tr>
613      *   <td>"flammable/inflammable"</td>
614      *   <td>20</td>
615      *   <td>"\n"</td>
616      *   <td>true</td>
617      *   <td>"/"</td>
618      *   <td>"flammable\ninflammable"</td>
619      *  </tr>
620      * </table>
621      * @param str  the String to be word wrapped, may be null
622      * @param wrapLength  the column to wrap the words at, less than 1 is treated as 1
623      * @param newLineStr  the string to insert for a new line,
624      *  {@code null} uses the system property line separator
625      * @param wrapLongWords  true if long words (such as URLs) should be wrapped
626      * @param wrapOn regex expression to be used as a breakable characters,
627      *               if blank string is provided a space character will be used
628      * @return a line with newlines inserted, {@code null} if null input
629      */
630     public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) {
631         if (str == null) {
632             return null;
633         }
634         if (newLineStr == null) {
635             newLineStr = System.lineSeparator();
636         }
637         if (wrapLength < 1) {
638             wrapLength = 1;
639         }
640         if (StringUtils.isBlank(wrapOn)) {
641             wrapOn = " ";
642         }
643         final Pattern patternToWrapOn = Pattern.compile(wrapOn);
644         final int inputLineLength = str.length();
645         int offset = 0;
646         final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
647 
648         while (offset < inputLineLength) {
649             int spaceToWrapAt = -1;
650             Matcher matcher = patternToWrapOn.matcher(
651                 str.substring(offset, Math.min((int) Math.min(Integer.MAX_VALUE, offset + wrapLength + 1L), inputLineLength)));
652             if (matcher.find()) {
653                 if (matcher.start() == 0) {
654                     offset += matcher.end();
655                     continue;
656                 }
657                 spaceToWrapAt = matcher.start() + offset;
658             }
659 
660             // only last line without leading spaces is left
661             if (inputLineLength - offset <= wrapLength) {
662                 break;
663             }
664 
665             while (matcher.find()) {
666                 spaceToWrapAt = matcher.start() + offset;
667             }
668 
669             if (spaceToWrapAt >= offset) {
670                 // normal case
671                 wrappedLine.append(str, offset, spaceToWrapAt);
672                 wrappedLine.append(newLineStr);
673                 offset = spaceToWrapAt + 1;
674 
675             } else // really long word or URL
676             if (wrapLongWords) {
677                 // wrap really long word one line at a time
678                 wrappedLine.append(str, offset, wrapLength + offset);
679                 wrappedLine.append(newLineStr);
680                 offset += wrapLength;
681             } else {
682                 // do not wrap really long word, just extend beyond limit
683                 matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
684                 if (matcher.find()) {
685                     spaceToWrapAt = matcher.start() + offset + wrapLength;
686                 }
687 
688                 if (spaceToWrapAt >= 0) {
689                     wrappedLine.append(str, offset, spaceToWrapAt);
690                     wrappedLine.append(newLineStr);
691                     offset = spaceToWrapAt + 1;
692                 } else {
693                     wrappedLine.append(str, offset, str.length());
694                     offset = inputLineLength;
695                 }
696             }
697         }
698 
699         // Whatever is left in line is short enough to just pass through
700         wrappedLine.append(str, offset, str.length());
701 
702         return wrappedLine.toString();
703     }
704 
705     /**
706      * {@link WordUtils} instances should NOT be constructed in
707      * standard programming. Instead, the class should be used as
708      * {@code WordUtils.wrap("foo bar", 20);}.
709      *
710      * <p>This constructor is public to permit tools that require a JavaBean
711      * instance to operate.</p>
712      */
713     public WordUtils() {
714     }
715 
716 }