blob: f48f1f430229015ae112dae83d4af8b89d911583 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.util.text;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.time.Time;
import com.google.common.base.CharMatcher;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
public class Strings {
/** The empty {@link String}. */
public static final String EMPTY = "";
/**
* Checks if the given string is null or is an empty string.
* Useful for pre-String.isEmpty. And useful for StringBuilder etc.
*
* @param s the String to check
* @return true if empty or null, false otherwise.
*
* @see #isNonEmpty(CharSequence)
* @see #isBlank(CharSequence)
* @see #isNonBlank(CharSequence)
*/
public static boolean isEmpty(CharSequence s) {
// Note guava has com.google.common.base.Strings.isNullOrEmpty(String),
// but that is just for String rather than CharSequence
return s == null || s.length()==0;
}
/**
* Checks if the given string is empty or only consists of whitespace.
*
* @param s the String to check
* @return true if blank, empty or null, false otherwise.
*
* @see #isEmpty(CharSequence)
* @see #isNonEmpty(CharSequence)
* @see #isNonBlank(CharSequence)
*/
public static boolean isBlank(CharSequence s) {
return isEmpty(s) || CharMatcher.whitespace().matchesAllOf(s);
}
/**
* The inverse of {@link #isEmpty(CharSequence)}.
*
* @param s the String to check
* @return true if non empty, false otherwise.
*
* @see #isEmpty(CharSequence)
* @see #isBlank(CharSequence)
* @see #isNonBlank(CharSequence)
*/
public static boolean isNonEmpty(CharSequence s) {
return !isEmpty(s);
}
/**
* The inverse of {@link #isBlank(CharSequence)}.
*
* @param s the String to check
* @return true if non blank, false otherwise.
*
* @see #isEmpty(CharSequence)
* @see #isNonEmpty(CharSequence)
* @see #isBlank(CharSequence)
*/
public static boolean isNonBlank(CharSequence s) {
return !isBlank(s);
}
/** @return a {@link Maybe} object which is absent if the argument {@link #isBlank(CharSequence)} */
public static <T extends CharSequence> Maybe<T> maybeNonBlank(T s) {
if (isNonBlank(s)) return Maybe.of(s);
return Maybe.absent();
}
public static <T extends CharSequence> T firstNonBlank(T ...s) {
if (s==null) return null;
for (T si: s) {
if (isNonBlank(si)) return si;
}
return null;
}
public static String firstNonNull(Object ...s) {
if (s==null) return null;
for (Object si: s) {
if (si!=null) return ""+si;
}
return null;
}
/** throws IllegalArgument if string not empty; cf. guava Preconditions.checkXxxx */
public static void checkNonEmpty(CharSequence s) {
if (s==null) throw new IllegalArgumentException("String must not be null");
if (s.length()==0) throw new IllegalArgumentException("String must not be empty");
}
/** throws IllegalArgument if string not empty; cf. guava Preconditions.checkXxxx */
public static void checkNonEmpty(CharSequence s, String message) {
if (isEmpty(s)) throw new IllegalArgumentException(message);
}
/**
* Removes suffix from the end of the string. Returns string if it does not end with suffix.
*/
public static String removeFromEnd(String string, String suffix) {
if (isEmpty(string)) {
return string;
} else if (!isEmpty(suffix) && string.endsWith(suffix)) {
return string.substring(0, string.length() - suffix.length());
} else {
return string;
}
}
/**
* As removeFromEnd, but repeats until all such suffixes are gone
*/
public static String removeAllFromEnd(String string, String... suffixes) {
if (isEmpty(string)) return string;
int index = string.length();
boolean anotherLoopNeeded = true;
while (anotherLoopNeeded) {
if (isEmpty(string)) return string;
anotherLoopNeeded = false;
for (String suffix : suffixes)
if (!isEmpty(suffix) && string.startsWith(suffix, index - suffix.length())) {
index -= suffix.length();
anotherLoopNeeded = true;
break;
}
}
return string.substring(0, index);
}
/**
* Removes prefix from the beginning of string. Returns string if it does not begin with prefix.
*/
public static String removeFromStart(String string, String prefix) {
if (isEmpty(string)) {
return string;
} else if (!isEmpty(prefix) && string.startsWith(prefix)) {
return string.substring(prefix.length());
} else {
return string;
}
}
/**
* As {@link #removeFromStart(String, String)}, repeating until all such prefixes are gone.
*/
public static String removeAllFromStart(String string, String... prefixes) {
int index = 0;
boolean anotherLoopNeeded = true;
while (anotherLoopNeeded) {
if (isEmpty(string)) return string;
anotherLoopNeeded = false;
for (String prefix : prefixes) {
if (!isEmpty(prefix) && string.startsWith(prefix, index)) {
index += prefix.length();
anotherLoopNeeded = true;
break;
}
}
}
return string.substring(index);
}
/**
* Removes everything after the marker, optionally also removing the marker.
* If marker not found the string is unchanged.
*/
public static String removeAfter(String input, String marker, boolean keepMarker) {
int i = input.indexOf(marker);
if (i==-1) return input;
return input.substring(0, i + (keepMarker ? marker.length() : 0));
}
/**
* Removes everything before the marker, optionally also removing the marker.
* If marker not found the string is unchanged.
*/
public static String removeBefore(String input, String marker, boolean keepMarker) {
int i = input.indexOf(marker);
if (i==-1) return input;
return input.substring(i + (keepMarker ? 0 : marker.length()));
}
/** convenience for {@link com.google.common.base.Joiner} */
public static String join(Iterable<? extends Object> list, String separator) {
if (list==null) return null;
boolean app = false;
StringBuilder out = new StringBuilder();
for (Object s: list) {
if (app) out.append(separator);
out.append(s);
app = true;
}
return out.toString();
}
/** convenience for {@link com.google.common.base.Joiner} */
public static String join(Object[] list, String separator) {
boolean app = false;
StringBuilder out = new StringBuilder();
for (Object s: list) {
if (app) out.append(separator);
out.append(s);
app = true;
}
return out.toString();
}
/** convenience for joining lines together */
public static String lines(String ...lines) {
if (lines==null) return null;
return lines(Arrays.asList(lines));
}
/** convenience for joining lines together */
public static String lines(Iterable<String> lines) {
if (lines==null) return null;
return Joiner.on("\n").join(lines);
}
public static String removeLines(String multiline, Predicate<CharSequence> patternToRemove) {
if (multiline==null) return null;
return lines(Iterables.filter(Arrays.asList(multiline.split("\n")), Predicates.not(patternToRemove)));
}
/** NON-REGEX - replaces all key->value entries from the replacement map in source (non-regex) */
@SuppressWarnings("rawtypes")
public static String replaceAll(String source, Map replacements) {
for (Object rr: replacements.entrySet()) {
Map.Entry r = (Map.Entry)rr;
source = replaceAllNonRegex(source, ""+r.getKey(), ""+r.getValue());
}
return source;
}
/** NON-REGEX replaceAll - see the better, explicitly named {@link #replaceAllNonRegex(String, String, String)}. */
public static String replaceAll(String source, String pattern, String replacement) {
return replaceAllNonRegex(source, pattern, replacement);
}
/**
* Replaces all instances in source, of the given pattern, with the given replacement
* (not interpreting any arguments as regular expressions).
* <p>
* This is actually the same as the very ambiguous {@link String#replace(CharSequence, CharSequence)},
* which does replace all, but not using regex like the similarly ambiguous {@link String#replaceAll(String, String)} as.
* Alternatively see {@link #replaceAllRegex(String, String, String)}.
*/
public static String replaceAllNonRegex(String source, String pattern, String replacement) {
if (source==null) return source;
StringBuilder result = new StringBuilder(source.length());
for (int i=0; i<source.length(); ) {
if (source.substring(i).startsWith(pattern)) {
result.append(replacement);
i += pattern.length();
} else {
result.append(source.charAt(i));
i++;
}
}
return result.toString();
}
/** REGEX replacement -- explicit method name for readability, doing same as {@link String#replaceAll(String, String)}. */
public static String replaceAllRegex(String source, String pattern, String replacement) {
return source.replaceAll(pattern, replacement);
}
/** Valid non alphanumeric characters for filenames. */
public static final String VALID_NON_ALPHANUM_FILE_CHARS = "-_.";
/**
* Returns a valid filename based on the input.
*
* A valid filename starts with the first alphanumeric character, then include
* all alphanumeric characters plus those in {@link #VALID_NON_ALPHANUM_FILE_CHARS},
* with any runs of invalid characters being replaced by {@literal _}.
*
* @throws NullPointerException if the input string is null.
* @throws IllegalArgumentException if the input string is blank.
*/
public static String makeValidFilename(String s) {
Preconditions.checkNotNull(s, "Cannot make valid filename from null string");
Preconditions.checkArgument(isNonBlank(s), "Cannot make valid filename from blank string");
return CharMatcher.anyOf(VALID_NON_ALPHANUM_FILE_CHARS).or(CharMatcher.javaLetterOrDigit())
.negate()
.trimAndCollapseFrom(s, '_');
}
/**
* A {@link CharMatcher} that matches valid Java identifier characters.
*
* @see Character#isJavaIdentifierPart(char)
*/
public static final CharMatcher IS_JAVA_IDENTIFIER_PART = CharMatcher.forPredicate(new Predicate<Character>() {
@Override
public boolean apply(@Nullable Character input) {
return input != null && Character.isJavaIdentifierPart(input);
}
});
/**
* Returns a valid Java identifier name based on the input.
*
* Removes certain characterss (like apostrophe), replaces one or more invalid
* characterss with {@literal _}, and prepends {@literal _} if the first character
* is only valid as an identifier part (not start).
* <p>
* The result is usually unique to s, though this isn't guaranteed, for example if
* all characters are invalid. For a unique identifier use {@link #makeValidUniqueJavaName(String)}.
*
* @see #makeValidUniqueJavaName(String)
*/
public static String makeValidJavaName(String s) {
if (s==null) return "__null";
if (s.length()==0) return "__empty";
String name = IS_JAVA_IDENTIFIER_PART.negate().collapseFrom(CharMatcher.is('\'').removeFrom(s), '_');
if (!Character.isJavaIdentifierStart(s.charAt(0))) return "_" + name;
return name;
}
/**
* Returns a unique valid java identifier name based on the input.
*
* Translated as per {@link #makeValidJavaName(String)} but with {@link String#hashCode()}
* appended where necessary to guarantee uniqueness.
*
* @see #makeValidJavaName(String)
*/
public static String makeValidUniqueJavaName(String s) {
String name = makeValidJavaName(s);
if (isEmpty(s) || IS_JAVA_IDENTIFIER_PART.matchesAllOf(s) || CharMatcher.is('\'').matchesNoneOf(s)) {
return name;
} else {
return name + "_" + s.hashCode();
}
}
/** @see {@link Identifiers#makeRandomId(int)} */
public static String makeRandomId(int l) {
return Identifiers.makeRandomId(l);
}
/** pads the string with 0's at the left up to len; no padding if i longer than len */
public static String makeZeroPaddedString(int i, int len) {
return makePaddedString(""+i, len, "0", "");
}
/** pads the string with "pad" at the left up to len; no padding if base longer than len */
public static String makePaddedString(String base, int len, String left_pad, String right_pad) {
String s = ""+(base==null ? "" : base);
while (s.length()<len) s=left_pad+s+right_pad;
return s;
}
public static void trimAll(String[] s) {
for (int i=0; i<s.length; i++)
s[i] = (s[i]==null ? "" : s[i].trim());
}
/** creates a string from a real number, with specified accuracy (more iff it comes for free, ie integer-part);
* switches to E notation if needed to fit within maxlen; can be padded left up too (not useful)
* @param x number to use
* @param maxlen maximum length for the numeric string, if possible (-1 to suppress)
* @param prec number of digits accuracy desired (more kept for integers)
* @param leftPadLen will add spaces at left if necessary to make string this long (-1 to suppress) [probably not usef]
* @return such a string
*/
public static String makeRealString(double x, int maxlen, int prec, int leftPadLen) {
return makeRealString(x, maxlen, prec, leftPadLen, 0.00000000001, true);
}
/** creates a string from a real number, with specified accuracy (more iff it comes for free, ie integer-part);
* switches to E notation if needed to fit within maxlen; can be padded left up too (not useful)
* @param x number to use
* @param maxlen maximum length for the numeric string, if possible (-1 to suppress)
* @param prec number of digits accuracy desired (more kept for integers)
* @param leftPadLen will add spaces at left if necessary to make string this long (-1 to suppress) [probably not usef]
* @param skipDecimalThreshhold if positive it will not add a decimal part if the fractional part is less than this threshhold
* (but for a value 3.00001 it would show zeroes, e.g. with 3 precision and positive threshhold <= 0.00001 it would show 3.00);
* if zero or negative then decimal digits are always shown
* @param useEForSmallNumbers whether to use E notation for numbers near zero (e.g. 0.001)
* @return such a string
*/
public static String makeRealString(double x, int maxlen, int prec, int leftPadLen, double skipDecimalThreshhold, boolean useEForSmallNumbers) {
if (x<0) return "-"+makeRealString(-x, maxlen, prec, leftPadLen);
NumberFormat df = DecimalFormat.getInstance();
//df.setMaximumFractionDigits(maxlen);
df.setMinimumFractionDigits(0);
//df.setMaximumIntegerDigits(prec);
df.setMinimumIntegerDigits(1);
df.setGroupingUsed(false);
String s;
if (x==0) {
if (skipDecimalThreshhold>0 || prec<=1) s="0";
else {
s="0.0";
while (s.length()<prec+1) s+="0";
}
} else {
// long bits= Double.doubleToLongBits(x);
// int s = ((bits >> 63) == 0) ? 1 : -1;
// int e = (int)((bits >> 52) & 0x7ffL);
// long m = (e == 0) ?
// (bits & 0xfffffffffffffL) << 1 :
// (bits & 0xfffffffffffffL) | 0x10000000000000L;
// //s*m*2^(e-1075);
int log = (int)Math.floor(Math.log10(x));
int numFractionDigits = (log>=prec ? 0 : prec-log-1);
if (numFractionDigits>0) { //need decimal digits
if (skipDecimalThreshhold>0) {
int checkFractionDigits = 0;
double multiplier = 1;
while (checkFractionDigits < numFractionDigits) {
if (Math.abs(x - Math.rint(x*multiplier)/multiplier)<skipDecimalThreshhold)
break;
checkFractionDigits++;
multiplier*=10;
}
numFractionDigits = checkFractionDigits;
}
df.setMinimumFractionDigits(numFractionDigits);
df.setMaximumFractionDigits(numFractionDigits);
} else {
//x = Math.rint(x);
df.setMaximumFractionDigits(0);
}
s = df.format(x);
if (maxlen>0 && s.length()>maxlen) {
//too long:
double signif = x/Math.pow(10,log);
if (s.indexOf(getDefaultDecimalSeparator())>=0) {
//have a decimal point; either we are very small 0.000001
//or prec is larger than maxlen
if (Math.abs(x)<1 && useEForSmallNumbers) {
//very small-- use alternate notation
s = makeRealString(signif, -1, prec, -1) + "E"+log;
} else {
//leave it alone, user error or E not wanted
}
} else {
//no decimal point, integer part is too large, use alt notation
s = makeRealString(signif, -1, prec, -1) + "E"+log;
}
}
}
if (leftPadLen>s.length())
return makePaddedString(s, leftPadLen, " ", "");
else
return s;
}
/** creates a string from a real number, with specified accuracy (more iff it comes for free, ie integer-part);
* switches to E notation if needed to fit within maxlen; can be padded left up too (not useful)
* @param x number to use
* @param maxlen maximum length for the numeric string, if possible (-1 to suppress)
* @param prec number of digits accuracy desired (more kept for integers)
* @param leftPadLen will add spaces at left if necessary to make string this long (-1 to suppress) [probably not usef]
* @return such a string
*/
public static String makeRealStringNearZero(double x, int maxlen, int prec, int leftPadLen) {
if (Math.abs(x)<0.0000000001) x=0;
NumberFormat df = DecimalFormat.getInstance();
//df.setMaximumFractionDigits(maxlen);
df.setMinimumFractionDigits(0);
//df.setMaximumIntegerDigits(prec);
df.setMinimumIntegerDigits(1);
df.setGroupingUsed(false);
String s;
if (x==0) {
if (prec<=1) s="0";
else {
s="0.0";
while (s.length()<prec+1) s+="0";
}
} else {
// long bits= Double.doubleToLongBits(x);
// int s = ((bits >> 63) == 0) ? 1 : -1;
// int e = (int)((bits >> 52) & 0x7ffL);
// long m = (e == 0) ?
// (bits & 0xfffffffffffffL) << 1 :
// (bits & 0xfffffffffffffL) | 0x10000000000000L;
// //s*m*2^(e-1075);
int log = (int)Math.floor(Math.log10(x));
int scale = (log>=prec ? 0 : prec-log-1);
if (scale>0) { //need decimal digits
double scale10 = Math.pow(10, scale);
x = Math.rint(x*scale10)/scale10;
df.setMinimumFractionDigits(scale);
df.setMaximumFractionDigits(scale);
} else {
//x = Math.rint(x);
df.setMaximumFractionDigits(0);
}
s = df.format(x);
if (maxlen>0 && s.length()>maxlen) {
//too long:
double signif = x/Math.pow(10,log);
if (s.indexOf('.')>=0) {
//have a decimal point; either we are very small 0.000001
//or prec is larger than maxlen
if (Math.abs(x)<1) {
//very small-- use alternate notation
s = makeRealString(signif, -1, prec, -1) + "E"+log;
} else {
//leave it alone, user error
}
} else {
//no decimal point, integer part is too large, use alt notation
s = makeRealString(signif, -1, prec, -1) + "E"+log;
}
}
}
if (leftPadLen>s.length())
return makePaddedString(s, leftPadLen, " ", "");
else
return s;
}
/** returns the first word (whitespace delimited text), or null if there is none (input null or all whitespace) */
public static String getFirstWord(String s) {
if (s==null) return null;
int start = 0;
while (start<s.length()) {
if (!Character.isWhitespace(s.charAt(start)))
break;
start++;
}
int end = start;
if (end >= s.length())
return null;
while (end<s.length()) {
if (Character.isWhitespace(s.charAt(end)))
break;
end++;
}
return s.substring(start, end);
}
/** returns the last word (whitespace delimited text), or null if there is none (input null or all whitespace) */
public static String getLastWord(String s) {
if (s==null) return null;
int end = s.length()-1;
while (end >= 0) {
if (!Character.isWhitespace(s.charAt(end)))
break;
end--;
}
int start = end;
if (start < 0)
return null;
while (start >= 0) {
if (Character.isWhitespace(s.charAt(start)))
break;
start--;
}
return s.substring(start+1, end+1);
}
/** returns the first word after the given phrase, or null if no such phrase;
* if the character immediately after the phrase is not whitespace, the non-whitespace
* sequence starting with that character will be returned */
public static String getFirstWordAfter(String context, String phrase) {
if (context==null || phrase==null) return null;
int index = context.indexOf(phrase);
if (index<0) return null;
return getFirstWord(context.substring(index + phrase.length()));
}
/**
* searches in context for the given phrase, and returns the <b>untrimmed</b> remainder of the first line
* on which the phrase is found
*/
public static String getRemainderOfLineAfter(String context, String phrase) {
if (context == null || phrase == null) return null;
int index = context.indexOf(phrase);
if (index < 0) return null;
int lineEndIndex = context.indexOf("\n", index);
if (lineEndIndex <= 0) {
return context.substring(index + phrase.length());
} else {
return context.substring(index + phrase.length(), lineEndIndex);
}
}
/** @deprecated use {@link Time#makeTimeStringRounded(long)} */
@Deprecated
public static String makeTimeString(long utcMillis) {
return Time.makeTimeStringRounded(utcMillis);
}
/** returns e.g. { "prefix01", ..., "prefix96" };
* see more functional NumericRangeGlobExpander for "prefix{01-96}"
*/
public static String[] makeArray(String prefix, int count) {
String[] result = new String[count];
int len = (""+count).length();
for (int i=1; i<=count; i++)
result[i-1] = prefix + makePaddedString("", len, "0", ""+i);
return result;
}
public static String[] combineArrays(String[] ...arrays) {
int totalLen = 0;
for (String[] array : arrays) {
if (array!=null) totalLen += array.length;
}
String[] result = new String[totalLen];
int i=0;
for (String[] array : arrays) {
if (array!=null) for (String s : array) {
result[i++] = s;
}
}
return result;
}
public static String toInitialCapOnly(String value) {
if (value==null || value.length()==0) return value;
return value.substring(0, 1).toUpperCase(Locale.ENGLISH) + value.substring(1).toLowerCase(Locale.ENGLISH);
}
public static String toInitialLowerCase(String value) {
if (value==null || value.length()==0) return value;
return value.substring(0, 1).toLowerCase(Locale.ENGLISH) + value.substring(1);
}
public static String reverse(String name) {
return new StringBuffer(name).reverse().toString();
}
public static boolean isLowerCase(String s) {
return s.toLowerCase().equals(s);
}
public static String makeRepeated(char c, int length) {
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; i++) {
result.append(c);
}
return result.toString();
}
public static String trim(String s) {
if (s==null) return null;
return s.trim();
}
public static String trimStart(String s) {
if (s==null) return null;
String v = (s+"x").trim();
return v.substring(0, v.length()-1);
}
public static String trimEnd(String s) {
if (s==null) return null;
return ("a"+s).trim().substring(1);
}
/** returns up to maxlen characters from the start of s */
public static String maxlen(String s, int maxlen) {
return maxlenWithEllipsis(s, maxlen, "");
}
/** as {@link #maxlenWithEllipsis(String, int, String) with "..." as the ellipsis */
public static String maxlenWithEllipsis(String s, int maxlen) {
return maxlenWithEllipsis(s, maxlen, "...");
}
/** as {@link #maxlenWithEllipsis(String, int) but replacing the last few chars with the given ellipsis */
public static String maxlenWithEllipsis(String s, int maxlen, String ellipsis) {
if (s==null) return null;
if (ellipsis==null) ellipsis="";
if (s.length()<=maxlen) return s;
return s.substring(0, Math.max(maxlen-ellipsis.length(), 0))+ellipsis;
}
/** returns toString of the object if it is not null, otherwise null */
public static String toString(Object o) {
return toStringWithValueForNull(o, null);
}
/** returns toString of the object if it is not null, otherwise the given value */
public static String toStringWithValueForNull(Object o, String valueIfNull) {
if (o==null) return valueIfNull;
return o.toString();
}
public static boolean containsLiteralIgnoreCase(CharSequence input, CharSequence fragment) {
if (input==null) return false;
if (isEmpty(fragment)) return true;
int lastValidStartPos = input.length()-fragment.length();
char f0u = Character.toUpperCase(fragment.charAt(0));
char f0l = Character.toLowerCase(fragment.charAt(0));
i: for (int i=0; i<=lastValidStartPos; i++) {
char ii = input.charAt(i);
if (ii==f0l || ii==f0u) {
for (int j=1; j<fragment.length(); j++) {
if (Character.toLowerCase(input.charAt(i+j))!=Character.toLowerCase(fragment.charAt(j)))
continue i;
}
return true;
}
}
return false;
}
public static boolean containsLiteral(CharSequence input, CharSequence fragment) {
if (input==null) return false;
if (isEmpty(fragment)) return true;
int lastValidStartPos = input.length()-fragment.length();
char f0 = fragment.charAt(0);
i: for (int i=0; i<=lastValidStartPos; i++) {
char ii = input.charAt(i);
if (ii==f0) {
for (int j=1; j<fragment.length(); j++) {
if (input.charAt(i+j)!=fragment.charAt(j))
continue i;
}
return true;
}
}
return false;
}
public static boolean containsAny(CharSequence input, CharSequence ...candidates) {
for (CharSequence candidate: candidates) {
if (containsLiteral(input, candidate)) return true;
}
return false;
}
/** Returns a size string using metric suffixes from {@link ByteSizeStrings#metric()}, e.g. 23.5MB */
public static String makeSizeString(long sizeInBytes) {
return ByteSizeStrings.metric().makeSizeString(sizeInBytes);
}
/** Returns a size string using ISO suffixes from {@link ByteSizeStrings#iso()}, e.g. 23.5MiB */
public static String makeISOSizeString(long sizeInBytes) {
return ByteSizeStrings.iso().makeSizeString(sizeInBytes);
}
/** Returns a size string using Java suffixes from {@link ByteSizeStrings#java()}, e.g. 23m */
public static String makeJavaSizeString(long sizeInBytes) {
return ByteSizeStrings.java().makeSizeString(sizeInBytes);
}
/** returns a configurable shortener */
public static StringShortener shortener() {
return new StringShortener();
}
public static Supplier<String> toStringSupplier(Object src) {
return Suppliers.compose(Functions.toStringFunction(), Suppliers.ofInstance(src));
}
/** wraps a call to {@link String#format(String, Object...)} in a toString, i.e. using %s syntax,
* useful for places where we want deferred evaluation
* (e.g. as message to {@link Preconditions} to skip concatenation when not needed) */
public static FormattedString format(String pattern, Object... args) {
return new FormattedString(pattern, args);
}
/** returns "s" if the argument is not 1, empty string otherwise; useful when constructing plurals */
public static String s(int count) {
return count==1 ? "" : "s";
}
/** as {@link #s(int)} based on size of argument */
public static String s(@Nullable Map<?,?> x) {
return s(x==null ? 0 : x.size());
}
/** as {@link #s(int)} based on size of argument */
public static String s(Iterable<?> x) {
if (x==null) return s(0);
return s(x.iterator());
}
/** as {@link #s(int)} based on size of argument */
public static String s(Iterator<?> x) {
int count = 0;
if (x==null || !x.hasNext()) {}
else {
x.next(); count++;
if (x.hasNext()) count++;
}
return s(count);
}
/** returns "ies" if the argument is not 1, "y" otherwise; useful when constructing plurals */
public static String ies(int count) {
return count==1 ? "y" : "ies";
}
/** as {@link #ies(int)} based on size of argument */
public static String ies(@Nullable Map<?,?> x) {
return ies(x==null ? 0 : x.size());
}
/** as {@link #ies(int)} based on size of argument */
public static String ies(Iterable<?> x) {
if (x==null) return ies(0);
return ies(x.iterator());
}
/** as {@link #ies(int)} based on size of argument */
public static String ies(Iterator<?> x) {
int count = 0;
if (x==null || !x.hasNext()) {}
else {
x.next(); count++;
if (x.hasNext()) count++;
}
return ies(count);
}
/** converts a map of any objects to a map of strings, using {@link Object#toString()},
* with the second argument used where a value (or key) is null */
public static Map<String, String> toStringMap(Map<?,?> map, String valueIfNull) {
if (map==null) return null;
Map<String,String> result = MutableMap.<String, String>of();
for (Map.Entry<?,?> e: map.entrySet()) {
result.put(toStringWithValueForNull(e.getKey(), valueIfNull), toStringWithValueForNull(e.getValue(), valueIfNull));
}
return result;
}
/** converts a list of any objects to a list of strings, using {@link Object#toString()},
* with the second argument used where an entry is null */
public static List<String> toStringList(List<?> list, String valueIfNull) {
if (list==null) return null;
List<String> result = MutableList.of();
for (Object v: list) result.add(toStringWithValueForNull(v, valueIfNull));
return result;
}
/**
* Tries to convert v to a list of strings.
* <p>
* If v is <code>null</code> then the method returns an empty immutable list.
* If v is {@link Iterable} or an <code>Object[]</code> then toString is called on its elements.
* If v is a {@link String} then a singleton list containing v is returned.
* Otherwise the method throws an {@link IllegalArgumentException}.
*/
public static List<String> toStringList(Object v) {
if (v == null) return ImmutableList.of();
List<String> result = Lists.newArrayList();
if (v instanceof Iterable) {
for (Object o : (Iterable<?>) v) {
result.add(o.toString());
}
} else if (v instanceof Object[]) {
for (int i = 0; i < ((Object[]) v).length; i++) {
result.add(((Object[]) v)[i].toString());
}
} else if (v instanceof String) {
result.add((String) v);
} else {
throw new IllegalArgumentException("Invalid type for List<String>: " + v + " of type " + v.getClass());
}
return result;
}
/** returns base repeated count times */
public static String repeat(String base, int count) {
if (base==null) return null;
StringBuilder result = new StringBuilder();
for (int i=0; i<count; i++)
result.append(base);
return result.toString();
}
/** returns comparator which compares based on length, with shorter ones first (and null before that);
* in event of a tie, it uses the toString order */
public static Ordering<String> lengthComparator() {
return Ordering.<Integer>natural().onResultOf(StringFunctions.length()).compound(Ordering.<String>natural()).nullsFirst();
}
public static boolean isMultiLine(String s) {
if (s==null) return false;
if (s.indexOf('\n')>=0 || s.indexOf('\r')>=0) return true;
return false;
}
public static String getFirstLine(String s) {
int idx = s.indexOf('\n');
if (idx==-1) return s;
return s.substring(0, idx);
}
/** looks for first section of text in following the prefix and, if present, before the suffix;
* null if the prefix is not present in the string, and everything after the prefix if suffix is not present in the string;
* if either prefix or suffix is null, it is treated as the start/end of the string */
public static String getFragmentBetween(String input, String prefix, String suffix) {
if (input==null) return null;
int index;
if (prefix!=null) {
index = input.indexOf(prefix);
if (index==-1) return null;
input = input.substring(index + prefix.length());
}
if (suffix!=null) {
index = input.indexOf(suffix);
if (index>=0) input = input.substring(0, index);
}
return input;
}
public static int getWordCount(String phrase, boolean respectQuotes) {
if (phrase==null) return 0;
phrase = phrase.trim();
if (respectQuotes)
return new QuotedStringTokenizer(phrase).remainderAsList().size();
else
return Collections.list(new StringTokenizer(phrase)).size();
}
public static char getDecimalSeparator(Locale locale) {
DecimalFormatSymbols dfs = new DecimalFormatSymbols(locale);
return dfs.getDecimalSeparator();
}
public static char getDefaultDecimalSeparator() {
return getDecimalSeparator(Locale.getDefault());
}
/** replaces each sequence of whitespace in the first string with the replacement in the second string */
public static String collapseWhitespace(String x, String whitespaceReplacement) {
if (x==null) return null;
return replaceAllRegex(x, "\\s+", whitespaceReplacement);
}
public static String toLowerCase(String value) {
if (value==null || value.length()==0) return value;
return value.toLowerCase(Locale.ENGLISH);
}
/**
* @return null if var is null or empty string, otherwise return var
*/
public static String emptyToNull(String var) {
if (isNonEmpty(var)) {
return var;
} else {
return null;
}
}
/** Returns canonicalized string from the given object, made "unique" by:
* <li> putting sets into the toString order
* <li> appending a hash code if it's longer than the max (and the max is bigger than 0) */
public static String toUniqueString(Object x, int optionalMax) {
if (x instanceof Iterable && !(x instanceof List)) {
// unsorted collections should have a canonical order imposed
MutableList<String> result = MutableList.of();
for (Object xi: (Iterable<?>)x) {
result.add(toUniqueString(xi, optionalMax));
}
Collections.sort(result);
x = result.toString();
}
if (x==null) return "{null}";
String xs = x.toString();
if (xs.length()<=optionalMax || optionalMax<=0) return xs;
return maxlenWithEllipsis(xs, optionalMax-8)+"/"+Integer.toHexString(xs.hashCode());
}
/**
* Parses a string as comma separated values and returns a list of the entries with whitespace removed.
* @param csv The "comma" separated values.
* @param separatorRegex Regex of separatorRegex.
*/
public static List<String> parseCsv(String csv, String separatorRegex) {
List<String> result = MutableList.of();
final String input = csv.trim();
if ("".equals(input)) {
return result;
}
for (String value: input.split(separatorRegex)) {
result.add(value.trim());
}
return result;
}
public static List<String> parseCsv(String csv) {
return parseCsv(csv, ",");
}
public static int countOccurrences(String phrase, char target) {
if (phrase == null) return 0;
int result = 0;
for (int i = 0; i < phrase.length(); i++) {
if (target == phrase.charAt(i)) {
result++;
}
}
return result;
}
public static int countOccurrences(String phrase, String target) {
if (phrase == null || isEmpty(target)) return 0;
int count = 0;
while (true) {
int index = phrase.indexOf(target);
if (index<0) return count;
count++;
phrase = phrase.substring(index+1);
}
}
/** indents every line in `body` by the given count of spaces */
public static String indent(int count, String body) {
return prefixAddedToEachLine(Strings.makePaddedString("", count, " ", ""), body);
}
/** prefixes every line in `body` (second argument) by the given string (first argument) */
public static String prefixAddedToEachLine(String prefix, String body) {
return prefixAddedToEachLine(prefix, body, false);
}
public static String prefixAddedToEachLine(String prefix, String body, boolean includeTrailingEmptyLines) {
if (body==null) return null;
return Arrays.stream(body.split("\n", includeTrailingEmptyLines ? -1 : 0)).map(s -> prefix + s).collect(Collectors.joining("\n"));
}
public static boolean containsLiteralAsWord(String context, String word) {
if (context==null || word==null) return false;
int i=-1;
while ((i = context.indexOf(word, i+1)) > -1) {
if (i==0 || !isAlphaOrDigit(context.charAt(i-1))) {
if (i+word.length()==context.length() || !isAlphaOrDigit(context.charAt(i+word.length()))) {
return true;
}
}
}
return false;
}
public static boolean isAlphaOrDigit(char c) {
return Character.isDigit(c) || Character.isAlphabetic(c);
}
}