View Javadoc
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 &lt; 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 &lt; 0 then begin start at end of string - begin
238      * @param end   if end &lt; 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 &gt; 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 }