001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3.text;
018
019import java.util.regex.Matcher;
020import java.util.regex.Pattern;
021
022import org.apache.commons.lang3.ArrayUtils;
023import org.apache.commons.lang3.StringUtils;
024
025/**
026 * <p>Operations on Strings that contain words.</p>
027 *
028 * <p>This class tries to handle <code>null</code> input gracefully.
029 * An exception will not be thrown for a <code>null</code> input.
030 * Each method documents its behaviour in more detail.</p>
031 *
032 * @since 2.0
033 * @deprecated as of 3.6, use commons-text
034 * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/WordUtils.html">
035 * WordUtils</a> instead
036 */
037@Deprecated
038public class WordUtils {
039
040    /**
041     * <p><code>WordUtils</code> instances should NOT be constructed in
042     * standard programming. Instead, the class should be used as
043     * <code>WordUtils.wrap("foo bar", 20);</code>.</p>
044     *
045     * <p>This constructor is public to permit tools that require a JavaBean
046     * instance to operate.</p>
047     */
048    public WordUtils() {
049      super();
050    }
051
052    // Wrapping
053    //--------------------------------------------------------------------------
054    /**
055     * <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
056     *
057     * <p>New lines will be separated by the system property line separator.
058     * Very long words, such as URLs will <i>not</i> be wrapped.</p>
059     *
060     * <p>Leading spaces on a new line are stripped.
061     * Trailing spaces are not stripped.</p>
062     *
063     * <table border="1" summary="Wrap Results">
064     *  <tr>
065     *   <th>input</th>
066     *   <th>wrapLength</th>
067     *   <th>result</th>
068     *  </tr>
069     *  <tr>
070     *   <td>null</td>
071     *   <td>*</td>
072     *   <td>null</td>
073     *  </tr>
074     *  <tr>
075     *   <td>""</td>
076     *   <td>*</td>
077     *   <td>""</td>
078     *  </tr>
079     *  <tr>
080     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
081     *   <td>20</td>
082     *   <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
083     *  </tr>
084     *  <tr>
085     *   <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
086     *   <td>20</td>
087     *   <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
088     *  </tr>
089     *  <tr>
090     *   <td>"Click here, http://commons.apache.org, to jump to the commons website"</td>
091     *   <td>20</td>
092     *   <td>"Click here,\nhttp://commons.apache.org,\nto jump to the\ncommons website"</td>
093     *  </tr>
094     * </table>
095     *
096     * (assuming that '\n' is the systems line separator)
097     *
098     * @param str  the String to be word wrapped, may be null
099     * @param wrapLength  the column to wrap the words at, less than 1 is treated as 1
100     * @return a line with newlines inserted, <code>null</code> if null input
101     */
102    public static String wrap(final String str, final int wrapLength) {
103        return wrap(str, wrapLength, null, false);
104    }
105
106    /**
107     * <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
108     *
109     * <p>Leading spaces on a new line are stripped.
110     * Trailing spaces are not stripped.</p>
111     *
112     * <table border="1" summary="Wrap Results">
113     *  <tr>
114     *   <th>input</th>
115     *   <th>wrapLength</th>
116     *   <th>newLineString</th>
117     *   <th>wrapLongWords</th>
118     *   <th>result</th>
119     *  </tr>
120     *  <tr>
121     *   <td>null</td>
122     *   <td>*</td>
123     *   <td>*</td>
124     *   <td>true/false</td>
125     *   <td>null</td>
126     *  </tr>
127     *  <tr>
128     *   <td>""</td>
129     *   <td>*</td>
130     *   <td>*</td>
131     *   <td>true/false</td>
132     *   <td>""</td>
133     *  </tr>
134     *  <tr>
135     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
136     *   <td>20</td>
137     *   <td>"\n"</td>
138     *   <td>true/false</td>
139     *   <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
140     *  </tr>
141     *  <tr>
142     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
143     *   <td>20</td>
144     *   <td>"&lt;br /&gt;"</td>
145     *   <td>true/false</td>
146     *   <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>
147     *  </tr>
148     *  <tr>
149     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
150     *   <td>20</td>
151     *   <td>null</td>
152     *   <td>true/false</td>
153     *   <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
154     *  </tr>
155     *  <tr>
156     *   <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
157     *   <td>20</td>
158     *   <td>"\n"</td>
159     *   <td>false</td>
160     *   <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
161     *  </tr>
162     *  <tr>
163     *   <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
164     *   <td>20</td>
165     *   <td>"\n"</td>
166     *   <td>true</td>
167     *   <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
168     *  </tr>
169     * </table>
170     *
171     * @param str  the String to be word wrapped, may be null
172     * @param wrapLength  the column to wrap the words at, less than 1 is treated as 1
173     * @param newLineStr  the string to insert for a new line,
174     *  <code>null</code> uses the system property line separator
175     * @param wrapLongWords  true if long words (such as URLs) should be wrapped
176     * @return a line with newlines inserted, <code>null</code> if null input
177     */
178    public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) {
179        return wrap(str, wrapLength, newLineStr, wrapLongWords, " ");
180    }
181
182    /**
183     * <p>Wraps a single line of text, identifying words by <code>wrapOn</code>.</p>
184     *
185     * <p>Leading spaces on a new line are stripped.
186     * Trailing spaces are not stripped.</p>
187     *
188     * <table border="1" summary="Wrap Results">
189     *  <tr>
190     *   <th>input</th>
191     *   <th>wrapLength</th>
192     *   <th>newLineString</th>
193     *   <th>wrapLongWords</th>
194     *   <th>wrapOn</th>
195     *   <th>result</th>
196     *  </tr>
197     *  <tr>
198     *   <td>null</td>
199     *   <td>*</td>
200     *   <td>*</td>
201     *   <td>true/false</td>
202     *   <td>*</td>
203     *   <td>null</td>
204     *  </tr>
205     *  <tr>
206     *   <td>""</td>
207     *   <td>*</td>
208     *   <td>*</td>
209     *   <td>true/false</td>
210     *   <td>*</td>
211     *   <td>""</td>
212     *  </tr>
213     *  <tr>
214     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
215     *   <td>20</td>
216     *   <td>"\n"</td>
217     *   <td>true/false</td>
218     *   <td>" "</td>
219     *   <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
220     *  </tr>
221     *  <tr>
222     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
223     *   <td>20</td>
224     *   <td>"&lt;br /&gt;"</td>
225     *   <td>true/false</td>
226     *   <td>" "</td>
227     *   <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>
228     *  </tr>
229     *  <tr>
230     *   <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
231     *   <td>20</td>
232     *   <td>null</td>
233     *   <td>true/false</td>
234     *   <td>" "</td>
235     *   <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
236     *  </tr>
237     *  <tr>
238     *   <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
239     *   <td>20</td>
240     *   <td>"\n"</td>
241     *   <td>false</td>
242     *   <td>" "</td>
243     *   <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
244     *  </tr>
245     *  <tr>
246     *   <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
247     *   <td>20</td>
248     *   <td>"\n"</td>
249     *   <td>true</td>
250     *   <td>" "</td>
251     *   <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
252     *  </tr>
253     *  <tr>
254     *   <td>"flammable/inflammable"</td>
255     *   <td>20</td>
256     *   <td>"\n"</td>
257     *   <td>true</td>
258     *   <td>"/"</td>
259     *   <td>"flammable\ninflammable"</td>
260     *  </tr>
261     * </table>
262     * @param str  the String to be word wrapped, may be null
263     * @param wrapLength  the column to wrap the words at, less than 1 is treated as 1
264     * @param newLineStr  the string to insert for a new line,
265     *  <code>null</code> uses the system property line separator
266     * @param wrapLongWords  true if long words (such as URLs) should be wrapped
267     * @param wrapOn regex expression to be used as a breakable characters,
268     *               if blank string is provided a space character will be used
269     * @return a line with newlines inserted, <code>null</code> if null input
270     */
271    public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) {
272        if (str == null) {
273            return null;
274        }
275        if (newLineStr == null) {
276            newLineStr = System.lineSeparator();
277        }
278        if (wrapLength < 1) {
279            wrapLength = 1;
280        }
281        if (StringUtils.isBlank(wrapOn)) {
282            wrapOn = " ";
283        }
284        final Pattern patternToWrapOn = Pattern.compile(wrapOn);
285        final int inputLineLength = str.length();
286        int offset = 0;
287        final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
288
289        while (offset < inputLineLength) {
290            int spaceToWrapAt = -1;
291            Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math.min(offset + wrapLength + 1, inputLineLength)));
292            if (matcher.find()) {
293                if (matcher.start() == 0) {
294                    offset += matcher.end();
295                    continue;
296                }
297                spaceToWrapAt = matcher.start() + offset;
298            }
299
300            // only last line without leading spaces is left
301            if(inputLineLength - offset <= wrapLength) {
302                break;
303            }
304
305            while(matcher.find()){
306                spaceToWrapAt = matcher.start() + offset;
307            }
308
309            if (spaceToWrapAt >= offset) {
310                // normal case
311                wrappedLine.append(str.substring(offset, spaceToWrapAt));
312                wrappedLine.append(newLineStr);
313                offset = spaceToWrapAt + 1;
314
315            } else {
316                // really long word or URL
317                if (wrapLongWords) {
318                    // wrap really long word one line at a time
319                    wrappedLine.append(str.substring(offset, wrapLength + offset));
320                    wrappedLine.append(newLineStr);
321                    offset += wrapLength;
322                } else {
323                    // do not wrap really long word, just extend beyond limit
324                    matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
325                    if (matcher.find()) {
326                        spaceToWrapAt = matcher.start() + offset + wrapLength;
327                    }
328
329                    if (spaceToWrapAt >= 0) {
330                        wrappedLine.append(str.substring(offset, spaceToWrapAt));
331                        wrappedLine.append(newLineStr);
332                        offset = spaceToWrapAt + 1;
333                    } else {
334                        wrappedLine.append(str.substring(offset));
335                        offset = inputLineLength;
336                    }
337                }
338            }
339        }
340
341        // Whatever is left in line is short enough to just pass through
342        wrappedLine.append(str.substring(offset));
343
344        return wrappedLine.toString();
345    }
346
347    // Capitalizing
348    //-----------------------------------------------------------------------
349    /**
350     * <p>Capitalizes all the whitespace separated words in a String.
351     * Only the first character of each word is changed. To convert the
352     * rest of each word to lowercase at the same time,
353     * use {@link #capitalizeFully(String)}.</p>
354     *
355     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
356     * A <code>null</code> input String returns <code>null</code>.
357     * Capitalization uses the Unicode title case, normally equivalent to
358     * upper case.</p>
359     *
360     * <pre>
361     * WordUtils.capitalize(null)        = null
362     * WordUtils.capitalize("")          = ""
363     * WordUtils.capitalize("i am FINE") = "I Am FINE"
364     * </pre>
365     *
366     * @param str  the String to capitalize, may be null
367     * @return capitalized String, <code>null</code> if null String input
368     * @see #uncapitalize(String)
369     * @see #capitalizeFully(String)
370     */
371    public static String capitalize(final String str) {
372        return capitalize(str, null);
373    }
374
375    /**
376     * <p>Capitalizes all the delimiter separated words in a String.
377     * Only the first character of each word is changed. To convert the
378     * rest of each word to lowercase at the same time,
379     * use {@link #capitalizeFully(String, char[])}.</p>
380     *
381     * <p>The delimiters represent a set of characters understood to separate words.
382     * The first string character and the first non-delimiter character after a
383     * delimiter will be capitalized. </p>
384     *
385     * <p>A <code>null</code> input String returns <code>null</code>.
386     * Capitalization uses the Unicode title case, normally equivalent to
387     * upper case.</p>
388     *
389     * <pre>
390     * WordUtils.capitalize(null, *)            = null
391     * WordUtils.capitalize("", *)              = ""
392     * WordUtils.capitalize(*, new char[0])     = *
393     * WordUtils.capitalize("i am fine", null)  = "I Am Fine"
394     * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
395     * </pre>
396     *
397     * @param str  the String to capitalize, may be null
398     * @param delimiters  set of characters to determine capitalization, null means whitespace
399     * @return capitalized String, <code>null</code> if null String input
400     * @see #uncapitalize(String)
401     * @see #capitalizeFully(String)
402     * @since 2.1
403     */
404    public static String capitalize(final String str, final char... delimiters) {
405        final int delimLen = delimiters == null ? -1 : delimiters.length;
406        if (StringUtils.isEmpty(str) || delimLen == 0) {
407            return str;
408        }
409        final char[] buffer = str.toCharArray();
410        boolean capitalizeNext = true;
411        for (int i = 0; i < buffer.length; i++) {
412            final char ch = buffer[i];
413            if (isDelimiter(ch, delimiters)) {
414                capitalizeNext = true;
415            } else if (capitalizeNext) {
416                buffer[i] = Character.toTitleCase(ch);
417                capitalizeNext = false;
418            }
419        }
420        return new String(buffer);
421    }
422
423    //-----------------------------------------------------------------------
424    /**
425     * <p>Converts all the whitespace separated words in a String into capitalized words,
426     * that is each word is made up of a titlecase character and then a series of
427     * lowercase characters.  </p>
428     *
429     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
430     * A <code>null</code> input String returns <code>null</code>.
431     * Capitalization uses the Unicode title case, normally equivalent to
432     * upper case.</p>
433     *
434     * <pre>
435     * WordUtils.capitalizeFully(null)        = null
436     * WordUtils.capitalizeFully("")          = ""
437     * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
438     * </pre>
439     *
440     * @param str  the String to capitalize, may be null
441     * @return capitalized String, <code>null</code> if null String input
442     */
443    public static String capitalizeFully(final String str) {
444        return capitalizeFully(str, null);
445    }
446
447    /**
448     * <p>Converts all the delimiter separated words in a String into capitalized words,
449     * that is each word is made up of a titlecase character and then a series of
450     * lowercase characters. </p>
451     *
452     * <p>The delimiters represent a set of characters understood to separate words.
453     * The first string character and the first non-delimiter character after a
454     * delimiter will be capitalized. </p>
455     *
456     * <p>A <code>null</code> input String returns <code>null</code>.
457     * Capitalization uses the Unicode title case, normally equivalent to
458     * upper case.</p>
459     *
460     * <pre>
461     * WordUtils.capitalizeFully(null, *)            = null
462     * WordUtils.capitalizeFully("", *)              = ""
463     * WordUtils.capitalizeFully(*, null)            = *
464     * WordUtils.capitalizeFully(*, new char[0])     = *
465     * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
466     * </pre>
467     *
468     * @param str  the String to capitalize, may be null
469     * @param delimiters  set of characters to determine capitalization, null means whitespace
470     * @return capitalized String, <code>null</code> if null String input
471     * @since 2.1
472     */
473    public static String capitalizeFully(String str, final char... delimiters) {
474        final int delimLen = delimiters == null ? -1 : delimiters.length;
475        if (StringUtils.isEmpty(str) || delimLen == 0) {
476            return str;
477        }
478        str = str.toLowerCase();
479        return capitalize(str, delimiters);
480    }
481
482    //-----------------------------------------------------------------------
483    /**
484     * <p>Uncapitalizes all the whitespace separated words in a String.
485     * Only the first character of each word is changed.</p>
486     *
487     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
488     * A <code>null</code> input String returns <code>null</code>.</p>
489     *
490     * <pre>
491     * WordUtils.uncapitalize(null)        = null
492     * WordUtils.uncapitalize("")          = ""
493     * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
494     * </pre>
495     *
496     * @param str  the String to uncapitalize, may be null
497     * @return uncapitalized String, <code>null</code> if null String input
498     * @see #capitalize(String)
499     */
500    public static String uncapitalize(final String str) {
501        return uncapitalize(str, null);
502    }
503
504    /**
505     * <p>Uncapitalizes all the whitespace separated words in a String.
506     * Only the first character of each word is changed.</p>
507     *
508     * <p>The delimiters represent a set of characters understood to separate words.
509     * The first string character and the first non-delimiter character after a
510     * delimiter will be uncapitalized. </p>
511     *
512     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
513     * A <code>null</code> input String returns <code>null</code>.</p>
514     *
515     * <pre>
516     * WordUtils.uncapitalize(null, *)            = null
517     * WordUtils.uncapitalize("", *)              = ""
518     * WordUtils.uncapitalize(*, null)            = *
519     * WordUtils.uncapitalize(*, new char[0])     = *
520     * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
521     * </pre>
522     *
523     * @param str  the String to uncapitalize, may be null
524     * @param delimiters  set of characters to determine uncapitalization, null means whitespace
525     * @return uncapitalized String, <code>null</code> if null String input
526     * @see #capitalize(String)
527     * @since 2.1
528     */
529    public static String uncapitalize(final String str, final char... delimiters) {
530        final int delimLen = delimiters == null ? -1 : delimiters.length;
531        if (StringUtils.isEmpty(str) || delimLen == 0) {
532            return str;
533        }
534        final char[] buffer = str.toCharArray();
535        boolean uncapitalizeNext = true;
536        for (int i = 0; i < buffer.length; i++) {
537            final char ch = buffer[i];
538            if (isDelimiter(ch, delimiters)) {
539                uncapitalizeNext = true;
540            } else if (uncapitalizeNext) {
541                buffer[i] = Character.toLowerCase(ch);
542                uncapitalizeNext = false;
543            }
544        }
545        return new String(buffer);
546    }
547
548    //-----------------------------------------------------------------------
549    /**
550     * <p>Swaps the case of a String using a word based algorithm.</p>
551     *
552     * <ul>
553     *  <li>Upper case character converts to Lower case</li>
554     *  <li>Title case character converts to Lower case</li>
555     *  <li>Lower case character after Whitespace or at start converts to Title case</li>
556     *  <li>Other Lower case character converts to Upper case</li>
557     * </ul>
558     *
559     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
560     * A <code>null</code> input String returns <code>null</code>.</p>
561     *
562     * <pre>
563     * StringUtils.swapCase(null)                 = null
564     * StringUtils.swapCase("")                   = ""
565     * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
566     * </pre>
567     *
568     * @param str  the String to swap case, may be null
569     * @return the changed String, <code>null</code> if null String input
570     */
571    public static String swapCase(final String str) {
572        if (StringUtils.isEmpty(str)) {
573            return str;
574        }
575        final char[] buffer = str.toCharArray();
576
577        boolean whitespace = true;
578
579        for (int i = 0; i < buffer.length; i++) {
580            final char ch = buffer[i];
581            if (Character.isUpperCase(ch)) {
582                buffer[i] = Character.toLowerCase(ch);
583                whitespace = false;
584            } else if (Character.isTitleCase(ch)) {
585                buffer[i] = Character.toLowerCase(ch);
586                whitespace = false;
587            } else if (Character.isLowerCase(ch)) {
588                if (whitespace) {
589                    buffer[i] = Character.toTitleCase(ch);
590                    whitespace = false;
591                } else {
592                    buffer[i] = Character.toUpperCase(ch);
593                }
594            } else {
595                whitespace = Character.isWhitespace(ch);
596            }
597        }
598        return new String(buffer);
599    }
600
601    //-----------------------------------------------------------------------
602    /**
603     * <p>Extracts the initial characters from each word in the String.</p>
604     *
605     * <p>All first characters after whitespace are returned as a new string.
606     * Their case is not changed.</p>
607     *
608     * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
609     * A <code>null</code> input String returns <code>null</code>.</p>
610     *
611     * <pre>
612     * WordUtils.initials(null)             = null
613     * WordUtils.initials("")               = ""
614     * WordUtils.initials("Ben John Lee")   = "BJL"
615     * WordUtils.initials("Ben J.Lee")      = "BJ"
616     * </pre>
617     *
618     * @param str  the String to get initials from, may be null
619     * @return String of initial letters, <code>null</code> if null String input
620     * @see #initials(String,char[])
621     * @since 2.2
622     */
623    public static String initials(final String str) {
624        return initials(str, null);
625    }
626
627    /**
628     * <p>Extracts the initial characters from each word in the String.</p>
629     *
630     * <p>All first characters after the defined delimiters are returned as a new string.
631     * Their case is not changed.</p>
632     *
633     * <p>If the delimiters array is null, then Whitespace is used.
634     * Whitespace is defined by {@link Character#isWhitespace(char)}.
635     * A <code>null</code> input String returns <code>null</code>.
636     * An empty delimiter array returns an empty String.</p>
637     *
638     * <pre>
639     * WordUtils.initials(null, *)                = null
640     * WordUtils.initials("", *)                  = ""
641     * WordUtils.initials("Ben John Lee", null)   = "BJL"
642     * WordUtils.initials("Ben J.Lee", null)      = "BJ"
643     * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
644     * WordUtils.initials(*, new char[0])         = ""
645     * </pre>
646     *
647     * @param str  the String to get initials from, may be null
648     * @param delimiters  set of characters to determine words, null means whitespace
649     * @return String of initial characters, <code>null</code> if null String input
650     * @see #initials(String)
651     * @since 2.2
652     */
653    public static String initials(final String str, final char... delimiters) {
654        if (StringUtils.isEmpty(str)) {
655            return str;
656        }
657        if (delimiters != null && delimiters.length == 0) {
658            return StringUtils.EMPTY;
659        }
660        final int strLen = str.length();
661        final char[] buf = new char[strLen / 2 + 1];
662        int count = 0;
663        boolean lastWasGap = true;
664        for (int i = 0; i < strLen; i++) {
665            final char ch = str.charAt(i);
666
667            if (isDelimiter(ch, delimiters)) {
668                lastWasGap = true;
669            } else if (lastWasGap) {
670                buf[count++] = ch;
671                lastWasGap = false;
672            } else {
673                continue; // ignore ch
674            }
675        }
676        return new String(buf, 0, count);
677    }
678
679    //-----------------------------------------------------------------------
680    /**
681     * <p>Checks if the String contains all words in the given array.</p>
682     *
683     * <p>
684     * A {@code null} String will return {@code false}. A {@code null}, zero
685     * length search array or if one element of array is null will return {@code false}.
686     * </p>
687     *
688     * <pre>
689     * WordUtils.containsAllWords(null, *)            = false
690     * WordUtils.containsAllWords("", *)              = false
691     * WordUtils.containsAllWords(*, null)            = false
692     * WordUtils.containsAllWords(*, [])              = false
693     * WordUtils.containsAllWords("abcd", "ab", "cd") = false
694     * WordUtils.containsAllWords("abc def", "def", "abc") = true
695     * </pre>
696     *
697     *
698     * @param word The CharSequence to check, may be null
699     * @param words The array of String words to search for, may be null
700     * @return {@code true} if all search words are found, {@code false} otherwise
701     * @since 3.5
702     */
703    public static boolean containsAllWords(final CharSequence word, final CharSequence... words) {
704        if (StringUtils.isEmpty(word) || ArrayUtils.isEmpty(words)) {
705            return false;
706        }
707        for (final CharSequence w : words) {
708            if (StringUtils.isBlank(w)) {
709                return false;
710            }
711            final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*");
712            if (!p.matcher(word).matches()) {
713                return false;
714            }
715        }
716        return true;
717    }
718
719    //-----------------------------------------------------------------------
720    /**
721     * Is the character a delimiter.
722     *
723     * @param ch  the character to check
724     * @param delimiters  the delimiters
725     * @return true if it is a delimiter
726     */
727    private static boolean isDelimiter(final char ch, final char[] delimiters) {
728        if (delimiters == null) {
729            return Character.isWhitespace(ch);
730        }
731        for (final char delimiter : delimiters) {
732            if (ch == delimiter) {
733                return true;
734            }
735        }
736        return false;
737    }
738
739}