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 (0 <= (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 (0 <= (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 " 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 >= 0 ? 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 + 1 , 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') + 1 + "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 (0 != 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() % 2 != 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] = (byte) Integer.parseInt(buffer.substring(2 * i, 2 * 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>"\n"</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 (-1 < (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>&quot;bread&quot; &amp; &quot;butter&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 }
|