1 /*
2 * #%L
3 * Nuiton Utils
4 * %%
5 * Copyright (C) 2004 - 2011 CodeLutin, Chatellier Eric
6 * %%
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Lesser Public License for more details.
16 *
17 * You should have received a copy of the GNU General Lesser Public
18 * License along with this program. If not, see
19 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
20 * #L%
21 */
22
23 package org.nuiton.util;
24
25 import org.apache.commons.lang3.SystemUtils;
26
27 import java.awt.Color;
28 import java.lang.reflect.Field;
29 import java.security.MessageDigest;
30 import java.security.NoSuchAlgorithmException;
31 import java.text.DateFormat;
32 import java.text.MessageFormat;
33 import java.text.ParseException;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Date;
37 import java.util.List;
38 import java.util.Locale;
39
40 /**
41 * Classe contenant un ensemle de methode static utiles pour la manipulation des
42 * chaine de caractere mais qui ne sont pas defini dans la classe String de
43 * Java.
44 *
45 * Created: 21 octobre 2003
46 *
47 * @author Benjamin Poussin - poussin@codelutin.com
48 * @author Tony Chemit - chemit@codelutin.com
49 *
50 */
51 public class StringUtil { // StringUtil
52
53 public static final String[] EMPTY_STRING_ARRAY = new String[0];
54
55 /** Constructor for the StringUtil object */
56 protected StringUtil() {
57 }
58
59 /**
60 * Know if a string is a valid e-mail.
61 *
62 * @param str a string
63 * @return true if {@code str} is syntactically a valid e-mail address
64 * @since 2.1
65 */
66 public static boolean isEmail(String str) {
67 return str.matches("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+((\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)?)+@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\\-]*[a-zA-Z0-9])?$");
68 }
69
70 /**
71 * Add quotes if needed to escape special csv chars (',', '\n', '\t', ',', ';', '"')
72 *
73 * @param value to escape
74 * @param csvSeparator separator used for csv
75 * @return escaped if needed value
76 */
77 public static String escapeCsvValue(String value, String csvSeparator) {
78
79 boolean valueNeedQuotes =
80 value.contains("\n")
81 || value.contains("\t")
82 || value.contains(",")
83 || value.contains(";")
84 || value.contains("\"")
85 || value.contains(csvSeparator);
86
87 if (valueNeedQuotes) {
88 // escape '"' char to prevent
89 value = value.replaceAll("\"", "\"\"");
90 value = "\"" + value + "\"";
91 }
92 return value;
93 }
94
95 /**
96 * Contract to use in {@link StringUtil#join(Iterable, ToString, String, boolean) }
97 * method. This will provide a toString method to convert an object in a
98 * string.
99 *
100 * @param <O> type of object manipulated
101 */
102 public interface ToString<O> {
103
104 /**
105 * Convert an object o in a string.
106 *
107 * @param o to convert
108 * @return the string for this object o
109 */
110 String toString(O o);
111 }
112
113 /**
114 * Used to build csv file using {@link StringUtil#join(Iterable, ToString, String, boolean) }
115 * method. This will provide a toString method to convert an object in a
116 * string and escape csv values if needed.
117 *
118 * @param <O> type of object manipulated
119 */
120 public static class ToCSV<O> implements StringUtil.ToString<O> {
121
122 protected String csvSeparator;
123
124 public ToCSV(String csvSeparator) {
125 this.csvSeparator = csvSeparator;
126 }
127
128 @Override
129 public String toString(O o) {
130 String value = getStringValue(o);
131 return escapeCsvValue(value, csvSeparator);
132 }
133
134 /**
135 * Use {@link Object#toString()} method by default
136 * Must be override to use other methods to get string value.
137 *
138 * @param o to convert
139 * @return String value
140 */
141 public String getStringValue(O o) {
142 return o.toString();
143 }
144 }
145
146 /**
147 * Used to concat an {@code iterable} of Object separated
148 * by {@code separator} using the toString() method of each object.
149 * You can specify if the string must be trimmed or not.
150 *
151 * @param iterable Iterable with objects to treate
152 * @param separator to used
153 * @param trim if each string must be trim
154 * @return the String chain of all elements separated by separator, never
155 * return null, will return an empty String for an empty list.
156 */
157 public static String join(Iterable<?> iterable, String separator,
158 boolean trim) {
159 String result = join(iterable, null, separator, trim);
160 return result;
161 }
162
163 /**
164 * Used to concat an {@code iterable} of object {@code <O>} separated by
165 * {@code separator}. This method need a {@code ts} contract to
166 * call on each object. The ToString can be null to use directly the
167 * toString() method on the object. The {@code trim} boolean is used
168 * to specify if each string object has to be trimmed. The null elements
169 * in the {@code list} will be ignored.
170 *
171 * @param <O> type of object in the list
172 * @param iterable Iterable with objects to treate
173 * @param ts used to specify how the object is converted in String
174 * @param separator to used between each object string
175 * @param trim if trim() method need to by apply on each object string
176 * @return the String chain of all elements separated by separator, never
177 * return null, will return an empty String for an empty list.
178 * @throws NullPointerException if iterable is {@code null}.
179 */
180 public static <O> String join(Iterable<O> iterable, ToString<O> ts,
181 String separator, boolean trim) throws NullPointerException {
182 if (iterable == null) {
183 throw new NullPointerException("null iterable can't be used" +
184 " to join the elements with " + separator);
185 }
186 // Do nothing for an empty list
187 if (!iterable.iterator().hasNext()) {
188 return "";
189 }
190 StringBuilder builder = new StringBuilder();
191 for (O o : iterable) {
192 // Ignore the null object in the list
193 if (o == null) {
194 continue;
195 }
196 String str;
197 // Use ToString contract from argument
198 if (ts != null) {
199 str = ts.toString(o);
200 // Or call toString() method directly on object
201 } else {
202 str = o.toString();
203 }
204 // Apply trim if needed
205 if (trim) {
206 str = str.trim();
207 }
208 builder.append(separator).append(str);
209 }
210 // Suppress the first separator at beginning of the chain
211 String result = builder.substring(separator.length());
212 return result;
213 }
214
215 /**
216 * substring from begin to end of s
217 *
218 * example:
219 * substring("tatetitotu", -4) → totu
220 *
221 * @param s the string to substring
222 * @param begin if begin < 0 then begin start at end of string - begin
223 * @return the result of substring
224 */
225 public static String substring(String s, int begin) {
226 String result = substring(s, begin, s.length());
227 return result;
228 }
229
230 /**
231 * substring from begin to end of s
232 *
233 * example:
234 * substring("tatetitotu", -4, -2) → to
235 *
236 * @param s the string to substring
237 * @param begin if begin < 0 then begin start at end of string - begin
238 * @param end if end < 0 then end start at end of string - end
239 * @return the result of substring
240 */
241 public static String substring(String s, int begin, int end) {
242 if (begin < 0) {
243 begin = s.length() + begin;
244 }
245 if (end < 0) {
246 end = s.length() + end;
247 }
248 if (end < begin) {
249 end = begin;
250 }
251
252 String result;
253 result = s.substring(begin, end);
254 return result;
255 }
256
257 private static final Character[] openingChars = {'(', '{', '['};
258
259 private static final Character[] closingChars = {')', '}', ']'};
260
261 /**
262 * Split string use 'separator' as separator. If String contains "'()[]{}
263 * this method count the number of open char end close char to split
264 * correctly argument
265 *
266 * WARNING: cette method ne fonctionne pas si le contenu contient
267 * des carateres utilisé pour le parsing et présent une seule fois.
268 * Par exemple: "l'idenfiant" contient ' qui empeche totalement le
269 * parsing de fonctionner.
270 *
271 * @param args string to split
272 * @param separator separator use to split string
273 * @return array of string
274 */
275 public static String[] split(String args, String separator) {
276 return split(openingChars, closingChars, args, separator);
277 }
278
279
280 /**
281 * Use to split string array representation in array according with ',' as
282 * default separator.
283 *
284 * WARNING: cette method ne fonctionne pas si le contenu contient
285 * des carateres utilisé pour le parsing et présent une seule fois.
286 * Par exemple: "l'idenfiant" contient ' qui empeche totalement le
287 * parsing de fonctionner.
288 *
289 * @param stringList string that represent array
290 * @return array with length > 0 if listAsString ≠ null or null
291 */
292 public static String[] split(String stringList) {
293 String[] result;
294 result = split(stringList, ",");
295 return result;
296 }
297
298 /**
299 * Split string use 'separator' as separator. If String contains "'
300 * and {@code openingChar} {@code closingChars}
301 *
302 * this method count the number of open char end close char to split
303 * correctly argument
304 *
305 * WARNING: cette method ne fonctionne pas si le contenu contient
306 * des carateres utilisé pour le parsing et présent une seule fois.
307 * Par exemple: "l'idenfiant" contient ' qui empeche totalement le
308 * parsing de fonctionner.
309 *
310 * @param openingChars list of opening caracteres
311 * @param closingChars list of closing caracteres
312 * @param args string to split
313 * @param separator separator use to split string
314 * @return array of string
315 */
316 public static String[] split(Character[] openingChars,
317 Character[] closingChars,
318 String args, String separator) {
319 if (args == null) {
320 return EMPTY_STRING_ARRAY;
321 }
322
323 List<String> result = new ArrayList<String>();
324
325 int start = 0;
326 int end;
327 StringBuilder op = new StringBuilder(); // stack of {([< currently open
328 char last = '\0'; // contains " or ' if string is openned
329
330 List<Character> opening = Arrays.asList(openingChars);
331
332 List<Character> closing = Arrays.asList(closingChars);
333
334 for (int i = 0; i < args.length(); i++) {
335 char c = args.charAt(i);
336 if (c == '\\') {
337 // pass next char
338 i++;
339 } else if (last != '"' && last != '\'') {
340 if (opening.contains(c)) {
341 op.append(c);
342 } else if (closing.contains(c)) {
343 op.deleteCharAt(op.length() - 1);
344 } else if (c == '"' || c == '\'') {
345 // open string " or '
346 last = c;
347 } else if (op.length() == 0 &&
348 args.regionMatches(i, separator, 0,
349 separator.length())) {
350 // end of one arguement
351 end = i;
352 // pass separator
353 i += separator.length() - 1;
354
355 String a = args.substring(start, end);
356 result.add(a);
357 // start of next argument
358 start = end + separator.length();
359 }
360 } else if (c == last) {
361 // close string " or '
362 last = '\0';
363 }
364 }
365
366 if (start < args.length()) {
367 String a = args.substring(start, args.length());
368 result.add(a);
369 }
370
371 return result.toArray(new String[result.size()]);
372 }
373
374 public static boolean toBoolean(String s) {
375 return "true".equalsIgnoreCase(s);
376 }
377
378 public static byte toByte(String s) {
379 return Byte.parseByte(s);
380 }
381
382 public static double toDouble(String s) {
383 return Double.parseDouble(s);
384 }
385
386 public static float toFloat(String s) {
387 return Float.parseFloat(s);
388 }
389
390 public static long toLong(String s) {
391 return Long.parseLong(s);
392 }
393
394 public static short toShort(String s) {
395 return Short.parseShort(s);
396 }
397
398 public static int toInt(String s) {
399 return Integer.parseInt(s);
400 }
401
402 public static char toChar(String s) {
403 // fixme a revoir
404 return s.charAt(0);
405 }
406
407 public static boolean[] toArrayBoolean(String... s) {
408 boolean[] result = new boolean[s.length];
409 for (int i = 0; i < result.length; i++) {
410 result[i] = toBoolean(s[i]);
411 }
412 return result;
413 }
414
415 public static byte[] toArrayByte(String... s) {
416 byte[] result = new byte[s.length];
417 for (int i = 0; i < result.length; i++) {
418 result[i] = toByte(s[i]);
419 }
420 return result;
421 }
422
423 public static double[] toArrayDouble(String... s) {
424 double[] result = new double[s.length];
425 for (int i = 0; i < result.length; i++) {
426 result[i] = toDouble(s[i]);
427 }
428 return result;
429 }
430
431 public static float[] toArrayFloat(String... s) {
432 float[] result = new float[s.length];
433 for (int i = 0; i < result.length; i++) {
434 result[i] = toFloat(s[i]);
435 }
436 return result;
437 }
438
439 public static long[] toArrayLong(String... s) {
440 long[] result = new long[s.length];
441 for (int i = 0; i < result.length; i++) {
442 result[i] = toLong(s[i]);
443 }
444 return result;
445 }
446
447 public static short[] toArrayShort(String... s) {
448 short[] result = new short[s.length];
449 for (int i = 0; i < result.length; i++) {
450 result[i] = toShort(s[i]);
451 }
452 return result;
453 }
454
455 public static int[] toArrayInt(String... s) {
456 int[] result = new int[s.length];
457 for (int i = 0; i < result.length; i++) {
458 result[i] = toInt(s[i]);
459 }
460 return result;
461 }
462
463 public static char[] toArrayChar(String... s) {
464 char[] result = new char[s.length];
465 for (int i = 0; i < result.length; i++) {
466 result[i] = toChar(s[i]);
467 }
468 // fixme a revoir
469 return result;
470 }
471
472 private static final char[] HEX_CHARS = {'0', '1', '2', '3',
473 '4', '5', '6', '7',
474 '8', '9', 'a', 'b',
475 'c', 'd', 'e', 'f',};
476
477 /**
478 * Turns array of bytes into string representing each byte as
479 * unsigned hex number.
480 *
481 * @param hash Array of bytes to convert to hex-string
482 * @return Generated hex string
483 */
484 public static String asHex(byte hash[]) {
485 char buf[] = new char[hash.length * 2];
486 for (int i = 0, x = 0; i < hash.length; i++) {
487 buf[x++] = HEX_CHARS[hash[i] >>> 4 & 0xf];
488 buf[x++] = HEX_CHARS[hash[i] & 0xf];
489 }
490 return new String(buf);
491 }
492
493 /**
494 * Essai de convertir une chaine de caractere en une couleur si possible si
495 * ce n'est pas possible retourne null.
496 *
497 * @param s la couleur sous la forme de string, par exemple "red",
498 * "yellow" ou bien en RGB "#FFAA99", et avec un canal alpha
499 * "#FFAA3366"
500 * @return la couleur demandé si possible sinon null
501 * @throws IllegalArgumentException FIXME
502 * @throws StringUtilException if any problem while conversion
503 */
504 public static Color toColor(String s) throws StringUtilException {
505 try {
506 if (s.startsWith("#")) {
507 // récuperation des valeurs hexa
508 String hr = s.substring(1, 3);
509 String hg = s.substring(3, 5);
510 String hb = s.substring(5, 7);
511
512 // conversion en entier
513 int r = Integer.parseInt(hr, 16);
514 int g = Integer.parseInt(hg, 16);
515 int b = Integer.parseInt(hb, 16);
516
517 if (s.length() == 9) {
518 // s'il y a un canal alpha on l'utilise
519 String ha = s.substring(7, 9);
520 int a = Integer.parseInt(ha, 16);
521 return new Color(r, g, b, a);
522 } else {
523 return new Color(r, g, b);
524 }
525 } else {
526 Field f;
527 f = Color.class.getField(s);
528 return (Color) f.get(Color.class);
529 }
530 } catch (NumberFormatException e) {
531 throw new StringUtilException(
532 "Error during conversion from string to color", e);
533 } catch (SecurityException e) {
534 throw new StringUtilException(
535 "Error during conversion from string to color", e);
536 } catch (NoSuchFieldException e) {
537 throw new StringUtilException(
538 "Error during conversion from string to color", e);
539 } catch (IllegalArgumentException e) {
540 throw new StringUtilException(
541 "Error during conversion from string to color", e);
542 } catch (IllegalAccessException e) {
543 throw new StringUtilException(
544 "Error during conversion from string to color", e);
545 }
546 }
547
548 public static Date toDate(String s) throws ParseException {
549 return DateFormat.getDateInstance().parse(s);
550 }
551
552 static final protected double[] timeFactors = {1000000, 1000, 60, 60, 24};
553
554 static final protected String[] timeUnites = {"ns", "ms", "s", "m", "h",
555 "d"};
556
557 /**
558 * Converts an time delay into a human readable format.
559 *
560 * @param value the delay to convert
561 * @return the memory representation of the given value
562 * @see #convert(long, double[], String[])
563 */
564 public static String convertTime(long value) {
565 return convert(value, timeFactors, timeUnites);
566 }
567
568 /**
569 * Converts an time period into a human readable format.
570 *
571 * @param value the begin time
572 * @param value2 the end time
573 * @return the time representation of the given value
574 * @see #convert(long, double[], String[])
575 */
576 public static String convertTime(long value, long value2) {
577 return convertTime(value2 - value);
578 }
579
580 static final protected double[] memoryFactors = {1024, 1024, 1024, 1024};
581
582 static final protected String[] memoryUnites = {"o", "Ko", "Mo", "Go",
583 "To"};
584
585 /**
586 * Converts an memory measure into a human readable format.
587 *
588 * @param value the memory measure to convert
589 * @return the memory representation of the given value
590 * @see #convert(long, double[], String[])
591 */
592 public static String convertMemory(long value) {
593 return convert(value, memoryFactors, memoryUnites);
594 }
595
596 /**
597 * Note: this method use the current locale
598 * (the {@link Locale#getDefault()}) in the method
599 * {@link MessageFormat#MessageFormat(String)}.
600 *
601 * @param value value to convert
602 * @param factors facotrs used form conversion
603 * @param unites libelle of unites to use
604 * @return the converted representation of the given value
605 */
606 public static String convert(long value, double[] factors, String[] unites) {
607 long sign = value == 0 ? 1 : value / Math.abs(value);
608 int i = 0;
609 double tmp = Math.abs(value);
610 while (i < factors.length && i < unites.length && tmp > factors[i]) {
611 tmp = tmp / factors[i++];
612 }
613
614 tmp *= sign;
615 String result;
616 result = MessageFormat.format("{0,number,0.###}{1}", tmp,
617 unites[i]);
618 return result;
619 }
620
621 /**
622 * Vérifie q'une chaine de caractère est valid pour les bloc openner closer, ie.
623 *
624 * que les blocs définit par les deux caractères s'entrechevauchent pas.
625 *
626 * Exemple avec '(' ')' :
627 *
628 * (a(b)) est valide, par contre ((aaa))) n'est pas valide
629 *
630 * @param txt txte a verifier
631 * @param opener le caractère ouvrant
632 * @param closer le caractère fermant
633 * @return {@code true} is la chaine est valide
634 */
635 public static boolean checkEnclosure(String txt, char opener, char closer) {
636 if (txt.indexOf(opener) == -1 && txt.indexOf(closer) == -1) {
637 // ok pas de block détectés
638 return true;
639 }
640 List<Integer> opens = new ArrayList<Integer>();
641 for (int i = 0; i < txt.length(); i++) {
642 char c = txt.charAt(i);
643 if (c == opener) {
644 // add a open block
645 opens.add(i);
646 continue;
647 }
648 if (c == closer) {
649 if (opens.isEmpty()) {
650 // problem no block left
651 return false;
652 }
653 // on supprime le dernier bloc
654 opens.remove(opens.size() - 1);
655 }
656 }
657 return opens.isEmpty();
658 }
659
660 /**
661 * Convertir un nom en une constante Java
662 *
663 * Les seuls caractères autorisés sont les alpha numériques, ains
664 * que l'underscore. tous les autres caractères seront ignorés.
665 *
666 * @param name le nom à convertir
667 * @return la constante générée
668 */
669 public static String convertToConstantName(String name) {
670 StringBuilder sb = new StringBuilder();
671 char lastChar = 0;
672 for (int i = 0, j = name.length(); i < j; i++) {
673 char c = name.charAt(i);
674 if (Character.isDigit(c)) {
675 sb.append(c);
676 lastChar = c;
677 continue;
678 }
679 if (!Character.isLetter(c)) {
680 if (lastChar != '_') {
681 sb.append('_');
682 }
683 lastChar = '_';
684 continue;
685 }
686 if (Character.isUpperCase(c)) {
687 if (!Character.isUpperCase(lastChar) && lastChar != '_') {
688 sb.append('_');
689 }
690 sb.append(c);
691 } else {
692 sb.append(Character.toUpperCase(c));
693 }
694 lastChar = c;
695 }
696 String result = sb.toString();
697 // clean tail
698 while (!result.isEmpty() && result.endsWith("_")) {
699 result = result.substring(0, result.length() - 1);
700 }
701 // clean head
702 while (!result.isEmpty() && result.startsWith("_")) {
703 result = result.substring(1);
704 }
705 return result;
706 }
707
708 /**
709 * Convert a String to MD5.
710 *
711 * @param toEncode string concerned
712 * @return md5 corresponding
713 * @throws IllegalStateException if could not found algorithm MD5
714 */
715 public static String encodeMD5(String toEncode) {
716
717 byte[] uniqueKey = toEncode.getBytes();
718 byte[] hash;
719 // on récupère un objet qui permettra de crypter la chaine
720 hash = MD5InputStream.getMD5Digest().digest(uniqueKey);
721 // hash = MessageDigest.getInstance("MD5").digest(uniqueKey);
722
723 StringBuilder hashString = new StringBuilder();
724 for (byte aHash : hash) {
725 String hex = Integer.toHexString(aHash);
726 if (hex.length() == 1) {
727 hashString.append("0");
728 hashString.append(hex.charAt(hex.length() - 1));
729 } else {
730 hashString.append(hex.substring(hex.length() - 2));
731 }
732 }
733 return hashString.toString();
734 }
735
736 /**
737 * Convert a String to SHA1.
738 *
739 * @param toEncode string to encode
740 * @return sha1 corresponding
741 * @throws IllegalStateException if could not found algorithm SHA1
742 */
743 public static String encodeSHA1(String toEncode) {
744 String result;
745
746 try {
747 MessageDigest sha1Md = MessageDigest.getInstance("SHA-1");
748
749 byte[] digest = sha1Md.digest(toEncode.getBytes());
750 result = asHex(digest);
751 } catch (NoSuchAlgorithmException ex) {
752 throw new IllegalStateException("Can't find SHA-1 message digest algorithm", ex);
753 }
754
755 return result;
756 }
757
758 /**
759 *
760 * @return the file separator escaped for a regex regarding the os used.
761 */
762 public static String getFileSeparatorRegex() {
763 String result;
764 if(SystemUtils.IS_OS_WINDOWS) {
765 result = "\\\\";
766 } else {
767 result = "/";
768 }
769 return result;
770 }
771 }