View Javadoc

1   /* This file is part of the project "Hilbert II" - http://www.qedeq.org" target="alexandria_uri">http://www.qedeq.org
2    *
3    * Copyright 2000-2014,  Michael Meyling <mime@qedeq.org>.
4    *
5    * "Hilbert II" is free software; you can redistribute
6    * it and/or modify it under the terms of the GNU General Public
7    * License as published by the Free Software Foundation; either
8    * version 2 of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU General Public License for more details.
14   */
15  
16  package org.qedeq.base.utility;
17  
18  import java.io.BufferedReader;
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.StringReader;
22  import java.io.UnsupportedEncodingException;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.Properties;
29  import java.util.Set;
30  
31  import org.apache.commons.lang.ArrayUtils;
32  import org.apache.commons.lang.StringEscapeUtils;
33  import org.apache.commons.lang.SystemUtils;
34  
35  
36  
37  /**
38   * A collection of useful static methods for strings.
39   *
40   * LATER mime 20070101: use StringBuilder instead of StringBuffer if working under JDK 1.5
41   *
42   * @author  Michael Meyling
43   */
44  public final class StringUtility {
45  
46      /** For trimming with zeros. */
47      static final String FORMATED_ZERO = "00000000000000000000";
48  
49      /** For trimming with spaces. */
50      static final String FORMATED_SPACES = "                    ";
51  
52      /**
53       * Constructor, should never be called.
54       */
55      private StringUtility() {
56          // don't call me
57      }
58  
59      /**
60       * Replaces all occurrences of <code>search</code> in <code>text</code>
61       * by <code>replace</code> and returns the result.
62       *
63       * @param   text    text to work on, can be <code>null</code>
64       * @param   search  replace this text by <code>replace</code>, can be <code>null</code>
65       * @param   replace replacement for <code>search</code>, can be <code>null</code>
66       * @return  resulting string (is never <code>null</code>)
67       */
68      public static String replace(final String text,
69              final String search, final String replace) {
70  
71          if (text == null) {
72              return "";
73          }
74          final int len = search != null ? search.length() : 0;
75          if (len == 0) {
76              return text;
77          }
78          final StringBuffer result = new StringBuffer();
79          int pos1 = 0;
80          int pos2;
81          while (0 <= (pos2 = text.indexOf(search, pos1))) {
82              result.append(text.substring(pos1, pos2));
83              if (replace != null) {
84                  result.append(replace);
85              }
86              pos1 = pos2 + len;
87          }
88          if (pos1 < text.length()) {
89              result.append(text.substring(pos1));
90          }
91          return result.toString();
92      }
93  
94      /**
95       * Replaces all occurrences of <code>search</code> in <code>text</code>
96       * by <code>replace</code>.
97       *
98       * @param   text    Text to work on. Must not be <code>null</code>.
99       * @param   search  replace this text by <code>replace</code>. Can be <code>null</code>.
100      * @param   replace replacement for <code>search</code>. Can be <code>null</code>.
101      * @throws  NullPointerException    <code>text</code> is <code>null</code>.
102      */
103     public static void replace(final StringBuffer text,
104             final String search, final String replace) {
105         if (search == null || search.length() <= 0) {
106             return;
107         }
108         final StringBuffer result = new StringBuffer(text.length() + 16);
109         int pos1 = 0;
110         int pos2;
111         final int len = search.length();
112         while (0 <= (pos2 = text.indexOf(search, pos1))) {
113             result.append(text.substring(pos1, pos2));
114             result.append(replace != null ? replace : "");
115             pos1 = pos2 + len;
116         }
117         if (pos1 < text.length()) {
118             result.append(text.substring(pos1));
119         }
120         text.setLength(0);
121         text.append(result);
122     }
123 
124     /**
125      * Return substring of text. Position might be negative if length is big enough. If the string
126      * limits are exceeded this method returns at least all characters within the boundaries.
127      * If no characters are within the given limits an empty string is returned.
128      *
129      * @param   text        Text to work on. Must not be <code>null</code>.
130      * @param   position    Starting position. Might be negative.
131      * @param   length      Maximum length to get.
132      * @return  Substring of maximum length <code>length</code> and starting with position.
133      * @throws  NullPointerException    <code>text</code> is <code>null</code>.
134      */
135     public static String substring(final String text, final int position, final int length) {
136         final int start = Math.max(0, position);
137         int l = position + length - start;
138         if (l <= 0) {
139             return "";
140         }
141         int end = start + l;
142         if (end < text.length()) {
143             return text.substring(start, end);
144         }
145         return text.substring(start);
146     }
147 
148     /**
149      * Returns a readable presentation of an Object array. Something like ("a", null, 13)" if we have
150      * the "a", null, 13. Objects of type {@link CharSequence} are quoted.
151      *
152      * @param   list  List of Objects.
153      * @return  List notation.
154      */
155     public static String toString(final Object[] list) {
156         final StringBuffer buffer = new StringBuffer(30);
157         buffer.append("(");
158         if (list != null) {
159             for (int i = 0; i < list.length; i++) {
160                 if (i > 0) {
161                     buffer.append(", ");
162                 }
163                 if (list[i] instanceof CharSequence) {
164                     buffer.append("\"");
165                     buffer.append(list[i].toString());
166                     buffer.append("\"");
167                 } else {
168                     buffer.append(String.valueOf(list[i]));
169                 }
170             }
171         }
172         buffer.append(")");
173         return buffer.toString();
174     }
175 
176     /**
177      * Returns a readable presentation of a Set. Something like {"a", null, "13} if we have
178      * "a", null, 13. Objects of type {@link CharSequence} are quoted.
179      *
180      * @param   set Set of objects.
181      * @return  Set notation for toString() results.
182      */
183     public static String toString(final Set set) {
184         final StringBuffer buffer = new StringBuffer(30);
185         buffer.append("{");
186         if (set != null) {
187             Iterator e = set.iterator();
188             boolean notFirst = false;
189             while (e.hasNext()) {
190                 if (notFirst) {
191                     buffer.append(", ");
192                 } else {
193                     notFirst = true;
194                 }
195                 final Object obj = e.next();
196                 if (obj instanceof CharSequence) {
197                     buffer.append("\"");
198                     buffer.append(String.valueOf(obj));
199                     buffer.append("\"");
200                 } else {
201                     buffer.append(String.valueOf(obj));
202                 }
203             }
204         }
205         buffer.append("}");
206         return buffer.toString();
207     }
208 
209     /**
210      * Returns a readable presentation of a Map. Something like "{a=2, b=null, c=12}" if the
211      * Map contains (a, 2), (b, null), (c, 12).
212      *
213      * @param   map Map of objects mappings.
214      * @return  Set notation for toString() results.
215      */
216     public static String toString(final Map map) {
217         final StringBuffer buffer = new StringBuffer(30);
218         buffer.append("{");
219         if (map != null) {
220             Iterator e = map.entrySet().iterator();
221             boolean notFirst = false;
222             while (e.hasNext()) {
223                 if (notFirst) {
224                     buffer.append(", ");
225                 } else {
226                     notFirst = true;
227                 }
228                 final Map.Entry entry = (Map.Entry) e.next();
229                 buffer.append(String.valueOf(entry.getKey()));
230                 buffer.append("=");
231                 final Object value = entry.getValue();
232                 if (value instanceof CharSequence) {
233                     buffer.append("\"");
234                     buffer.append(String.valueOf(value));
235                     buffer.append("\"");
236                 } else {
237                     buffer.append(String.valueOf(value));
238                 }
239             }
240         }
241         buffer.append("}");
242         return buffer.toString();
243     }
244 
245     /**
246      * Evaluates toString at the elements of a Set and returns them as line separated Strings.
247      * After the last element no carriage return is inserted.
248      *
249      * @param   set Set of objects.
250      * @return  List of toString() results.
251      */
252     public static String asLines(final Set set) {
253         final StringBuffer buffer = new StringBuffer(30);
254         if (set != null) {
255             Iterator e = set.iterator();
256             boolean notFirst = false;
257             while (e.hasNext()) {
258                 if (notFirst) {
259                     buffer.append("\n");
260                 } else {
261                     notFirst = true;
262                 }
263                 buffer.append(String.valueOf(e.next()));
264             }
265         }
266         return buffer.toString();
267     }
268 
269 
270     /**
271      * Quotes a <code>String</code>. A a quote character &quot; is appended at the
272      * beginning and the end of the <code>String</code>. If a quote character occurs
273      * within the string it is replaced by two quotes.
274      *
275      * @param   unquoted    the unquoted <code>String</code>, must not be <code>null</code>
276      * @return  quoted <code>String</code>
277      * @throws  NullPointerException if <code>unquoted == null</code>
278      */
279     public static String quote(final String unquoted) {
280         StringBuffer result = new StringBuffer(unquoted.length() + 4);
281         result.append('\"');
282 
283         for (int i = 0; i < unquoted.length(); i++) {
284             if (unquoted.charAt(i) == '\"') {
285                 result.append("\"\"");
286             } else {
287                 result.append(unquoted.charAt(i));
288             }
289         }
290         result.append('\"');
291         return result.toString();
292     }
293 
294     /**
295      * Tests if given <code>String</code> begins with a letter and contains
296      * only letters and digits.
297      *
298      * @param   text    test this
299      * @return  is <code>text</code> only made of letters and digits and has
300      *          a leading letter?
301      * @throws  NullPointerException if <code>text == null</code>
302      */
303     public static boolean isLetterDigitString(final String text) {
304         if (text.length() <= 0) {
305             return false;
306         }
307         if (!Character.isLetter(text.charAt(0))) {
308             return false;
309         }
310         for (int i = 1; i < text.length(); i++) {
311             if (!Character.isLetterOrDigit(text.charAt(i))) {
312                 return false;
313             }
314         }
315         return true;
316     }
317 
318     /**
319      * Get amount of spaces.
320      *
321      * @param   length  number of spaces
322      * @return  String contains exactly <code>number</code> spaces
323      */
324     public static StringBuffer getSpaces(final int length) {
325         final StringBuffer buffer = new StringBuffer(length >= 0 ? length : 0);
326         for (int i = 0; i < length; i++) {
327             buffer.append(' ');
328         }
329         return buffer;
330     }
331 
332     /**
333      * Get last dot separated string part.
334      *
335      * @param   full    String with dots in it. Also <code>null</code> is accepted.
336      * @return  All after the last dot in <code>full</code>. Is never <code>null</code>.
337      */
338     public static String getLastDotString(final String full) {
339         if (full == null) {
340             return "";
341         }
342         final int p = full.lastIndexOf('.');
343         if (p < 0) {
344             return full;
345         }
346         return full.substring(p + 1);
347     }
348 
349     /**
350      * Get last two dot separated string part.
351      *
352      * @param   full    String with dots in it. Also <code>null</code> is accepted.
353      * @return  All after the next to last dot in <code>full</code>. Is never <code>null</code>.
354      */
355     public static String getLastTwoDotStrings(final String full) {
356         if (full == null) {
357             return "";
358         }
359         final int p = full.lastIndexOf('.');
360         if (p < 1) {
361             return full;
362         }
363         final int q = full.lastIndexOf('.', p - 1);
364         if (q < 0) {
365             return full;
366         }
367         return full.substring(q + 1);
368     }
369 
370     /**
371      * Get non qualified class name.
372      *
373      * @param   clazz   Class.
374      * @return  Non qualified class name.
375      */
376     public static String getClassName(final Class clazz) {
377         return getLastDotString(clazz.getName());
378     }
379 
380     /**
381      * Search for first line followed by whitespace and delete this string within the whole
382      * text.
383      * <p>
384      * For example the following text
385      *<pre>
386      *       Do you know the muffin man,
387      *       The muffin man, the muffin man,
388      *       Do you know the muffin man,
389      *       Who lives on Drury Lane?
390      *</pre>
391      * will be converted into:
392      *<pre>
393      *Do you know the muffin man,
394      *The muffin man, the muffin man,
395      *Do you know the muffin man,
396      *Who lives on Drury Lane?
397      *</pre>
398      *
399      * @param   buffer  Work on this text.
400      */
401     public static void deleteLineLeadingWhitespace(final StringBuffer buffer) {
402         int current = 0;
403         int lastLf = -1;
404 
405         // detect position of last line feed before content starts (lastLf)
406         while (current < buffer.length()) {
407             if (!Character.isWhitespace(buffer.charAt(current))) {
408                 break;
409             }
410             if ('\n' == buffer.charAt(current)) {
411                 lastLf = current;
412             }
413             current++;
414         }
415         // string from last whitespace line feed until first non whitespace
416         final String empty = buffer.substring(lastLf + 1, current);
417 
418         // delete this string out of the text
419         if (empty.length() > 0) {
420 //            System.out.println(string2Hex(empty));
421             buffer.delete(lastLf + 1 , current);    // delete first occurence
422             replace(buffer, "\n" + empty, "\n");    // delete same whitespace on all following lines
423         }
424     }
425 
426     /**
427      * Return a String like it appears in an property file. Thus certain characters are escaped.
428      *
429      * @param   value   Escape this value.
430      * @return  Escaped form.
431      */
432     public static String escapeProperty(final String value) {
433         Properties newprops = new Properties();
434         newprops.put("key", value);
435         ByteArrayOutputStream out = new ByteArrayOutputStream();
436         try {
437             newprops.store(out, null);
438         } catch (IOException e) {
439             // should never occur
440             throw new RuntimeException(e);
441         }
442         try {
443             final String file = out.toString("ISO-8859-1");
444             return file.substring(file.indexOf('\n') + 1 + "key=".length()).trim();
445         } catch (UnsupportedEncodingException e) {
446             // should never occur
447             throw new RuntimeException(e);
448         }
449     }
450 
451     /**
452      * Trim an integer with leading spaces to a given maximum length. If <code>length</code> is
453      * shorter than the String representation of <code>number</code> no digit is cut. So the
454      * resulting String might be longer than <code>length</code>.
455      *
456      * @param   number  Format this long.
457      * @param   length  Maximum length. Must not be bigger than 20 and less than 1.
458      * @return  String with minimum <code>length</code>, trimmed with leading spaces.
459      */
460     public static final String alignRight(final long number, final int length) {
461         return alignRight("" + number, length);
462     }
463 
464     /**
465      * Trim a String with leading spaces to a given maximum length.
466      *
467      * @param   string  Format this string.
468      * @param   length  Minimum length. Must not be bigger than 20 and less than 1.
469      * @return  String with minimum <code>length</code>, trimmed with leading spaces.
470      */
471     public static final String alignRight(final String string, final int length) {
472         if (length > FORMATED_SPACES.length()) {
473             throw new IllegalArgumentException("maximum length " + FORMATED_SPACES + " exceeded: "
474                 + length);
475         }
476         if (length < 1) {
477             throw new IllegalArgumentException("length must be bigger than 0: " + length);
478         }
479         final String temp = FORMATED_SPACES + string;
480         return temp.substring(Math.min(temp.length() - length, FORMATED_SPACES.length()));
481     }
482 
483     /**
484      * Trim an integer with leading zeros to a given maximum length.
485      *
486      * @param   number  Format this long.
487      * @param   length  Maximum length. Must not be bigger than 20 and less than 1.
488      * @return  String with minimum <code>length</code>, trimmed with leading spaces.
489      */
490     public static final String format(final long number, final int length) {
491         if (length > FORMATED_ZERO.length()) {
492             throw new IllegalArgumentException("maximum length " + FORMATED_ZERO + " exceeded: "
493                 + length);
494         }
495         if (length < 1) {
496             throw new IllegalArgumentException("length must be bigger than 0: " + length);
497         }
498         final String temp = FORMATED_ZERO + number;
499         return temp.substring(Math.min(temp.length() - length, FORMATED_ZERO.length()));
500     }
501 
502     /**
503      * Get a hex string representation for an byte array.
504      *
505      * @param   data    <code>byte</code> array to work on
506      * @return  hex     String of hex codes.
507      */
508     public static String byte2Hex(final byte[] data) {
509 
510         StringBuffer buffer = new StringBuffer();
511         for (int i = 0; i < data.length; i++) {
512             String b = Integer.toHexString(255 & data[i]);
513             if (i != 0) {
514                 if (0 != i % 16) {
515                     buffer.append(" ");
516                 } else {
517                     buffer.append("\n");
518                 }
519             }
520             if (b.length() < 2) {
521                 buffer.append("0");
522             }
523             buffer.append(b.toUpperCase(Locale.US));
524         }
525         return buffer.toString();
526     }
527 
528     /**
529      * Get a hex string representation for a String. Uses UTF-8 encoding.
530      *
531      * @param   data    String to work on
532      * @return  hex     String of hex codes.
533      */
534     public static String string2Hex(final String data) {
535         try {
536             return byte2Hex(data.getBytes("UTF8"));
537         } catch (UnsupportedEncodingException e) {
538             // should not happen
539             throw new RuntimeException(e);
540         }
541     }
542 
543     /**
544      * Get a hex string representation for a String.
545      *
546      * @param   data    String to work on
547      * @param   encoding    Use this String encoding.
548      * @return  hex     String of hex codes.
549      * @throws  UnsupportedEncodingException    Encoding not supported.
550      */
551     public static String string2Hex(final String data, final String encoding)
552             throws UnsupportedEncodingException {
553         return byte2Hex(data.getBytes(encoding));
554     }
555 
556     /**
557      * Get a byte array of a hex string representation.
558      *
559      * @param   hex     Hex string representation of data.
560      * @return  Data array.
561      * @throws  IllegalArgumentException Padding wrong or illegal hexadecimal character.
562      */
563     public static byte[] hex2byte(final String hex) {
564 
565         StringBuffer buffer = new StringBuffer(hex.length());
566         char c;
567         for (int i = 0; i < hex.length(); i++) {
568             c = hex.charAt(i);
569             if (!Character.isWhitespace(c)) {
570                 if (!Character.isLetterOrDigit(c)) {
571                     throw new IllegalArgumentException("Illegal hex char");
572                 }
573                 buffer.append(c);
574             }
575         }
576         if (buffer.length() % 2 != 0) {
577             throw new IllegalArgumentException("Bad padding");
578         }
579         byte[] result = new byte[buffer.length() / 2];
580         for (int i = 0; i < buffer.length() / 2; i++) {
581             try {
582                 result[i] = (byte) Integer.parseInt(buffer.substring(2 * i, 2 * i + 2), 16);
583             } catch (Exception e) {
584                 throw new IllegalArgumentException("Illegal hex char");
585             }
586         }
587         return result;
588     }
589 
590     /**
591      * Get a String out of a hex string representation. Uses UTF-8 encoding.
592      *
593      * @param   hex     Hex string representation of data.
594      * @return  Data as String.
595      * @throws  IllegalArgumentException Padding wrong or illegal hexadecimal character.
596      */
597     public static String hex2String(final String hex) {
598         try {
599             return new String(hex2byte(hex), "UTF8");
600         } catch (UnsupportedEncodingException e) {
601             // should not happen
602             throw new RuntimeException(e);
603         }
604     }
605 
606     /**
607      * Get a String out of a hex string representation.
608      *
609      * @param   hex         Hex string representation of data.
610      * @param   encoding    Use this String encoding.
611      * @return  Data as String.
612      * @throws  UnsupportedEncodingException    Encoding not supported.
613      * @throws  IllegalArgumentException    Padding wrong or illegal hexadecimal character.
614      */
615     public static String hex2String(final String hex, final String encoding)
616         throws UnsupportedEncodingException {
617         return new String(hex2byte(hex), encoding);
618     }
619 
620     /**
621      * Get platform dependent line separator. (<code>&quot;\n&quot;</code> on UNIX).
622      *
623      * @return  Platform dependent line separator.
624      */
625     public static String getSystemLineSeparator() {
626         return (SystemUtils.LINE_SEPARATOR != null ? SystemUtils.LINE_SEPARATOR
627             : "\n");
628     }
629 
630     /**
631      * Creates String with platform dependent line ends. If <code>text</code> is <code>null</code>
632      * or empty nothing is changed. At the end of the String the platform dependent line end is
633      * added whether or not the original text ends with such a sequence.
634      *
635      * @param  text Text with CR or CR LF as line end markers. Might be <code>null</code>.
636      * @return Text with platform dependent line ends.
637      */
638     public static String useSystemLineSeparator(final String text) {
639         if (text == null) {
640             return null;
641         }
642         final StringBuffer buffer = new StringBuffer(text.length());
643         final BufferedReader reader = new BufferedReader(new StringReader(text));
644         final String separator = getSystemLineSeparator();
645         String line;
646         try {
647             while (null != (line = reader.readLine())) {
648                 buffer.append(line);
649                 buffer.append(separator);
650             }
651         } catch (IOException e) {
652             throw new RuntimeException(e);
653         }
654         return buffer.toString();
655     }
656 
657     /**
658      * Split String by given delimiter.
659      * "a:b:c" is converted to "a", "b", "c".
660      * "a:b:c:" is converted to "a", "b", "c", "".
661      *
662      * @param   text        Text to split.
663      * @param   delimiter   Split at these points.
664      * @return  Split text.
665      */
666     public static String[] split(final String text, final String delimiter) {
667         final List list = new ArrayList();
668         int start = 0;
669         int found = -delimiter.length();
670         while (-1 < (found = text.indexOf(delimiter, start))) {
671             list.add(text.substring(start, found));
672             start = found + delimiter.length();
673         }
674         list.add(text.substring(start));
675         return (String[]) list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
676     }
677 
678     /**
679      * <p>Escapes the characters in a <code>String</code> using XML entities.</p>
680      *
681      * <p>For example: <tt>"bread" & "butter"</tt> =>
682      * <tt>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</tt>.
683      * </p>
684      *
685      * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
686      * Does not support DTDs or external entities.</p>
687      *
688      * <p>Note that unicode characters greater than 0x7f are currently escaped to
689      * their numerical \\u equivalent. This may change in future releases. </p>
690      *
691      * @param   value   The <code>String</code> to escape, may be null.
692      * @return  A new escaped <code>String</code>, <code>null</code> if null string input
693      * @see #unescapeXml(java.lang.String)
694      */
695     public static String escapeXml(final String value) {
696         return StringEscapeUtils.escapeXml(value);
697     }
698 
699     /**
700      * <p>Unescapes a string containing XML entity escapes to a string
701      * containing the actual Unicode characters corresponding to the
702      * escapes.</p>
703      *
704      * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
705      * Does not support DTDs or external entities.</p>
706      *
707      * <p>Note that numerical \\u unicode codes are unescaped to their respective
708      *    unicode characters. This may change in future releases. </p>
709      *
710      * @param   value   The <code>String</code> to unescape, may be null.
711      * @return  A new unescaped <code>String</code>, <code>null</code> if null string input
712      * @see #escapeXml(String)
713      */
714     public static String unescapeXml(final String value) {
715         return StringEscapeUtils.unescapeXml(value);
716     }
717 
718     /**
719      * Does a given string is not an element of a given string array?
720      *
721      * @param   lookFor Look for this string.
722      * @param   array   The array we look through.
723      * @return  Is the given string not an element of the array?
724      */
725     public static boolean isNotIn(final String lookFor, final String[] array) {
726         if (lookFor == null || lookFor.length() <= 0) {
727             return false;
728         }
729         for (int i = 0; i < array.length; i++) {
730             if (lookFor.equals(array[i])) {
731                 return false;
732             }
733         }
734         return true;
735     }
736 
737     /**
738      * Does a given string is an element of a given string array?
739      *
740      * @param   lookFor Look for this string.
741      * @param   array   The array we look through.
742      * @return  Is the given string an element of the array?
743      */
744     public static boolean isIn(final String lookFor, final String[] array) {
745         return !isNotIn(lookFor, array);
746     }
747 
748 
749 }