package org.dcache.util;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.collect.Iterables.transform;
import static java.util.Arrays.asList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
*
* @author timur
*/
public final class Strings {
private static final Logger LOGGER =
LoggerFactory.getLogger( Strings.class);
private static final String ANSI_ESCAPE = "\u001b[";
private static final String[] ZERO_LENGTH_STRING_ARRAY=new String[0];
private static final String INFINITY = "infinity";
/**
* Splits a string into an array of strings using white space as dividers
* Substring surrounded by white space and single or double quotes is
* treated as a single indivisible string, and white space inside such
* substring is not used as a divider.
* So the following string
* <code> arg1 arg2 "this is an argument 3" 'arg 4'</code>
* will be split into String array
* <code> {"arg1","arg2","this is an argument 3", "arg 4"} </code>
* Quotes embedded into the strings of non white spaces
* (i.e. <code> aaa"bbb </code> or <code> ccc"ddd eee"fff </code> )
* are not supported at this time and the behavior is undefined.
* @param argumentString
* @return String array, a result of argument string split,
* zero length array of strings if the argument string is null
*/
public static String[] splitArgumentString(String argumentString) {
LOGGER.debug("splitting argument string {}",argumentString);
if(argumentString == null) {
return ZERO_LENGTH_STRING_ARRAY;
}
argumentString = argumentString.trim();
Pattern regex = Pattern.compile(
"\"([^\"]*)\""+ // first group matches string surronded
// by double quotes
"|'([^']*)'"+ // second group is for strings in single
// quotes
"|([^\\s]+)"); // last group matches everything else
// without the spaces
Matcher regexMatcher = regex.matcher(argumentString);
List<String> matchList = new ArrayList<>();
while(regexMatcher.find()) {
if (regexMatcher.group(1) != null) {
// Add double-quoted string without the quotes
String groupMatch= regexMatcher.group(1);
LOGGER.debug("first group matched [{}]",groupMatch);
matchList.add(groupMatch);
} else if (regexMatcher.group(2) != null) {
// Add single-quoted string without the quotes
String groupMatch= regexMatcher.group(2);
LOGGER.debug("second group matched [{}]",groupMatch);
matchList.add(groupMatch);
} else if (regexMatcher.group(3) != null) {
//everything else
String groupMatch= regexMatcher.group(3);
LOGGER.debug("third group matched [{}]",groupMatch);
matchList.add(groupMatch);
}
}
return matchList.toArray(new String[matchList.size()]);
}
public static int plainLength(String s)
{
int length = s.length();
int plainLength = length;
int i = s.indexOf(ANSI_ESCAPE);
while (i > -1) {
plainLength -= ANSI_ESCAPE.length();
i += ANSI_ESCAPE.length();
if (i < length) {
while (i + 1 < length && (s.charAt(i) < 64 || s.charAt(i) > 126)) {
i++;
plainLength--;
}
i++;
plainLength--;
}
i = s.indexOf(ANSI_ESCAPE, i);
}
return plainLength;
}
/**
* Locates the last occurrence of a white space character after fromIndex and before
* wrapLength characters, or the first occurrence of a white space character after
* fromIndex if there is no white space before wrapLength characters.
*
* ANSI escape sequences are considered to have zero width.
*/
private static int indexOfNextWrap(char[] chars, int fromIndex, int wrapLength)
{
int lastWrap = -1;
int max = fromIndex + wrapLength;
int length = chars.length;
for (int i = fromIndex; i < length && (i <= max || lastWrap == -1); i++) {
if (Character.isWhitespace(chars[i])) {
lastWrap = i;
} else if (chars[i] == 27 && i < length && chars[i + 1] == '[') {
i += 2;
max += 2;
for (;i < length && (chars[i] < 64 || chars[i] > 126); i++) {
max++;
}
max++;
}
}
return length <= max || lastWrap == -1 ? length : lastWrap;
}
/**
* Wraps a text to a particular width. Leading white space is
* repeated in front of every wrapped line.
*
* ANSI escape sequences are considered to have zero width.
*
* @param indent String to place at the beginning of each line
* @param str String to wrap
* @param wrapLength Width to wrap to excluding indent
* @return Wrapped string.
*/
public static String wrap(String indent, String str, int wrapLength)
{
int offset = 0;
StringBuilder out = new StringBuilder(str.length());
char[] chars = str.toCharArray();
int length = chars.length;
while (offset < length) {
int bop = offset; // beginning of paragraph
while (offset < length && chars[offset] == ' ') {
offset++;
}
int pil = offset - bop; // paragraph indentation length
int eop = offset; // end of paragraph
while (eop < length && chars[eop] != '\n') {
eop++;
}
int spaceToWrapAt;
while ((spaceToWrapAt = indexOfNextWrap(chars, offset, wrapLength - indent.length() - pil)) < eop) {
out.append(indent);
out.append(chars, bop, pil);
out.append(chars, offset, spaceToWrapAt - offset);
out.append('\n');
offset = spaceToWrapAt + 1;
// Skip leading spaces on next line
while (offset < length && chars[offset] == ' ') {
offset++;
}
}
out.append(indent).append(chars, bop, pil).append(chars, offset, eop - offset).append('\n');
offset = eop + 1;
}
return out.toString();
}
/**
* Convert a {@link Method} to a String signature. The provided {@link Character}
* {@code c} used as a delimiter in the resulting string.
* @param m method to get signature from
* @param c delimiter to use
* @return method's signature as a String
*/
public static String toStringSignature(Method m, Character c) {
StringBuilder sb = new StringBuilder();
sb.append(m.getName());
sb.append('(');
Joiner.on(c).appendTo(sb, transform(asList(m.getParameterTypes()), GET_SIMPLE_NAME));
sb.append(')');
return sb.toString();
}
/**
* Like Integer#parseInt, but parses "infinity" to Integer.MAX_VALUE.
*/
public static int parseInt(String s)
throws NumberFormatException
{
return s.equals(INFINITY) ? Integer.MAX_VALUE : Integer.parseInt(s);
}
/**
* Like Long#parseLong, but parses "infinity" to Long.MAX_VALUE.
*/
public static long parseLong(String s)
throws NumberFormatException
{
return s.equals(INFINITY) ? Long.MAX_VALUE : Long.parseLong(s);
}
/**
* Parses a string to a time value, converting from milliseconds to the specified unit. Parses
* "infinity" to Long.MAX_VALUE.
*/
public static long parseTime(String s, TimeUnit unit)
{
return s.equals(INFINITY) ? Long.MAX_VALUE : MILLISECONDS.convert(Long.parseLong(s), unit);
}
/**
* Returns a string representation of the specified object or the empty string
* for a null argument. In contrast to Object#toString, this method recognizes
* array arguments and returns a suitable string form.
*/
public static String toString(Object value)
{
if (value == null) {
return "";
} else if (value.getClass().isArray()) {
Class<?> componentType = value.getClass().getComponentType();
if (componentType == Boolean.TYPE) {
return Arrays.toString((boolean[]) value);
} else if (componentType == Byte.TYPE) {
return Arrays.toString((byte[]) value);
} else if (componentType == Character.TYPE) {
return Arrays.toString((char[]) value);
} else if (componentType == Double.TYPE) {
return Arrays.toString((double[]) value);
} else if (componentType == Float.TYPE) {
return Arrays.toString((float[]) value);
} else if (componentType == Integer.TYPE) {
return Arrays.toString((int[]) value);
} else if (componentType == Long.TYPE) {
return Arrays.toString((long[]) value);
} else if (componentType == Short.TYPE) {
return Arrays.toString((short[]) value);
} else {
return Arrays.deepToString((Object[]) value);
}
} else {
return value.toString();
}
}
/**
* Returns a string representation of the specified object or the empty string
* for a null argument. In contrast to Object#toString, this method recognizes
* array arguments and returns a suitable string form. In contrast to Strings#toString,
* the object arrays are split over multiple lines.
*/
public static String toMultilineString(Object value)
{
if (value == null) {
return "";
} else if (value.getClass().isArray()) {
Class<?> componentType = value.getClass().getComponentType();
if (componentType == Boolean.TYPE) {
return Arrays.toString((boolean[]) value);
} else if (componentType == Byte.TYPE) {
return Arrays.toString((byte[]) value);
} else if (componentType == Character.TYPE) {
return Arrays.toString((char[]) value);
} else if (componentType == Double.TYPE) {
return Arrays.toString((double[]) value);
} else if (componentType == Float.TYPE) {
return Arrays.toString((float[]) value);
} else if (componentType == Integer.TYPE) {
return Arrays.toString((int[]) value);
} else if (componentType == Long.TYPE) {
return Arrays.toString((long[]) value);
} else if (componentType == Short.TYPE) {
return Arrays.toString((short[]) value);
} else {
return Joiner.on('\n').join(transform(asList((Object[]) value), TO_STRING));
}
} else {
return value.toString();
}
}
public static final Function<Object, Object> TO_STRING = new Function<Object, Object>()
{
@Override
public Object apply(Object input)
{
return Strings.toString(input);
}
};
private static final Function<Class<?>, String> GET_SIMPLE_NAME = new Function<Class<?>, String>() {
@Override
public String apply(Class<?> c) {
return c.getSimpleName();
}
};
}