View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.util;
16  
17  import java.nio.charset.Charset;
18  import java.util.Locale;
19  import java.util.Map;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  import org.htmlunit.html.impl.Color;
25  
26  /**
27   * String utilities class for utility functions not covered by third party libraries.
28   *
29   * @author Daniel Gredler
30   * @author Ahmed Ashour
31   * @author Martin Tamme
32   * @author Ronald Brill
33   */
34  public final class StringUtils {
35  
36      private static final Pattern HEX_COLOR = Pattern.compile("#([\\da-fA-F]{3}|[\\da-fA-F]{6})");
37      private static final Pattern RGB_COLOR =
38          Pattern.compile("rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
39                              + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
40                              + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*\\)");
41      private static final Pattern RGBA_COLOR =
42              Pattern.compile("rgba\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
43                                   + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
44                                   + "\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%?\\s*,"
45                                   + "\\s*((0?.[1-9])|[01])\\s*\\)");
46      private static final Pattern HSL_COLOR =
47              Pattern.compile("hsl\\(\\s*((0|[1-9]\\d?|[12]\\d\\d?|3[0-5]\\d)(.\\d*)?)\\s*,"
48                                  + "\\s*((0|[1-9]\\d?|100)(.\\d*)?)%\\s*,"
49                                  + "\\s*((0|[1-9]\\d?|100)(.\\d*)?)%\\s*\\)");
50      private static final Pattern ILLEGAL_FILE_NAME_CHARS = Pattern.compile("\\\\|/|\\||:|\\?|\\*|\"|<|>|\\p{Cntrl}");
51  
52      private static final Map<String, String> CAMELIZE_CACHE = new ConcurrentHashMap<>();
53  
54      /**
55       * Disallow instantiation of this class.
56       */
57      private StringUtils() {
58          // Empty.
59      }
60  
61      /**
62       * Returns true if the param is not null and empty. This is different from
63       * {@link org.apache.commons.lang3.StringUtils#isEmpty(CharSequence)} because
64       * this returns false if the provided string is null.
65       *
66       * @param s the string to check
67       * @return true if the param is not null and empty
68       */
69      public static boolean isEmptyString(final CharSequence s) {
70          return s != null && s.length() == 0;
71      }
72  
73      /**
74       * Returns true if the param is null or empty.
75       *
76       * @param s the string to check
77       * @return true if the param is null or empty
78       */
79      public static boolean isEmptyOrNull(final CharSequence s) {
80          return s == null || s.length() == 0;
81      }
82  
83      /**
84       * Returns either the passed in CharSequence, or if the CharSequence is
85       * empty or {@code null}, the default value.
86       *
87       * @param <T> the kind of CharSequence
88       * @param s  the CharSequence to check
89       * @param defaultString the default to return if the input is empty or null
90       * @return the passed in CharSequence, or the defaultString
91       */
92      public static <T extends CharSequence> T defaultIfEmptyOrNull(final T s, final T defaultString) {
93          return isEmptyOrNull(s) ? defaultString : s;
94      }
95  
96      /**
97       * Tests if a CharSequence is null, empty, or contains only whitespace.
98       *
99       * @param s the CharSequence to check
100      * @return true if a CharSequence is null, empty, or contains only whitespace
101      */
102     public static boolean isBlank(final CharSequence s) {
103         if (s == null) {
104             return true;
105         }
106 
107         final int length = s.length();
108         if (length == 0) {
109             return true;
110         }
111 
112         for (int i = 0; i < length; i++) {
113             if (!Character.isWhitespace(s.charAt(i))) {
114                 return false;
115             }
116         }
117         return true;
118     }
119 
120     /**
121      * Tests if a CharSequence is NOT null, empty, or contains only whitespace.
122      *
123      * @param s the CharSequence to check
124      * @return false if a CharSequence is null, empty, or contains only whitespace
125      */
126     public static boolean isNotBlank(final CharSequence s) {
127         if (s == null) {
128             return false;
129         }
130 
131         final int length = s.length();
132         if (length == 0) {
133             return false;
134         }
135 
136         for (int i = 0; i < length; i++) {
137             if (!Character.isWhitespace(s.charAt(i))) {
138                 return true;
139             }
140         }
141         return false;
142     }
143 
144     /**
145      * @param expected the char that we expect
146      * @param s the string to check
147      * @return true if the provided string has only one char and this matches the expectation
148      */
149     public static boolean equalsChar(final char expected, final CharSequence s) {
150         return s != null && s.length() == 1 && expected == s.charAt(0);
151     }
152 
153     /**
154      * Tests if a CharSequence starts with a specified prefix.
155      *
156      * @param s the string to check
157      * @param expectedStart the string that we expect at the beginning (has to be not null and not empty)
158      * @return true if the provided string has only one char and this matches the expectation
159      */
160     public static boolean startsWithIgnoreCase(final String s, final String expectedStart) {
161         if (expectedStart == null || expectedStart.length() == 0) {
162             throw new IllegalArgumentException("Expected start string can't be null or empty");
163         }
164 
165         if (s == null) {
166             return false;
167         }
168         if (s == expectedStart) {
169             return true;
170         }
171 
172         return s.regionMatches(true, 0, expectedStart, 0, expectedStart.length());
173     }
174 
175     /**
176      * Tests if a CharSequence ends with a specified prefix.
177      *
178      * @param s the string to check
179      * @param expectedEnd the string that we expect at the end (has to be not null and not empty)
180      * @return true if the provided string has only one char and this matches the expectation
181      */
182     public static boolean endsWithIgnoreCase(final String s, final String expectedEnd) {
183         if (expectedEnd == null) {
184             throw new IllegalArgumentException("Expected end string can't be null or empty");
185         }
186 
187         final int expectedEndLength = expectedEnd.length();
188         if (expectedEndLength == 0) {
189             throw new IllegalArgumentException("Expected end string can't be null or empty");
190         }
191 
192         if (s == null) {
193             return false;
194         }
195         if (s == expectedEnd) {
196             return true;
197         }
198 
199         return s.regionMatches(true, s.length() - expectedEndLength, expectedEnd, 0, expectedEndLength);
200     }
201 
202     /**
203      * Tests if a CharSequence ends with a specified prefix.
204      *
205      * @param s the string to check
206      * @param expected the string that we expect to be a substring (has to be not null and not empty)
207      * @return true if the provided string has only one char and this matches the expectation
208      */
209     public static boolean containsIgnoreCase(final String s, final String expected) {
210         if (expected == null) {
211             throw new IllegalArgumentException("Expected string can't be null or empty");
212         }
213 
214         final int expectedLength = expected.length();
215         if (expectedLength == 0) {
216             throw new IllegalArgumentException("Expected string can't be null or empty");
217         }
218 
219         if (s == null) {
220             return false;
221         }
222         if (s == expected) {
223             return true;
224         }
225 
226         final int max = s.length() - expectedLength;
227         for (int i = 0; i <= max; i++) {
228             if (s.regionMatches(true, i, expected, 0, expectedLength)) {
229                 return true;
230             }
231         }
232         return false;
233     }
234 
235     /**
236      * Replaces multiple characters in a String in one go.
237      * This method can also be used to delete characters.
238      *
239      * @param str          String to replace characters in, may be null.
240      * @param searchChars  a set of characters to search for, may be null.
241      * @param replaceChars a set of characters to replace, may be null.
242      * @return modified String, or the input string if no replace was done.
243      */
244     @SuppressWarnings("null")
245     public static String replaceChars(final String str, final String searchChars, final String replaceChars) {
246         if (isEmptyOrNull(str) || isEmptyOrNull(searchChars)) {
247             return str;
248         }
249 
250         final int replaceCharsLength = replaceChars == null ? 0 : replaceChars.length();
251         final int strLength = str.length();
252 
253         StringBuilder buf = null;
254         int i = 0;
255         for ( ; i < strLength; i++) {
256             final char ch = str.charAt(i);
257             final int index = searchChars.indexOf(ch);
258             if (index != -1) {
259                 buf = new StringBuilder(strLength);
260                 buf.append(str, 0, i);
261                 if (index < replaceCharsLength) {
262                     buf.append(replaceChars.charAt(index));
263                 }
264                 break;
265             }
266         }
267 
268         if (buf == null) {
269             return str;
270         }
271 
272         i++;
273         for ( ; i < strLength; i++) {
274             final char ch = str.charAt(i);
275             final int index = searchChars.indexOf(ch);
276             if (index != -1) {
277                 if (index < replaceCharsLength) {
278                     buf.append(replaceChars.charAt(index));
279                 }
280             }
281             else {
282                 buf.append(ch);
283             }
284         }
285 
286         return buf.toString();
287     }
288 
289     /**
290      * Escapes the characters '&lt;', '&gt;' and '&amp;' into their XML entity equivalents.
291      *
292      * @param s the string to escape
293      * @return the escaped form of the specified string
294      */
295     public static String escapeXmlChars(final String s) {
296         return org.apache.commons.lang3.StringUtils.
297                 replaceEach(s, new String[] {"&", "<", ">"}, new String[] {"&amp;", "&lt;", "&gt;"});
298     }
299 
300     /**
301      * Escape the string to be used as xml 1.0 content be replacing the
302      * characters '&quot;', '&amp;', '&#39;', '&lt;', and '&gt;' into their XML entity equivalents.
303      * @param text the attribute value
304      * @return the escaped value
305      */
306     public static String escapeXml(final String text) {
307         if (text == null) {
308             return null;
309         }
310 
311         StringBuilder escaped = null;
312 
313         final int offset = 0;
314         final int max = text.length();
315 
316         int readOffset = offset;
317 
318         for (int i = offset; i < max; i++) {
319             final int codepoint = Character.codePointAt(text, i);
320             final boolean codepointValid = supportedByXML10(codepoint);
321 
322             if (!codepointValid
323                     || codepoint == '<'
324                     || codepoint == '>'
325                     || codepoint == '&'
326                     || codepoint == '\''
327                     || codepoint == '"') {
328 
329                 // replacement required
330                 if (escaped == null) {
331                     escaped = new StringBuilder(max);
332                 }
333 
334                 if (i > readOffset) {
335                     escaped.append(text, readOffset, i);
336                 }
337 
338                 if (Character.charCount(codepoint) > 1) {
339                     i++;
340                 }
341                 readOffset = i + 1;
342 
343                 // skip
344                 if (!codepointValid) {
345                     continue;
346                 }
347 
348                 if (codepoint == '<') {
349                     escaped.append("&lt;");
350                 }
351                 else if (codepoint == '>') {
352                     escaped.append("&gt;");
353                 }
354                 else if (codepoint == '&') {
355                     escaped.append("&amp;");
356                 }
357                 else if (codepoint == '\'') {
358                     escaped.append("&apos;");
359                 }
360                 else if (codepoint == '\"') {
361                     escaped.append("&quot;");
362                 }
363             }
364         }
365 
366         if (escaped == null) {
367             return text;
368         }
369 
370         if (max > readOffset) {
371             escaped.append(text, readOffset, max);
372         }
373 
374         return escaped.toString();
375     }
376 
377     /**
378      * Escape the string to be used as attribute value.
379      * Only {@code <}, {@code &} and {@code "} have to be escaped (see
380      * <a href="http://www.w3.org/TR/REC-xml/#d0e888">http://www.w3.org/TR/REC-xml/#d0e888</a>).
381      * @param attValue the attribute value
382      * @return the escaped value
383      */
384     public static String escapeXmlAttributeValue(final String attValue) {
385         if (attValue == null) {
386             return null;
387         }
388 
389         StringBuilder escaped = null;
390 
391         final int offset = 0;
392         final int max = attValue.length();
393 
394         int readOffset = offset;
395 
396         for (int i = offset; i < max; i++) {
397             final int codepoint = Character.codePointAt(attValue, i);
398             final boolean codepointValid = supportedByXML10(codepoint);
399 
400             if (!codepointValid
401                     || codepoint == '<'
402                     || codepoint == '&'
403                     || codepoint == '"') {
404 
405                 // replacement required
406                 if (escaped == null) {
407                     escaped = new StringBuilder(max);
408                 }
409 
410                 if (i > readOffset) {
411                     escaped.append(attValue, readOffset, i);
412                 }
413 
414                 if (Character.charCount(codepoint) > 1) {
415                     i++;
416                 }
417                 readOffset = i + 1;
418 
419                 // skip
420                 if (!codepointValid) {
421                     continue;
422                 }
423 
424                 if (codepoint == '<') {
425                     escaped.append("&lt;");
426                 }
427                 else if (codepoint == '&') {
428                     escaped.append("&amp;");
429                 }
430                 else if (codepoint == '\"') {
431                     escaped.append("&quot;");
432                 }
433             }
434         }
435 
436         if (escaped == null) {
437             return attValue;
438         }
439 
440         if (max > readOffset) {
441             escaped.append(attValue, readOffset, max);
442         }
443 
444         return escaped.toString();
445     }
446 
447     /*
448      * XML 1.0 does not allow control characters or unpaired Unicode surrogate codepoints.
449      * We will remove characters that do not fit in the following ranges:
450      * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
451      */
452     private static boolean supportedByXML10(final int codepoint) {
453         if (codepoint < 0x20) {
454             return codepoint == 0x9 || codepoint == 0xA || codepoint == 0xD;
455         }
456         if (codepoint <= 0xD7FF) {
457             return true;
458         }
459 
460         if (codepoint < 0xE000) {
461             return false;
462         }
463         if (codepoint <= 0xFFFD) {
464             return true;
465         }
466 
467         if (codepoint < 0x10000) {
468             return false;
469         }
470         if (codepoint <= 0x10FFFF) {
471             return true;
472         }
473 
474         return true;
475     }
476 
477     /**
478      * Returns the index within the specified string of the first occurrence of
479      * the specified search character.
480      *
481      * @param s the string to search
482      * @param searchChar the character to search for
483      * @param beginIndex the index at which to start the search
484      * @param endIndex the index at which to stop the search
485      * @return the index of the first occurrence of the character in the string or <code>-1</code>
486      */
487     public static int indexOf(final String s, final char searchChar, final int beginIndex, final int endIndex) {
488         for (int i = beginIndex; i < endIndex; i++) {
489             if (s.charAt(i) == searchChar) {
490                 return i;
491             }
492         }
493         return -1;
494     }
495 
496     /**
497      * Returns a Color parsed from the given RGB in hexadecimal notation.
498      * @param token the token to parse
499      * @return a Color whether the token is a color RGB in hexadecimal notation; otherwise null
500      */
501     public static Color asColorHexadecimal(final String token) {
502         if (token == null) {
503             return null;
504         }
505         final Matcher tmpMatcher = HEX_COLOR.matcher(token);
506         final boolean tmpFound = tmpMatcher.matches();
507         if (!tmpFound) {
508             return null;
509         }
510 
511         final String tmpHex = tmpMatcher.group(1);
512         if (tmpHex.length() == 6) {
513             final int tmpRed = Integer.parseInt(tmpHex.substring(0, 2), 16);
514             final int tmpGreen = Integer.parseInt(tmpHex.substring(2, 4), 16);
515             final int tmpBlue = Integer.parseInt(tmpHex.substring(4, 6), 16);
516             return new Color(tmpRed, tmpGreen, tmpBlue);
517         }
518 
519         final int tmpRed = Integer.parseInt(tmpHex.substring(0, 1) + tmpHex.substring(0, 1), 16);
520         final int tmpGreen = Integer.parseInt(tmpHex.substring(1, 2) + tmpHex.substring(1, 2), 16);
521         final int tmpBlue = Integer.parseInt(tmpHex.substring(2, 3) + tmpHex.substring(2, 3), 16);
522         return new Color(tmpRed, tmpGreen, tmpBlue);
523     }
524 
525     /**
526      * Returns a Color parsed from the given rgb notation if found inside the given string.
527      * @param token the token to parse
528      * @return a Color whether the token contains a color in RGB notation; otherwise null
529      */
530     public static Color findColorRGB(final String token) {
531         if (token == null) {
532             return null;
533         }
534         final Matcher tmpMatcher = RGB_COLOR.matcher(token);
535         if (!tmpMatcher.find()) {
536             return null;
537         }
538 
539         final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
540         final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
541         final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
542         return new Color(tmpRed, tmpGreen, tmpBlue);
543     }
544 
545     /**
546      * Returns a Color parsed from the given rgb notation.
547      * @param token the token to parse
548      * @return a Color whether the token is a color in RGB notation; otherwise null
549      */
550     public static Color findColorRGBA(final String token) {
551         if (token == null) {
552             return null;
553         }
554         final Matcher tmpMatcher = RGBA_COLOR.matcher(token);
555         if (!tmpMatcher.find()) {
556             return null;
557         }
558 
559         final int tmpRed = Integer.parseInt(tmpMatcher.group(1));
560         final int tmpGreen = Integer.parseInt(tmpMatcher.group(2));
561         final int tmpBlue = Integer.parseInt(tmpMatcher.group(3));
562         final int tmpAlpha = (int) (Float.parseFloat(tmpMatcher.group(4)) * 255);
563         return new Color(tmpRed, tmpGreen, tmpBlue, tmpAlpha);
564     }
565 
566     /**
567      * Returns a Color parsed from the given hsl notation if found inside the given string.
568      * @param token the token to parse
569      * @return a Color whether the token contains a color in RGB notation; otherwise null
570      */
571     public static Color findColorHSL(final String token) {
572         if (token == null) {
573             return null;
574         }
575         final Matcher tmpMatcher = HSL_COLOR.matcher(token);
576         if (!tmpMatcher.find()) {
577             return null;
578         }
579 
580         final float tmpHue = Float.parseFloat(tmpMatcher.group(1)) / 360f;
581         final float tmpSaturation = Float.parseFloat(tmpMatcher.group(4)) / 100f;
582         final float tmpLightness = Float.parseFloat(tmpMatcher.group(7)) / 100f;
583         return hslToRgb(tmpHue, tmpSaturation, tmpLightness);
584     }
585 
586     /**
587      * Converts an HSL color value to RGB. Conversion formula
588      * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
589      * Assumes h, s, and l are contained in the set [0, 1]
590      *
591      * @param h the hue
592      * @param s the saturation
593      * @param l the lightness
594      * @return {@link Color}
595      */
596     private static Color hslToRgb(final float h, final float s, final float l) {
597         if (s == 0f) {
598             return new Color(to255(l), to255(l), to255(l));
599         }
600 
601         final float q = l < 0.5f ? l * (1 + s) : l + s - l * s;
602         final float p = 2 * l - q;
603         final float r = hueToRgb(p, q, h + 1f / 3f);
604         final float g = hueToRgb(p, q, h);
605         final float b = hueToRgb(p, q, h - 1f / 3f);
606 
607         return new Color(to255(r), to255(g), to255(b));
608     }
609 
610     private static float hueToRgb(final float p, final float q, float t) {
611         if (t < 0f) {
612             t += 1f;
613         }
614 
615         if (t > 1f) {
616             t -= 1f;
617         }
618 
619         if (t < 1f / 6f) {
620             return p + (q - p) * 6f * t;
621         }
622 
623         if (t < 1f / 2f) {
624             return q;
625         }
626 
627         if (t < 2f / 3f) {
628             return p + (q - p) * (2f / 3f - t) * 6f;
629         }
630 
631         return p;
632     }
633 
634     private static int to255(final float value) {
635         return (int) Math.min(255, 256 * value);
636     }
637 
638     /**
639      * Formats the specified color.
640      *
641      * @param color the color to format
642      * @return the specified color, formatted
643      */
644     public static String formatColor(final Color color) {
645         return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")";
646     }
647 
648     /**
649      * Sanitize a string for use in Matcher.appendReplacement.
650      * Replaces all \ with \\ and $ as \$ because they are used as control
651      * characters in appendReplacement.
652      *
653      * @param toSanitize the string to sanitize
654      * @return sanitized version of the given string
655      */
656     public static String sanitizeForAppendReplacement(final String toSanitize) {
657         return org.apache.commons.lang3.StringUtils.replaceEach(toSanitize,
658                                     new String[] {"\\", "$"}, new String[]{"\\\\", "\\$"});
659     }
660 
661     /**
662      * Sanitizes a string for use as filename.
663      * Replaces \, /, |, :, ?, *, &quot;, &lt;, &gt;, control chars by _ (underscore).
664      *
665      * @param toSanitize the string to sanitize
666      * @return sanitized version of the given string
667      */
668     public static String sanitizeForFileName(final String toSanitize) {
669         return ILLEGAL_FILE_NAME_CHARS.matcher(toSanitize).replaceAll("_");
670     }
671 
672     /**
673      * Transforms the specified string from delimiter-separated (e.g. <code>font-size</code>)
674      * to camel-cased (e.g. <code>fontSize</code>).
675      * @param string the string to camelize
676      * @return the transformed string
677      */
678     public static String cssCamelize(final String string) {
679         if (string == null) {
680             return null;
681         }
682 
683         String result = CAMELIZE_CACHE.get(string);
684         if (null != result) {
685             return result;
686         }
687 
688         // not found in CamelizeCache_; convert and store in cache
689         final int pos = string.indexOf('-');
690         if (pos == -1 || pos == string.length() - 1) {
691             // cache also this strings for performance
692             CAMELIZE_CACHE.put(string, string);
693             return string;
694         }
695 
696         final StringBuilder builder = new StringBuilder(string);
697         builder.deleteCharAt(pos);
698         builder.setCharAt(pos, Character.toUpperCase(builder.charAt(pos)));
699 
700         int i = pos + 1;
701         while (i < builder.length() - 1) {
702             if (builder.charAt(i) == '-') {
703                 builder.deleteCharAt(i);
704                 builder.setCharAt(i, Character.toUpperCase(builder.charAt(i)));
705             }
706             i++;
707         }
708         result = builder.toString();
709         CAMELIZE_CACHE.put(string, result);
710 
711         return result;
712     }
713 
714     /**
715      * Lowercases a string by checking and check for null first. There
716      * is no cache involved and the ROOT locale is used to convert it.
717      *
718      * @param s the string to lowercase
719      * @return the lowercased string
720      */
721     public static String toRootLowerCase(final String s) {
722         return s == null ? null : s.toLowerCase(Locale.ROOT);
723     }
724 
725     /**
726      * Transforms the specified string from camel-cased (e.g. <code>fontSize</code>)
727      * to delimiter-separated (e.g. <code>font-size</code>).
728      * to camel-cased .
729      * @param string the string to decamelize
730      * @return the transformed string
731      */
732     public static String cssDeCamelize(final String string) {
733         if (string == null || string.isEmpty()) {
734             return string;
735         }
736 
737         final StringBuilder builder = new StringBuilder();
738         for (int i = 0; i < string.length(); i++) {
739             final char ch = string.charAt(i);
740             if (Character.isUpperCase(ch)) {
741                 builder.append('-').append(Character.toLowerCase(ch));
742             }
743             else {
744                 builder.append(ch);
745             }
746         }
747         return builder.toString();
748     }
749 
750     /**
751      * Converts a string into a byte array using the specified encoding.
752      *
753      * @param charset the charset
754      * @param content the string to convert
755      * @return the String as a byte[]; if the specified encoding is not supported an empty byte[] will be returned
756      */
757     public static byte[] toByteArray(final String content, final Charset charset) {
758         if (content ==  null || content.isEmpty()) {
759             return new byte[0];
760         }
761 
762         return content.getBytes(charset);
763     }
764 
765     /**
766      * Splits the provided text into an array, using whitespace as the
767      * separator.
768      * Whitespace is defined by {@link Character#isWhitespace(char)}.
769      *
770      * @param str  the String to parse, may be null
771      * @return an array of parsed Strings, an empty array if null String input
772      */
773     public static String[] splitAtJavaWhitespace(final String str) {
774         final String[] parts = org.apache.commons.lang3.StringUtils.split(str);
775         if (parts == null) {
776             return new String[0];
777         }
778         return parts;
779     }
780 
781     /**
782      * Splits the provided text into an array, using blank as the
783      * separator.
784      *
785      * @param str  the String to parse, may be null
786      * @return an array of parsed Strings, an empty array if null String input
787      */
788     public static String[] splitAtBlank(final String str) {
789         final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ' ');
790         if (parts == null) {
791             return new String[0];
792         }
793         return parts;
794     }
795 
796     /**
797      * Splits the provided text into an array, using blank as the
798      * separator.
799      *
800      * @param str  the String to parse, may be null
801      * @return an array of parsed Strings, an empty array if null String input
802      */
803     public static String[] splitAtComma(final String str) {
804         final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ',');
805         if (parts == null) {
806             return new String[0];
807         }
808         return parts;
809     }
810 
811     /**
812      * Splits the provided text into an array, using comma or blank as the
813      * separator.
814      *
815      * @param str the String to parse, may be null
816      * @return an array of parsed Strings, an empty array if null String input
817      */
818     public static String[] splitAtCommaOrBlank(final String str) {
819         final String[] parts = org.apache.commons.lang3.StringUtils.split(str, ", ");
820         if (parts == null) {
821             return new String[0];
822         }
823         return parts;
824     }
825 }