Strings.java

  1. package no.motif;

  2. import static no.motif.Base.all;
  3. import static no.motif.Base.always;
  4. import static no.motif.Base.alwaysThrow;
  5. import static no.motif.Base.both;
  6. import static no.motif.Base.equalTo;
  7. import static no.motif.Base.exists;
  8. import static no.motif.Base.not;
  9. import static no.motif.Base.notNull;
  10. import static no.motif.Base.when;
  11. import static no.motif.Base.where;
  12. import static no.motif.Chars.digit;
  13. import static no.motif.Chars.letter;
  14. import static no.motif.Chars.letterOrDigit;
  15. import static no.motif.Chars.whitespace;
  16. import static no.motif.Exceptions.asRuntimeException;
  17. import static no.motif.Ints.add;
  18. import static no.motif.Iterate.on;
  19. import static no.motif.Singular.optional;
  20. import static no.motif.f.Apply.argsReversed;

  21. import java.io.UnsupportedEncodingException;
  22. import java.util.Collections;

  23. import no.motif.f.Apply;
  24. import no.motif.f.Fn;
  25. import no.motif.f.Fn2;
  26. import no.motif.f.Predicate;
  27. import no.motif.f.Predicate.Always;
  28. import no.motif.f.base.FalseIfNull;
  29. import no.motif.iter.SplitOnCharacter;
  30. import no.motif.iter.SplitOnSubstring;
  31. import no.motif.single.Optional;

  32. /**
  33.  * Functions operating on {@link String strings}.
  34.  */
  35. public final class Strings {

  36.     /**
  37.      * Converts a string to a <code>int</code> value using {@link Integer#valueOf(String)}.
  38.      * If the string is <code>null</code>, 0 is yielded.
  39.      */
  40.     public static final Fn<String, Integer> toInt = new Fn<String, Integer>() {
  41.         @Override public Integer $(String numeric) { return numeric != null ? Integer.valueOf(numeric) : 0; }};


  42.     /**
  43.      * Converts a string to a <code>long</code> value using {@link Long#valueOf(String)}.
  44.      * If the string is <code>null</code>, 0 is yielded.
  45.      */
  46.     public static final Fn<String, Long> toLong = new Fn<String, Long>() {
  47.         @Override public Long $(String numeric) { return numeric != null ? Long.valueOf(numeric) : 0; }};


  48.     /**
  49.      * Converts a string to a <code>double</code> value using {@link Double#valueOf(String)}.
  50.      * If the string is <code>null</code>, 0 is yielded.
  51.      */
  52.     public static final Fn<String, Double> toDouble = new Fn<String, Double>() {
  53.         @Override public Double $(String decimalValue) { return decimalValue != null ? Double.valueOf(decimalValue) : 0; }};


  54.     /**
  55.      * Splits a string into characters.
  56.      */
  57.     public static final Fn<String, Iterable<Character>> toChars = new Fn<String, Iterable<Character>>() {
  58.         @Override public Iterable<Character> $(String value) { return Iterate.on(value); }};


  59.     /**
  60.      * Yields the bytes of a String.
  61.      * @see String#getBytes()
  62.      */
  63.     public static final Fn<String, Iterable<Byte>> bytes = when(notNull, new Fn<String, Iterable<Byte>>() {
  64.         @Override public Iterable<Byte> $(String s) {
  65.             try {
  66.                 return Iterate.on(s.getBytes(Implicits.getEncoding()));
  67.             } catch (UnsupportedEncodingException e) {
  68.                 throw asRuntimeException(e);
  69.             }
  70.         }
  71.     }).orElse(Iterate.<Byte>none());




  72.     /**
  73.      * A blank string is either <code>null</code>, empty, or
  74.      * all characters {@link Chars#whitespace are whitespace}.
  75.      */
  76.     public static final Predicate<String> blank = where(toChars, all(whitespace));


  77.     /**
  78.      * A nonblank string has at least one character, and must contain at least
  79.      * one character which is {@link Chars#whitespace not whitespace}.
  80.      */
  81.     public static final Predicate<String> nonblank = where(toChars, exists(not(whitespace)));


  82.     /**
  83.      * A numeric string must have at least one character, and all of them
  84.      * must be {@link Chars#digit digits}.
  85.      */
  86.     public static final Predicate<String> numeric = nonblankAllChars(digit);


  87.     /**
  88.      * Alphanumeric strings are at least one character, and all
  89.      * {@link Chars#digit digits} and/or {@link Chars#letter letters}.
  90.      */
  91.     public static final Predicate<String> alphanumeric = nonblankAllChars(letterOrDigit);


  92.     /**
  93.      * Alphabetic strings are at least one character, and all {@link Chars#letter letters}.
  94.      */
  95.     public static final Predicate<String> alphabetic = nonblankAllChars(letter);


  96.     /**
  97.      * Predicate verifying that all characters in a string satifies a given predicate.
  98.      *
  99.      * @param valid The predicate evaluating all characters in a string.
  100.      */
  101.     public static final Predicate<String> allChars(Predicate<Character> valid) {
  102.         return where(toChars, all(valid)); }

  103.     /**
  104.      * Predicate verifying that strings are {@link #nonblank not blank} and
  105.      * each char satisfies a given predicate.
  106.      *
  107.      * @param valid
  108.      */
  109.     public static final Predicate<String> nonblankAllChars(Predicate<Character> valid) {
  110.         return both(nonblank).and(allChars(valid)); }


  111.     /**
  112.      * Trims a string, removing all leading and trailing whitespace.
  113.      */
  114.     public static final Fn<String, String> trimmed = when(notNull, new Fn<String, String>() {
  115.         @Override public String $(String s) { return s.trim(); }});


  116.     /**
  117.      * Convert a string to {@link String#toLowerCase() lower case}.
  118.      */
  119.     public static final Fn<String, String> lowerCased = when(notNull, new Fn<String, String>() {
  120.         @Override public String $(String s) { return s.toLowerCase(Implicits.getLocale()); }});


  121.     /**
  122.      * Convert a string to {@link String#toUpperCase() upper case}
  123.      */
  124.     public static final Fn<String, String> upperCased = when(notNull, new Fn<String, String>() {
  125.         @Override public String $(String s) { return s.toUpperCase(Implicits.getLocale()); }});


  126.     /**
  127.      * Gives the length of a string, i.e. the amount of characters. <code>null</code>
  128.      * yields length 0.
  129.      */
  130.     public static final Fn<String, Integer> length = when(notNull, new Fn<String, Integer>() {
  131.         @Override public Integer $(String s) { return s.length(); }}).orElse(0);


  132.     /**
  133.      * Evaluate if strings are of a exact length.
  134.      * <code>null<code>s are considered to have length zero.
  135.      */
  136.     public static final Predicate<String> hasLength(int exactLength) { return hasLength(equalTo(exactLength)); }


  137.     /**
  138.      * Evaluate if strings have accepted lengths.
  139.      * <code>null<code>s are considered to have length zero.
  140.      *
  141.      * @param accepted The predicate evaluating accepted length.
  142.      * @return The predicate evaluating string length.
  143.      */
  144.     public static final Predicate<String> hasLength(Predicate<? super Integer> accepted) { return where(length, accepted); }


  145.     /**
  146.      * Concatenate a string with the {@link Object#toString() string representation}
  147.      * of an arbitrary object, i.e. <em>reduces</em> two strings to one.
  148.      */
  149.     public static final Fn2<Object, Object, String> concat = new Fn2<Object, Object, String>() {
  150.         String emptyIfNull(Object o) { return (o != null? String.valueOf(o) : ""); }
  151.         @Override public String $(Object acc, Object c) { return emptyIfNull(acc) + emptyIfNull(c); }};



  152.     /**
  153.      * Yields the given string with its characters in reversed order.
  154.      */
  155.     public static final Fn<String, String> reversed = when(notNull, new Fn<String, String>() {
  156.         @Override public String $(String s) { return new StringBuilder(s).reverse().toString(); }});




  157.     /**
  158.      * Determines if a substring is present in a string. A string never contains <code>null</code>,
  159.      * nor does <code>null</code> contain any substring.
  160.      *
  161.      * @param charSequence The substring to find.
  162.      * @return The predicate.
  163.      */
  164.     public static Predicate<String> contains(final CharSequence charSequence) {
  165.         return charSequence == null ? Always.<String>no() : new FalseIfNull<String>() {
  166.         @Override protected boolean orElse(String string) { return string.contains(charSequence); }}; }


  167.     /**
  168.      * Determines if a string starts with a given prefix string.
  169.      *
  170.      * @param prefix The prefix.
  171.      * @return The predicate.
  172.      */
  173.     public static Predicate<String> startsWith(final String prefix) {
  174.         return prefix == null ? Always.<String>no() : new FalseIfNull<String>() {
  175.         @Override protected boolean orElse(String string) { return string.startsWith(prefix); }}; }


  176.     /**
  177.      * Determines if a string ends with a given suffix string.
  178.      *
  179.      * @param suffix The suffix.
  180.      * @return The predicate.
  181.      */
  182.     public static Predicate<String> endsWith(final String suffix) {
  183.         return suffix == null ? Always.<String>no() : new FalseIfNull<String>() {
  184.         @Override protected boolean orElse(String string) { return string.endsWith(suffix); }}; }


  185.     /**
  186.      * Does a {@link String#matches(String) regular expression match} on strings.
  187.      *
  188.      * @param regex The regular expression to use for matching.
  189.      * @return the predicate.
  190.      */
  191.     public static Predicate<String> matches(final String regex) {
  192.         return regex == null ? Always.<String>no() : new FalseIfNull<String>() {
  193.         @Override protected boolean orElse(String string) { return string.matches(regex); }};}


  194.     /**
  195.      * Create a new strings by prepending a prefix.
  196.      * @param prefix the prefix to prepend
  197.      */
  198.     public static Fn<Object, String> prepend(String prefix) { return Apply.partially(concat).of(prefix); }


  199.     /**
  200.      * Create a new strings by appending a suffix.
  201.      * @param suffix the suffix to append
  202.      */
  203.     public static Fn<Object, String> append(String suffix) { return Apply.partially(argsReversed(concat)).of(suffix); }


  204.     /**
  205.      * Extract substring from strings. As
  206.      * this function simply delegates to {@link String#substring(int, int)}, it
  207.      * may throw an {@link IndexOutOfBoundsException} if the given indexes
  208.      * are invalid.
  209.      *
  210.      * @param beginIndex The index of the first character to include.
  211.      * @param endIndex The index to end the extraction.
  212.      */
  213.     public static Fn<String, String> substring(final int beginIndex, final int endIndex) {
  214.         if (beginIndex < 0 || endIndex < 0)
  215.             return alwaysThrow(new StringIndexOutOfBoundsException(
  216.                     "Cannot extract substring using negative index. " +
  217.                     "beginIndex: " + beginIndex + ", endIndex: " + endIndex));
  218.         return substring(always(beginIndex), always(endIndex));
  219.     }

  220.     public static Fn<String, String> substring(final Fn<? super String, Integer> beginIndex, final Fn<? super String, Integer> endIndex) {
  221.         return when(notNull, new Fn<String, String>() { @Override public String $(String s) {
  222.             return optional(s).map(before(endIndex)).map(from(beginIndex)).orNull();
  223.         }});
  224.     }


  225.     /**
  226.      * Get at most a given amount of the first characters of strings.
  227.      * If the string is shorter than the amount, the original string is returned.
  228.      */
  229.     public static Fn<String, String> first(final int charAmount) {
  230.         return when(notNull, new Fn<String, String>() { @Override public String $(String s) {
  231.                     return (charAmount > s.length()) ? s : s.substring(0, charAmount);
  232.                 }}).orElse("");
  233.     }


  234.     /**
  235.      * Get at most a given amount of the last characters of strings.
  236.      * If the string is shorter than the amount, the original string is returned.
  237.      */
  238.     public static Fn<String, String> last(final int charAmount) {
  239.         return when(notNull, new Fn<String, String>() { @Override public String $(String s) {
  240.                     return (charAmount > s.length()) ? s : s.substring(s.length() - charAmount, s.length());
  241.                 }}).orElse("");
  242.     }


  243.     /**
  244.      * Insert strings in between a prefix and a suffix.
  245.      *
  246.      * @param prefix The prefix to appear before the string.
  247.      * @param suffix The suffix to appear after the string
  248.      */
  249.     public static Fn<Object,String> inBetween(final String prefix, final String suffix) {
  250.         return Base.first(prepend(prefix)).then(append(suffix)); }


  251.     /**
  252.      * Repeats a string a given amount of times.
  253.      *
  254.      * @param times The amount of times to repeat the string.
  255.      */
  256.     public static Fn<String, String> repeat(final int times) { return when(notNull, new Fn<String, String>() {
  257.         @Override public String $(String s) { return on((Object) s).repeat(times).join(); }}); }


  258.     /**
  259.      * Repeats a string, insterting given separator, a given amount of times.
  260.      *
  261.      * @param times The amount of times to repeat the string.
  262.      * @param separator The separator string to insert between the repeating strings.
  263.      */
  264.     public static Fn<String, String> repeat(final int times, final String separator) {
  265.         return when(notNull, new Fn<String, String>() {
  266.             @Override public String $(String s) { return on((Object) s).repeat(times).join(separator); }}); }



  267.     /**
  268.      * Inside strings, searches for the <em>first</em> occurence of a substring, and yields the
  269.      * rest of the string <em>after</em> the substring occurence, not including the
  270.      * substring itself.
  271.      * <p>
  272.      * Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
  273.      * </p>
  274.      * <p>
  275.      * If the substring is not found (or it is <code>null</code>), then <code>null</code> is returned.
  276.      * </p><p>
  277.      * If the substring is the empty string, the original string is returned.
  278.      * </p>
  279.      *
  280.      * @param substring the substring to search for.
  281.      */
  282.     public static Fn<String, String> after(final String substring) {
  283.         if (substring == null || substring.isEmpty()) return NOP.fn();
  284.         return from(Base.first(indexOf(substring)).then(add(substring.length())));
  285.     }


  286.     /**
  287.      * Inside strings, searches for the <em>last</em> occurence of a substring, and yields the
  288.      * rest of the string <em>after</em> the substring occurence, not including the
  289.      * substring itself.
  290.      * <p>
  291.      * Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
  292.      * </p>
  293.      * <p>
  294.      * If the substring is not found, or if it is <code>null</code>, then <code>null</code> is returned.
  295.      * If the substring is empty, the empty string is returned.
  296.      * </p>
  297.      *
  298.      * @param substring the substring to search for.
  299.      */
  300.     public static Fn<String, String> afterLast(final String substring) {
  301.         if (substring == null || substring.isEmpty()) return when(notNull, Base.<String, String, String>always(""));
  302.         return from(Base.first(lastIndexOf(substring)).then(add(substring.length())));
  303.     }



  304.     /**
  305.      * Yields substrings <em>from</em> a position index.
  306.      * @see #from(Fn)
  307.      */
  308.     public static Fn<String, String> from(int index) { return from(always(index)); }



  309.     /**
  310.      * Yields substrings <em>from</em> a position index. If the given index
  311.      * {@link Fn} yields <code>null</code>, then <code>null</code> is returned.
  312.      * If a positive index out of bounds with the length of the string
  313.      * is yielded, the empty string is returned.
  314.      *
  315.      * <p>A negative index value is invalid and will throw an {@link StringIndexOutOfBoundsException}.</p>
  316.      * <p>Passing the <code>null</code>-String always yields <code>null</code>.</p>
  317.      *
  318.      * @param index The {@link Fn} to resolve the index.
  319.      */
  320.     public static Fn<String, String> from(final Fn<? super String, Integer> index) {
  321.         return when(notNull, new Fn<String, String>() {
  322.             @Override
  323.             public String $(String s) {
  324.                 Integer idx = index.$(s);
  325.                 if (idx == null) return null;
  326.                 if (idx >= s.length()) return "";
  327.                 return s.substring(idx);
  328.             }});
  329.     }





  330.     /**
  331.      * Inside strings, searches for the <em>first</em> occurence of a substring, and yields the
  332.      * the string <em>before</em> the substring occurence, not including the
  333.      * substring itself.
  334.      * <p>
  335.      * Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
  336.      * </p>
  337.      * <p>
  338.      * If the substring is <code>null</code>, the original string is returned.
  339.      * If the substring is not found, <code>null</code> is returned.
  340.      * </p><p>
  341.      * If the substring is the empty string, or found from the beginning of the string, the empty string is returned.
  342.      * </p>
  343.      *
  344.      * @param substring the substring to search for.
  345.      */
  346.     public static Fn<String, String> before(final String substring) {
  347.         return substring == null ? NOP.<String>fn() : before(indexOf(substring));
  348.     }


  349.     /**
  350.      * Inside strings, searches for the <em>last</em> occurence of a substring, and yields the
  351.      * the string <em>before</em> the substring occurence, not including the
  352.      * substring itself.
  353.      * <p>
  354.      * Passing <code>null</code> to the {@link Fn} always yields <code>null</code>
  355.      * </p><p>
  356.      * If the substring is <code>null</code> or empty, the original string is returned.
  357.      * If the substring is not found, <code>null</code> is returned.
  358.      * </p>
  359.      *
  360.      * @param substring the substring to search for.
  361.      */
  362.     public static Fn<String, String> beforeLast(final String substring) {
  363.         return substring != null ? before(lastIndexOf(substring)) : NOP.<String>fn();
  364.     }


  365.     /**
  366.      * Yields substrings <em>before</em> a position index.
  367.      * @see #before(Fn)
  368.      */
  369.     public static Fn<String, String> before(int index) { return before(always(index)); }


  370.     /**
  371.      * Yields substrings <em>before</em> a position index. If the given index
  372.      * {@link Fn} yields <code>null</code>, or a positive index out of bounds
  373.      * with the length of the string, the original string is returned.
  374.      *
  375.      * <p>A negative index is invalid and will throw an {@link StringIndexOutOfBoundsException}.</p>
  376.      * <p>Passing the <code>null</code>-String always yields <code>null</code>.</p>
  377.      *
  378.      * @param index The {@link Fn} to resolve the index.
  379.      */
  380.     public static Fn<String, String> before(final Fn<? super String, Integer> index) {
  381.         return when(notNull, new Fn<String, String>() {
  382.             @Override
  383.             public String $(String s) {
  384.                 Integer idx = index.$(s);
  385.                 if (idx == null) return null;
  386.                 if (idx >= s.length()) return s;
  387.                 return s.substring(0, idx);
  388.             }});
  389.     }


  390.     /**
  391.      * Yield the string _between_ two substrings. The substrings will be the first possible
  392.      * matches, which means <code>between("x", "y")</code> applied to the string
  393.      * <code>"xxyy"</code> will yield <code>"x"</code>.
  394.      *
  395.      * @param openSubstring
  396.      * @param closeSubstring
  397.      */
  398.     public static Fn<String,String> between(String openSubstring, String closeSubstring) {
  399.         if (openSubstring == null || closeSubstring == null) return always(null);
  400.         return Base.first(after(openSubstring)).then(before(closeSubstring));
  401.     }


  402.     /**
  403.      * Yield the string _between_ two outermost substrings. This means
  404.      * <code>betweenOuter("x", "y")</code> applied to the string
  405.      * <code>"xxyy"</code> will yield <code>"xy"</code>.
  406.      *
  407.      * @param openSubstring
  408.      * @param closeSubstring
  409.      */
  410.     public static Fn<String,String> betweenOuter(String openSubstring, String closeSubstring) {
  411.         if (openSubstring == null || closeSubstring == null) return always(null);
  412.         return Base.first(after(openSubstring)).then(beforeLast(closeSubstring));
  413.     }


  414.     /**
  415.      * Yield all strings occurring _between_ two substrings.
  416.      *
  417.      * @param openSubstring
  418.      * @param closeSubstring
  419.      */
  420.     public static Fn<String, Iterable<String>> allBetween(final String openSubstring, final String closeSubstring) {
  421.         if (openSubstring == null || closeSubstring == null) return always((Iterable<String>) Collections.<String>emptySet());
  422.         if ("".equals(openSubstring) && "".equals(closeSubstring)) return alwaysThrow(new IllegalArgumentException(
  423.                 "Extracting all strings between two empty strings would yield an infinite amount of empty strings!"));
  424.         return when(notNull, new Fn<String, Iterable<String>>() {
  425.             final Fn<String, String> firstSubstring = between(openSubstring, closeSubstring);
  426.             @Override
  427.             public Iterable<String> $(String s) {
  428.                 Optional<String> original = optional(s);
  429.                 Optional<String> first = original.map(firstSubstring);
  430.                 if (!first.isSome()) return Collections.emptySet();
  431.                 Optional<String> rest = original.map(nonblank, after(first.map(inBetween(openSubstring, closeSubstring)).orElse(null)));
  432.                 return first.append(this.$(rest.orElse("")));
  433.             }});
  434.     }



  435.     /**
  436.      * Yields index position of first occurence of a <code>char</code>, or <code>null</code>
  437.      * if the <code>char</code> cannot be found.
  438.      *
  439.      * @see String#indexOf(int)
  440.      */
  441.     public static Fn<String, Integer> indexOf(final char c) {
  442.         return when(notNull, new Fn<String, Integer>() {
  443.             @Override public Integer $(String s) {
  444.                 int index = s.indexOf(c);
  445.                 return index >= 0 ? index : null;
  446.             }});
  447.     }


  448.     /**
  449.      * Yields index position of last occurence of a <code>char</code>, or <code>null</code>
  450.      * if the <code>char</code> cannot be found.
  451.      *
  452.      * @see String#indexOf(int)
  453.      */
  454.     public static Fn<String, Integer> lastIndexOf(final char c) { return when(notNull, new Fn<String, Integer>() {
  455.         @Override public Integer $(String s) {
  456.             int index = s.lastIndexOf(c);
  457.             return index >= 0 ? index : null;
  458.         }});
  459.     }


  460.     /**
  461.      * Yields index position of first occurence of a substring, or <code>null</code>
  462.      * if the substring cannot be found.
  463.      *
  464.      * @see String#indexOf(String)
  465.      */
  466.     public static Fn<String, Integer> indexOf(final String substring) {
  467.         if (substring == null) return always(null);
  468.         return when(notNull, new Fn<String, Integer>() {
  469.             @Override public Integer $(String s) {
  470.                 int index = s.indexOf(substring);
  471.                 return index >= 0 ? index : null;
  472.             }});
  473.     }



  474.     /**
  475.      * Yields index position of last occurence of a substring, or <code>null</code>
  476.      * if the substring cannot be found.
  477.      *
  478.      * @see String#indexOf(String)
  479.      */
  480.     public static Fn<String, Integer> lastIndexOf(final String substring) {
  481.         if (substring == null) return always(null);
  482.         return when(notNull, new Fn<String, Integer>() {
  483.             @Override public Integer $(String s) {
  484.                 int index = s.lastIndexOf(substring);
  485.                 return index >= 0 ? index : null;
  486.             }});
  487.     }


  488.     /**
  489.      * Split a string on each occurence of a substring.
  490.      * The substring is not included in the resulting strings, and any
  491.      * consecutive substring are treated as one delimiter instance.
  492.      *
  493.      * @param substring
  494.      */
  495.     public static Fn<String, Iterable<String>> splittingOn(final String substring) {
  496.         return new Fn<String, Iterable<String>>() {
  497.             @Override public Iterable<String> $(String string) {
  498.                 return new SplitOnSubstring(string, substring);
  499.             }};
  500.     }


  501.     /**
  502.      * Split a string on each occurence of a <code>char</code> delimiter.
  503.      * The splitting character is not included in the resulting strings, and any
  504.      * consecutive occurrences of the character are treated as one delimiter instance.
  505.      *
  506.      * @param character the delimiter character
  507.      */
  508.     public static Fn<String, Iterable<String>> splittingOn(char character) { return splittingOn(equalTo(character)); }


  509.     /**
  510.      * Split a string into several on any character passing the given
  511.      * <code>Character</code> predicate.
  512.      * The characters accepted by the predicate is not included in the
  513.      * resulting strings, and any consecutive accepted characters are
  514.      * treated as one delimiter instance.
  515.      *
  516.      * @param character the predicate which decides if a character is
  517.      *                  a delimiter.
  518.      */
  519.     public static Fn<String, Iterable<String>> splittingOn(final Predicate<? super Character> character) {
  520.         return new Fn<String, Iterable<String>>() {
  521.             @Override public Iterable<String> $(String string) {
  522.                 return new SplitOnCharacter(string, character);
  523.             }};
  524.     }


  525.     private Strings() {}
  526. }