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 (0 <= (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 (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 " 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 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 (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>"\n"</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>&quot;bread&quot; &amp; &quot;butter&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 }
|