001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.util;
007:
008: import java.io.UnsupportedEncodingException;
009: import java.net.URLEncoder;
010: import java.sql.SQLException;
011: import java.text.ParseException;
012: import java.text.SimpleDateFormat;
013: import java.util.ArrayList;
014: import java.util.Date;
015: import java.util.Locale;
016: import java.util.TimeZone;
017:
018: import org.h2.constant.ErrorCode;
019: import org.h2.constant.SysProperties;
020: import org.h2.engine.Constants;
021: import org.h2.message.Message;
022:
023: /**
024: * A few String utility functions.
025: */
026: public class StringUtils {
027:
028: public static boolean equals(String a, String b) {
029: if (a == null) {
030: return b == null;
031: }
032: return a.equals(b);
033: }
034:
035: public static String toUpperEnglish(String s) {
036: return s.toUpperCase(Locale.ENGLISH);
037: }
038:
039: public static String toLowerEnglish(String s) {
040: return s.toLowerCase(Locale.ENGLISH);
041: }
042:
043: public static String getDefaultCharset() {
044: return SysProperties.FILE_ENCODING;
045: }
046:
047: public static String quoteStringSQL(String s) {
048: StringBuffer buff = new StringBuffer(s.length() + 2);
049: buff.append('\'');
050: for (int i = 0; i < s.length(); i++) {
051: char c = s.charAt(i);
052: if (c == '\'') {
053: buff.append(c);
054: } else if (c < ' ' || c > 127) {
055: // need to start from the beginning because maybe there was a \
056: // that was not quoted
057: return "STRINGDECODE(" + quoteStringSQL(javaEncode(s))
058: + ")";
059: }
060: buff.append(c);
061: }
062: buff.append('\'');
063: return buff.toString();
064: }
065:
066: public static String javaEncode(String s) {
067: StringBuffer buff = new StringBuffer(s.length());
068: for (int i = 0; i < s.length(); i++) {
069: char c = s.charAt(i);
070: switch (c) {
071: // case '\b':
072: // // BS backspace
073: // // not supported in properties files
074: // buff.append("\\b");
075: // break;
076: case '\t':
077: // HT horizontal tab
078: buff.append("\\t");
079: break;
080: case '\n':
081: // LF linefeed
082: buff.append("\\n");
083: break;
084: case '\f':
085: // FF form feed
086: buff.append("\\f");
087: break;
088: case '\r':
089: // CR carriage return
090: buff.append("\\r");
091: break;
092: case '"':
093: // double quote
094: buff.append("\\\"");
095: break;
096: case '\\':
097: // backslash
098: buff.append("\\\\");
099: break;
100: default:
101: int ch = (c & 0xffff);
102: if (ch >= ' ' && (ch < 0x80)) {
103: buff.append(c);
104: // not supported in properties files
105: // } else if(ch < 0xff) {
106: // buff.append("\\");
107: // // make sure it's three characters (0x200 is octal 1000)
108: // buff.append(Integer.toOctalString(0x200 | ch).substring(1));
109: } else {
110: buff.append("\\u");
111: // make sure it's four characters
112: buff.append(Integer.toHexString(0x10000 | ch)
113: .substring(1));
114: }
115: }
116: }
117: return buff.toString();
118: }
119:
120: public static String addAsterisk(String s, int index) {
121: if (s != null && index < s.length()) {
122: s = s.substring(0, index) + "[*]" + s.substring(index);
123: }
124: return s;
125: }
126:
127: private static SQLException getFormatException(String s, int i) {
128: return Message.getSQLException(ErrorCode.STRING_FORMAT_ERROR_1,
129: addAsterisk(s, i));
130: }
131:
132: public static String javaDecode(String s) throws SQLException {
133: StringBuffer buff = new StringBuffer(s.length());
134: for (int i = 0; i < s.length(); i++) {
135: char c = s.charAt(i);
136: if (c == '"') {
137: break;
138: } else if (c == '\\') {
139: if (i >= s.length()) {
140: throw getFormatException(s, s.length() - 1);
141: }
142: c = s.charAt(++i);
143: switch (c) {
144: case 't':
145: buff.append('\t');
146: break;
147: case 'r':
148: buff.append('\r');
149: break;
150: case 'n':
151: buff.append('\n');
152: break;
153: case 'b':
154: buff.append('\b');
155: break;
156: case 'f':
157: buff.append('\f');
158: break;
159: case '#':
160: // for properties files
161: buff.append('#');
162: break;
163: case '=':
164: // for properties files
165: buff.append('=');
166: break;
167: case ':':
168: // for properties files
169: buff.append(':');
170: break;
171: case '"':
172: buff.append('"');
173: break;
174: case '\\':
175: buff.append('\\');
176: break;
177: case 'u': {
178: try {
179: c = (char) (Integer.parseInt(s.substring(i + 1,
180: i + 5), 16));
181: } catch (NumberFormatException e) {
182: throw getFormatException(s, i);
183: }
184: i += 4;
185: buff.append(c);
186: break;
187: }
188: default:
189: if (c >= '0' && c <= '9') {
190: try {
191: c = (char) (Integer.parseInt(s.substring(i,
192: i + 3), 8));
193: } catch (NumberFormatException e) {
194: throw getFormatException(s, i);
195: }
196: i += 2;
197: buff.append(c);
198: } else {
199: throw getFormatException(s, i);
200: }
201: }
202: } else {
203: buff.append(c);
204: }
205: }
206: return buff.toString();
207: }
208:
209: public static String quoteJavaString(String s) {
210: if (s == null) {
211: return "null";
212: } else {
213: return "\"" + javaEncode(s) + "\"";
214: }
215: }
216:
217: public static byte[] utf8Encode(String s) throws SQLException {
218: try {
219: // TODO UTF8: String.getBytes("UTF-8") only returns 1 byte for
220: // 0xd800-0xdfff
221: return s.getBytes(Constants.UTF8);
222: } catch (UnsupportedEncodingException e) {
223: throw Message.convert(e);
224: }
225: }
226:
227: public static String utf8Decode(byte[] utf8) {
228: try {
229: return new String(utf8, Constants.UTF8);
230: } catch (UnsupportedEncodingException e) {
231: throw Message.convertToInternal(e);
232: }
233: }
234:
235: public static String utf8Decode(byte[] bytes, int offset, int length) {
236: try {
237: return new String(bytes, offset, length, Constants.UTF8);
238: } catch (UnsupportedEncodingException e) {
239: throw Message.convertToInternal(e);
240: }
241: }
242:
243: public static String quoteJavaStringArray(String[] array) {
244: if (array == null) {
245: return "null";
246: }
247: StringBuffer buff = new StringBuffer(5 * array.length);
248: buff.append("new String[]{");
249: for (int i = 0; i < array.length; i++) {
250: if (i > 0) {
251: buff.append(", ");
252: }
253: buff.append(quoteJavaString(array[i]));
254: }
255: buff.append("}");
256: return buff.toString();
257: }
258:
259: public static String quoteJavaIntArray(int[] array) {
260: if (array == null) {
261: return "null";
262: }
263: StringBuffer buff = new StringBuffer(2 * array.length);
264: buff.append("new int[]{");
265: for (int i = 0; i < array.length; i++) {
266: if (i > 0) {
267: buff.append(", ");
268: }
269: buff.append(array[i]);
270: }
271: buff.append("}");
272: return buff.toString();
273: }
274:
275: public static String enclose(String s) {
276: if (s.startsWith("(")) {
277: return s;
278: } else {
279: return "(" + s + ")";
280: }
281: }
282:
283: public static String unEnclose(String s) {
284: if (s.startsWith("(") && s.endsWith(")")) {
285: return s.substring(1, s.length() - 1);
286: } else {
287: return s;
288: }
289: }
290:
291: public static String urlEncode(String s) {
292: //#ifdef JDK14
293: try {
294: return URLEncoder.encode(s, "UTF-8");
295: } catch (UnsupportedEncodingException e) {
296: e.printStackTrace();
297: return s;
298: }
299: //#endif
300: //#ifdef JDK13
301: /*
302: return URLEncoder.encode(s);
303: */
304: //#endif
305: // byte[] utf = utf8Encode(s);
306: // StringBuffer buff = new StringBuffer(utf.length);
307: // for(int i=0; i<utf.length; i++) {
308: //
309: // buff.append()
310: // }
311: }
312:
313: public static String urlDecode(String encoded) throws SQLException {
314: byte[] buff = new byte[encoded.length()];
315: int j = 0;
316: for (int i = 0; i < encoded.length(); i++) {
317: char ch = encoded.charAt(i);
318: if (ch == '+') {
319: buff[j++] = ' ';
320: } else if (ch == '%') {
321: buff[j++] = (byte) Integer.parseInt(encoded.substring(
322: i + 1, i + 3), 16);
323: i += 2;
324: } else {
325: if (SysProperties.CHECK && (ch > 127 || ch < ' ')) {
326: throw new IllegalArgumentException(
327: "unexpected char " + (int) ch
328: + " decoding " + encoded);
329: }
330: buff[j++] = (byte) ch;
331: }
332: }
333: String s = utf8Decode(buff, 0, j);
334: return s;
335: }
336:
337: public static String[] arraySplit(String s, char separatorChar,
338: boolean trim) {
339: if (s == null) {
340: return null;
341: }
342: if (s.length() == 0) {
343: return new String[0];
344: }
345: ArrayList list = new ArrayList();
346: StringBuffer buff = new StringBuffer(s.length());
347: for (int i = 0; i < s.length(); i++) {
348: char c = s.charAt(i);
349: if (c == separatorChar) {
350: String e = buff.toString();
351: list.add(trim ? e.trim() : e);
352: buff.setLength(0);
353: } else if (c == '\\' && i < s.length() - 1) {
354: buff.append(s.charAt(++i));
355: } else {
356: buff.append(c);
357: }
358: }
359: String e = buff.toString();
360: list.add(trim ? e.trim() : e);
361: String[] array = new String[list.size()];
362: list.toArray(array);
363: return array;
364: }
365:
366: public static String arrayCombine(String[] list, char separatorChar) {
367: StringBuffer buff = new StringBuffer(5 * list.length);
368: for (int i = 0; i < list.length; i++) {
369: if (i > 0) {
370: buff.append(separatorChar);
371: }
372: String s = list[i];
373: if (s == null) {
374: s = "";
375: }
376: for (int j = 0; j < s.length(); j++) {
377: char c = s.charAt(j);
378: if (c == '\\' || c == separatorChar) {
379: buff.append('\\');
380: }
381: buff.append(c);
382: }
383: }
384: return buff.toString();
385: }
386:
387: /**
388: * Formats a date using a format string
389: */
390: public static String formatDateTime(Date date, String format,
391: String locale, String timezone) throws SQLException {
392: SimpleDateFormat dateFormat = getDateFormat(format, locale,
393: timezone);
394: synchronized (dateFormat) {
395: return dateFormat.format(date);
396: }
397: }
398:
399: /**
400: * Parses a date using a format string
401: */
402: public static Date parseDateTime(String date, String format,
403: String locale, String timezone) throws SQLException {
404: SimpleDateFormat dateFormat = getDateFormat(format, locale,
405: timezone);
406: try {
407: synchronized (dateFormat) {
408: return dateFormat.parse(date);
409: }
410: } catch (ParseException e) {
411: throw Message.getSQLException(ErrorCode.PARSE_ERROR_1,
412: new String[] { date }, e);
413: }
414: }
415:
416: private static SimpleDateFormat getDateFormat(String format,
417: String locale, String timezone) throws SQLException {
418: try {
419: // currently, a new instance is create for each call
420: // however, could cache the last few instances
421: SimpleDateFormat df;
422: if (locale == null) {
423: df = new SimpleDateFormat(format);
424: } else {
425: //#ifdef JDK14
426: Locale l = new Locale(locale);
427: //#endif
428: //#ifdef JDK13
429: /*
430: Locale l = new Locale(locale, "");
431: */
432: //#endif
433: df = new SimpleDateFormat(format, l);
434: }
435: if (timezone != null) {
436: df.setTimeZone(TimeZone.getTimeZone(timezone));
437: }
438: return df;
439: } catch (Exception e) {
440: throw Message.getSQLException(ErrorCode.PARSE_ERROR_1,
441: new String[] { format + "/" + locale + "/"
442: + timezone }, e);
443: }
444: }
445:
446: /**
447: * Creates an XML attribute of the form name="value".
448: * A single space is prepended to the name,
449: * so that multiple attributes can be concatenated.
450: * @param name
451: * @param value
452: * @return the attribute
453: */
454: public static String xmlAttr(String name, String value) {
455: return " " + name + "=\"" + xmlText(value) + "\"";
456: }
457:
458: /**
459: * Create an XML node with optional attributes and content.
460: * The data is indented with 4 spaces if it contains a newline character.
461: *
462: * @param name the element name
463: * @param attributes the attributes (may be null)
464: * @param content the content (may be null)
465: * @return the node
466: */
467: public static String xmlNode(String name, String attributes,
468: String content) {
469: String start = attributes == null ? name : name + attributes;
470: if (content == null) {
471: return "<" + start + "/>\n";
472: } else {
473: if (content.indexOf('\n') >= 0) {
474: content = "\n" + indent(content);
475: }
476: return "<" + start + ">" + content + "</" + name + ">\n";
477: }
478: }
479:
480: /**
481: * Indents a string with 4 spaces.
482: * @param s the string
483: * @return the indented string
484: */
485: public static String indent(String s) {
486: return indent(s, 4);
487: }
488:
489: /**
490: * Indents a string with spaces.
491: * @param s the string
492: * @param spaces the number of spaces
493: * @return the indented string
494: */
495: public static String indent(String s, int spaces) {
496: StringBuffer buff = new StringBuffer(s.length() + spaces);
497: for (int i = 0; i < s.length();) {
498: for (int j = 0; j < spaces; j++) {
499: buff.append(' ');
500: }
501: int n = s.indexOf('\n', i);
502: n = n < 0 ? s.length() : n + 1;
503: buff.append(s.substring(i, n));
504: i = n;
505: }
506: if (!s.endsWith("\n")) {
507: buff.append('\n');
508: }
509: return buff.toString();
510: }
511:
512: /**
513: * Escapes a comment.
514: * If the data contains '--', it is converted to '- -'.
515: * The data is indented with 4 spaces if it contains a newline character.
516: *
517: * @param data
518: * @return <!-- data -->
519: */
520: public static String xmlComment(String data) {
521: int idx = 0;
522: while (true) {
523: idx = data.indexOf("--", idx);
524: if (idx < 0) {
525: break;
526: }
527: data = data.substring(0, idx + 1) + " "
528: + data.substring(idx + 1);
529: }
530: // must have a space at the beginning and at the end,
531: // otherwise the data must not contain '-' as the first/last character
532: if (data.indexOf('\n') >= 0) {
533: return "<!--\n" + indent(data) + "-->\n";
534: } else {
535: return "<!-- " + data + " -->\n";
536: }
537: }
538:
539: /**
540: * Converts the data to a CDATA element.
541: * If the data contains ']]>', it is escaped as a text element.
542: * @param data
543: * @return <![CDATA[data]]>
544: */
545: public static String xmlCData(String data) {
546: if (data.indexOf("]]>") >= 0) {
547: return xmlText(data);
548: }
549: boolean newline = data.endsWith("\n");
550: data = "<![CDATA[" + data + "]]>";
551: return newline ? data + "\n" : data;
552: }
553:
554: /**
555: * Returns <?xml version="1.0"?>
556: * @return <?xml version="1.0"?>
557: */
558: public static String xmlStartDoc() {
559: return "<?xml version=\"1.0\"?>\n";
560: }
561:
562: /**
563: * Escapes an XML text element.
564: *
565: * @param text
566: * @return the escaped text
567: */
568: public static String xmlText(String text) {
569: StringBuffer buff = new StringBuffer(text.length());
570: for (int i = 0; i < text.length(); i++) {
571: char ch = text.charAt(i);
572: switch (ch) {
573: case '<':
574: buff.append("<");
575: break;
576: case '>':
577: buff.append(">");
578: break;
579: case '&':
580: buff.append("&");
581: break;
582: case '\'':
583: buff.append("'");
584: break;
585: case '\"':
586: buff.append(""");
587: break;
588: case '\r':
589: case '\n':
590: case '\t':
591: buff.append(ch);
592: break;
593: default:
594: if (ch < ' ' || ch > 127) {
595: buff.append("&#x");
596: buff.append(Integer.toHexString(ch));
597: buff.append(';');
598: } else {
599: buff.append(ch);
600: }
601: }
602: }
603: return buff.toString();
604: }
605:
606: public static String replaceAll(String s, String before,
607: String after) {
608: int index = 0;
609: while (true) {
610: int next = s.indexOf(before, index);
611: if (next < 0) {
612: return s;
613: }
614: s = s.substring(0, next) + after
615: + s.substring(next + before.length());
616: index = next + after.length();
617: }
618: }
619:
620: public static String quoteIdentifier(String s) {
621: StringBuffer buff = new StringBuffer(s.length() + 2);
622: buff.append('\"');
623: for (int i = 0; i < s.length(); i++) {
624: char c = s.charAt(i);
625: if (c == '"') {
626: buff.append(c);
627: }
628: buff.append(c);
629: }
630: return buff.append('\"').toString();
631: }
632:
633: /**
634: * Check if a String is null or empty (the length is null).
635: *
636: * @return true if it is null or empty
637: */
638: public static boolean isNullOrEmpty(String s) {
639: return s == null || s.length() == 0;
640: }
641:
642: public static String quoteRemarkSQL(String sql) {
643: while (true) {
644: int idx = sql.indexOf("*/");
645: if (idx < 0) {
646: break;
647: }
648: sql = sql.substring(0, idx) + "++/"
649: + sql.substring(idx + 2);
650: }
651: while (true) {
652: int idx = sql.indexOf("/*");
653: if (idx < 0) {
654: break;
655: }
656: sql = sql.substring(0, idx) + "/++"
657: + sql.substring(idx + 2);
658: }
659: return sql;
660: }
661:
662: /**
663: * Pad a string. This method is used for the SQL function RPAD and LPAD.
664: *
665: * @param string the original string
666: * @param n the target length
667: * @param padding the padding string
668: * @param right true if the padding should be appended at the end
669: * @return the padded string
670: */
671: public static String pad(String string, int n, String padding,
672: boolean right) {
673: if (n < 0) {
674: n = 0;
675: }
676: if (n < string.length()) {
677: return string.substring(0, n);
678: } else if (n == string.length()) {
679: return string;
680: }
681: char paddingChar;
682: if (padding == null || padding.length() == 0) {
683: paddingChar = ' ';
684: } else {
685: paddingChar = padding.charAt(0);
686: }
687: StringBuffer buff = new StringBuffer(n);
688: n -= string.length();
689: if (right) {
690: buff.append(string);
691: }
692: for (int i = 0; i < n; i++) {
693: buff.append(paddingChar);
694: }
695: if (!right) {
696: buff.append(string);
697: }
698: return buff.toString();
699: }
700:
701: }
|