/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kr.debop4j.core.tools;
import com.google.common.base.*;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.primitives.Chars;
import kr.debop4j.core.BinaryStringFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.*;
import static java.lang.String.format;
/**
* 문자열 처리를 위한 Utility Class 입니다.
*
* @author 배성혁 ( sunghyouk.bae@gmail.com )
* @since 12. 9. 12
*/
@Slf4j
public final class StringTool {
/**
* 멀티바이트 문자열을 바이트 배열로 변환 시에 선두번지에 접두사로 넣는 값입니다.
* 이 값이 있으면 꼭 UTF-8 으로 변환해야 한다는 뜻입니다.
*/
protected static final byte[] MULTI_BYTES_PREFIX = new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF };
/** The constant TRIMMING_STR. */
public static final String TRIMMING_STR = "...";
/** NULL 을 표현하는 문자열 */
public static final String NULL_STR = "<null>";
/** 빈 문자열 */
public static final String EMPTY_STR = "";
/** The constant COMMA String. */
public static final String COMMA_STR = ",";
/** The constant UTF8. */
public static final Charset UTF8 = Charsets.UTF_8;
private StringTool() { }
//region << isNull / isEmpty / isWhiteSpace / isMultiByteString >>
/**
* 문자열이 null 이면 true 를, null 이 아니면 false 를 반환한다.
*
* @param str 문자열
* @return the boolean
*/
public static boolean isNull(final String str) {
return (str == null);
}
/**
* 문자열이 null 이 아니라면 true를 반환하고, null 이라면 false를 반환한다.
*
* @param str 문자열
* @return the boolean
*/
public static boolean isNotNull(final String str) {
return (str != null);
}
/**
* Is empty.
*
* @param str 문자열
* @return the boolean
*/
public static boolean isEmpty(final String str) {
return isEmpty(str, false);
}
/**
* Is empty.
*
* @param str the str
* @param withTrim the with trim
* @return the boolean
*/
public static boolean isEmpty(final String str, final boolean withTrim) {
return isNull(str) || (((withTrim) ? str.trim() : str).length() == 0);
}
/**
* Is not empty.
*
* @param str 문자열
* @return the boolean
*/
public static boolean isNotEmpty(final String str) {
return !isEmpty(str);
}
/**
* Is not empty.
*
* @param str the str
* @param withTrim the with trim
* @return the boolean
*/
public static boolean isNotEmpty(final String str, final boolean withTrim) {
return !isEmpty(str, withTrim);
}
/**
* Is white space.
*
* @param str 문자열
* @return the boolean
*/
public static boolean isWhiteSpace(final String str) {
return isEmpty(str, true);
}
/**
* Is not white space.
*
* @param str 문자열
* @return the boolean
*/
public static boolean isNotWhiteSpace(final String str) {
return !isEmpty(str, true);
}
/**
* Is multi byte string.
*
* @param bytes the bytes
* @return the boolean
*/
public static boolean isMultiByteString(final byte[] bytes) {
if (bytes == null || bytes.length < MULTI_BYTES_PREFIX.length)
return false;
return Arrays.equals(MULTI_BYTES_PREFIX,
Arrays.copyOf(bytes, MULTI_BYTES_PREFIX.length));
}
/**
* 문자열이 멀티바이트 (한글,일어,중국어 등 2바이트 이상의 언어) 인가 확인한다.
*
* @param str 문자열
* @return 멀티바이트 언어라면 true, 아니면 false
*/
public static boolean isMultiByteString(final String str) {
if (isWhiteSpace(str))
return false;
try {
byte[] bytes = StringUtils.getBytesUsAscii(str.substring(0, Math.min(2, str.length())));
return isMultiByteString(bytes);
} catch (Exception e) {
log.error("멀티바이트 문자열인지 확인하는데 실패했습니다. str=" + ellipsisChar(str, 24), e);
return false;
}
}
/**
* Contains boolean.
*
* @param str the str
* @param subStr the sub str
* @return the boolean
*/
public static boolean contains(final String str, final String subStr) {
return str.contains(subStr);
}
//endregion
// region << ellipsis >>
/**
* Need ellipsis.
*
* @param str the str
* @param maxLength the max length
* @return the boolean
*/
public static boolean needEllipsis(final String str, final int maxLength) {
return isNotEmpty(str) && str.length() > maxLength;
}
/**
* Ellipsis char.
*
* @param str the str
* @param maxLength the max length
* @return the string
*/
public static String ellipsisChar(final String str, final int maxLength) {
if (isEmpty(str) || !needEllipsis(str, maxLength))
return str;
return str.substring(0, maxLength - TRIMMING_STR.length()) + TRIMMING_STR;
}
/**
* Ellipsis path.
*
* @param str the str
* @param maxLength the max length
* @return the string
*/
public static String ellipsisPath(final String str, final int maxLength) {
if (isEmpty(str) || !needEllipsis(str, maxLength))
return str;
int length = maxLength / 2;
StringBuilder builder = new StringBuilder();
builder.append(str.substring(0, length))
.append(TRIMMING_STR);
if (maxLength % 2 == 0)
builder.append(str.substring(str.length() - length));
else
builder.append(str.substring(str.length() - length - 1));
return builder.toString();
}
/**
* Ellipsis first.
*
* @param str the str
* @param maxLength the max length
* @return the string
*/
public static String ellipsisFirst(final String str, final int maxLength) {
if (isEmpty(str) || !needEllipsis(str, maxLength))
return str;
return TRIMMING_STR + str.substring(str.length() - maxLength);
}
//endregion
//region << encoding string - hex decimal / base64 >>
/**
* Int to hex.
*
* @param n the n
* @return the char
*/
public static char intToHex(final int n) {
if (n <= 9)
return (char) (n + 48);
return (char) (n - 10 + 97);
}
/**
* Hex to int.
*
* @param h the h
* @return the int
*/
public static int hexToInt(final char h) {
if (h >= '0' && h <= '9')
return h - '0';
if (h >= 'a' && h <= 'f')
return h - 'a' + 10;
if (h >= 'A' && h <= 'F')
return h - 'A' + 10;
return -1;
}
/**
* 16 진수로 표현된 데이타를 바이트 배열로 변환합니다.
*
* @param hexString 16진수로 표현된 문자열
* @return 16 진수 바이트 배열
*/
public static byte[] getBytesFromHexString(final String hexString) {
if (isEmpty(hexString))
return new byte[0];
try {
return Hex.decodeHex(hexString.toCharArray());
} catch (DecoderException e) {
log.error("16진수로 표현된 문자열을 바이트 배열로 변환하는데 실패했습니다.", e);
throw new RuntimeException(e);
}
}
/**
* 데이터를 16 진수로 표현합니다.
*
* @param bytes 바이트 배열
* @return 바이트를 16진수로 표현한 문자열
*/
public static String getHexString(final byte[] bytes) {
return Hex.encodeHexString(bytes);
}
/**
* Encode base 64.
*
* @param input the input
* @return the byte [ ]
*/
public static byte[] encodeBase64(final byte[] input) {
return Base64.encodeBase64URLSafe(input);
}
/**
* 문자열을 Base64 형식으로 인코딩을 합니다.
*
* @param input the input
* @return the string
*/
public static String encodeBase64String(final byte[] input) {
return StringUtils.newStringUtf8(encodeBase64(input));
}
/**
* Decode base 64.
*
* @param base64Data the base 64 data
* @return the byte [ ]
*/
public static byte[] decodeBase64(final byte[] base64Data) {
return Base64.decodeBase64(base64Data);
}
/**
* Decode base 64.
*
* @param base64String the base 64 string
* @return the byte [ ]
*/
public static byte[] decodeBase64(String base64String) {
return Base64.decodeBase64(base64String);
}
/**
* Decode base 64 string.
*
* @param base64Data the base 64 data
* @return the string
*/
public static String decodeBase64String(final byte[] base64Data) {
return StringUtils.newStringUtf8(Base64.decodeBase64(base64Data));
}
/**
* Decode base 64 string.
*
* @param base64String the base 64 string
* @return the string
*/
public static String decodeBase64String(final String base64String) {
return StringUtils.newStringUtf8(Base64.decodeBase64(base64String));
}
/**
* Get utf 8 bytes.
*
* @param str 문자열
* @return the byte [ ]
*/
public static byte[] getUtf8Bytes(final String str) {
return StringUtils.getBytesUtf8(str);
}
/**
* Gets utf 8 string.
*
* @param bytes the bytes
* @return the utf 8 string
*/
public static String getUtf8String(final byte[] bytes) {
return StringUtils.newStringUtf8(bytes);
}
/**
* Gets string.
*
* @param bytes the bytes
* @param charsetName the charset name
* @return the string
*/
public static String getString(final byte[] bytes, final String charsetName) {
if (isEmpty(charsetName))
return StringUtils.newStringUtf8(bytes);
return StringUtils.newString(bytes, charsetName);
}
/**
* Gets string from bytes.
*
* @param bytes the bytes
* @param format the format
* @return the string from bytes
*/
public static String getStringFromBytes(final byte[] bytes, final BinaryStringFormat format) {
return format == BinaryStringFormat.HexDecimal
? getHexString(bytes)
: encodeBase64String(bytes);
}
/**
* Get bytes from string.
*
* @param str the str
* @param format the format
* @return the byte [ ]
*/
public static byte[] getBytesFromString(final String str, final BinaryStringFormat format) {
try {
return format == BinaryStringFormat.HexDecimal
? getBytesFromHexString(str)
: decodeBase64(str);
} catch (Exception e) {
log.error("문자열로부터 Byte[] 를 얻는데 실패했습니다.", e);
throw new RuntimeException(e);
}
}
// endregion
// region << String manipulation >>
/**
* Delete char any.
*
* @param str the str
* @param chars the chars
* @return the string
*/
public static String deleteCharAny(final String str, final char... chars) {
if (isEmpty(str))
return str;
StringBuilder builder = new StringBuilder();
final List<Character> charList = Chars.asList(chars);
char[] strArray = str.toCharArray();
for (char c : strArray) {
if (!charList.contains(c))
builder.append(c);
}
return builder.toString();
}
/**
* Delete char.
*
* @param str the str
* @param chars the chars
* @return the string
*/
public static String deleteChar(final String str, final char[] chars) {
if (isEmpty(str))
return str;
StringBuilder builder = new StringBuilder();
final List<Character> charList = Chars.asList(chars);
char[] strArray = str.toCharArray();
for (char c : strArray) {
if (!charList.contains(c))
builder.append(c);
}
return builder.toString();
}
/**
* Delete char.
*
* @param str 문자열
* @param dc the dc
* @return the string
*/
public static String deleteChar(final String str, final char dc) {
if (isEmpty(str))
return str;
StringBuilder builder = new StringBuilder();
char[] strArray = str.toCharArray();
for (char c : strArray) {
if (c != dc)
builder.append(c);
}
return builder.toString();
}
/**
* Join string.
*
* @param items the items
* @return the string
*/
public static String join(final Object... items) {
return join(items, ",");
}
// public static String join(Object[] items) {
// return join(items, ",");
// }
/**
* Join string.
*
* @param items the items
* @param separator the separator
* @return the string
*/
public static String join(final Object[] items, final String separator) {
if (items == null || items.length == 0) return "";
List<Object> strings =
Lists.transform(Lists.newArrayList(items), new Function<Object, Object>() {
@Nullable
@Override
public Object apply(final @Nullable Object input) {
return (input != null) ? input : NULL_STR;
}
});
return Joiner.on(separator).join(strings);
}
/**
* Join string.
*
* @param strs the strs
* @return the string
*/
public static String join(final Iterable<?> strs) {
return join(strs, COMMA_STR);
}
/**
* Join string.
*
* @param items the items
* @param separator the separator
* @return the string
*/
public static String join(final Iterable<?> items, final String separator) {
if (items == null) return "";
try {
List<Object> strings =
Lists.transform(Lists.newArrayList(items), new Function<Object, Object>() {
@Nullable
@Override
public Object apply(@Nullable Object input) {
return (input != null) ? input : NULL_STR;
}
});
return Joiner.on(separator).join(strings);
} catch (Throwable t) {
return items.toString();
}
}
/**
* Quoted str.
*
* @param str 문자열
* @return the string
*/
public static String quotedStr(final String str) {
return isNull(str) ? NULL_STR : format("\'%s\'", str.replace("\'", "\'\'"));
}
/**
* Quoted str.
*
* @param str the str
* @param defaultStr the default str
* @return the string
*/
public static String quotedStr(final String str, final String defaultStr) {
return isWhiteSpace(str) ? quotedStr(defaultStr) : quotedStr(str);
}
/**
* Reverse string.
*
* @param str 문자열
* @return the string
*/
public static String reverse(final String str) {
if (isEmpty(str))
return str;
char[] cs = new char[str.length()];
for (int i = cs.length - 1, j = 0; i >= 0; i--, j++) {
cs[i] = str.charAt(j);
}
return new String(cs);
}
/**
* Replicate string.
*
* @param str 문자열
* @param n the n
* @return the string
*/
public static String replicate(final String str, int n) {
return Strings.repeat(str, n);
}
/**
* Split iterable.
*
* @param str the str
* @param separators the separators
* @return the iterable
*/
public static Iterable<String> split(final String str, final char... separators) {
if (isEmpty(str))
return Lists.newArrayList();
List<String> result = Lists.newArrayList();
List<Character> seps = Chars.asList(separators);
int length = str.length();
int startIndex = 0;
for (int i = 0; i < length; i++) {
if (seps.contains(str.charAt(i))) {
if (i - 1 - startIndex > 0) {
result.add(str.substring(startIndex, i - 1));
}
startIndex = i + 1;
}
}
return result;
}
/**
* Split list.
*
* @param str the str
* @param separators the separators
* @return the list
*/
public static List<String> split(final String str, final String... separators) {
return split(str, true, separators);
}
/**
* Split list.
*
* @param str the str
* @param ignoreCase the ignore case
* @param separators the separators
* @return the list
*/
public static List<String> split(final String str, boolean ignoreCase, final String... separators) {
return split(str, ignoreCase, true, separators);
}
/**
* 지정된 문자열을 구분자로 분리하여 배열로 반환합니다. @param str the str
*
* @param str the str
* @param ignoreCase the ignore case
* @param removeEmptyEntries the remove empty entries
* @param separators the separators
* @return the list
*/
public static List<String> split(final String str, boolean ignoreCase, boolean removeEmptyEntries, final String... separators) {
if (isEmpty(str))
return Lists.newArrayList();
List<String> result = Lists.newArrayList();
List<char[]> seps = Lists.newArrayList();
for (String sep : separators) {
if (ignoreCase) seps.add(sep.toLowerCase().toCharArray());
else seps.add(sep.toCharArray());
}
char[] strArray = str.toCharArray();
char[] strArray2 = (ignoreCase) ? str.toLowerCase().toCharArray() : str.toCharArray();
int startIndex = 0;
int prevIndex = 0;
while (startIndex < strArray.length) {
for (char[] sep : seps) {
if (Arrays.equals(sep, Arrays.copyOfRange(strArray2, startIndex, startIndex + sep.length))) {
String item = new String(Arrays.copyOfRange(strArray, prevIndex, startIndex));
if (!(removeEmptyEntries && StringTool.isWhiteSpace(item)))
result.add(item);
prevIndex = startIndex + sep.length;
startIndex = startIndex + sep.length;
}
}
startIndex++;
}
if (prevIndex < strArray.length - 1)
result.add(new String(Arrays.copyOfRange(strArray, prevIndex, strArray.length)));
// if (removeEmptyEntries) {
// int index = result.size() - 1;
// while (index >= 0) {
// if (StringTool.isWhiteSpace(result.get(index)))
// result.remove(index);
// else
// result.set(index, result.get(index).trim());
// index--;
// }
// }
return result;
}
// endregion
// region << WordCount, FirstOf, LastOf >>
/**
* Word count.
*
* @param str the str
* @param word the word
* @return the int
*/
public static int wordCount(final String str, final String word) {
return wordCount(str, word, false);
}
/**
* Word count.
*
* @param str the str
* @param word the word
* @param ignoreCase the ignore case
* @return the int
*/
public static int wordCount(final String str, final String word, final boolean ignoreCase) {
if (isEmpty(str) || isEmpty(word))
return 0;
final String targetStr = (ignoreCase) ? str.toUpperCase() : str;
final String searchWord = (ignoreCase) ? word.toUpperCase() : word;
int wordLength = searchWord.length();
int maxLength = targetStr.length() - wordLength;
int count = 0;
int index = 0;
while (index >= 0 && index <= maxLength) {
index = targetStr.indexOf(searchWord, index);
if (index > 0) {
count++;
index += wordLength;
}
}
return count;
}
/**
* Gets first line.
*
* @param str 문자열
* @return the first line
*/
public static String getFirstLine(final String str) {
if (isEmpty(str))
return str;
int index = str.indexOf('\n');
if (index > 0)
return str.substring(0, index - 1);
return str;
}
/**
* Gets between.
*
* @param text the text
* @param start the start
* @param end the end
* @return the between
*/
public static String getBetween(final String text, final String start, final String end) {
if (isEmpty(text))
return text;
int startIndex = 0;
if (!isEmpty(start)) {
int index = text.indexOf(start);
if (index > -1) {
startIndex = index + start.length();
}
}
int endIndex = text.length() - 1;
if (!isEmpty(end)) {
int index = text.lastIndexOf(end);
if (index > -1)
endIndex = index - 1;
}
if (endIndex > startIndex)
return text.substring(startIndex, endIndex);
return EMPTY_STR;
}
// endregion
// region << objectToString, listToString, mapToString >>
/**
* 객체의 필드 정보를 이용하여, 객체를 문자열로 표현합니다. @param obj the obj
*
* @param obj the obj
* @return the string
*/
public static String objectToString(final Object obj) {
if (obj == null)
return NULL_STR;
Objects.ToStringHelper helper = Objects.toStringHelper(obj);
try {
Class objClazz = obj.getClass();
Field[] fields = objClazz.getFields();
for (Field field : fields)
helper.add(field.getName(), field.get(obj));
} catch (IllegalAccessException ignored) {
log.warn("필드 정보를 얻는데 실패했습니다.", ignored);
}
return helper.toString();
}
/**
* {@link Iterable} 정보를 문자열로 표현합니다. @param items the items
*
* @param items the items
* @return the string
*/
public static <T> String listToString(final Iterable<? extends T> items) {
return items == null ? NULL_STR : join(items, COMMA_STR);
}
/**
* 객체 배열 정보를 문자열로 표현합니다.
*
* @param items the items
* @return the string
*/
public static String listToString(final Object[] items) {
return items == null || items.length == 0 ? NULL_STR : join(items, COMMA_STR);
}
/**
* {@link java.util.Map} 정보를 문자열로 표현합니다.
*
* @param map the map
* @return map 정보를 표현한 문자열
*/
public static String mapToString(final Map map) {
return mapToString(map, "{", COMMA_STR, "}");
}
public static String mapToString(final Map map, final String openStr, final String delimiter, final String closeStr) {
return map == null ? NULL_STR : openStr + join(mapToEntryList(map), delimiter) + closeStr;
}
/**
* {@link Map}의 항목들을 key=value 형태의 문자열의 컬렉션으로 빌드합니다.
*
* @param map Map
* @return Map 내용을 문자열로 표현한 컬렉션
*/
@SuppressWarnings("unchecked")
private static List<String> mapToEntryList(final Map map) {
List<String> list = new ArrayList<String>();
if (map == null) {
return list;
}
Set<Map.Entry> entrySet = (Set<Map.Entry>) map.entrySet();
for (Map.Entry entry : entrySet) {
list.add(entry.getKey() + "=" + entry.getValue());
}
return list;
}
// endregion
}