StringUtility.java
001 /* This file is part of the project "Hilbert II" - http://www.qedeq.org
002  *
003  * Copyright 2000-2011,  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.SystemUtils;
032 import org.apache.commons.lang.StringEscapeUtils;
033 
034 
035 
036 /**
037  * A collection of useful static methods for strings.
038  *
039  * LATER mime 20070101: use StringBuilder instead of StringBuffer if working under JDK 1.5
040  *
041  @author  Michael Meyling
042  */
043 public final class StringUtility {
044 
045     /** For trimming with zeros. */
046     static final String FORMATED_ZERO = "00000000000000000000";
047 
048     /** For trimming with spaces. */
049     static final String FORMATED_SPACES = "                    ";
050 
051     /**
052      * Constructor, should never be called.
053      */
054     private StringUtility() {
055         // don't call me
056     }
057 
058     /**
059      * Replaces all occurrences of <code>search</code> in <code>text</code>
060      * by <code>replace</code> and returns the result.
061      *
062      @param   text    text to work on, can be <code>null</code>
063      @param   search  replace this text by <code>replace</code>, can be <code>null</code>
064      @param   replace replacement for <code>search</code>, can be <code>null</code>
065      @return  resulting string (is never <code>null</code>)
066      */
067     public static String replace(final String text,
068             final String search, final String replace) {
069 
070         if (text == null) {
071             return "";
072         }
073         final int len = search != null ? search.length() 0;
074         if (len == 0) {
075             return text;
076         }
077         final StringBuffer result = new StringBuffer();
078         int pos1 = 0;
079         int pos2;
080         while (<= (pos2 = text.indexOf(search, pos1))) {
081             result.append(text.substring(pos1, pos2));
082             if (replace != null) {
083                 result.append(replace);
084             }
085             pos1 = pos2 + len;
086         }
087         if (pos1 < text.length()) {
088             result.append(text.substring(pos1));
089         }
090         return result.toString();
091     }
092 
093     /**
094      * Replaces all occurrences of <code>search</code> in <code>text</code>
095      * by <code>replace</code> and returns the result.
096      *
097      @param   text    Text to work on. Must not be <code>null</code>.
098      @param   search  replace this text by <code>replace</code>. Can be <code>null</code>.
099      @param   replace replacement for <code>search</code>. Can be <code>null</code>.
100      @throws  NullPointerException    <code>text</code> is <code>null</code>.
101      */
102     public static void replace(final StringBuffer text,
103             final String search, final String replace) {
104         if (search == null || search.length() <= 0) {
105             return;
106         }
107         final StringBuffer result = new StringBuffer(text.length() 16);
108         int pos1 = 0;
109         int pos2;
110         final int len = search.length();
111         while (<= (pos2 = text.indexOf(search, pos1))) {
112             result.append(text.substring(pos1, pos2));
113             result.append(replace != null ? replace : "");
114             pos1 = pos2 + len;
115         }
116         if (pos1 < text.length()) {
117             result.append(text.substring(pos1));
118         }
119         text.setLength(0);
120         text.append(result);
121     }
122 
123     /**
124      * Return substring of text. Position might be negative if length is big enough. If the string
125      * limits are exceeded this method returns at least all characters within the boundaries.
126      * If no characters are within the given limits an empty string is returned.
127      *
128      @param   text        Text to work on. Must not be <code>null</code>.
129      @param   position    Starting position. Might be negative.
130      @param   length      Maximum length to get.
131      @return  Substring of maximum length <code>length</code> and starting with position.
132      @throws  NullPointerException    <code>text</code> is <code>null</code>.
133      */
134     public static String substring(final String text, final int position, final int length) {
135         final int start = Math.max(0, position);
136         int l = position + length - start;
137         if (l <= 0) {
138             return "";
139         }
140         int end = start + l;
141         if (end < text.length()) {
142             return text.substring(start, end);
143         }
144         return text.substring(start);
145     }
146 
147     /**
148      * Returns a readable presentation of a String array. Something like "(a, null, c)" if the
149      * Strings are "a", null, "c"
150      *
151      @param   list  List of Strings.
152      @return  Set notation for list.
153      */
154     public static String toString(final Object[] list) {
155         final StringBuffer buffer = new StringBuffer(30);
156         buffer.append("(");
157         if (list != null) {
158             for (int i = 0; i < list.length; i++) {
159                 if (i > 0) {
160                     buffer.append(", ");
161                 }
162                 if (list[i== null) {
163                     buffer.append("null");
164                 else {
165                     buffer.append("\"");
166                     buffer.append(list[i]);
167                     buffer.append("\"");
168                 }
169             }
170         }
171         buffer.append(")");
172         return buffer.toString();
173     }
174 
175     /**
176      * Returns a readable presentation of a Set. Something like "{a, null, c}" if the
177      * Strings are "a", null, "c"
178      *
179      @param   set Set of objects.
180      @return  Set notation for toString() results.
181      */
182     public static String toString(final Set set) {
183         final StringBuffer buffer = new StringBuffer(30);
184         buffer.append("{");
185         if (set != null) {
186             Iterator e = set.iterator();
187             boolean notFirst = false;
188             while (e.hasNext()) {
189                 if (notFirst) {
190                     buffer.append(", ");
191                 else {
192                     notFirst = true;
193                 }
194                 buffer.append(String.valueOf(e.next()));
195             }
196         }
197         buffer.append("}");
198         return buffer.toString();
199     }
200 
201     /**
202      * Returns a readable presentation of a Map. Something like "{a=2, b=null, c=12}" if the
203      * Map contains (a, 2), (b, null), (c, 12).
204      *
205      @param   map Map of objects mappings.
206      @return  Set notation for toString() results.
207      */
208     public static String toString(final Map map) {
209         final StringBuffer buffer = new StringBuffer(30);
210         buffer.append("{");
211         if (map != null) {
212             Iterator e = map.entrySet().iterator();
213             boolean notFirst = false;
214             while (e.hasNext()) {
215                 if (notFirst) {
216                     buffer.append(", ");
217                 else {
218                     notFirst = true;
219                 }
220                 final Object key = e.next();
221                 buffer.append(String.valueOf(key));
222 //                buffer.append("=");
223 //                buffer.append(String.valueOf(map.get(key)));
224             }
225         }
226         buffer.append("}");
227         return buffer.toString();
228     }
229 
230     /**
231      * Evaluates toString at the elements of a Set and returns them as line separated Strings.
232      *
233      @param   set Set of objects.
234      @return  List of toString() results.
235      */
236     public static String asLines(final Set set) {
237         final StringBuffer buffer = new StringBuffer(30);
238         if (set != null) {
239             Iterator e = set.iterator();
240             boolean notFirst = false;
241             while (e.hasNext()) {
242                 if (notFirst) {
243                     buffer.append("\n");
244                 else {
245                     notFirst = true;
246                 }
247                 buffer.append(String.valueOf(e.next()));
248             }
249         }
250         return buffer.toString();
251     }
252 
253 
254     /**
255      * Quotes a <code>String</code>. A a quote character &quot; is appended at the
256      * beginning and the end of the <code>String</code>. If a quote character occurs
257      * within the string it is replaced by two quotes.
258      *
259      @param   unquoted    the unquoted <code>String</code>, must not be <code>null</code>
260      @return  quoted <code>String</code>
261      @throws  NullPointerException if <code>unquoted == null</code>
262      */
263     public static String quote(final String unquoted) {
264         StringBuffer result = new StringBuffer(unquoted.length() 4);
265         result.append('\"');
266 
267         for (int i = 0; i < unquoted.length(); i++) {
268             if (unquoted.charAt(i== '\"') {
269                 result.append("\"\"");
270             else {
271                 result.append(unquoted.charAt(i));
272             }
273         }
274         result.append('\"');
275         return result.toString();
276     }
277 
278     /**
279      * Tests if given <code>String</code> begins with a letter and contains
280      * only letters and digits.
281      *
282      @param   text    test this
283      @return  is <code>text</code> only made of letters and digits and has
284      *          a leading letter?
285      @throws  NullPointerException if <code>text == null</code>
286      */
287     public static boolean isLetterDigitString(final String text) {
288         if (text.length() <= 0) {
289             return false;
290         }
291         if (!Character.isLetter(text.charAt(0))) {
292             return false;
293         }
294         for (int i = 1; i < text.length(); i++) {
295             if (!Character.isLetterOrDigit(text.charAt(i))) {
296                 return false;
297             }
298         }
299         return true;
300     }
301 
302     /**
303      * Get amount of spaces.
304      *
305      @param   length  number of spaces
306      @return  String contains exactly <code>number</code> spaces
307      */
308     public static StringBuffer getSpaces(final int length) {
309         final StringBuffer buffer = new StringBuffer(length >= ? length : 0);
310         for (int i = 0; i < length; i++) {
311             buffer.append(' ');
312         }
313         return buffer;
314     }
315 
316     /**
317      * Get non qualified class name.
318      *
319      @param   clazz   Class.
320      @return  Non qualified class name.
321      */
322     public static String getClassName(final Class clazz) {
323         return clazz.getName().substring(clazz.getName().lastIndexOf('.'1);
324     }
325 
326     /**
327      * Search for first line followed by whitespace and delete this string within the whole
328      * text.
329      <p>
330      * For example the following text
331      *<pre>
332      *       Do you know the muffin man,
333      *       The muffin man, the muffin man,
334      *       Do you know the muffin man,
335      *       Who lives on Drury Lane?
336      *</pre>
337      * will be converted into:
338      *<pre>
339      *Do you know the muffin man,
340      *The muffin man, the muffin man,
341      *Do you know the muffin man,
342      *Who lives on Drury Lane?
343      *</pre>
344      *
345      @param   buffer  Work on this text.
346      */
347     public static void deleteLineLeadingWhitespace(final StringBuffer buffer) {
348         int current = 0;
349         int lastLf = -1;
350 
351         // detect position of last line feed before content starts (lastLf)
352         while (current < buffer.length()) {
353             if (!Character.isWhitespace(buffer.charAt(current))) {
354                 break;
355             }
356             if ('\n' == buffer.charAt(current)) {
357                 lastLf = current;
358             }
359             current++;
360         }
361         // string from last whitespace line feed until first non whitespace
362         final String empty = buffer.substring(lastLf + 1, current);
363 
364         // delete this string out of the text
365         if (empty.length() 0) {
366 //            System.out.println(string2Hex(empty));
367             buffer.delete(lastLf + , current);    // delete first occurence
368             replace(buffer, "\n" + empty, "\n");    // delete same whitespace on all following lines
369         }
370     }
371 
372     /**
373      * Return a String like it appears in an property file. Thus certain characters are escaped.
374      *
375      @param   value   Escape this value.
376      @return  Escaped form.
377      */
378     public static String escapeProperty(final String value) {
379         Properties newprops = new Properties();
380         newprops.put("key", value);
381         ByteArrayOutputStream out = new ByteArrayOutputStream();
382         try {
383             newprops.store(out, null);
384         catch (IOException e) {
385             throw new RuntimeException(e);
386         }
387         try {
388             final String file = out.toString("ISO-8859-1");
389             return file.substring(file.indexOf('\n'"key=".length()).trim();
390         catch (UnsupportedEncodingException e) {
391             throw new RuntimeException(e);
392         }
393     }
394 
395     /**
396      * Trim an integer with leading spaces to a given maximum length.
397      *
398      @param   number  Format this long.
399      @param   length  Maximum length. Must not be bigger than 20 and less than 1.
400      @return  String with minimum <code>length</code>, trimmed with leading zeros.
401      */
402     public static final String alignRight(final long number, final int length) {
403         if (length > FORMATED_SPACES.length()) {
404             throw new IllegalArgumentException("maximum length " + FORMATED_SPACES + " exceeded: "
405                 + length);
406         }
407         if (length < 1) {
408             throw new IllegalArgumentException("length must be bigger than 0: " + length);
409         }
410         final String temp = FORMATED_SPACES + number;
411         return temp.substring(temp.length() - length);
412     }
413 
414     /**
415      * Trim a String with leading spaces to a given maximum length.
416      *
417      @param   string  Format this string.
418      @param   length  Maximum length. Must not be bigger than 20 and less than 1.
419      @return  String with minimum <code>length</code>, trimmed with leading zeros.
420      */
421     public static final String alignRight(final String string, final int length) {
422         if (length > FORMATED_SPACES.length()) {
423             throw new IllegalArgumentException("maximum length " + FORMATED_SPACES + " exceeded: "
424                 + length);
425         }
426         if (length < 1) {
427             throw new IllegalArgumentException("length must be bigger than 0: " + length);
428         }
429         final String temp = FORMATED_SPACES + string;
430         return temp.substring(temp.length() - length);
431     }
432 
433     /**
434      * Trim an integer with leading zeros to a given maximum length.
435      *
436      @param   number  Format this long.
437      @param   length  Maximum length. Must not be bigger than 20 and less than 1.
438      @return  String with minimum <code>length</code>, trimmed with leading spaces.
439      */
440     public static final String format(final long number, final int length) {
441         if (length > FORMATED_ZERO.length()) {
442             throw new IllegalArgumentException("maximum length " + FORMATED_ZERO + " exceeded: "
443                 + length);
444         }
445         if (length < 1) {
446             throw new IllegalArgumentException("length must be bigger than 0: " + length);
447         }
448         final String temp = FORMATED_ZERO + number;
449         return temp.substring(temp.length() - length);
450     }
451 
452     /**
453      * Get a hex string representation for an byte array.
454      *
455      @param   data    <code>byte</code> array to work on
456      @return  hex     String of hex codes.
457      */
458     public static String byte2Hex(final byte[] data) {
459 
460         StringBuffer buffer = new StringBuffer();
461         for (int i = 0; i < data.length; i++) {
462             String b = Integer.toHexString(255 & data[i]);
463             if (i != 0) {
464                 if (!= i % 16) {
465                     buffer.append(" ");
466                 else {
467                     buffer.append("\n");
468                 }
469             }
470             if (b.length() 2) {
471                 buffer.append("0");
472             }
473             buffer.append(b.toUpperCase(Locale.US));
474         }
475         return buffer.toString();
476     }
477 
478     /**
479      * Get a hex string representation for a String.
480      *
481      @param   data    String to work on
482      @return  hex     String of hex codes.
483      */
484     public static String string2Hex(final String data) {
485         try {
486             return byte2Hex(data.getBytes("UTF8"));
487         catch (UnsupportedEncodingException e) {
488             // should not happen
489             throw new RuntimeException(e);
490         }
491     }
492 
493     /**
494      * Get a hex string representation for a String.
495      *
496      @param   data    String to work on
497      @param   encoding    Use this String encoding.
498      @return  hex     String of hex codes.
499      @throws  UnsupportedEncodingException    Encoding not supported.
500      */
501     public static String string2Hex(final String data, final String encoding)
502             throws UnsupportedEncodingException {
503         return byte2Hex(data.getBytes(encoding));
504     }
505 
506     /**
507      * Get a byte array of a hex string representation.
508      *
509      @param   hex     Hex string representation of data.
510      @return  Data array.
511      @throws  IllegalArgumentException Padding wrong or illegal hexadecimal character.
512      */
513     public static byte[] hex2byte(final String hex) {
514 
515         StringBuffer buffer = new StringBuffer(hex.length());
516         char c;
517         for (int i = 0; i < hex.length(); i++) {
518             c = hex.charAt(i);
519             if (!Character.isWhitespace(c)) {
520                 if (!Character.isLetterOrDigit(c)) {
521                     throw new IllegalArgumentException("Illegal hex char");
522                 }
523                 buffer.append(c);
524             }
525         }
526         if (buffer.length() != 0) {
527             throw new IllegalArgumentException("Bad padding");
528         }
529         byte[] result = new byte[buffer.length() 2];
530         for (int i = 0; i < buffer.length() 2; i++) {
531             try {
532                 result[i(byteInteger.parseInt(buffer.substring(* i, * i + 2)16);
533             catch (Exception e) {
534                 throw new IllegalArgumentException("Illegal hex char");
535             }
536         }
537         return result;
538     }
539 
540     /**
541      * Get a String out of a hex string representation.
542      *
543      @param   hex     Hex string representation of data.
544      @return  Data as String.
545      @throws  IllegalArgumentException Padding wrong or illegal hexadecimal character.
546      */
547     public static String hex2String(final String hex) {
548         try {
549             return new String(hex2byte(hex)"UTF8");
550         catch (UnsupportedEncodingException e) {
551             // should not happen
552             throw new RuntimeException(e);
553         }
554     }
555 
556     /**
557      * Get a String out of a hex string representation.
558      *
559      @param   hex         Hex string representation of data.
560      @param   encoding    Use this String encoding.
561      @return  Data as String.
562      @throws  UnsupportedEncodingException    Encoding not supported.
563      @throws  IllegalArgumentException    Padding wrong or illegal hexadecimal character.
564      */
565     public static String hex2String(final String hex, final String encoding)
566         throws UnsupportedEncodingException {
567         return new String(hex2byte(hex), encoding);
568     }
569 
570     /**
571      * Get platform dependent line separator. (<code>&quot;\n&quot;</code> on UNIX).
572      *
573      @return  Platform dependent line separator.
574      */
575     public static String getSystemLineSeparator() {
576         return (SystemUtils.LINE_SEPARATOR != null ? SystemUtils.LINE_SEPARATOR
577             "\n");
578     }
579 
580     /**
581      * Creates String with platform dependent line ends. If <code>text</code> is <code>null</code>
582      * or empty nothing is changed. At the end of the String the platform dependent line end is
583      * added whether or not the original text ends with such a sequence.
584      *
585      @param  text Text with CR or CR LF as line end markers. Might be <code>null</code>.
586      @return Text with platform dependent line ends.
587      */
588     public static String useSystemLineSeparator(final String text) {
589         if (text == null) {
590             return null;
591         }
592         final StringBuffer buffer = new StringBuffer(text.length());
593         final BufferedReader reader = new BufferedReader(new StringReader(text));
594         final String separator = getSystemLineSeparator();
595         String line;
596         try {
597             while (null != (line = reader.readLine())) {
598                 buffer.append(line);
599                 buffer.append(separator);
600             }
601         catch (IOException e) {
602             throw new RuntimeException(e);
603         }
604         return buffer.toString();
605     }
606 
607     /**
608      * Split String by given delimiter.
609      * "a:b:c" is converted to "a", "b", "c".
610      * "a:b:c:" is converted to "a", "b", "c", "".
611      *
612      @param   text        Text to split.
613      @param   delimiter   Split at these points.
614      @return  Split text.
615      */
616     public static String[] split(final String text, final String delimiter) {
617         final List list = new ArrayList();
618         int start = 0;
619         int found = -delimiter.length();
620         while (-(found = text.indexOf(delimiter, start))) {
621             list.add(text.substring(start, found));
622             start = found + delimiter.length();
623         }
624         list.add(text.substring(start));
625         return (String[]) list.toArray(new String[]{});
626     }
627 
628     /**
629      <p>Escapes the characters in a <code>String</code> using XML entities.</p>
630      *
631      <p>For example: <tt>"bread" & "butter"</tt> =>
632      <tt>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</tt>.
633      </p>
634      *
635      <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
636      * Does not support DTDs or external entities.</p>
637      *
638      <p>Note that unicode characters greater than 0x7f are currently escaped to
639      * their numerical \\u equivalent. This may change in future releases. </p>
640      *
641      @param   value   The <code>String</code> to escape, may be null.
642      @return  A new escaped <code>String</code><code>null</code> if null string input
643      @see #unescapeXml(java.lang.String)
644      */
645     public static String escapeXml(final String value) {
646         return StringEscapeUtils.escapeXml(value);
647     }
648 
649     /**
650      <p>Unescapes a string containing XML entity escapes to a string
651      * containing the actual Unicode characters corresponding to the
652      * escapes.</p>
653      *
654      <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
655      * Does not support DTDs or external entities.</p>
656      *
657      <p>Note that numerical \\u unicode codes are unescaped to their respective
658      *    unicode characters. This may change in future releases. </p>
659      *
660      @param   value   The <code>String</code> to unescape, may be null.
661      @return  A new unescaped <code>String</code><code>null</code> if null string input
662      @see #escapeXml(String)
663      */
664     public static String unescapeXml(final String value) {
665         return StringEscapeUtils.unescapeXml(value);
666     }
667 
668     /**
669      * Does a given string is not an element of a given string array?
670      *
671      @param   lookFor Look for this string.
672      @param   array   The array we look through.
673      @return  Is the given string not an element of the array?
674      */
675     public static boolean isNotIn(final String lookFor, final String[] array) {
676         if (lookFor == null || lookFor.length() <= 0) {
677             return false;
678         }
679         for (int i = 0; i < array.length; i++) {
680             if (lookFor.equals(array[i])) {
681                 return false;
682             }
683         }
684         return true;
685     }
686 
687 
688 }