Strings.java
package no.motif;
import static no.motif.Base.all;
import static no.motif.Base.always;
import static no.motif.Base.alwaysThrow;
import static no.motif.Base.both;
import static no.motif.Base.equalTo;
import static no.motif.Base.exists;
import static no.motif.Base.not;
import static no.motif.Base.notNull;
import static no.motif.Base.when;
import static no.motif.Base.where;
import static no.motif.Chars.digit;
import static no.motif.Chars.letter;
import static no.motif.Chars.letterOrDigit;
import static no.motif.Chars.whitespace;
import static no.motif.Exceptions.asRuntimeException;
import static no.motif.Ints.add;
import static no.motif.Iterate.on;
import static no.motif.Singular.optional;
import static no.motif.f.Apply.argsReversed;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import no.motif.f.Apply;
import no.motif.f.Fn;
import no.motif.f.Fn2;
import no.motif.f.Predicate;
import no.motif.f.Predicate.Always;
import no.motif.f.base.FalseIfNull;
import no.motif.iter.SplitOnCharacter;
import no.motif.iter.SplitOnSubstring;
import no.motif.single.Optional;
/**
* Functions operating on {@link String strings}.
*/
public final class Strings {
/**
* Converts a string to a <code>int</code> value using {@link Integer#valueOf(String)}.
* If the string is <code>null</code>, 0 is yielded.
*/
public static final Fn<String, Integer> toInt = new Fn<String, Integer>() {
@Override public Integer $(String numeric) { return numeric != null ? Integer.valueOf(numeric) : 0; }};
/**
* Converts a string to a <code>long</code> value using {@link Long#valueOf(String)}.
* If the string is <code>null</code>, 0 is yielded.
*/
public static final Fn<String, Long> toLong = new Fn<String, Long>() {
@Override public Long $(String numeric) { return numeric != null ? Long.valueOf(numeric) : 0; }};
/**
* Converts a string to a <code>double</code> value using {@link Double#valueOf(String)}.
* If the string is <code>null</code>, 0 is yielded.
*/
public static final Fn<String, Double> toDouble = new Fn<String, Double>() {
@Override public Double $(String decimalValue) { return decimalValue != null ? Double.valueOf(decimalValue) : 0; }};
/**
* Splits a string into characters.
*/
public static final Fn<String, Iterable<Character>> toChars = new Fn<String, Iterable<Character>>() {
@Override public Iterable<Character> $(String value) { return Iterate.on(value); }};
/**
* Yields the bytes of a String.
* @see String#getBytes()
*/
public static final Fn<String, Iterable<Byte>> bytes = when(notNull, new Fn<String, Iterable<Byte>>() {
@Override public Iterable<Byte> $(String s) {
try {
return Iterate.on(s.getBytes(Implicits.getEncoding()));
} catch (UnsupportedEncodingException e) {
throw asRuntimeException(e);
}
}
}).orElse(Iterate.<Byte>none());
/**
* A blank string is either <code>null</code>, empty, or
* all characters {@link Chars#whitespace are whitespace}.
*/
public static final Predicate<String> blank = where(toChars, all(whitespace));
/**
* A nonblank string has at least one character, and must contain at least
* one character which is {@link Chars#whitespace not whitespace}.
*/
public static final Predicate<String> nonblank = where(toChars, exists(not(whitespace)));
/**
* A numeric string must have at least one character, and all of them
* must be {@link Chars#digit digits}.
*/
public static final Predicate<String> numeric = nonblankAllChars(digit);
/**
* Alphanumeric strings are at least one character, and all
* {@link Chars#digit digits} and/or {@link Chars#letter letters}.
*/
public static final Predicate<String> alphanumeric = nonblankAllChars(letterOrDigit);
/**
* Alphabetic strings are at least one character, and all {@link Chars#letter letters}.
*/
public static final Predicate<String> alphabetic = nonblankAllChars(letter);
/**
* Predicate verifying that all characters in a string satifies a given predicate.
*
* @param valid The predicate evaluating all characters in a string.
*/
public static final Predicate<String> allChars(Predicate<Character> valid) {
return where(toChars, all(valid)); }
/**
* Predicate verifying that strings are {@link #nonblank not blank} and
* each char satisfies a given predicate.
*
* @param valid
*/
public static final Predicate<String> nonblankAllChars(Predicate<Character> valid) {
return both(nonblank).and(allChars(valid)); }
/**
* Trims a string, removing all leading and trailing whitespace.
*/
public static final Fn<String, String> trimmed = when(notNull, new Fn<String, String>() {
@Override public String $(String s) { return s.trim(); }});
/**
* Convert a string to {@link String#toLowerCase() lower case}.
*/
public static final Fn<String, String> lowerCased = when(notNull, new Fn<String, String>() {
@Override public String $(String s) { return s.toLowerCase(Implicits.getLocale()); }});
/**
* Convert a string to {@link String#toUpperCase() upper case}
*/
public static final Fn<String, String> upperCased = when(notNull, new Fn<String, String>() {
@Override public String $(String s) { return s.toUpperCase(Implicits.getLocale()); }});
/**
* Gives the length of a string, i.e. the amount of characters. <code>null</code>
* yields length 0.
*/
public static final Fn<String, Integer> length = when(notNull, new Fn<String, Integer>() {
@Override public Integer $(String s) { return s.length(); }}).orElse(0);
/**
* Evaluate if strings are of a exact length.
* <code>null<code>s are considered to have length zero.
*/
public static final Predicate<String> hasLength(int exactLength) { return hasLength(equalTo(exactLength)); }
/**
* Evaluate if strings have accepted lengths.
* <code>null<code>s are considered to have length zero.
*
* @param accepted The predicate evaluating accepted length.
* @return The predicate evaluating string length.
*/
public static final Predicate<String> hasLength(Predicate<? super Integer> accepted) { return where(length, accepted); }
/**
* Concatenate a string with the {@link Object#toString() string representation}
* of an arbitrary object, i.e. <em>reduces</em> two strings to one.
*/
public static final Fn2<Object, Object, String> concat = new Fn2<Object, Object, String>() {
String emptyIfNull(Object o) { return (o != null? String.valueOf(o) : ""); }
@Override public String $(Object acc, Object c) { return emptyIfNull(acc) + emptyIfNull(c); }};
/**
* Yields the given string with its characters in reversed order.
*/
public static final Fn<String, String> reversed = when(notNull, new Fn<String, String>() {
@Override public String $(String s) { return new StringBuilder(s).reverse().toString(); }});
/**
* Determines if a substring is present in a string. A string never contains <code>null</code>,
* nor does <code>null</code> contain any substring.
*
* @param charSequence The substring to find.
* @return The predicate.
*/
public static Predicate<String> contains(final CharSequence charSequence) {
return charSequence == null ? Always.<String>no() : new FalseIfNull<String>() {
@Override protected boolean orElse(String string) { return string.contains(charSequence); }}; }
/**
* Determines if a string starts with a given prefix string.
*
* @param prefix The prefix.
* @return The predicate.
*/
public static Predicate<String> startsWith(final String prefix) {
return prefix == null ? Always.<String>no() : new FalseIfNull<String>() {
@Override protected boolean orElse(String string) { return string.startsWith(prefix); }}; }
/**
* Determines if a string ends with a given suffix string.
*
* @param suffix The suffix.
* @return The predicate.
*/
public static Predicate<String> endsWith(final String suffix) {
return suffix == null ? Always.<String>no() : new FalseIfNull<String>() {
@Override protected boolean orElse(String string) { return string.endsWith(suffix); }}; }
/**
* Does a {@link String#matches(String) regular expression match} on strings.
*
* @param regex The regular expression to use for matching.
* @return the predicate.
*/
public static Predicate<String> matches(final String regex) {
return regex == null ? Always.<String>no() : new FalseIfNull<String>() {
@Override protected boolean orElse(String string) { return string.matches(regex); }};}
/**
* Create a new strings by prepending a prefix.
* @param prefix the prefix to prepend
*/
public static Fn<Object, String> prepend(String prefix) { return Apply.partially(concat).of(prefix); }
/**
* Create a new strings by appending a suffix.
* @param suffix the suffix to append
*/
public static Fn<Object, String> append(String suffix) { return Apply.partially(argsReversed(concat)).of(suffix); }
/**
* Extract substring from strings. As
* this function simply delegates to {@link String#substring(int, int)}, it
* may throw an {@link IndexOutOfBoundsException} if the given indexes
* are invalid.
*
* @param beginIndex The index of the first character to include.
* @param endIndex The index to end the extraction.
*/
public static Fn<String, String> substring(final int beginIndex, final int endIndex) {
if (beginIndex < 0 || endIndex < 0)
return alwaysThrow(new StringIndexOutOfBoundsException(
"Cannot extract substring using negative index. " +
"beginIndex: " + beginIndex + ", endIndex: " + endIndex));
return substring(always(beginIndex), always(endIndex));
}
public static Fn<String, String> substring(final Fn<? super String, Integer> beginIndex, final Fn<? super String, Integer> endIndex) {
return when(notNull, new Fn<String, String>() { @Override public String $(String s) {
return optional(s).map(before(endIndex)).map(from(beginIndex)).orNull();
}});
}
/**
* Get at most a given amount of the first characters of strings.
* If the string is shorter than the amount, the original string is returned.
*/
public static Fn<String, String> first(final int charAmount) {
return when(notNull, new Fn<String, String>() { @Override public String $(String s) {
return (charAmount > s.length()) ? s : s.substring(0, charAmount);
}}).orElse("");
}
/**
* Get at most a given amount of the last characters of strings.
* If the string is shorter than the amount, the original string is returned.
*/
public static Fn<String, String> last(final int charAmount) {
return when(notNull, new Fn<String, String>() { @Override public String $(String s) {
return (charAmount > s.length()) ? s : s.substring(s.length() - charAmount, s.length());
}}).orElse("");
}
/**
* Insert strings in between a prefix and a suffix.
*
* @param prefix The prefix to appear before the string.
* @param suffix The suffix to appear after the string
*/
public static Fn<Object,String> inBetween(final String prefix, final String suffix) {
return Base.first(prepend(prefix)).then(append(suffix)); }
/**
* Repeats a string a given amount of times.
*
* @param times The amount of times to repeat the string.
*/
public static Fn<String, String> repeat(final int times) { return when(notNull, new Fn<String, String>() {
@Override public String $(String s) { return on((Object) s).repeat(times).join(); }}); }
/**
* Repeats a string, insterting given separator, a given amount of times.
*
* @param times The amount of times to repeat the string.
* @param separator The separator string to insert between the repeating strings.
*/
public static Fn<String, String> repeat(final int times, final String separator) {
return when(notNull, new Fn<String, String>() {
@Override public String $(String s) { return on((Object) s).repeat(times).join(separator); }}); }
/**
* Inside strings, searches for the <em>first</em> occurence of a substring, and yields the
* rest of the string <em>after</em> the substring occurence, not including the
* substring itself.
* <p>
* Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
* </p>
* <p>
* If the substring is not found (or it is <code>null</code>), then <code>null</code> is returned.
* </p><p>
* If the substring is the empty string, the original string is returned.
* </p>
*
* @param substring the substring to search for.
*/
public static Fn<String, String> after(final String substring) {
if (substring == null || substring.isEmpty()) return NOP.fn();
return from(Base.first(indexOf(substring)).then(add(substring.length())));
}
/**
* Inside strings, searches for the <em>last</em> occurence of a substring, and yields the
* rest of the string <em>after</em> the substring occurence, not including the
* substring itself.
* <p>
* Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
* </p>
* <p>
* If the substring is not found, or if it is <code>null</code>, then <code>null</code> is returned.
* If the substring is empty, the empty string is returned.
* </p>
*
* @param substring the substring to search for.
*/
public static Fn<String, String> afterLast(final String substring) {
if (substring == null || substring.isEmpty()) return when(notNull, Base.<String, String, String>always(""));
return from(Base.first(lastIndexOf(substring)).then(add(substring.length())));
}
/**
* Yields substrings <em>from</em> a position index.
* @see #from(Fn)
*/
public static Fn<String, String> from(int index) { return from(always(index)); }
/**
* Yields substrings <em>from</em> a position index. If the given index
* {@link Fn} yields <code>null</code>, then <code>null</code> is returned.
* If a positive index out of bounds with the length of the string
* is yielded, the empty string is returned.
*
* <p>A negative index value is invalid and will throw an {@link StringIndexOutOfBoundsException}.</p>
* <p>Passing the <code>null</code>-String always yields <code>null</code>.</p>
*
* @param index The {@link Fn} to resolve the index.
*/
public static Fn<String, String> from(final Fn<? super String, Integer> index) {
return when(notNull, new Fn<String, String>() {
@Override
public String $(String s) {
Integer idx = index.$(s);
if (idx == null) return null;
if (idx >= s.length()) return "";
return s.substring(idx);
}});
}
/**
* Inside strings, searches for the <em>first</em> occurence of a substring, and yields the
* the string <em>before</em> the substring occurence, not including the
* substring itself.
* <p>
* Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
* </p>
* <p>
* If the substring is <code>null</code>, the original string is returned.
* If the substring is not found, <code>null</code> is returned.
* </p><p>
* If the substring is the empty string, or found from the beginning of the string, the empty string is returned.
* </p>
*
* @param substring the substring to search for.
*/
public static Fn<String, String> before(final String substring) {
return substring == null ? NOP.<String>fn() : before(indexOf(substring));
}
/**
* Inside strings, searches for the <em>last</em> occurence of a substring, and yields the
* the string <em>before</em> the substring occurence, not including the
* substring itself.
* <p>
* Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
* </p><p>
* If the substring is <code>null</code> or empty, the original string is returned.
* If the substring is not found, <code>null</code> is returned.
* </p>
*
* @param substring the substring to search for.
*/
public static Fn<String, String> beforeLast(final String substring) {
return substring != null ? before(lastIndexOf(substring)) : NOP.<String>fn();
}
/**
* Yields substrings <em>before</em> a position index.
* @see #before(Fn)
*/
public static Fn<String, String> before(int index) { return before(always(index)); }
/**
* Yields substrings <em>before</em> a position index. If the given index
* {@link Fn} yields <code>null</code>, or a positive index out of bounds
* with the length of the string, the original string is returned.
*
* <p>A negative index is invalid and will throw an {@link StringIndexOutOfBoundsException}.</p>
* <p>Passing the <code>null</code>-String always yields <code>null</code>.</p>
*
* @param index The {@link Fn} to resolve the index.
*/
public static Fn<String, String> before(final Fn<? super String, Integer> index) {
return when(notNull, new Fn<String, String>() {
@Override
public String $(String s) {
Integer idx = index.$(s);
if (idx == null) return null;
if (idx >= s.length()) return s;
return s.substring(0, idx);
}});
}
/**
* Yield the string _between_ two substrings. The substrings will be the first possible
* matches, which means <code>between("x", "y")</code> applied to the string
* <code>"xxyy"</code> will yield <code>"x"</code>.
*
* @param openSubstring
* @param closeSubstring
*/
public static Fn<String,String> between(String openSubstring, String closeSubstring) {
if (openSubstring == null || closeSubstring == null) return always(null);
return Base.first(after(openSubstring)).then(before(closeSubstring));
}
/**
* Yield the string _between_ two outermost substrings. This means
* <code>betweenOuter("x", "y")</code> applied to the string
* <code>"xxyy"</code> will yield <code>"xy"</code>.
*
* @param openSubstring
* @param closeSubstring
*/
public static Fn<String,String> betweenOuter(String openSubstring, String closeSubstring) {
if (openSubstring == null || closeSubstring == null) return always(null);
return Base.first(after(openSubstring)).then(beforeLast(closeSubstring));
}
/**
* Yield all strings occurring _between_ two substrings.
*
* @param openSubstring
* @param closeSubstring
*/
public static Fn<String, Iterable<String>> allBetween(final String openSubstring, final String closeSubstring) {
if (openSubstring == null || closeSubstring == null) return always((Iterable<String>) Collections.<String>emptySet());
if ("".equals(openSubstring) && "".equals(closeSubstring)) return alwaysThrow(new IllegalArgumentException(
"Extracting all strings between two empty strings would yield an infinite amount of empty strings!"));
return when(notNull, new Fn<String, Iterable<String>>() {
final Fn<String, String> firstSubstring = between(openSubstring, closeSubstring);
@Override
public Iterable<String> $(String s) {
Optional<String> original = optional(s);
Optional<String> first = original.map(firstSubstring);
if (!first.isSome()) return Collections.emptySet();
Optional<String> rest = original.map(nonblank, after(first.map(inBetween(openSubstring, closeSubstring)).orElse(null)));
return first.append(this.$(rest.orElse("")));
}});
}
/**
* Yields index position of first occurence of a <code>char</code>, or <code>null</code>
* if the <code>char</code> cannot be found.
*
* @see String#indexOf(int)
*/
public static Fn<String, Integer> indexOf(final char c) {
return when(notNull, new Fn<String, Integer>() {
@Override public Integer $(String s) {
int index = s.indexOf(c);
return index >= 0 ? index : null;
}});
}
/**
* Yields index position of last occurence of a <code>char</code>, or <code>null</code>
* if the <code>char</code> cannot be found.
*
* @see String#indexOf(int)
*/
public static Fn<String, Integer> lastIndexOf(final char c) { return when(notNull, new Fn<String, Integer>() {
@Override public Integer $(String s) {
int index = s.lastIndexOf(c);
return index >= 0 ? index : null;
}});
}
/**
* Yields index position of first occurence of a substring, or <code>null</code>
* if the substring cannot be found.
*
* @see String#indexOf(String)
*/
public static Fn<String, Integer> indexOf(final String substring) {
if (substring == null) return always(null);
return when(notNull, new Fn<String, Integer>() {
@Override public Integer $(String s) {
int index = s.indexOf(substring);
return index >= 0 ? index : null;
}});
}
/**
* Yields index position of last occurence of a substring, or <code>null</code>
* if the substring cannot be found.
*
* @see String#indexOf(String)
*/
public static Fn<String, Integer> lastIndexOf(final String substring) {
if (substring == null) return always(null);
return when(notNull, new Fn<String, Integer>() {
@Override public Integer $(String s) {
int index = s.lastIndexOf(substring);
return index >= 0 ? index : null;
}});
}
/**
* Split a string on each occurence of a substring.
* The substring is not included in the resulting strings, and any
* consecutive substring are treated as one delimiter instance.
*
* @param substring
*/
public static Fn<String, Iterable<String>> splittingOn(final String substring) {
return new Fn<String, Iterable<String>>() {
@Override public Iterable<String> $(String string) {
return new SplitOnSubstring(string, substring);
}};
}
/**
* Split a string on each occurence of a <code>char</code> delimiter.
* The splitting character is not included in the resulting strings, and any
* consecutive occurrences of the character are treated as one delimiter instance.
*
* @param character the delimiter character
*/
public static Fn<String, Iterable<String>> splittingOn(char character) { return splittingOn(equalTo(character)); }
/**
* Split a string into several on any character passing the given
* <code>Character</code> predicate.
* The characters accepted by the predicate is not included in the
* resulting strings, and any consecutive accepted characters are
* treated as one delimiter instance.
*
* @param character the predicate which decides if a character is
* a delimiter.
*/
public static Fn<String, Iterable<String>> splittingOn(final Predicate<? super Character> character) {
return new Fn<String, Iterable<String>>() {
@Override public Iterable<String> $(String string) {
return new SplitOnCharacter(string, character);
}};
}
private Strings() {}
}