1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
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  
28  
29  
30  
31  
32  
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  
56  
57      private StringUtils() {
58          
59      }
60  
61      
62  
63  
64  
65  
66  
67  
68  
69      public static boolean isEmptyString(final CharSequence s) {
70          return s != null && s.length() == 0;
71      }
72  
73      
74  
75  
76  
77  
78  
79      public static boolean isEmptyOrNull(final CharSequence s) {
80          return s == null || s.length() == 0;
81      }
82  
83      
84  
85  
86  
87  
88  
89  
90  
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  
98  
99  
100 
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 
122 
123 
124 
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 
146 
147 
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 
155 
156 
157 
158 
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 
177 
178 
179 
180 
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 
204 
205 
206 
207 
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 
237 
238 
239 
240 
241 
242 
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 
291 
292 
293 
294 
295     public static String escapeXmlChars(final String s) {
296         return org.apache.commons.lang3.StringUtils.
297                 replaceEach(s, new String[] {"&", "<", ">"}, new String[] {"&", "<", ">"});
298     }
299 
300     
301 
302 
303 
304 
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                 
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                 
344                 if (!codepointValid) {
345                     continue;
346                 }
347 
348                 if (codepoint == '<') {
349                     escaped.append("<");
350                 }
351                 else if (codepoint == '>') {
352                     escaped.append(">");
353                 }
354                 else if (codepoint == '&') {
355                     escaped.append("&");
356                 }
357                 else if (codepoint == '\'') {
358                     escaped.append("'");
359                 }
360                 else if (codepoint == '\"') {
361                     escaped.append(""");
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 
379 
380 
381 
382 
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                 
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                 
420                 if (!codepointValid) {
421                     continue;
422                 }
423 
424                 if (codepoint == '<') {
425                     escaped.append("<");
426                 }
427                 else if (codepoint == '&') {
428                     escaped.append("&");
429                 }
430                 else if (codepoint == '\"') {
431                     escaped.append(""");
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 
449 
450 
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 
479 
480 
481 
482 
483 
484 
485 
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 
498 
499 
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 
527 
528 
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 
547 
548 
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 
568 
569 
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 
588 
589 
590 
591 
592 
593 
594 
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 
640 
641 
642 
643 
644     public static String formatColor(final Color color) {
645         return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")";
646     }
647 
648     
649 
650 
651 
652 
653 
654 
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 
663 
664 
665 
666 
667 
668     public static String sanitizeForFileName(final String toSanitize) {
669         return ILLEGAL_FILE_NAME_CHARS.matcher(toSanitize).replaceAll("_");
670     }
671 
672     
673 
674 
675 
676 
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         
689         final int pos = string.indexOf('-');
690         if (pos == -1 || pos == string.length() - 1) {
691             
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 
716 
717 
718 
719 
720 
721     public static String toRootLowerCase(final String s) {
722         return s == null ? null : s.toLowerCase(Locale.ROOT);
723     }
724 
725     
726 
727 
728 
729 
730 
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 
752 
753 
754 
755 
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 
767 
768 
769 
770 
771 
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 
783 
784 
785 
786 
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 
798 
799 
800 
801 
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 
813 
814 
815 
816 
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 
826     
827 
828 
829 
830 
831 
832 
833 
834 
835 
836     public static String substringBefore(final String str, final String find) {
837         if (isEmptyOrNull(find)) {
838             throw new IllegalArgumentException("'find' string parameter has to be not empty and not null");
839         }
840 
841         if (isEmptyString(str)) {
842             return str;
843         }
844 
845         final int pos = str.indexOf(find);
846         if (pos == -1) {
847             return str;
848         }
849         return str.substring(0, pos);
850     }
851 }