File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\util\StringUtils.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.util;
8
9 import java.lang.ref.SoftReference;
10 import java.net.URLEncoder;
11 import java.util.ArrayList;
12 import java.util.Locale;
13 import org.h2.constant.ErrorCode;
14 import org.h2.constant.SysProperties;
15 import org.h2.engine.Constants;
16 import org.h2.message.DbException;
17
18 /**
19 * A few String utility functions.
20 */
21 public class StringUtils {
22
23 private static SoftReference<String[]> softCache = new SoftReference<String[]>(null);
24 private static long softCacheCreated;
25 private static final char[] HEX = "0123456789abcdef".toCharArray();
26 private static final int[] HEX_DECODE = new int['f' + 1];
27
28 static {
29 for (int i = 0; i < HEX_DECODE.length; i++) {
30 HEX_DECODE[i] = -1;
31 }
32 for (int i = 0; i <= 9; i++) {
33 HEX_DECODE[i + '0'] = i;
34 }
35 for (int i = 0; i <= 5; i++) {
36 HEX_DECODE[i + 'a'] = HEX_DECODE[i + 'A'] = i + 10;
37 }
38 }
39
40 private StringUtils() {
41 // utility class
42 }
43
44 private static String[] getCache() {
45 String[] cache;
46 // softCache can be null due to a Tomcat problem
47 // a workaround is disable the system property org.apache.
48 // catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES
49 if (softCache != null) {
50 cache = softCache.get();
51 if (cache != null) {
52 return cache;
53 }
54 }
55 // create a new cache at most every 5 seconds
56 // so that out of memory exceptions are not delayed
57 long time = System.currentTimeMillis();
58 if (softCacheCreated != 0 && time - softCacheCreated < 5000) {
59 return null;
60 }
61 try {
62 cache = new String[SysProperties.OBJECT_CACHE_SIZE];
63 softCache = new SoftReference<String[]>(cache);
64 return cache;
65 } finally {
66 softCacheCreated = System.currentTimeMillis();
67 }
68 }
69
70 /**
71 * Check if two strings are equal. Here, null is equal to null.
72 *
73 * @param a the first value
74 * @param b the second value
75 * @return true if both are null or both are equal
76 */
77 public static boolean equals(String a, String b) {
78 if (a == null) {
79 return b == null;
80 }
81 return a.equals(b);
82 }
83
84 /**
85 * Convert a string to uppercase using the English locale.
86 *
87 * @param s the test to convert
88 * @return the uppercase text
89 */
90 public static String toUpperEnglish(String s) {
91 return s.toUpperCase(Locale.ENGLISH);
92 }
93
94 /**
95 * Convert a string to lowercase using the English locale.
96 *
97 * @param s the text to convert
98 * @return the lowercase text
99 */
100 public static String toLowerEnglish(String s) {
101 return s.toLowerCase(Locale.ENGLISH);
102 }
103
104 /**
105 * Check is a string starts with another string, ignoring the case.
106 *
107 * @param s the string to check (must be longer than start)
108 * @param start the prefix of s
109 * @return true if start is a prefix of s
110 */
111 public static boolean startsWithIgnoreCase(String s, String start) {
112 if (s.length() < start.length()) {
113 return false;
114 }
115 return s.substring(0, start.length()).equalsIgnoreCase(start);
116 }
117
118 /**
119 * Convert a string to a SQL literal. Null is converted to NULL. The text is
120 * enclosed in single quotes. If there are any special characters, the method
121 * STRINGDECODE is used.
122 *
123 * @param s the text to convert.
124 * @return the SQL literal
125 */
126 public static String quoteStringSQL(String s) {
127 if (s == null) {
128 return "NULL";
129 }
130 int length = s.length();
131 StringBuilder buff = new StringBuilder(length + 2);
132 buff.append('\'');
133 for (int i = 0; i < length; i++) {
134 char c = s.charAt(i);
135 if (c == '\'') {
136 buff.append(c);
137 } else if (c < ' ' || c > 127) {
138 // need to start from the beginning because maybe there was a \
139 // that was not quoted
140 return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
141 }
142 buff.append(c);
143 }
144 buff.append('\'');
145 return buff.toString();
146 }
147
148 /**
149 * Convert a string to a Java literal using the correct escape sequences.
150 * The literal is not enclosed in double quotes. The result can be used in
151 * properties files or in Java source code.
152 *
153 * @param s the text to convert
154 * @return the Java representation
155 */
156 public static String javaEncode(String s) {
157 int length = s.length();
158 StringBuilder buff = new StringBuilder(length);
159 for (int i = 0; i < length; i++) {
160 char c = s.charAt(i);
161 switch (c) {
162 // case '\b':
163 // // BS backspace
164 // // not supported in properties files
165 // buff.append("\\b");
166 // break;
167 case '\t':
168 // HT horizontal tab
169 buff.append("\\t");
170 break;
171 case '\n':
172 // LF linefeed
173 buff.append("\\n");
174 break;
175 case '\f':
176 // FF form feed
177 buff.append("\\f");
178 break;
179 case '\r':
180 // CR carriage return
181 buff.append("\\r");
182 break;
183 case '"':
184 // double quote
185 buff.append("\\\"");
186 break;
187 case '\\':
188 // backslash
189 buff.append("\\\\");
190 break;
191 default:
192 int ch = c & 0xffff;
193 if (ch >= ' ' && (ch < 0x80)) {
194 buff.append(c);
195 // not supported in properties files
196 // } else if(ch < 0xff) {
197 // buff.append("\\");
198 // // make sure it's three characters (0x200 is octal 1000)
199 // buff.append(Integer.toOctalString(0x200 | ch).substring(1));
200 } else {
201 buff.append("\\u");
202 String hex = Integer.toHexString(ch);
203 // make sure it's four characters
204 for (int len = hex.length(); len < 4; len++) {
205 buff.append('0');
206 }
207 buff.append(hex);
208 }
209 }
210 }
211 return buff.toString();
212 }
213
214 /**
215 * Add an asterisk ('[*]') at the given position. This format is used to
216 * show where parsing failed in a statement.
217 *
218 * @param s the text
219 * @param index the position
220 * @return the text with asterisk
221 */
222 public static String addAsterisk(String s, int index) {
223 if (s != null && index < s.length()) {
224 s = s.substring(0, index) + "[*]" + s.substring(index);
225 }
226 return s;
227 }
228
229 private static DbException getFormatException(String s, int i) {
230 return DbException.get(ErrorCode.STRING_FORMAT_ERROR_1, addAsterisk(s, i));
231 }
232
233 /**
234 * Decode a text that is encoded as a Java string literal. The Java
235 * properties file format and Java source code format is supported.
236 *
237 * @param s the encoded string
238 * @return the string
239 */
240 public static String javaDecode(String s) {
241 int length = s.length();
242 StringBuilder buff = new StringBuilder(length);
243 for (int i = 0; i < length; i++) {
244 char c = s.charAt(i);
245 if (c == '\\') {
246 if (i + 1 >= s.length()) {
247 throw getFormatException(s, i);
248 }
249 c = s.charAt(++i);
250 switch (c) {
251 case 't':
252 buff.append('\t');
253 break;
254 case 'r':
255 buff.append('\r');
256 break;
257 case 'n':
258 buff.append('\n');
259 break;
260 case 'b':
261 buff.append('\b');
262 break;
263 case 'f':
264 buff.append('\f');
265 break;
266 case '#':
267 // for properties files
268 buff.append('#');
269 break;
270 case '=':
271 // for properties files
272 buff.append('=');
273 break;
274 case ':':
275 // for properties files
276 buff.append(':');
277 break;
278 case '"':
279 buff.append('"');
280 break;
281 case '\\':
282 buff.append('\\');
283 break;
284 case 'u': {
285 try {
286 c = (char) (Integer.parseInt(s.substring(i + 1, i + 5), 16));
287 } catch (NumberFormatException e) {
288 throw getFormatException(s, i);
289 }
290 i += 4;
291 buff.append(c);
292 break;
293 }
294 default:
295 if (c >= '0' && c <= '9') {
296 try {
297 c = (char) (Integer.parseInt(s.substring(i, i + 3), 8));
298 } catch (NumberFormatException e) {
299 throw getFormatException(s, i);
300 }
301 i += 2;
302 buff.append(c);
303 } else {
304 throw getFormatException(s, i);
305 }
306 }
307 } else {
308 buff.append(c);
309 }
310 }
311 return buff.toString();
312 }
313
314 /**
315 * Convert a string to the Java literal and enclose it with double quotes.
316 * Null will result in "null" (without double quotes).
317 *
318 * @param s the text to convert
319 * @return the Java representation
320 */
321 public static String quoteJavaString(String s) {
322 if (s == null) {
323 return "null";
324 }
325 return "\"" + javaEncode(s) + "\"";
326 }
327
328 /**
329 * Convert the text to UTF-8 format. For the Unicode characters
330 * 0xd800-0xdfff only one byte is returned.
331 *
332 * @param s the text
333 * @return the UTF-8 representation
334 */
335 public static byte[] utf8Encode(String s) {
336 try {
337 return s.getBytes(Constants.UTF8);
338 } catch (Exception e) {
339 // UnsupportedEncodingException
340 throw DbException.convert(e);
341 }
342 }
343
344 /**
345 * Convert a UTF-8 representation of a text to the text.
346 *
347 * @param utf8 the UTF-8 representation
348 * @return the text
349 */
350 public static String utf8Decode(byte[] utf8) {
351 try {
352 return new String(utf8, Constants.UTF8);
353 } catch (Exception e) {
354 // UnsupportedEncodingException
355 throw DbException.convert(e);
356 }
357 }
358
359 /**
360 * Convert a UTF-8 representation of a text to the text using the given
361 * offset and length.
362 *
363 * @param bytes the UTF-8 representation
364 * @param offset the offset in the bytes array
365 * @param length the number of bytes
366 * @return the text
367 */
368 private static String utf8Decode(byte[] bytes, int offset, int length) {
369 try {
370 return new String(bytes, offset, length, Constants.UTF8);
371 } catch (Exception e) {
372 // UnsupportedEncodingException
373 throw DbException.convert(e);
374 }
375 }
376
377 /**
378 * Convert a string array to the Java source code that represents this
379 * array. Null will be converted to 'null'.
380 *
381 * @param array the string array
382 * @return the Java source code (including new String[]{})
383 */
384 public static String quoteJavaStringArray(String[] array) {
385 if (array == null) {
386 return "null";
387 }
388 StatementBuilder buff = new StatementBuilder("new String[]{");
389 for (String a : array) {
390 buff.appendExceptFirst(", ");
391 buff.append(quoteJavaString(a));
392 }
393 return buff.append('}').toString();
394 }
395
396 /**
397 * Convert an int array to the Java source code that represents this array.
398 * Null will be converted to 'null'.
399 *
400 * @param array the int array
401 * @return the Java source code (including new int[]{})
402 */
403 public static String quoteJavaIntArray(int[] array) {
404 if (array == null) {
405 return "null";
406 }
407 StatementBuilder buff = new StatementBuilder("new int[]{");
408 for (int a : array) {
409 buff.appendExceptFirst(", ");
410 buff.append(a);
411 }
412 return buff.append('}').toString();
413 }
414
415 /**
416 * Enclose a string with '(' and ')' if this is not yet done.
417 *
418 * @param s the string
419 * @return the enclosed string
420 */
421 public static String enclose(String s) {
422 if (s.startsWith("(")) {
423 return s;
424 }
425 return "(" + s + ")";
426 }
427
428 /**
429 * Remove enclosing '(' and ')' if this text is enclosed.
430 *
431 * @param s the potentially enclosed string
432 * @return the string
433 */
434 public static String unEnclose(String s) {
435 if (s.startsWith("(") && s.endsWith(")")) {
436 return s.substring(1, s.length() - 1);
437 }
438 return s;
439 }
440
441 /**
442 * Encode the string as an URL.
443 *
444 * @param s the string to encode
445 * @return the encoded string
446 */
447 public static String urlEncode(String s) {
448 try {
449 return URLEncoder.encode(s, "UTF-8");
450 } catch (Exception e) {
451 // UnsupportedEncodingException
452 throw DbException.convert(e);
453 }
454 }
455
456 /**
457 * Decode the URL to a string.
458 *
459 * @param encoded the encoded URL
460 * @return the decoded string
461 */
462 public static String urlDecode(String encoded) {
463 int length = encoded.length();
464 byte[] buff = new byte[length];
465 int j = 0;
466 for (int i = 0; i < length; i++) {
467 char ch = encoded.charAt(i);
468 if (ch == '+') {
469 buff[j++] = ' ';
470 } else if (ch == '%') {
471 buff[j++] = (byte) Integer.parseInt(encoded.substring(i + 1, i + 3), 16);
472 i += 2;
473 } else {
474 if (SysProperties.CHECK) {
475 if (ch > 127 || ch < ' ') {
476 throw new IllegalArgumentException("Unexpected char " + (int) ch + " decoding " + encoded);
477 }
478 }
479 buff[j++] = (byte) ch;
480 }
481 }
482 String s = utf8Decode(buff, 0, j);
483 return s;
484 }
485
486 /**
487 * Split a string into an array of strings using the given separator. A null
488 * string will result in a null array, and an empty string in a zero element
489 * array.
490 *
491 * @param s the string to split
492 * @param separatorChar the separator character
493 * @param trim whether each element should be trimmed
494 * @return the array list
495 */
496 public static String[] arraySplit(String s, char separatorChar, boolean trim) {
497 if (s == null) {
498 return null;
499 }
500 int length = s.length();
501 if (length == 0) {
502 return new String[0];
503 }
504 ArrayList<String> list = New.arrayList();
505 StringBuilder buff = new StringBuilder(length);
506 for (int i = 0; i < length; i++) {
507 char c = s.charAt(i);
508 if (c == separatorChar) {
509 String e = buff.toString();
510 list.add(trim ? e.trim() : e);
511 buff.setLength(0);
512 } else if (c == '\\' && i < length - 1) {
513 buff.append(s.charAt(++i));
514 } else {
515 buff.append(c);
516 }
517 }
518 String e = buff.toString();
519 list.add(trim ? e.trim() : e);
520 String[] array = new String[list.size()];
521 list.toArray(array);
522 return array;
523 }
524
525 /**
526 * Combine an array of strings to one array using the given separator
527 * character. A backslash and the separator character and escaped using a
528 * backslash.
529 *
530 * @param list the string array
531 * @param separatorChar the separator character
532 * @return the combined string
533 */
534 public static String arrayCombine(String[] list, char separatorChar) {
535 StatementBuilder buff = new StatementBuilder();
536 for (String s : list) {
537 buff.appendExceptFirst(String.valueOf(separatorChar));
538 if (s == null) {
539 s = "";
540 }
541 for (int j = 0, length = s.length(); j < length; j++) {
542 char c = s.charAt(j);
543 if (c == '\\' || c == separatorChar) {
544 buff.append('\\');
545 }
546 buff.append(c);
547 }
548 }
549 return buff.toString();
550 }
551
552 /**
553 * Creates an XML attribute of the form name="value".
554 * A single space is prepended to the name,
555 * so that multiple attributes can be concatenated.
556 * @param name the attribute name
557 * @param value the attribute value
558 * @return the attribute
559 */
560 public static String xmlAttr(String name, String value) {
561 return " " + name + "=\"" + xmlText(value) + "\"";
562 }
563
564 /**
565 * Create an XML node with optional attributes and content.
566 * The data is indented with 4 spaces if it contains a newline character.
567 *
568 * @param name the element name
569 * @param attributes the attributes (may be null)
570 * @param content the content (may be null)
571 * @return the node
572 */
573 public static String xmlNode(String name, String attributes, String content) {
574 return xmlNode(name, attributes, content, true);
575 }
576
577 /**
578 * Create an XML node with optional attributes and content. The data is
579 * indented with 4 spaces if it contains a newline character and the indent
580 * parameter is set to true.
581 *
582 * @param name the element name
583 * @param attributes the attributes (may be null)
584 * @param content the content (may be null)
585 * @param indent whether to indent the content if it contains a newline
586 * @return the node
587 */
588 public static String xmlNode(String name, String attributes, String content, boolean indent) {
589 String start = attributes == null ? name : name + attributes;
590 if (content == null) {
591 return "<" + start + "/>\n";
592 }
593 if (indent && content.indexOf('\n') >= 0) {
594 content = "\n" + indent(content);
595 }
596 return "<" + start + ">" + content + "</" + name + ">\n";
597 }
598
599 /**
600 * Indents a string with 4 spaces.
601 *
602 * @param s the string
603 * @return the indented string
604 */
605 public static String indent(String s) {
606 return indent(s, 4, true);
607 }
608
609 /**
610 * Indents a string with spaces.
611 *
612 * @param s the string
613 * @param spaces the number of spaces
614 * @param newline append a newline if there is none
615 * @return the indented string
616 */
617 public static String indent(String s, int spaces, boolean newline) {
618 StringBuilder buff = new StringBuilder(s.length() + spaces);
619 for (int i = 0; i < s.length();) {
620 for (int j = 0; j < spaces; j++) {
621 buff.append(' ');
622 }
623 int n = s.indexOf('\n', i);
624 n = n < 0 ? s.length() : n + 1;
625 buff.append(s.substring(i, n));
626 i = n;
627 }
628 if (newline && !s.endsWith("\n")) {
629 buff.append('\n');
630 }
631 return buff.toString();
632 }
633
634 /**
635 * Escapes a comment.
636 * If the data contains '--', it is converted to '- -'.
637 * The data is indented with 4 spaces if it contains a newline character.
638 *
639 * @param data the comment text
640 * @return <!-- data -->
641 */
642 public static String xmlComment(String data) {
643 int idx = 0;
644 while (true) {
645 idx = data.indexOf("--", idx);
646 if (idx < 0) {
647 break;
648 }
649 data = data.substring(0, idx + 1) + " " + data.substring(idx + 1);
650 }
651 // must have a space at the beginning and at the end,
652 // otherwise the data must not contain '-' as the first/last character
653 if (data.indexOf('\n') >= 0) {
654 return "<!--\n" + indent(data) + "-->\n";
655 }
656 return "<!-- " + data + " -->\n";
657 }
658
659 /**
660 * Converts the data to a CDATA element.
661 * If the data contains ']]>', it is escaped as a text element.
662 *
663 * @param data the text data
664 * @return <![CDATA[data]]>
665 */
666 public static String xmlCData(String data) {
667 if (data.indexOf("]]>") >= 0) {
668 return xmlText(data);
669 }
670 boolean newline = data.endsWith("\n");
671 data = "<![CDATA[" + data + "]]>";
672 return newline ? data + "\n" : data;
673 }
674
675 /**
676 * Returns <?xml version="1.0"?>
677 * @return <?xml version="1.0"?>
678 */
679 public static String xmlStartDoc() {
680 return "<?xml version=\"1.0\"?>\n";
681 }
682
683 /**
684 * Escapes an XML text element.
685 *
686 * @param text the text data
687 * @return the escaped text
688 */
689 public static String xmlText(String text) {
690 return xmlText(text, false);
691 }
692
693 /**
694 * Escapes an XML text element.
695 *
696 * @param text the text data
697 * @param escapeNewline whether to escape newlines
698 * @return the escaped text
699 */
700 public static String xmlText(String text, boolean escapeNewline) {
701 int length = text.length();
702 StringBuilder buff = new StringBuilder(length);
703 for (int i = 0; i < length; i++) {
704 char ch = text.charAt(i);
705 switch (ch) {
706 case '<':
707 buff.append("&lt;");
708 break;
709 case '>':
710 buff.append("&gt;");
711 break;
712 case '&':
713 buff.append("&amp;");
714 break;
715 case '\'':
716 buff.append("&apos;");
717 break;
718 case '\"':
719 buff.append("&quot;");
720 break;
721 case '\r':
722 case '\n':
723 if (escapeNewline) {
724 buff.append("&#x").
725 append(Integer.toHexString(ch)).
726 append(';');
727 } else {
728 buff.append(ch);
729 }
730 break;
731 case '\t':
732 buff.append(ch);
733 break;
734 default:
735 if (ch < ' ' || ch > 127) {
736 buff.append("&#x").
737 append(Integer.toHexString(ch)).
738 append(';');
739 } else {
740 buff.append(ch);
741 }
742 }
743 }
744 return buff.toString();
745 }
746
747 /**
748 * Replace all occurrences of the before string with the after string.
749 *
750 * @param s the string
751 * @param before the old text
752 * @param after the new text
753 * @return the string with the before string replaced
754 */
755 public static String replaceAll(String s, String before, String after) {
756 int next = s.indexOf(before);
757 if (next < 0) {
758 return s;
759 }
760 StringBuilder buff = new StringBuilder(s.length() - before.length() + after.length());
761 int index = 0;
762 while (true) {
763 buff.append(s.substring(index, next)).append(after);
764 index = next + before.length();
765 next = s.indexOf(before, index);
766 if (next < 0) {
767 buff.append(s.substring(index));
768 break;
769 }
770 }
771 return buff.toString();
772 }
773
774 /**
775 * Enclose a string with double quotes. A double quote inside the string is
776 * escaped using a double quote.
777 *
778 * @param s the text
779 * @return the double quoted text
780 */
781 public static String quoteIdentifier(String s) {
782 int length = s.length();
783 StringBuilder buff = new StringBuilder(length + 2);
784 buff.append('\"');
785 for (int i = 0; i < length; i++) {
786 char c = s.charAt(i);
787 if (c == '"') {
788 buff.append(c);
789 }
790 buff.append(c);
791 }
792 return buff.append('\"').toString();
793 }
794
795 /**
796 * Check if a String is null or empty (the length is null).
797 *
798 * @param s the string to check
799 * @return true if it is null or empty
800 */
801 public static boolean isNullOrEmpty(String s) {
802 return s == null || s.length() == 0;
803 }
804
805 /**
806 * In a string, replace block comment marks with /++ .. ++/.
807 *
808 * @param sql the string
809 * @return the resulting string
810 */
811 public static String quoteRemarkSQL(String sql) {
812 sql = replaceAll(sql, "*/", "++/");
813 return replaceAll(sql, "/*", "/++");
814 }
815
816 /**
817 * Pad a string. This method is used for the SQL function RPAD and LPAD.
818 *
819 * @param string the original string
820 * @param n the target length
821 * @param padding the padding string
822 * @param right true if the padding should be appended at the end
823 * @return the padded string
824 */
825 public static String pad(String string, int n, String padding, boolean right) {
826 if (n < 0) {
827 n = 0;
828 }
829 if (n < string.length()) {
830 return string.substring(0, n);
831 } else if (n == string.length()) {
832 return string;
833 }
834 char paddingChar;
835 if (padding == null || padding.length() == 0) {
836 paddingChar = ' ';
837 } else {
838 paddingChar = padding.charAt(0);
839 }
840 StringBuilder buff = new StringBuilder(n);
841 n -= string.length();
842 if (right) {
843 buff.append(string);
844 }
845 for (int i = 0; i < n; i++) {
846 buff.append(paddingChar);
847 }
848 if (!right) {
849 buff.append(string);
850 }
851 return buff.toString();
852 }
853
854 /**
855 * Create a new char array and copy all the data. If the size of the byte
856 * array is zero, the same array is returned.
857 *
858 * @param chars the char array (may be null)
859 * @return a new char array
860 */
861 public static char[] cloneCharArray(char[] chars) {
862 if (chars == null) {
863 return null;
864 }
865 int len = chars.length;
866 if (len == 0) {
867 return chars;
868 }
869 char[] copy = new char[len];
870 System.arraycopy(chars, 0, copy, 0, len);
871 return copy;
872 }
873
874 /**
875 * Trim a character from a string.
876 *
877 * @param s the string
878 * @param leading if leading characters should be removed
879 * @param trailing if trailing characters should be removed
880 * @param sp what to remove (only the first character is used)
881 * or null for a space
882 * @return the trimmed string
883 */
884 public static String trim(String s, boolean leading, boolean trailing, String sp) {
885 char space = (sp == null || sp.length() < 1) ? ' ' : sp.charAt(0);
886 if (leading) {
887 int len = s.length(), i = 0;
888 while (i < len && s.charAt(i) == space) {
889 i++;
890 }
891 s = (i == 0) ? s : s.substring(i);
892 }
893 if (trailing) {
894 int endIndex = s.length() - 1;
895 int i = endIndex;
896 while (i >= 0 && s.charAt(i) == space) {
897 i--;
898 }
899 s = i == endIndex ? s : s.substring(0, i + 1);
900 }
901 return s;
902 }
903
904 /**
905 * Get the string from the cache if possible. If the string has not been
906 * found, it is added to the cache. If there is such a string in the cache,
907 * that one is returned.
908 *
909 * @param s the original string
910 * @return a string with the same content, if possible from the cache
911 */
912 public static String cache(String s) {
913 if (!SysProperties.OBJECT_CACHE) {
914 return s;
915 }
916 if (s == null) {
917 return s;
918 } else if (s.length() == 0) {
919 return "";
920 }
921 int hash = s.hashCode();
922 String[] cache = getCache();
923 if (cache != null) {
924 int index = hash & (SysProperties.OBJECT_CACHE_SIZE - 1);
925 String cached = cache[index];
926 if (cached != null) {
927 if (s.equals(cached)) {
928 return cached;
929 }
930 }
931 cache[index] = s;
932 }
933 return s;
934 }
935
936 /**
937 * Get a string from the cache, and if no such string has been found, create
938 * a new one with only this content. This solves out of memory problems if
939 * the string is a substring of another, large string. In Java, strings are
940 * shared, which could lead to memory problems. This avoid such problems.
941 *
942 * @param s the string
943 * @return a string that is guaranteed not be a substring of a large string
944 */
945 public static String fromCacheOrNew(String s) {
946 if (!SysProperties.OBJECT_CACHE) {
947 return s;
948 }
949 if (s == null) {
950 return s;
951 } else if (s.length() == 0) {
952 return "";
953 }
954 int hash = s.hashCode();
955 String[] cache = getCache();
956 int index = hash & (SysProperties.OBJECT_CACHE_SIZE - 1);
957 if (cache == null) {
958 return s;
959 }
960 String cached = cache[index];
961 if (cached != null) {
962 if (s.equals(cached)) {
963 return cached;
964 }
965 }
966 // create a new object that is not shared
967 // (to avoid out of memory if it is a substring of a big String)
968 // NOPMD
969 s = new String(s);
970 cache[index] = s;
971 return s;
972 }
973
974 /**
975 * Clear the cache. This method is used for testing.
976 */
977 public static void clearCache() {
978 softCache = new SoftReference<String[]>(null);
979 }
980
981 /**
982 * Convert a hex encoded string to a byte array.
983 *
984 * @param s the hex encoded string
985 * @return the byte array
986 */
987 public static byte[] convertHexToBytes(String s) {
988 int len = s.length();
989 if (len % 2 != 0) {
990 throw DbException.get(ErrorCode.HEX_STRING_ODD_1, s);
991 }
992 len /= 2;
993 byte[] buff = new byte[len];
994 int mask = 0;
995 int[] hex = HEX_DECODE;
996 try {
997 for (int i = 0; i < len; i++) {
998 int d = hex[s.charAt(i + i)] << 4 | hex[s.charAt(i + i + 1)];
999 mask |= d;
1000 buff[i] = (byte) d;
1001 }
1002 } catch (ArrayIndexOutOfBoundsException e) {
1003 throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s);
1004 }
1005 if ((mask & ~255) != 0) {
1006 throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s);
1007 }
1008 return buff;
1009 }
1010
1011 /**
1012 * Convert a byte array to a hex encoded string.
1013 *
1014 * @param value the byte array
1015 * @return the hex encoded string
1016 */
1017 public static String convertBytesToHex(byte[] value) {
1018 return convertBytesToHex(value, value.length);
1019 }
1020
1021 /**
1022 * Convert a byte array to a hex encoded string.
1023 *
1024 * @param value the byte array
1025 * @param len the number of bytes to encode
1026 * @return the hex encoded string
1027 */
1028 public static String convertBytesToHex(byte[] value, int len) {
1029 char[] buff = new char[len + len];
1030 char[] hex = HEX;
1031 for (int i = 0; i < len; i++) {
1032 int c = value[i] & 0xff;
1033 buff[i + i] = hex[c >> 4];
1034 buff[i + i + 1] = hex[c & 0xf];
1035 }
1036 return new String(buff);
1037 }
1038
1039 /**
1040 * Check if this string is a decimal number.
1041 *
1042 * @param s the string
1043 * @return true if it is
1044 */
1045 public static boolean isNumber(String s) {
1046 if (s.length() == 0) {
1047 return false;
1048 }
1049 for (char c : s.toCharArray()) {
1050 if (!Character.isDigit(c)) {
1051 return false;
1052 }
1053 }
1054 return true;
1055 }
1056
1057 /**
1058 * Append a zero-padded number to a string builder.
1059 *
1060 * @param buff the string builder
1061 * @param length the number of characters to append
1062 * @param positiveValue the number to append
1063 */
1064 public static void appendZeroPadded(StringBuilder buff, int length, long positiveValue) {
1065 if (length == 2) {
1066 if (positiveValue < 10) {
1067 buff.append('0');
1068 }
1069 buff.append(positiveValue);
1070 } else {
1071 String s = Long.toString(positiveValue);
1072 length -= s.length();
1073 while (length > 0) {
1074 buff.append('0');
1075 length--;
1076 }
1077 buff.append(s);
1078 }
1079 }
1080
1081 }
1082