StringUtility.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2013,  Michael Meyling <mime@qedeq.org>.
004  *
005  * "Hilbert II" is free software; you can redistribute
006  * it and/or modify it under the terms of the GNU General Public
007  * License as published by the Free Software Foundation; either
008  * version 2 of the License, or (at your option) any later version.
009  *
010  * This program is distributed in the hope that it will be useful,
011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013  * GNU General Public License for more details.
014  */
015 
016 package org.qedeq.base.utility;
017 
018 import java.io.BufferedReader;
019 import java.io.ByteArrayOutputStream;
020 import java.io.IOException;
021 import java.io.StringReader;
022 import java.io.UnsupportedEncodingException;
023 import java.util.ArrayList;
024 import java.util.Iterator;
025 import java.util.List;
026 import java.util.Locale;
027 import java.util.Map;
028 import java.util.Properties;
029 import java.util.Set;
030 
031 import org.apache.commons.lang.ArrayUtils;
032 import org.apache.commons.lang.StringEscapeUtils;
033 import org.apache.commons.lang.SystemUtils;
034 
035 
036 
037 /**
038  * A collection of useful static methods for strings.
039  *
040  * LATER mime 20070101: use StringBuilder instead of StringBuffer if working under JDK 1.5
041  *
042  @author  Michael Meyling
043  */
044 public final class StringUtility {
045 
046     /** For trimming with zeros. */
047     static final String FORMATED_ZERO = "00000000000000000000";
048 
049     /** For trimming with spaces. */
050     static final String FORMATED_SPACES = "                    ";
051 
052     /**
053      * Constructor, should never be called.
054      */
055     private StringUtility() {
056         // don't call me
057     }
058 
059     /**
060      * Replaces all occurrences of <code>search</code> in <code>text</code>
061      * by <code>replace</code> and returns the result.
062      *
063      @param   text    text to work on, can be <code>null</code>
064      @param   search  replace this text by <code>replace</code>, can be <code>null</code>
065      @param   replace replacement for <code>search</code>, can be <code>null</code>
066      @return  resulting string (is never <code>null</code>)
067      */
068     public static String replace(final String text,
069             final String search, final String replace) {
070 
071         if (text == null) {
072             return "";
073         }
074         final int len = search != null ? search.length() 0;
075         if (len == 0) {
076             return text;
077         }
078         final StringBuffer result = new StringBuffer();
079         int pos1 = 0;
080         int pos2;
081         while (<= (pos2 = text.indexOf(search, pos1))) {
082             result.append(text.substring(pos1, pos2));
083             if (replace != null) {
084                 result.append(replace);
085             }
086             pos1 = pos2 + len;
087         }
088         if (pos1 < text.length()) {
089             result.append(text.substring(pos1));
090         }
091         return result.toString();
092     }
093 
094     /**
095      * Replaces all occurrences of <code>search</code> in <code>text</code>
096      * by <code>replace</code>.
097      *
098      @param   text    Text to work on. Must not be <code>null</code>.
099      @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 (<= (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[iinstanceof 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.Entrye.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 >= ? 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 + , 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'"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  Maximum 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 (!= 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() != 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(byteInteger.parseInt(buffer.substring(* i, * 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 (-(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 }