/* * 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 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.Supplier; import com.google.common.base.Suppliers; 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(); } /** 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); } /** 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) { return Joiner.on("\n").join(Arrays.asList(lines)); } /** 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.JAVA_LETTER_OR_DIGIT) .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 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 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; } /** 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 the tostring, and returning "null" for nulls * @deprecated since 0.7.0 use {@link #toStringMap(Map, String)} to remove ambiguity about how to handle null */ // NB previously the javadoc here was wrong, said it returned null not "null" @Deprecated public static Map<String, String> toStringMap(Map<?,?> map) { return toStringMap(map, "null"); } /** 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; } /** 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()); } }