Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.util;
import java.lang.ref.SoftReference;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import org.h2.api.ErrorCode;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
/**
* A few String utility functions.
*/
public class StringUtils {
private static SoftReference softCache =
new SoftReference<>(null);
private static long softCacheCreatedNs;
private static final char[] HEX = "0123456789abcdef".toCharArray();
private static final int[] HEX_DECODE = new int['f' + 1];
// memory used by this cache:
// 4 * 1024 * 2 (strings per pair) * 64 * 2 (bytes per char) = 0.5 MB
private static final int TO_UPPER_CACHE_LENGTH = 2 * 1024;
private static final int TO_UPPER_CACHE_MAX_ENTRY_LENGTH = 64;
private static final String[][] TO_UPPER_CACHE = new String[TO_UPPER_CACHE_LENGTH][];
static {
for (int i = 0; i < HEX_DECODE.length; i++) {
HEX_DECODE[i] = -1;
}
for (int i = 0; i <= 9; i++) {
HEX_DECODE[i + '0'] = i;
}
for (int i = 0; i <= 5; i++) {
HEX_DECODE[i + 'a'] = HEX_DECODE[i + 'A'] = i + 10;
}
}
private StringUtils() {
// utility class
}
private static String[] getCache() {
String[] cache;
// softCache can be null due to a Tomcat problem
// a workaround is disable the system property org.apache.
// catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES
if (softCache != null) {
cache = softCache.get();
if (cache != null) {
return cache;
}
}
// create a new cache at most every 5 seconds
// so that out of memory exceptions are not delayed
long time = System.nanoTime();
if (softCacheCreatedNs != 0 && time - softCacheCreatedNs < TimeUnit.SECONDS.toNanos(5)) {
return null;
}
try {
cache = new String[SysProperties.OBJECT_CACHE_SIZE];
softCache = new SoftReference<>(cache);
return cache;
} finally {
softCacheCreatedNs = System.nanoTime();
}
}
/**
* Convert a string to uppercase using the English locale.
*
* @param s the test to convert
* @return the uppercase text
*/
public static String toUpperEnglish(String s) {
if (s.length() > TO_UPPER_CACHE_MAX_ENTRY_LENGTH) {
return s.toUpperCase(Locale.ENGLISH);
}
int index = s.hashCode() & (TO_UPPER_CACHE_LENGTH - 1);
String[] e = TO_UPPER_CACHE[index];
if (e != null) {
if (e[0].equals(s)) {
return e[1];
}
}
String s2 = s.toUpperCase(Locale.ENGLISH);
e = new String[] { s, s2 };
TO_UPPER_CACHE[index] = e;
return s2;
}
/**
* Convert a string to lowercase using the English locale.
*
* @param s the text to convert
* @return the lowercase text
*/
public static String toLowerEnglish(String s) {
return s.toLowerCase(Locale.ENGLISH);
}
/**
* Check is a string starts with another string, ignoring the case.
*
* @param s the string to check (must be longer than start)
* @param start the prefix of s
* @return true if start is a prefix of s
*/
public static boolean startsWithIgnoreCase(String s, String start) {
if (s.length() < start.length()) {
return false;
}
return s.substring(0, start.length()).equalsIgnoreCase(start);
}
/**
* Convert a string to a SQL literal. Null is converted to NULL. The text is
* enclosed in single quotes. If there are any special characters, the
* method STRINGDECODE is used.
*
* @param s the text to convert.
* @return the SQL literal
*/
public static String quoteStringSQL(String s) {
if (s == null) {
return "NULL";
}
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\'');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\'') {
buff.append(c);
} else if (c < ' ' || c > 127) {
// need to start from the beginning because maybe there was a \
// that was not quoted
return "STRINGDECODE(" + quoteStringSQL(javaEncode(s)) + ")";
}
buff.append(c);
}
buff.append('\'');
return buff.toString();
}
public static String quoteStringSQL1(String s) {
if (s == null) {
return "NULL";
}
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\'');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\'') {
buff.append(c);
}
buff.append(c);
}
buff.append('\'');
return buff.toString();
}
/**
* Convert a string to a Java literal using the correct escape sequences.
* The literal is not enclosed in double quotes. The result can be used in
* properties files or in Java source code.
*
* @param s the text to convert
* @return the Java representation
*/
public static String javaEncode(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
switch (c) {
// case '\b':
// // BS backspace
// // not supported in properties files
// buff.append("\\b");
// break;
case '\t':
// HT horizontal tab
buff.append("\\t");
break;
case '\n':
// LF linefeed
buff.append("\\n");
break;
case '\f':
// FF form feed
buff.append("\\f");
break;
case '\r':
// CR carriage return
buff.append("\\r");
break;
case '"':
// double quote
buff.append("\\\"");
break;
case '\\':
// backslash
buff.append("\\\\");
break;
default:
int ch = c & 0xffff;
if (ch >= ' ' && (ch < 0x80)) {
buff.append(c);
// not supported in properties files
// } else if (ch < 0xff) {
// buff.append("\\");
// // make sure it's three characters (0x200 is octal 1000)
// buff.append(Integer.toOctalString(0x200 | ch).substring(1));
} else {
buff.append("\\u");
String hex = Integer.toHexString(ch);
// make sure it's four characters
for (int len = hex.length(); len < 4; len++) {
buff.append('0');
}
buff.append(hex);
}
}
}
return buff.toString();
}
/**
* Add an asterisk ('[*]') at the given position. This format is used to
* show where parsing failed in a statement.
*
* @param s the text
* @param index the position
* @return the text with asterisk
*/
public static String addAsterisk(String s, int index) {
if (s != null) {
index = Math.min(index, s.length());
s = s.substring(0, index) + "[*]" + s.substring(index);
}
return s;
}
private static DbException getFormatException(String s, int i) {
return DbException.get(ErrorCode.STRING_FORMAT_ERROR_1, addAsterisk(s, i));
}
/**
* Decode a text that is encoded as a Java string literal. The Java
* properties file format and Java source code format is supported.
*
* @param s the encoded string
* @return the string
*/
public static String javaDecode(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '\\') {
if (i + 1 >= s.length()) {
throw getFormatException(s, i);
}
c = s.charAt(++i);
switch (c) {
case 't':
buff.append('\t');
break;
case 'r':
buff.append('\r');
break;
case 'n':
buff.append('\n');
break;
case 'b':
buff.append('\b');
break;
case 'f':
buff.append('\f');
break;
case '#':
// for properties files
buff.append('#');
break;
case '=':
// for properties files
buff.append('=');
break;
case ':':
// for properties files
buff.append(':');
break;
case '"':
buff.append('"');
break;
case '\\':
buff.append('\\');
break;
case 'u': {
try {
c = (char) (Integer.parseInt(s.substring(i + 1, i + 5), 16));
} catch (NumberFormatException e) {
throw getFormatException(s, i);
}
i += 4;
buff.append(c);
break;
}
default:
if (c >= '0' && c <= '9') {
try {
c = (char) (Integer.parseInt(s.substring(i, i + 3), 8));
} catch (NumberFormatException e) {
throw getFormatException(s, i);
}
i += 2;
buff.append(c);
} else {
throw getFormatException(s, i);
}
}
} else {
buff.append(c);
}
}
return buff.toString();
}
/**
* Convert a string to the Java literal and enclose it with double quotes.
* Null will result in "null" (without double quotes).
*
* @param s the text to convert
* @return the Java representation
*/
public static String quoteJavaString(String s) {
if (s == null) {
return "null";
}
return "\"" + javaEncode(s) + "\"";
}
/**
* Convert a string array to the Java source code that represents this
* array. Null will be converted to 'null'.
*
* @param array the string array
* @return the Java source code (including new String[]{})
*/
public static String quoteJavaStringArray(String[] array) {
if (array == null) {
return "null";
}
StatementBuilder buff = new StatementBuilder("new String[]{");
for (String a : array) {
buff.appendExceptFirst(", ");
buff.append(quoteJavaString(a));
}
return buff.append('}').toString();
}
/**
* Convert an int array to the Java source code that represents this array.
* Null will be converted to 'null'.
*
* @param array the int array
* @return the Java source code (including new int[]{})
*/
public static String quoteJavaIntArray(int[] array) {
if (array == null) {
return "null";
}
StatementBuilder buff = new StatementBuilder("new int[]{");
for (int a : array) {
buff.appendExceptFirst(", ");
buff.append(a);
}
return buff.append('}').toString();
}
/**
* Enclose a string with '(' and ')' if this is not yet done.
*
* @param s the string
* @return the enclosed string
*/
public static String enclose(String s) {
if (s.startsWith("(")) {
return s;
}
return "(" + s + ")";
}
/**
* Remove enclosing '(' and ')' if this text is enclosed.
*
* @param s the potentially enclosed string
* @return the string
*/
public static String unEnclose(String s) {
if (s.startsWith("(") && s.endsWith(")")) {
return s.substring(1, s.length() - 1);
}
return s;
}
/**
* Encode the string as an URL.
*
* @param s the string to encode
* @return the encoded string
*/
public static String urlEncode(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (Exception e) {
// UnsupportedEncodingException
throw DbException.convert(e);
}
}
/**
* Decode the URL to a string.
*
* @param encoded the encoded URL
* @return the decoded string
*/
public static String urlDecode(String encoded) {
int length = encoded.length();
byte[] buff = new byte[length];
int j = 0;
for (int i = 0; i < length; i++) {
char ch = encoded.charAt(i);
if (ch == '+') {
buff[j++] = ' ';
} else if (ch == '%') {
buff[j++] = (byte) Integer.parseInt(encoded.substring(i + 1, i + 3), 16);
i += 2;
} else {
if (SysProperties.CHECK) {
if (ch > 127 || ch < ' ') {
throw new IllegalArgumentException(
"Unexpected char " + (int) ch + " decoding " + encoded);
}
}
buff[j++] = (byte) ch;
}
}
return new String(buff, 0, j, StandardCharsets.UTF_8);
}
/**
* Split a string into an array of strings using the given separator. A null
* string will result in a null array, and an empty string in a zero element
* array.
*
* @param s the string to split
* @param separatorChar the separator character
* @param trim whether each element should be trimmed
* @return the array list
*/
public static String[] arraySplit(String s, char separatorChar, boolean trim) {
if (s == null) {
return null;
}
int length = s.length();
if (length == 0) {
return new String[0];
}
ArrayList list = New.arrayList();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == separatorChar) {
String e = buff.toString();
list.add(trim ? e.trim() : e);
buff.setLength(0);
} else if (c == '\\' && i < length - 1) {
buff.append(s.charAt(++i));
} else {
buff.append(c);
}
}
String e = buff.toString();
list.add(trim ? e.trim() : e);
return list.toArray(new String[0]);
}
/**
* Splits up a string where elements are separated by a specific
* character and return all elements.
* @param data the string to split up.
* @param ch the separator character.
* @return the list of elements.
*/
public static Vector split(String data, int ch) {
Vector elems = new Vector();
int pos = -1;
int i = 0;
while ((pos = data.indexOf(ch, i)) != -1) {
String elem = data.substring(i, pos);
elems.addElement(elem);
i = pos + 1;
}
elems.addElement(data.substring(i));
return elems;
}
/**
* Combine an array of strings to one array using the given separator
* character. A backslash and the separator character and escaped using a
* backslash.
*
* @param list the string array
* @param separatorChar the separator character
* @return the combined string
*/
public static String arrayCombine(String[] list, char separatorChar) {
StatementBuilder buff = new StatementBuilder();
for (String s : list) {
buff.appendExceptFirst(String.valueOf(separatorChar));
if (s == null) {
s = "";
}
for (int j = 0, length = s.length(); j < length; j++) {
char c = s.charAt(j);
if (c == '\\' || c == separatorChar) {
buff.append('\\');
}
buff.append(c);
}
}
return buff.toString();
}
/**
* Creates an XML attribute of the form name="value".
* A single space is prepended to the name,
* so that multiple attributes can be concatenated.
* @param name the attribute name
* @param value the attribute value
* @return the attribute
*/
public static String xmlAttr(String name, String value) {
return " " + name + "=\"" + xmlText(value) + "\"";
}
/**
* Create an XML node with optional attributes and content.
* The data is indented with 4 spaces if it contains a newline character.
*
* @param name the element name
* @param attributes the attributes (may be null)
* @param content the content (may be null)
* @return the node
*/
public static String xmlNode(String name, String attributes, String content) {
return xmlNode(name, attributes, content, true);
}
/**
* Create an XML node with optional attributes and content. The data is
* indented with 4 spaces if it contains a newline character and the indent
* parameter is set to true.
*
* @param name the element name
* @param attributes the attributes (may be null)
* @param content the content (may be null)
* @param indent whether to indent the content if it contains a newline
* @return the node
*/
public static String xmlNode(String name, String attributes,
String content, boolean indent) {
String start = attributes == null ? name : name + attributes;
if (content == null) {
return "<" + start + "/>\n";
}
if (indent && content.indexOf('\n') >= 0) {
content = "\n" + indent(content);
}
return "<" + start + ">" + content + "" + name + ">\n";
}
/**
* Indents a string with 4 spaces.
*
* @param s the string
* @return the indented string
*/
public static String indent(String s) {
return indent(s, 4, true);
}
/**
* Indents a string with spaces.
*
* @param s the string
* @param spaces the number of spaces
* @param newline append a newline if there is none
* @return the indented string
*/
public static String indent(String s, int spaces, boolean newline) {
StringBuilder buff = new StringBuilder(s.length() + spaces);
for (int i = 0; i < s.length();) {
for (int j = 0; j < spaces; j++) {
buff.append(' ');
}
int n = s.indexOf('\n', i);
n = n < 0 ? s.length() : n + 1;
buff.append(s, i, n);
i = n;
}
if (newline && !s.endsWith("\n")) {
buff.append('\n');
}
return buff.toString();
}
/**
* Escapes a comment.
* If the data contains '--', it is converted to '- -'.
* The data is indented with 4 spaces if it contains a newline character.
*
* @param data the comment text
* @return <!-- data -->
*/
public static String xmlComment(String data) {
int idx = 0;
while (true) {
idx = data.indexOf("--", idx);
if (idx < 0) {
break;
}
data = data.substring(0, idx + 1) + " " + data.substring(idx + 1);
}
// must have a space at the beginning and at the end,
// otherwise the data must not contain '-' as the first/last character
if (data.indexOf('\n') >= 0) {
return "\n";
}
return "\n";
}
/**
* Converts the data to a CDATA element.
* If the data contains ']]>', it is escaped as a text element.
*
* @param data the text data
* @return <![CDATA[data]]>
*/
public static String xmlCData(String data) {
if (data.contains("]]>")) {
return xmlText(data);
}
boolean newline = data.endsWith("\n");
data = "";
return newline ? data + "\n" : data;
}
/**
* Returns <?xml version="1.0"?>
* @return <?xml version="1.0"?>
*/
public static String xmlStartDoc() {
return "\n";
}
/**
* Escapes an XML text element.
*
* @param text the text data
* @return the escaped text
*/
public static String xmlText(String text) {
return xmlText(text, false);
}
/**
* Escapes an XML text element.
*
* @param text the text data
* @param escapeNewline whether to escape newlines
* @return the escaped text
*/
public static String xmlText(String text, boolean escapeNewline) {
int length = text.length();
StringBuilder buff = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char ch = text.charAt(i);
switch (ch) {
case '<':
buff.append("<");
break;
case '>':
buff.append(">");
break;
case '&':
buff.append("&");
break;
case '\'':
// ' is not valid in HTML
buff.append("'");
break;
case '\"':
buff.append(""");
break;
case '\r':
case '\n':
if (escapeNewline) {
buff.append("").
append(Integer.toHexString(ch)).
append(';');
} else {
buff.append(ch);
}
break;
case '\t':
buff.append(ch);
break;
default:
if (ch < ' ' || ch > 127) {
buff.append("").
append(Integer.toHexString(ch)).
append(';');
} else {
buff.append(ch);
}
}
}
return buff.toString();
}
/**
* Replace all occurrences of the before string with the after string. Unlike
* {@link String#replaceAll(String, String)} this method reads {@code before}
* and {@code after} arguments as plain strings and if {@code before} argument
* is an empty string this method returns original string {@code s}.
*
* @param s the string
* @param before the old text
* @param after the new text
* @return the string with the before string replaced
*/
public static String replaceAll(String s, String before, String after) {
int next = s.indexOf(before);
if (next < 0 || before.isEmpty()) {
return s;
}
StringBuilder buff = new StringBuilder(
s.length() - before.length() + after.length());
int index = 0;
while (true) {
buff.append(s, index, next).append(after);
index = next + before.length();
next = s.indexOf(before, index);
if (next < 0) {
buff.append(s, index, s.length());
break;
}
}
return buff.toString();
}
/**
* Enclose a string with double quotes. A double quote inside the string is
* escaped using a double quote.
*
* @param s the text
* @return the double quoted text
*/
public static String quoteIdentifier(String s) {
int length = s.length();
StringBuilder buff = new StringBuilder(length + 2);
buff.append('\"');
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c == '"') {
buff.append(c);
}
buff.append(c);
}
return buff.append('\"').toString();
}
/**
* Check if a String is null or empty (the length is null).
*
* @param s the string to check
* @return true if it is null or empty
*/
public static boolean isNullOrEmpty(String s) {
return s == null || s.length() == 0;
}
/**
* In a string, replace block comment marks with /++ .. ++/.
*
* @param sql the string
* @return the resulting string
*/
public static String quoteRemarkSQL(String sql) {
sql = replaceAll(sql, "*/", "++/");
return replaceAll(sql, "/*", "/++");
}
/**
* Pad a string. This method is used for the SQL function RPAD and LPAD.
*
* @param string the original string
* @param n the target length
* @param padding the padding string
* @param right true if the padding should be appended at the end
* @return the padded string
*/
public static String pad(String string, int n, String padding, boolean right) {
if (n < 0) {
n = 0;
}
if (n < string.length()) {
return string.substring(0, n);
} else if (n == string.length()) {
return string;
}
char paddingChar;
if (padding == null || padding.length() == 0) {
paddingChar = ' ';
} else {
paddingChar = padding.charAt(0);
}
StringBuilder buff = new StringBuilder(n);
n -= string.length();
if (right) {
buff.append(string);
}
for (int i = 0; i < n; i++) {
buff.append(paddingChar);
}
if (!right) {
buff.append(string);
}
return buff.toString();
}
/**
* Create a new char array and copy all the data. If the size of the byte
* array is zero, the same array is returned.
*
* @param chars the char array (may be null)
* @return a new char array
*/
public static char[] cloneCharArray(char[] chars) {
if (chars == null) {
return null;
}
int len = chars.length;
if (len == 0) {
return chars;
}
return Arrays.copyOf(chars, len);
}
/**
* Trim a character from a string.
*
* @param s the string
* @param leading if leading characters should be removed
* @param trailing if trailing characters should be removed
* @param sp what to remove (only the first character is used)
* or null for a space
* @return the trimmed string
*/
public static String trim(String s, boolean leading, boolean trailing,
String sp) {
char space = sp == null || sp.isEmpty() ? ' ' : sp.charAt(0);
int begin = 0, end = s.length();
if (leading) {
while (begin < end && s.charAt(begin) == space) {
begin++;
}
}
if (trailing) {
while (end > begin && s.charAt(end - 1) == space) {
end--;
}
}
// substring() returns self if start == 0 && end == length()
return s.substring(begin, end);
}
/**
* Get the string from the cache if possible. If the string has not been
* found, it is added to the cache. If there is such a string in the cache,
* that one is returned.
*
* @param s the original string
* @return a string with the same content, if possible from the cache
*/
public static String cache(String s) {
if (!SysProperties.OBJECT_CACHE) {
return s;
}
if (s == null) {
return s;
} else if (s.length() == 0) {
return "";
}
int hash = s.hashCode();
String[] cache = getCache();
if (cache != null) {
int index = hash & (SysProperties.OBJECT_CACHE_SIZE - 1);
String cached = cache[index];
if (cached != null) {
if (s.equals(cached)) {
return cached;
}
}
cache[index] = s;
}
return s;
}
/**
* Clear the cache. This method is used for testing.
*/
public static void clearCache() {
softCache = new SoftReference<>(null);
}
/**
* Convert a hex encoded string to a byte array.
*
* @param s the hex encoded string
* @return the byte array
*/
public static byte[] convertHexToBytes(String s) {
int len = s.length();
if (len % 2 != 0) {
throw DbException.get(ErrorCode.HEX_STRING_ODD_1, s);
}
len /= 2;
byte[] buff = new byte[len];
int mask = 0;
int[] hex = HEX_DECODE;
try {
for (int i = 0; i < len; i++) {
int d = hex[s.charAt(i + i)] << 4 | hex[s.charAt(i + i + 1)];
mask |= d;
buff[i] = (byte) d;
}
} catch (ArrayIndexOutOfBoundsException e) {
throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s);
}
if ((mask & ~255) != 0) {
throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s);
}
return buff;
}
/**
* Convert a byte array to a hex encoded string.
*
* @param value the byte array
* @return the hex encoded string
*/
public static String convertBytesToHex(byte[] value) {
return convertBytesToHex(value, value.length);
}
/**
* Convert a byte array to a hex encoded string.
*
* @param value the byte array
* @param len the number of bytes to encode
* @return the hex encoded string
*/
public static String convertBytesToHex(byte[] value, int len) {
char[] buff = new char[len + len];
char[] hex = HEX;
for (int i = 0; i < len; i++) {
int c = value[i] & 0xff;
buff[i + i] = hex[c >> 4];
buff[i + i + 1] = hex[c & 0xf];
}
return new String(buff);
}
/**
* Check if this string is a decimal number.
*
* @param s the string
* @return true if it is
*/
public static boolean isNumber(String s) {
if (s.length() == 0) {
return false;
}
for (char c : s.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
/**
* Append a zero-padded number to a string builder.
*
* @param buff the string builder
* @param length the number of characters to append
* @param positiveValue the number to append
*/
public static void appendZeroPadded(StringBuilder buff, int length,
long positiveValue) {
if (length == 2) {
if (positiveValue < 10) {
buff.append('0');
}
buff.append(positiveValue);
} else {
String s = Long.toString(positiveValue);
length -= s.length();
while (length > 0) {
buff.append('0');
length--;
}
buff.append(s);
}
}
/**
* Escape table or schema patterns used for DatabaseMetaData functions.
*
* @param pattern the pattern
* @return the escaped pattern
*/
public static String escapeMetaDataPattern(String pattern) {
if (pattern == null || pattern.length() == 0) {
return pattern;
}
return replaceAll(pattern, "\\", "\\\\");
}
}